Driver config reporting — stage 2: full DRIVER_CONFIG report - #968
Driver config reporting — stage 2: full DRIVER_CONFIG report#968nikagra wants to merge 24 commits into
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:
📝 WalkthroughWalkthroughThe driver now reports expanded default-profile configuration through control-connection Sequence Diagram(s)sequenceDiagram
participant StartupOptionsBuilder
participant ProtocolInitHandler
participant FeatureStore
participant DriverConfigReporter
StartupOptionsBuilder->>ProtocolInitHandler: provide stable SESSION_ID
ProtocolInitHandler->>FeatureStore: read sharding information
ProtocolInitHandler->>DriverConfigReporter: build control-connection DRIVER_CONFIG
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Comment |
ffe0609 to
9a2f6ca
Compare
9a2f6ca to
c6f7ca3
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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
`@core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java`:
- Around line 1178-1187: Make the reporting documentation backend-neutral across
DefaultDriverOption, TypedDriverOption, and reference.conf: replace
ScyllaDB-only wording with server-side terminology or explicitly document both
storage paths, system.clients for ScyllaDB and system_views.clients for
Cassandra 4.1. Update all three affected sites consistently without changing the
reporting 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ab5c16aa-71fa-4f9a-9659-654b6920ae50
📒 Files selected for processing (12)
core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.java
c6f7ca3 to
24062e9
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (4)
core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java (1)
51-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueContract extension is consistent with implementation and callers.
The new
scyllaDbparam and its "only meaningful withreportDriverConfig" contract matchDefaultDriverConfigReporter.populateStartupOptionsandProtocolInitHandler's caller.One minor note for the future: this interface now has two adjacent
booleanparameters (reportDriverConfig,scyllaDb), which is a classic call-site readability/mix-up risk (e.g.populateStartupOptions(opts, true, false)reads ambiguously without named-parameter comments, as seen in the test file). Not blocking, but if a third flag is ever added, consider a small options value object instead.🤖 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 `@core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java` around lines 51 - 57, The comment identifies no required code change; the current scyllaDb parameter and contract are consistent with the implementation and callers. Leave DriverConfigReporter.populateStartupOptions and its call sites unchanged, and only consider introducing an options value object if another boolean flag is added later.core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java (1)
194-210: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCentralize the ScyllaDB predicate. Both this startup path and
CassandraSchemaQueries.shouldApplyUsingTimeout()key off the sameshardingInfo != nullsignal; a shared helper would keep control-plane reporting and schema-query behavior in sync.🤖 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 `@core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java` around lines 194 - 210, Centralize the ScyllaDB detection based on getShardingInfo() != null in a shared helper, then update ProtocolInitHandler’s startup reporting and CassandraSchemaQueries.shouldApplyUsingTimeout() to use it. Preserve the existing featureStore population flow and behavior while ensuring both paths rely on the same predicate.core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java (1)
136-145: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
controlno longer exercises the control-connection path.Both calls pass
reportDriverConfig=false, so the map namedcontrolis identical topool. Passingtruefor the control map keeps the test name honest and additionally proves the session id is stable when the config blob is built.♻️ Suggested tweak
- reporter.populateStartupOptions(control, false, false); + reporter.populateStartupOptions(control, /* reportDriverConfig= */ true, false); reporter.populateStartupOptions(pool, false, false);🤖 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 `@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java` around lines 136 - 145, Update should_use_a_stable_session_id_across_connections so the control map calls reporter.populateStartupOptions with reportDriverConfig=true, while keeping the pool call false and preserving the session ID equality assertion.integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java (1)
145-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStage-2 payload assertion is copy-pasted across both integration tests. Both classes carry an identical
assertDriverConfigPayload(same Javadoc, same checks); every future stage-2 assertion has to be added twice and will silently drift otherwise.
integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java#L145-L169: move this helper into a shared test utility (e.g. a package-privateDriverConfigReportAssertionsclass in this package) and call it from here.integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.java#L125-L149: delete the local copy and call the shared helper instead.🤖 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 `@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java` around lines 145 - 169, Extract the duplicated assertDriverConfigPayload helper into a package-private shared DriverConfigReportAssertions test utility, preserving its existing JSON parsing and stage-2 validation checks. In integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java lines 145-169, replace the local helper with a call to the shared utility; in integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.java lines 125-149, delete the local copy and call the same utility.
🤖 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.
Nitpick comments:
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java`:
- Around line 194-210: Centralize the ScyllaDB detection based on
getShardingInfo() != null in a shared helper, then update ProtocolInitHandler’s
startup reporting and CassandraSchemaQueries.shouldApplyUsingTimeout() to use
it. Preserve the existing featureStore population flow and behavior while
ensuring both paths rely on the same predicate.
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java`:
- Around line 51-57: The comment identifies no required code change; the current
scyllaDb parameter and contract are consistent with the implementation and
callers. Leave DriverConfigReporter.populateStartupOptions and its call sites
unchanged, and only consider introducing an options value object if another
boolean flag is added later.
In
`@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java`:
- Around line 136-145: Update should_use_a_stable_session_id_across_connections
so the control map calls reporter.populateStartupOptions with
reportDriverConfig=true, while keeping the pool call false and preserving the
session ID equality assertion.
In
`@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java`:
- Around line 145-169: Extract the duplicated assertDriverConfigPayload helper
into a package-private shared DriverConfigReportAssertions test utility,
preserving its existing JSON parsing and stage-2 validation checks. In
integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java
lines 145-169, replace the local helper with a call to the shared utility; in
integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.java
lines 125-149, delete the local copy and call the same utility.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5db2451e-3027-4740-bf80-230694676062
📒 Files selected for processing (15)
core/pom.xmlcore/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javacore/src/test/resources/config/driver-config-report-v1.schema.jsonintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.javapom.xml
🚧 Files skipped from review as they are similar to previous changes (5)
- core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java
- core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java
- core/src/main/resources/reference.conf
- core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
- core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
24062e9 to
d6ac737
Compare
|
@coderabbitai Thanks for the review — dispositions for the nitpicks, all pushed into the amended stage-2 commit:
Unrelated to the nitpicks but included in the same push: a TLS reporting correctness fix — |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Relevant files =="
fd -t f . core/src/main integration-tests/src/test | rg 'DefaultDriverConfigReporter|DriverConfigReporting|SslEngineFactory|ProtocolInitHandler'
echo
echo "== Updated stable-session test and config reporting calls =="
rg -n -C 5 'should_use_a_stable_session_id_across_connections|populateStartupOptions\s*\(' \
core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java \
core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java \
core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java \
core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java
echo
echo "== Shared integration assertion helper and callers =="
rg -n -C 4 'class DriverConfigReportingAssertions|assertDriverConfigPayload\s*\(' \
integration-tests/src/test/java/com/datastax/oss/driver/core/config
echo
echo "== TLS hostname-verification reporting and SSL factory contract =="
rg -n -C 5 'hostname-verification|requireHostnameValidation\s*\(' \
core/src/main/java core/src/test/javaLength of output: 40821
✏️ Learnings added
You are interacting with an AI system. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java (1)
129-138: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftProve that the reporting row is the control connection.
This verifies cardinality, not identity: a regression that emits
DRIVER_CONFIGon one pool connection and omits it on the control connection still passes. Add a reliable control-connection marker/correlation before validating the payload, as the Simulacron test already does.🤖 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 `@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java` around lines 129 - 138, Update the reporting-row assertions in DriverConfigReportingCcmIT to correlate the DRIVER_CONFIG row with the control connection using the same reliable marker or correlation mechanism as the existing Simulacron test. Validate that the identified row is the control connection before calling assertDriverConfigPayload, while preserving the existing single-row cardinality check.core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java (1)
321-352: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider single-argument override helpers to cut the
reporterWith(...)boilerplate.The 7-arg
reporterWith(defaults(map -> {}), mock(ExponentialReconnectionPolicy.class), mock(DefaultRetryPolicy.class), mock(NoSpeculativeExecutionPolicy.class), mock(DefaultLoadBalancingPolicy.class), mock(TimestampGenerator.class), Optional.empty())call is repeated ~15 times across this file, varying in exactly one argument. Thin wrappers (or a small builder) would make each test's intent obvious.♻️ Sketch
private DefaultDriverConfigReporter reporterWithReconnection(ReconnectionPolicy p) { return reporterWith( defaults(map -> {}), p, mock(DefaultRetryPolicy.class), mock(NoSpeculativeExecutionPolicy.class), mock(DefaultLoadBalancingPolicy.class), mock(TimestampGenerator.class), Optional.empty()); } // likewise reporterWithRetry / reporterWithSpecEx / reporterWithLb / reporterWithSsl🤖 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 `@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java` around lines 321 - 352, Reduce repeated seven-argument setup in DefaultDriverConfigReporterTest by adding thin single-argument reporterWith helper methods for the varying policy/configuration dependencies, including reconnection policy and the analogous retry, speculative execution, load-balancing, and SSL cases. Update the affected tests, such as should_report_constant_reconnection_policy and should_report_custom_reconnection_policy, to use the appropriate helper while preserving their existing mocks and assertions.
🤖 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 `@core/src/test/resources/config/driver-config-report-v1.schema.json`:
- Around line 779-801: Update the consistency enum in the schema near the
consistency and serial-consistency properties to accept SERIAL and LOCAL_SERIAL
alongside the existing request consistency values. Keep the serial-consistency
property unchanged.
In
`@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.java`:
- Around line 29-43: Configure the shared OBJECT_MAPPER used by
assertDriverConfigPayload to enable
DeserializationFeature.FAIL_ON_TRAILING_TOKENS, ensuring readTree rejects valid
JSON followed by extra tokens while preserving the existing payload assertions.
---
Nitpick comments:
In
`@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java`:
- Around line 321-352: Reduce repeated seven-argument setup in
DefaultDriverConfigReporterTest by adding thin single-argument reporterWith
helper methods for the varying policy/configuration dependencies, including
reconnection policy and the analogous retry, speculative execution,
load-balancing, and SSL cases. Update the affected tests, such as
should_report_constant_reconnection_policy and
should_report_custom_reconnection_policy, to use the appropriate helper while
preserving their existing mocks and assertions.
In
`@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java`:
- Around line 129-138: Update the reporting-row assertions in
DriverConfigReportingCcmIT to correlate the DRIVER_CONFIG row with the control
connection using the same reliable marker or correlation mechanism as the
existing Simulacron test. Validate that the identified row is the control
connection before calling assertDriverConfigPayload, while preserving the
existing single-row cardinality check.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 74f644f4-90dd-478c-b6ac-fecd6b72bac0
📒 Files selected for processing (20)
core/pom.xmlcore/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javacore/src/test/resources/config/driver-config-report-v1.schema.jsonintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.javapom.xml
🚧 Files skipped from review as they are similar to previous changes (4)
- core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
- core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java
- core/src/main/resources/reference.conf
- core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
d6ac737 to
5e69715
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java (1)
194-203: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCentralize the ScyllaDB detection check
getShardingInfo() != nullis used here and again inCassandraSchemaQueries.shouldApplyUsingTimeout(). A shared helper would keep driver-config reporting andUSING TIMEOUTgating aligned if the detection logic changes later.🤖 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 `@core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java` around lines 194 - 203, Centralize the ScyllaDB detection currently implemented by getShardingInfo() != null into a shared helper, then update the ProtocolInitHandler flow and CassandraSchemaQueries.shouldApplyUsingTimeout() to use it. Preserve the existing featureStore null handling and ensure both driver-config reporting and USING TIMEOUT gating rely on the same detection logic.
🤖 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.
Nitpick comments:
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.java`:
- Around line 194-203: Centralize the ScyllaDB detection currently implemented
by getShardingInfo() != null into a shared helper, then update the
ProtocolInitHandler flow and CassandraSchemaQueries.shouldApplyUsingTimeout() to
use it. Preserve the existing featureStore null handling and ensure both
driver-config reporting and USING TIMEOUT gating rely on the same detection
logic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: af57b7b6-6e4e-44fd-9609-e06c113aa08b
📒 Files selected for processing (20)
core/pom.xmlcore/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javacore/src/test/resources/config/driver-config-report-v1.schema.jsonintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.javapom.xml
🚧 Files skipped from review as they are similar to previous changes (4)
- core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
- core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java
- core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
- core/src/main/resources/reference.conf
Fills in the full DRIVER_CONFIG JSON report in the approved v2
cross-driver schema shape, replacing the stage-1 {"version":1}
placeholder. All groups are populated from Configuration and
Policies on each control-connection init.
Adds public getters to DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy needed to report node-location-preference
and dc-failover, and makes PagingOptimizingLoadBalancingPolicy
implement ChainableLoadBalancingPolicy so the reporter can unwrap the
LB policy Cluster.Manager wraps at runtime.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema is shipped as a test resource and
validated via com.networknt:json-schema-validator (pinned to 1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit, plus a
negative test proving additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fills in the full DRIVER_CONFIG JSON report in the approved v2
cross-driver schema shape, replacing the stage-1 {"version":1}
placeholder. All groups are populated from Configuration and
Policies on each control-connection init.
Adds public getters to DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy needed to report node-location-preference
and dc-failover, and makes PagingOptimizingLoadBalancingPolicy
implement ChainableLoadBalancingPolicy so the reporter can unwrap the
LB policy Cluster.Manager wraps at runtime.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema is shipped as a test resource and
validated via com.networknt:json-schema-validator (pinned to 1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit, plus a
negative test proving additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fills in the full DRIVER_CONFIG JSON report in the approved v2
cross-driver schema shape, replacing the stage-1 {"version":1}
placeholder. All groups are populated from Configuration and
Policies on each control-connection init.
Adds public getters to DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy needed to report node-location-preference
and dc-failover, and makes PagingOptimizingLoadBalancingPolicy
implement ChainableLoadBalancingPolicy so the reporter can unwrap the
LB policy Cluster.Manager wraps at runtime.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema is shipped as a test resource and
validated via com.networknt:json-schema-validator (pinned to 1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit, plus a
negative test proving additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fills in the full DRIVER_CONFIG JSON report in the approved v2
cross-driver schema shape, replacing the stage-1 {"version":1}
placeholder. All groups are populated from Configuration and Policies
when the report is built, i.e. once per Cluster as it initializes.
Adds public getters to DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy needed to report node-location-preference
and dc-failover, and makes PagingOptimizingLoadBalancingPolicy
implement ChainableLoadBalancingPolicy so the reporter can unwrap the
LB policy Cluster.Manager wraps at runtime.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema is shipped as a test resource and
validated via com.networknt:json-schema-validator (pinned to 1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit, plus a
negative test proving additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5e69715 to
cfda714
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java (1)
930-966: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffConsider a small builder for the reporter fixtures.
The 7-argument and 8-argument
reporterWithcalls repeat across about twenty tests, and each call varies only one argument. A builder that starts from the default policy set and overrides one collaborator would remove that repetition and make each test state its single variable.Example shape:
private final class ReporterBuilder { private DriverExecutionProfile profile = defaults(map -> {}); private ReconnectionPolicy reconnection = mock(ExponentialReconnectionPolicy.class); // ... remaining collaborators with the same defaults as defaultsReporter() ReporterBuilder reconnection(ReconnectionPolicy p) { this.reconnection = p; return this; } DefaultDriverConfigReporter build() { /* wire the mock context */ } }Each test then reads
builder().reconnection(mock(ConstantReconnectionPolicy.class)).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 `@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java` around lines 930 - 966, Refactor the repeated reporter fixture setup around the overloaded reporterWith methods into a small ReporterBuilder that initializes the same defaults as defaultsReporter() and exposes fluent overrides for individual collaborators, including the programmatic local datacenter. Update the affected tests to build reporters by overriding only the variable under test, while preserving the existing mock context wiring and behavior.core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java (1)
152-152: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider package-private visibility for
buildJson.
DefaultDriverConfigReporterTestis in the same package,com.datastax.oss.driver.internal.core.context. Package-private visibility therefore supports the test override without adding a subclass extension point that the javadoc must then qualify with thread-safety caveats.♻️ Proposed change
- protected String buildJson(boolean scyllaDb) { + String buildJson(boolean scyllaDb) {If a production subclass hook is intended, keep
protectedand disregard this suggestion.🤖 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 `@core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java` at line 152, Change the buildJson method in DefaultDriverConfigReporter from protected to package-private visibility, allowing DefaultDriverConfigReporterTest to override it within the same package without exposing a production subclass extension point.
🤖 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 `@core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java`:
- Line 403: Restore opt-in driver configuration reporting by setting
TypedDriverOption.DRIVER_CONFIG_REPORTING_ENABLED to false in OptionsMap. Update
DriverConfigReportingSimulacronIT at lines 53-60 and 145-160 to assert and
enable reporting explicitly as needed, and update upgrade_guide/README.md lines
40-44 to document that reporting is disabled by default.
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.java`:
- Around line 23-33: Change driver-config reporting defaults from enabled to
disabled across OptionsMap.fillWithDriverDefaults and
DefaultDriverConfigReporter, then update the corresponding documentation and
tests to reflect false as the default. Preserve explicit opt-in behavior,
ensuring default sessions do not send the DRIVER_CONFIG startup option.
In
`@integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.java`:
- Around line 133-142: Update the assertions in DriverConfigReportingCcmIT to
identify the DRIVER_CONFIG row by matching its connection local address and port
against the control connection, using the existing control-connection details
and clientOptions helpers. Preserve the single-row and payload assertions, but
ensure a pooled connection cannot satisfy the test.
In `@upgrade_guide/README.md`:
- Around line 42-44: Update the fenced configuration block in the README to
specify the HOCON language identifier, changing the opening fence to use hocon
while preserving the existing configuration content.
---
Nitpick comments:
In
`@core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java`:
- Line 152: Change the buildJson method in DefaultDriverConfigReporter from
protected to package-private visibility, allowing
DefaultDriverConfigReporterTest to override it within the same package without
exposing a production subclass extension point.
In
`@core/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.java`:
- Around line 930-966: Refactor the repeated reporter fixture setup around the
overloaded reporterWith methods into a small ReporterBuilder that initializes
the same defaults as defaultsReporter() and exposes fluent overrides for
individual collaborators, including the programmatic local datacenter. Update
the affected tests to build reporters by overriding only the variable under
test, while preserving the existing mock context wiring and 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e40d8974-45a6-4285-bc5b-2d6d30c334dc
📒 Files selected for processing (26)
core/pom.xmlcore/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.javacore/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandler.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/DriverConfigReporter.javacore/src/main/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilder.javacore/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.javacore/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.javacore/src/main/resources/reference.confcore/src/test/java/com/datastax/dse/driver/internal/core/context/DseStartupOptionsBuilderTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.javacore/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporterTest.javacore/src/test/java/com/datastax/oss/driver/internal/core/context/StartupOptionsBuilderTest.javacore/src/test/resources/config/driver-config-report-v1.schema.jsonintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingCcmIT.javaintegration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingSimulacronIT.javapom.xmlupgrade_guide/README.md
🚧 Files skipped from review as they are similar to previous changes (13)
- core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java
- core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java
- core/src/main/java/com/datastax/oss/driver/internal/core/ssl/DefaultSslEngineFactory.java
- core/src/main/java/com/datastax/oss/driver/api/core/ssl/ProgrammaticSslEngineFactory.java
- integration-tests/src/test/java/com/datastax/oss/driver/core/config/DriverConfigReportingAssertions.java
- core/src/main/java/com/datastax/oss/driver/internal/core/metadata/schema/queries/CassandraSchemaQueries.java
- core/pom.xml
- core/src/main/java/com/datastax/oss/driver/internal/core/ssl/SniSslEngineFactory.java
- core/src/main/java/com/datastax/oss/driver/api/core/ssl/SslEngineFactory.java
- core/src/test/resources/config/driver-config-report-v1.schema.json
- pom.xml
- core/src/main/resources/reference.conf
- core/src/test/java/com/datastax/oss/driver/internal/core/channel/ChannelFactoryTestBase.java
cfda714 to
312b522
Compare
|
Dispositions for the two nitpicks:
|
|
On the two nitpicks from the last CodeRabbit pass: |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 35 changed files in this pull request and generated no new comments.
Suppressed comments (4)
core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java:690
- The report can invent a
dc-autopreference for an arbitrary custom load-balancing policy. With no configured DC/rack and a policy that is not aBasicLoadBalancingPolicy, this method returns aNodeLocationwith all-null fields, which both serializers turn intodc-auto; however, the custom-policy SPI does not require DC inference and a custom policy may remain DC-agnostic. Omit the preference when neither configured nor resolved location evidence exists, except for built-ins known to infer one, and add coverage for a DC-agnostic custom policy.
// Exact-class check, like the policy branches above: a user subclass may well discover a local
// DC of its own, so only the built-in itself is known to stay datacenter-agnostic.
if (configuredDc == null && policy.getClass() == BasicLoadBalancingPolicy.class) {
return null;
core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java:899
- Trimming changes the value that the running policy actually uses.
OptionalLocalDcHelperandOptionalLocalRackHelperpreserve configured strings verbatim and compare them exactly, so a configured" dc1 "does not matchdc1, while this report claims that it does. Preserve non-empty DC/rack strings exactly (and handle only the truly empty value by omission or explicit schema-gap behavior) so diagnostics do not fabricate an effective preference.
/** The string with surrounding whitespace removed, or {@code null} if that leaves it empty. */
@Nullable
private static String trimmedToNull(@Nullable String s) {
if (s == null) {
return null;
}
String trimmed = s.trim();
return trimmed.isEmpty() ? null : trimmed;
core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java:69
- This acknowledged staleness contradicts the report's purpose of describing effective configuration. These load-balancing and speculative-execution options are non-modifiable at runtime and are latched into final policy fields, but the reporter reads the reloaded profile, so after a reload it can advertise behavior the running policies do not implement. Expose and read the latched values, as
reconnectionPolicy()already does, instead of knowingly emitting inaccurate diagnostics.
This issue also appears in the following locations of the same file:
- line 687
- line 892
* The same staleness applies elsewhere — {@code DefaultLoadBalancingPolicy}'s slow-avoidance flag
* behind {@code adaptive-ordering}, {@code BasicLoadBalancingPolicy}'s max-nodes-per-remote-dc
* behind {@code fallback-to-non-preferred-nodes}, the speculative-execution delays — but those
* policies expose no accessor for the latched value, so reading them would need new API. There,
* immediately after a reload, the report can still show a value the already-running policy doesn't
* reflect yet, until that policy is rebuilt.
core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java:343
- This comment is stale: the reporter now uses only the presence of sharding info as the ScyllaDB signal and no longer derives pool sizing from the shard count. Describe the count assertion as verification that
ConnectionShardingInfowas unwrapped correctly.
// The reporter receives the node-level info unwrapped from ConnectionShardingInfo, carrying the
// SCYLLA_NR_SHARDS count above — which is what it reports the connection pool's sizing from.
assertThat(capturedShardingInfo.get()).isNotNull();
assertThat(capturedShardingInfo.get().getShardsCount()).isEqualTo(4);
Fills in the full DRIVER_CONFIG JSON report in the normative cross-driver
schema shape, replacing the stage-1 {"version":1} placeholder. All groups
are populated from Configuration and Policies when the report is built,
i.e. once per Cluster as it initializes.
Everything hangs off three groups. connection carries the connect/read
timeouts, the per-connection request capacity, the pool, the socket
options, the reconnection policy and -- only when TLS is on -- tls.
control-plane carries the system-query and schema-agreement timeouts.
query carries the per-request defaults plus the three policies acting on
a query: retry, load-balancing (with the node preference beside it) and,
when configured, speculative-execution.
The schema also has an optional connection.node-preference, for a driver
that scopes its connection pools independently of its query routing. 3.x
has no such second knob -- one LoadBalancingPolicy decides both, since
distance(Host) governs whether a host is pooled at all -- so the
preference is reported once, under the policy it derives from, rather
than duplicated.
token-aware is the only built-in load balancing shape the schema defines,
so every other built-in policy -- a bare DCAwareRoundRobinPolicy,
RoundRobinPolicy, WhiteListPolicy -- is reported as custom with its class
name, which identifies it but carries none of the normalized flags; its
datacenter and rack still show up in query.load-balancing.node-preference.
A token-aware chain reports load-distribution from its replica ordering
(RANDOM, the 3.x default, is "shuffle"; TOPOLOGICAL is "replica-set";
NEUTRAL keeps the child's plan order, so "round-robin").
fallback-to-non-preferred-nodes is true whenever the policy can reach a
node outside the preference reported beside it. For DCAwareRoundRobin
that means used-hosts-per-remote-DC, since the preference is the
datacenter. RackAwareRoundRobin reports a rack, and the other racks of
its local datacenter are outside that yet are the second tier of every
query plan -- distance() returns REMOTE, not IGNORED, for them -- so it
is always true there, remote datacenter hosts or not.
adaptive-ordering has no "enabled" flag and cannot carry an empty signal
list, so it is reported only when a LatencyAwarePolicy is in the chain --
latency being the only runtime observation a 3.x policy can reorder
candidates on. tls likewise has no "enabled" flag: the group's presence
is what says TLS is on.
Where a configured value falls outside what the schema can express, an
optional key or group is omitted rather than emitted as a value the
schema rejects: a disabled connect timeout, a disabled read timeout (all
three of connection.read, control-plane.queries.system.timeout
.client-side-ms and query.defaults.request), a negative SO_LINGER, a
non-positive socket buffer size, an unbounded page size, and a default
serial consistency level that is not serial -- QueryOptions, unlike
Statement, does not check that one. Two optional bounds are omitted for
the opposite reason -- 3.x has no such bound to report at all:
connection.reconnection.policy.max-attempts, since its reconnection
policies retry forever, and query.retry.policy.max-retries, since no
single number describes a 3.x retry policy. Both built-ins are
parameterless singletons, and while they stop after one attempt on a
read timeout, a write timeout or an unavailable error -- all three
sharing one counter, so one retry between them rather than one each --
onRequestError leaves nbRetry unread and keeps trying the next host
until the query plan runs out. Which of the two applies is decided per
statement rather than by configuration: RequestHandler only consults
onRequestError and onWriteTimeout for an idempotent statement, so the
same policy bounds a non-idempotent request at one retry and an
idempotent one at the length of the query plan, and setIdempotent
overrides the reported query.defaults.idempotence per statement.
Omission is not always available, so these required keys are left in the
one state that is accurate:
- connection.requests.orphaned.max has no 3.x equivalent to report at
all. A request the driver stopped waiting for keeps its stream
identifier until the response arrives, with no configurable bound and
no connection replacement, so the key is omitted -- which its
required-ness then rejects. This is the one violation every report
carries.
- connection.requests.in-flight.max is bounded to 1..32767 by the
schema, while PoolingOptions accepts 0..32768 -- one above at the top
(protocol v3 provides 32768 stream identifiers) and one below at the
bottom. Only PoolingOptions.UNSET falls back to the protocol default,
so a limit of 0 an operator set deliberately is not reported as 1024.
- query.defaults.consistency is an enum without the serial levels that
QueryOptions.setConsistencyLevel accepts unvalidated.
- query.speculative-execution.policy.percentile is bounded to 0..100
exclusive, while PercentileSpeculativeExecutionPolicy accepts a
percentile of 0.
Such a value is reported as-is and the limitation is documented on the
class: the reporter neither fabricates an in-range value -- which would
misreport a setting an operator may have chosen on purpose, or a policy
3.x does not implement -- nor drops the whole report over one field.
Recorded as a cross-driver schema gap, to be fixed the way
control-plane.schema.agreement.timeout-ms already admits 0.
QueryOptions.setConsistencyLevel now rejects null. Every query needs a
consistency level, so a null default already failed any statement that
did not set one of its own -- RoundRobinPolicy and DCAwareRoundRobin
call isDCLocal() on it while building a query plan -- which turned a
schema-required key into a missing one. The reporter keeps omitting a
null it is handed anyway: the field is private, so only a QueryOptions
subclass overriding the getter can still produce one, and letting it
through would throw and cost the whole report rather than one key.
Adds the public getters the report needs: local DC/rack, their explicit
flags and used-hosts-per-remote-DC on DCAwareRoundRobinPolicy, the same
minus used-hosts-per-remote-DC on RackAwareRoundRobinPolicy, replica
ordering on TokenAwarePolicy, and max-executions plus the delay or
percentile on the two built-in speculative execution policies -- whose
parameters are immutable and land in the schema's range exactly, so
they are reported as constant/percentile rather than as custom. Also
makes PagingOptimizingLoadBalancingPolicy implement
ChainableLoadBalancingPolicy so the reporter can unwrap the LB policy
Cluster.Manager wraps at runtime.
Caps the report at 32KiB of UTF-8 (MAX_DRIVER_CONFIG_LENGTH), matching
the 4.x sibling PR scylladb#968, gocql scylladb#964 and csharp-driver scylladb#262. Beyond
cross-driver parity this is a correctness fix: CBUtil.writeString
writes each STARTUP value with a 16-bit length prefix and no bounds
check, so a value over 65535 bytes truncates the prefix modulo 65536
while still appending the whole body -- a corrupt frame and a failed
handshake, and not something the fail-safe try/catch can contain since
nothing throws. Parts of the report are user-supplied and unbounded
(DC/rack names, consistency levels, custom policy class names). Over
the limit means WARN and no DRIVER_CONFIG.
Hardens the other two ways reporting could break a connection rather
than merely fail to report:
- The fail-safe catch also covers InternalError, since customPolicy()
calls getClass().getSimpleName() on arbitrary user policy objects
(documented JDK edge case for certain synthetic classes). Not a bare
Error, so OutOfMemoryError/StackOverflowError still surface.
- The load balancing policy chain walk is bounded at 16 policies and
shared by both callers. It follows getChildPolicy() on arbitrary user
policies, so a cyclic chain used to spin forever on the Cluster
initialization path -- the one failure mode the try/catch cannot
contain, because it hangs rather than throws.
A custom load balancing policy is now named after the policy the user
configured rather than PagingOptimizingLoadBalancingPolicy. Cluster
.Manager wraps every session's policy in that internal class, and it is
the outermost element of the chain, so every custom policy was reported
as {"type":"custom","name":"PagingOptimizingLoadBalancingPolicy"}. An
anonymous policy class falls back to its binary name, since it has no
simple name and the schema requires a non-empty one.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema block is shipped verbatim as a test
resource and validated via com.networknt:json-schema-validator (1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit. Since one
required key has no value to report, the assertion is that a report
violates the schema in exactly the documented ways and no other, with
a test naming the gap and a negative test proving
additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 35 changed files in this pull request and generated no new comments.
Suppressed comments (4)
core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java:940
trimmedToNullchanges the reported DC/rack even though the load-balancing helpers use the configured string verbatim (OptionalLocalDcHelper.java:71-79andOptionalLocalRackHelper.java:34-38). For example," dc1 "matches nodc1nodes at runtime but is reported as"dc1"; an empty/whitespace value can also be turned intodc-autoeven though the policy treats it as explicitly configured and does not infer. Preserve non-empty values exactly, and omit an unrepresentable empty value without treating it as absent for inference.
String trimmed = s.trim();
core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java:626
- This reports
truefrom the raw max-remote-nodes option even whenBasicLoadBalancingPolicyhas no local DC. In that modemaybeAddDcFailoverreturns immediately and all nodes are treated as local (BasicLoadBalancingPolicy.java:577-578, 724-726), so the option is inert and both node-preference groups are omitted, yet the report claims fallback to non-preferred nodes is active. Gate this value on an effective node preference/local DC and cover the DC-agnostic Basic policy with a positive failover setting.
n.put(
"fallback-to-non-preferred-nodes",
config.getInt(DefaultDriverOption.LOAD_BALANCING_DC_FAILOVER_MAX_NODES_PER_REMOTE_DC, 0)
> 0);
core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverConfigReporter.java:333
- The class documentation now correctly lists two required fields with no schema-valid form (
in-flight.maxandquery.defaults.consistency), but this comment still says there is only one. Keep the count consistent.
// clamped — the one field left with no schema-valid form, see the class javadoc.
core/src/test/java/com/datastax/oss/driver/internal/core/channel/ProtocolInitHandlerTest.java:341
- This comment is stale: the reporter intentionally reads only whether sharding info is present and no longer reports pool sizing or consumes the shard count. Update it to describe the unwrapping/backend signal being tested.
// The reporter receives the node-level info unwrapped from ConnectionShardingInfo, carrying the
// SCYLLA_NR_SHARDS count above — which is what it reports the connection pool's sizing from.
Fills in the full DRIVER_CONFIG JSON report in the normative cross-driver
schema shape, replacing the stage-1 {"version":1} placeholder. All groups
are populated from Configuration and Policies when the report is built,
i.e. once per Cluster as it initializes.
Everything hangs off three groups. connection carries the connect/read
timeouts, the per-connection request capacity, the pool, the socket
options, the reconnection policy and -- only when TLS is on -- tls.
control-plane carries the system-query and schema-agreement timeouts.
query carries the per-request defaults plus the three policies acting on
a query: retry, load-balancing (with the node preference beside it) and,
when configured, speculative-execution.
The schema also has an optional connection.node-preference, for a driver
that scopes its connection pools independently of its query routing. 3.x
has no such second knob -- one LoadBalancingPolicy decides both, since
distance(Host) governs whether a host is pooled at all -- so the
preference is reported once, under the policy it derives from, rather
than duplicated.
token-aware is the only built-in load balancing shape the schema defines,
so every other built-in policy -- a bare DCAwareRoundRobinPolicy,
RoundRobinPolicy, WhiteListPolicy -- is reported as custom with its class
name, which identifies it but carries none of the normalized flags; its
datacenter and rack still show up in query.load-balancing.node-preference.
A token-aware chain reports load-distribution from its replica ordering
(RANDOM, the 3.x default, is "shuffle"; TOPOLOGICAL is "replica-set";
NEUTRAL keeps the child's plan order, so "round-robin").
fallback-to-non-preferred-nodes is true whenever the policy can reach a
node outside the preference reported beside it. For DCAwareRoundRobin
that means used-hosts-per-remote-DC, since the preference is the
datacenter. RackAwareRoundRobin reports a rack, and the other racks of
its local datacenter are outside that yet are the second tier of every
query plan -- distance() returns REMOTE, not IGNORED, for them -- so it
is always true there, remote datacenter hosts or not.
adaptive-ordering has no "enabled" flag and cannot carry an empty signal
list, so it is reported only when a LatencyAwarePolicy is in the chain --
latency being the only runtime observation a 3.x policy can reorder
candidates on. tls likewise has no "enabled" flag: the group's presence
is what says TLS is on.
Where a configured value falls outside what the schema can express, an
optional key or group is omitted rather than emitted as a value the
schema rejects: a disabled connect timeout, a disabled read timeout (all
three of connection.read, control-plane.queries.system.timeout
.client-side-ms and query.defaults.request), a negative SO_LINGER, a
non-positive socket buffer size, an unbounded page size, and a default
serial consistency level that is not serial -- QueryOptions, unlike
Statement, does not check that one. Two optional bounds are omitted for
the opposite reason -- 3.x has no such bound to report at all:
connection.reconnection.policy.max-attempts, since its reconnection
policies retry forever (the maxAttempts field ExponentialReconnection
Policy carries is an overflow guard on the doubling, not a give-up
bound: past it nextDelayMs() keeps returning maxDelayMs), and
query.retry.policy.max-retries, since no
single number describes a 3.x retry policy. Both built-ins are
parameterless singletons, and while they stop after one attempt on a
read timeout, a write timeout or an unavailable error -- all three
sharing one counter, so one retry between them rather than one each --
onRequestError leaves nbRetry unread and keeps trying the next host
until the query plan runs out. Which of the two applies is decided per
statement rather than by configuration: RequestHandler only consults
onRequestError and onWriteTimeout for an idempotent statement, so the
same policy bounds a non-idempotent request at one retry and an
idempotent one at the length of the query plan, and setIdempotent
overrides the reported query.defaults.idempotence per statement.
Two more keys are omitted for a third reason -- the schema admits only a
boolean, and 3.x cannot observe which one applies.
query.defaults.client-timestamps is false for ServerSideTimestamp
Generator, whose next() always returns Long.MIN_VALUE, and true for an
AbstractMonotonicTimestampGenerator, which never can; any other
generator makes that a per-call decision, so whether timestamps are
assigned client-side is not a property of the configuration at all.
connection.tls.hostname-verification is true for SniSSLOptions, the
driver's only setEndpointIdentificationAlgorithm call, and omitted for
every other SSLOptions, which builds its engine from a user SSLContext
or hands the whole handler to Netty. Both keys are documented as absent
exactly when the behavior is unknown, which is this case. The tls group
can therefore be empty -- its presence is still what reports TLS is on.
Omission is not always available, so these required keys are left in the
one state that is accurate:
- connection.requests.orphaned.max has no 3.x equivalent to report at
all. A request the driver stopped waiting for keeps its stream
identifier until the response arrives, with no configurable bound and
no connection replacement, so the key is omitted -- which its
required-ness then rejects. This is the one violation every report
carries.
- connection.requests.in-flight.max must be positive, while
PoolingOptions also accepts 0. Only PoolingOptions.UNSET falls back to
a protocol default, so a limit of 0 an operator set deliberately is
not reported as 1024.
- query.speculative-execution.policy.percentile is bounded to 0..100
exclusive, while PercentileSpeculativeExecutionPolicy accepts a
percentile of 0.
Such a value is reported as-is and the limitation is documented on the
class: the reporter neither fabricates an in-range value -- which would
misreport a setting an operator may have chosen on purpose, or a policy
3.x does not implement -- nor drops the whole report over one field.
Recorded as a cross-driver schema gap, to be fixed the way
control-plane.schema.agreement.timeout-ms already admits 0.
QueryOptions.setConsistencyLevel now rejects null -- a behavior change
to a public setter. Every query needs a consistency level, so a null
default already failed any statement that did not set one of its own:
SessionManager falls back to it for every request, and
CBUtil.writeConsistencyLevel then dereferences it to write the frame.
That turned a schema-required key into a missing one for a
configuration that could never work. setSerialConsistencyLevel is
deliberately left as it is: the schema makes serial-consistency
optional, so a null there is faithfully reported as an omission rather
than as a missing required key. The reporter keeps omitting a
null it is handed anyway: the field is private, so only a QueryOptions
subclass overriding the getter can still produce one, and letting it
through would throw and cost the whole report rather than one key.
Adds the public getters the report needs: local DC/rack, their explicit
flags and used-hosts-per-remote-DC on DCAwareRoundRobinPolicy, the same
minus used-hosts-per-remote-DC on RackAwareRoundRobinPolicy, replica
ordering on TokenAwarePolicy, and max-executions plus the delay or
percentile on the two built-in speculative execution policies -- whose
parameters are immutable and land in the schema's range exactly, so
they are reported as constant/percentile rather than as custom. Also
makes PagingOptimizingLoadBalancingPolicy implement
ChainableLoadBalancingPolicy so the reporter can unwrap the LB policy
Cluster.Manager wraps at runtime.
in-flight.max needs a fallback because PoolingOptions is still UNSET
when the report is built: the protocol version is only negotiated once
the control connection is up. The default row is resolved with the same
walk PoolingOptions.setProtocolVersion applies -- the highest DEFAULTS
key not above the version -- driven by the version the user pinned with
withProtocolVersion when they pinned one, and by v3 otherwise, that
being the lowest version ScyllaDB negotiates and the reference row for
everything above it. DEFAULTS holds only v1 and v3 rows, so a cluster
pinned to v2 is sized from v1's 128 rather than v3's 1024, and pinning
is the one part of negotiation knowable at report time.
Caps the report at 32KiB of UTF-8 (MAX_DRIVER_CONFIG_LENGTH), matching
the 4.x sibling PR scylladb#968, gocql scylladb#964 and csharp-driver scylladb#262. Beyond
cross-driver parity this is a correctness fix: CBUtil.writeString
writes each STARTUP value with a 16-bit length prefix and no bounds
check, so a value over 65535 bytes truncates the prefix modulo 65536
while still appending the whole body -- a corrupt frame and a failed
handshake, and not something the fail-safe try/catch can contain since
nothing throws. Parts of the report are user-supplied and unbounded
(DC/rack names, consistency levels, custom policy class names). Over
the limit means WARN and no DRIVER_CONFIG.
Hardens the other two ways reporting could break a connection rather
than merely fail to report:
- The fail-safe catch also covers InternalError, since customPolicy()
calls getClass().getSimpleName() on arbitrary user policy objects
(documented JDK edge case for certain synthetic classes). Not a bare
Error, so OutOfMemoryError/StackOverflowError still surface.
- The load balancing policy chain walk is bounded at 16 policies and
shared by both callers. It follows getChildPolicy() on arbitrary user
policies, so a cyclic chain used to spin forever on the Cluster
initialization path -- the one failure mode the try/catch cannot
contain, because it hangs rather than throws.
A custom load balancing policy is now named after the policy the user
configured rather than PagingOptimizingLoadBalancingPolicy. Cluster
.Manager wraps every session's policy in that internal class, and it is
the outermost element of the chain, so every custom policy was reported
as {"type":"custom","name":"PagingOptimizingLoadBalancingPolicy"}. An
anonymous policy class falls back to its binary name, since it has no
simple name and the schema requires a non-empty one.
Adds a JSON-Schema conformance test suite (mirroring the 4.x sibling
PR scylladb#968): the normative schema block is shipped verbatim as a test
resource -- design-doc revision v5, whose report version field is still
1 -- and validated via com.networknt:json-schema-validator (1.5.x,
the last line still targeting Java 8), covering every discriminated-
union branch and optional group the 3.x reporter can emit. Since one
required key has no value to report, the assertion is that a report
violates the schema in exactly the documented ways and no other, with
a test naming the gap and a negative test proving
additionalProperties=false is enforced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four internal accessors, each for a value that only the running instance knows, so that DRIVER_CONFIG can describe what is actually in force rather than what the profile currently says: * `ConstantReconnectionPolicy.getDelay()` — the delay is captured in a lambda at construction and never re-read, so a configuration reload does not reach the running policy. `ExponentialReconnectionPolicy` already exposed its two equivalents. * `ConstantSpeculativeExecutionPolicy.getMaxExecutions()` / `getConstantDelayMillis()` — the same story as the reconnection delay: read from the profile once, in the constructor, and `advanced.speculative-execution-policy` is documented as not modifiable at runtime, so the profile can carry numbers no request executes with. * `JdkSslHandlerFactory.getSslEngineFactory()` — the engine factory a handler wraps is not necessarily the one behind `getSslEngineFactory()`: a context that overrides `buildSslHandlerFactory()` may supply its own, leaving the configured one unused on the connection path. * `BasicLoadBalancingPolicy.getLocalDatacenter()` / `getLocalRack()` widened from protected to public — both are on an internal class, so this adds no public API surface. Diagnostic only; no behavior change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cross-driver design doc revised v1 in place while this PR was in review.
The change is breaking but not a version bump — `$id` and `version` both stay
at 1, which is only safe because v1 has not shipped in a release yet.
The flat envelope becomes three groups — `connection`, `control-plane` and
`query` — with `additionalProperties: false` at the root, so every former
top-level group moves under one of them:
socket -> connection.socket (now required)
reconnection-policy -> connection.reconnection.policy
tls -> connection.tls (optional)
retry-policy -> query.retry.policy
load-balancing-policy -> query.load-balancing.policy
speculative-execution-policy -> query.speculative-execution.policy
query-defaults -> query.defaults
control-plane.system-queries.timeout
-> control-plane.queries.system.timeout
control-plane.schema-agreement.timeout-ms
-> control-plane.schema.agreement.timeout-ms
Beyond the re-homing, four changes alter what is emitted:
* `tls.enabled` and `adaptive-ordering.enabled` are gone — presence of each
group is what reports it as on, so both are omitted rather than emitted
with a false flag. `adaptive-ordering` also now requires a non-empty
signal list, which rules out the old empty-array form.
* `dc-failover` is renamed `fallback-to-non-preferred-nodes`.
* `query.defaults.request` is optional, so a disabled request timeout is
reported by omission. That was one of the two fields with no schema-valid
form; only `connection.requests.in-flight.max` is left.
* `node-location-preference` now has two homes, and they are filled
differently. `computeNodeDistance` derives node distance from the local DC
alone — a node outside it is IGNORED, and an IGNORED node gets no pool — so
the datacenter genuinely scopes connections and goes under
`connection.node-preference`. The rack never reaches that method; it only
reorders replicas at the head of a query plan, so the full preference
belongs under `query.load-balancing.node-preference` and the connection
group carries the datacenter half alone.
Also addresses three review comments from @dkropachev:
* Hostname verification is read from the engine factory the active
`JdkSslHandlerFactory` wraps, not from `getSslEngineFactory()`. The two can
differ, and going through the context could be the first caller to resolve
a `LazyReference` nothing uses — reading keystore files on a Netty event
loop, and costing the whole report if it throws.
* Reconnection delays are read from the running policy instead of the
profile, since both built-ins latch them at construction. This also makes
the schema's new `max-ms >= base-ms` invariant hold for free.
* The local DC the policy has already inferred is now reported, via the
schema's `dc-auto.local-dc` and `rack-auto.inferred-local-dc` slots. It is
null on the first control connection and resolved on every reconnect,
which is exactly the distinction those fields exist to draw.
The five JSON syntax errors in the doc's schema block (four stray commas and
two missing ones) are fixed in the shipped resource, which is otherwise a
verbatim copy so it stays auditable against the doc.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cross-driver design doc revised v1 in place again while this PR was in review. The delta from the previous revision is one optional key: `max-retries` is now permitted on the `standard-error-aware`, `never`, `downgrading-consistency` and `custom` retry-policy variants. `simple` already required it and `fallthrough` deliberately has no such key. Adding a key is backward-compatible per the spec's own evolution rule, so `$id` and `version` both stay at 1. The shipped schema resource is again byte-identical to the doc's schema block, which the doc revision also fixed the JSON syntax of (those five errors were already corrected here). Nothing new is emitted. The key reports a retry limit taken *from configuration*, and Java has no such option — no `max-retries` equivalent exists in `reference.conf`, `DefaultDriverOption` or `TypedDriverOption`, which is why the doc's own per-driver mapping table lists it as n/a for java. What the two built-ins have instead are per-error-type rules hardcoded in Java: a single attempt for read timeouts, write timeouts and unavailable, but an unbounded walk down the query plan for aborted requests and error responses. No single number describes that, so reporting one would be worse than omitting it. A custom policy cannot be introspected for a limit either. The three retry-policy tests now pin that omission, so the new schema slot is not later filled with a hardcoded count. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A field-by-field audit of the shipped v1 schema against the code that actually consumes each option found 30 of the 34 emitted fields exact. Three were not, and are fixed here; the other nine findings are approximations the schema shape cannot avoid, or items for the schema owner, and are listed in the PR description rather than acted on. 1. `InternalError` was caught around the whole report build. It is a `VirtualMachineError`, so that also swallowed one raised by anything else on the path -- config access, a user policy, Jackson -- rather than only the documented `getSimpleName()` JDK edge case it was added for. The top-level catch is now `RuntimeException`, and the `InternalError` catch sits next to the `getSimpleName()` call it guards, behind a package-private `simpleName` seam so the branch stays testable (no class a test can declare provokes the error). Raised in review. 2. `query.defaults.serial-consistency` was reported verbatim. `basic.request.serial-consistency` is an unvalidated string that nothing checks until the first conditional statement runs (`Conversions`), while the schema's enum admits only `SERIAL` and `LOCAL_SERIAL` -- so a session configured with anything else produced a document that fails validation as a whole. Unlike its *required* sibling `consistency`, this key is optional, so the reporter's own documented omission principle applies here and simply was not being followed. Now emitted only for the two schema members, with the class javadoc explaining why this one is not a third known gap. 3. `dc-auto` was fabricated for policies that never infer a datacenter. The preference was omitted only for the exact `BasicLoadBalancingPolicy`; every custom policy with no configured DC still reported `dc-auto`, which claims a datacenter *will* be settled on -- something `LoadBalancingPolicy` nowhere requires an implementation to do. Now reported when a DC is configured, or the policy has already resolved one (evidence, read through `instanceof`, so a subclass counts), or its exact class is one of the four built-ins known to infer; otherwise omitted from both parents, where the group is optional. This subsumes the old exclusion including its rack-only case: no built-in looks for a rack before it knows a datacenter. Raised in review. Test suite goes from 96 to 101 cases in `DefaultDriverConfigReporterTest`: the `getSimpleName()` fail-safe test is reworked to assert the binary-name fallback rather than a dropped report, plus an anonymous-policy naming case, an out-of-enum and a `LOCAL_SERIAL` serial-consistency case, a DC-agnostic custom policy, and a non-inferring policy that has nonetheless resolved a DC. The default-report test now also pins `serial-consistency`. Full `core` suite green (3889 tests). Deliberately not folded into the commits that introduce this code, unlike the previous rounds: a review is pending, and rewriting five SHAs mid-pass would throw away the reviewer's "what changed since I last looked" diff. The three regions all originate in commit `f445494`, so they can be folded on request. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The normative document has been revised again. Four changes reach this
driver:
- connection.requests.in-flight.max drops its 1..32767 range for the
shared positiveInteger definition;
- query.defaults.consistency gains SERIAL and LOCAL_SERIAL;
- query.defaults.client-timestamps and tls.hostname-verification
become optional, absent when the behavior is unknown;
- query.retry forbids a backoff on a fallthrough policy, which is
vacuous here since the reporter emits neither.
The first two close both documents this reporter knowingly emitted out
of schema. What is left of each is much narrower: in-flight.max must
still be positive and nothing in the driver enforces that, and a
consistency name outside the enum now needs a custom
ConsistencyLevelRegistry, since the built-in load balancing policies
reject anything the default registry does not know before a report is
ever built. The class javadoc and the two tests that pinned the old
bounds say so; a third test pins the serial levels as now valid.
The vendored resource is again byte-identical to the document's schema
block. Two description rewrites come with it, one of which fixes the
dangling ../../node-preferences pointer this branch reported upstream.
Also assert the absent retry backoff on the group rather than on the
policy node, which is where the schema puts it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The accessor this branch added returns a plain boolean, defaulting to false, so a custom factory that does validate host names is reported as one that does not. That default was chosen because the schema had no way to say "unknown" — tls.hostname-verification was required. It is optional now, and absent is defined to mean exactly that, so the accessor can stop guessing: it returns an Optional, empty by default, and the reporter omits the key rather than inventing a boolean for a security control it cannot read. The three built-in factories all know their own answer and keep reporting it. The other unknown case is unchanged in substance and now says so the same way: when the handler factory in force is not the driver's own JdkSslHandlerFactory, host name validation is a property of a JDK SSLEngine that is not on that path, so the key is omitted instead of reported false. The method is new in this branch and unreleased, so the signature change breaks nothing; revapi diffs against the last published release, where it does not exist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same defect as the SSL accessor, and the same fix. isClientSide() defaulted to true, on the grounds that assigning timestamps client-side is the interface's contract — but it is only the usual contract: returning NO_DEFAULT_TIMESTAMP from next() and letting the coordinator assign is documented and legal, and nothing short of calling next() can detect it. So the default reported every custom generator as client-side, which is the over-claim moving the check off the class was meant to remove; it only moved. query.defaults.client-timestamps is optional now, with absent defined as unknown, so the accessor returns an Optional and defaults to empty. Both monotonic built-ins always assign the timestamp themselves, so one override on their shared base covers them, and the server-side one keeps reporting false. The test that pinned the server-side path relied on Mockito answering an unstubbed boolean with false; it stubs explicitly now, since with an Optional return that silence would have turned it into an omission test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Twelve of this class's twenty-five profile reads used the no-fallback getters, which throw when an option is absent. One throw is caught by the single top-level handler, so a config source that omitted any one of them dropped all thirty-odd fields behind one warning — and the seven reads that did pass a fallback looked arbitrary next to them. They are not arbitrary any more, because the schema decides. Where the field or its enclosing group is optional, the fallback is the same "disabled" sentinel that already omits it, so an undefined option is reported exactly the way a disabled one is and no new branch is needed: an undefined page size reads as unbounded, an undefined timeout as off, an undefined max-executions drops the speculative-execution group. Where the field is required, omitting it would invalidate the document, so the fallback is the value reference.conf documents. That covers all twenty-five reads — nineteen with a fallback, six behind an isDefined guard — and makes the invariant statable: no missing option costs more than the field it describes. Two of the five required-field fallbacks cannot fire anyway, since ChannelFactory and the built-in load balancing policies read those options before any report is built; the javadoc says which and why. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fallback-to-non-preferred-nodes was read off max-nodes-per-remote-dc alone, but BasicLoadBalancingPolicy#maybeAddDcFailover appends remote nodes to a query plan only when that option is positive AND the policy has a local DC to treat as preferred. So a config that changed nothing but the option reported failover as on for a session where no remote node is ever appended — and the key is defined in terms of leaving the node preference, which such a report does not even carry. The second term is the predicate the reporter already computes for the node-preference groups, non-null exactly when a DC is configured, has already been resolved by the policy, or the policy is one of the four built-ins known to infer one. Reused rather than restated, so no policy logic is duplicated. One term is still missing on purpose: maybeAddDcFailover also consults isDcFailoverAllowedForRequest, which is false for a DC-local consistency while allow-for-local-consistency-levels is off. That is a per-request decision a statement can override, and re-deriving it in a diagnostic would duplicate exactly what this commit avoids. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…bsent The driver declares Jackson as a required dependency but documents that it can be excluded when unused (manual/core/integration), and enforces that for Insights by checking for it before building the lifecycle listener. Driver config reporting is a third Jackson user, it ships enabled, and it runs on the connection initialization path -- with no such check. On a classpath without Jackson, merely linking DefaultDriverConfigReporter raises NoClassDefFoundError. That is an Error rather than an exception, and it is raised while resolving the class rather than from any method it declares, so neither the reporter's own fail-safe nor ProtocolInitHandler can contain it: every control connection fails, and the session cannot be built at all. A documented, supported configuration went from "the report is skipped" to "the driver does not work", which is the opposite of the invariant the reporter is written around. So pick the implementation up front, the way buildLifecycleListeners() already does, and fall back to a no-op reporter that names no Jackson type anywhere -- one reference would make loading it fail for exactly the deployments it exists to serve. Logged unconditionally, unlike the Insights equivalent: nobody opted in to reporting, so nobody would think to look for a message saying it is off. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DefaultSession's init eagerly forces every user-facing policy before opening any connection, so that a bad configuration fails the session rather than each connect. The config reporter was the one component the reporting path touches that was left out, which had a second consequence: a Netty event loop became the first thread to load DefaultDriverConfigReporter and, with it, Jackson -- reading jars from an event loop, mid-Startup. Adding it to that list costs nothing (the reporter only stores the context) and makes the ordering the reporter's javadoc already relied on true by construction rather than by coincidence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The class javadoc, the comment in requests(), and the test that pins the
behavior all said a non-positive max-requests-per-connection "starts a
session -- one that cannot acquire a stream id" and is then reported. It
does not start one:
- a negative value makes StreamIdGenerator's BitSet throw while
ChannelFactory is still building the channel;
- zero leaves no stream id at all, so ChannelHandlerRequest fails the
control connection's own OPTIONS on preAcquireId, before Startup is
composed and long before anything asks for a report.
So of the two required fields the schema constrains more tightly than the
option behind them, only query.defaults.consistency is reachable through a
running driver -- and even that needs a custom ConsistencyLevelRegistry. The
same configuration would also drive orphaned.max negative, which is a second
reason to read this as one unreachable shape rather than one field's gap.
Behavior is unchanged: the value is still passed through, and still pinned,
so it stays defined if the driver ever stops failing that early. Only the
claims about it change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
F6 in the value audit covers a configured rack reported for a policy that ignores it, and X3 covers not fabricating dc-auto for a policy that may never infer one. Neither covers the case in between: a configured *datacenter* reported for a custom load balancing policy. Both parents claim an effect only the built-ins produce. connection's node-preference says the datacenter decides which nodes hold a pool, which holds because BasicLoadBalancingPolicy#computeNodeDistance makes an out-of-DC node IGNORED; query.load-balancing's says it scopes routing. A custom LoadBalancingPolicy computes distance itself and need not read basic.load-balancing-policy.local-datacenter or withLocalDatacenter at all, so it may honor neither. Kept as-is, on the same grounds as F6 -- hiding a setting the operator really did make is the worse failure mode -- but the asymmetry with X3 is worth stating where the decision lives: nothing is inferred on a custom policy's behalf, while what was configured is passed through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The conformance suite covers every branch of every discriminated union the
reporter can emit, except one: speculative-execution's custom variant. The
constant variant is validated, and the subclass-reported-as-custom test
asserts the shape but never runs it past the schema.
It passes as written -- {type, name} with additionalProperties: true is valid
-- so this closes coverage rather than fixing anything. Worth having because
the enclosing group is optional and behaves differently per branch: it is
dropped entirely for NoSpeculativeExecutionPolicy but kept here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
connectionsPerShard() factored out arithmetic that initialize() and resize() each spelled out, which is a fine change but has nothing to do with driver config reporting. Reverted to keep the branch to its subject. ProtocolFeatureStore#getNodeShardingInfo and the DriverChannel simplification stay: the reporter needs the former, and the latter is its call site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tls() restated, at length, the argument buildJson()'s javadoc already makes for reading the engine factory off the handler in force rather than through the context. Replaced with a pointer, leaving the javadoc as the single home for it and the method comment to explain only what it does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g with query.speculative-execution.policy read max-executions and delay-ms from the default profile at report time, while the policy latched both into final fields when the context built it -- and advanced.speculative-execution-policy is documented as not modifiable at runtime. After a configuration reload the report published numbers no request executes with: a max-executions lowered to 1 dropped the whole group, claiming no speculative execution while the policy still fired three, and a negative delay put a value the schema's nonNegativeInteger rejects into an otherwise valid document. A context that overrides buildSpeculativeExecutionPolicies() reaches the same divergence with no reload at all. Both values now come off the running ConstantSpeculativeExecutionPolicy, the way the reconnection policy already is, so that policy's own constructor validation (max-executions >= 1, delay >= 0) keeps the report inside the schema's ranges by construction. Raised by @dkropachev on the 3.x port (scylladb#974), where the same two fields were misreported as a custom policy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bd539a1 to
4ddf4ca
Compare
What ☑️
Stage 2 (the payload) of driver configuration reporting: replaces the stage-1
{"version":1}placeholder with the full
DRIVER_CONFIGreport — the effective configuration of the driver'sdefault execution profile plus the context's policies, serialized to the normative cross-driver JSON
schema shape. Stage 1 (#967) is merged; this branch is rebased onto
scylla-4.x, so there is nostage-1 noise in the diff.
Gated behind
advanced.driver-config-reporting.enabled, which ships enabled (per@dkropachev's cross-driver review). Turning it off suppresses only the
DRIVER_CONFIGblob —SESSION_IDrides on every connection unconditionally, independently of this flag, so "off" is not"zero change on the wire".
Read the commits in order; each is formatter-clean and green on its own.
The report 🧩
Built from the default execution profile + the context's policies, and rebuilt on every
control-connection init, so it always reflects the current (possibly runtime-reloaded) config.
Three groups:
connection(connect timeout, request capacity, pooling, socket options,reconnection policy, TLS when on, and the datacenter preference that scopes pooling),
control-plane(internal-query and schema-agreement timeouts), andquery(per-requestdefaults, plus the retry, load-balancing and speculative-execution policies).
Against the shipped default configuration, 938 bytes (pretty):
{ "version": 1, "connection": { "connect": { "timeout-ms": 5000 }, "requests": { "in-flight": { "max": 1024 }, "orphaned": { "max": 256 } }, "pool": { "shard-aware": { "enabled": true } }, "socket": { "tcp-no-delay": true, "keep-alive": false, "reuse-address": false }, "reconnection": { "policy": { "type": "exponential", "base-ms": 1000, "max-ms": 60000 } }, "node-preference": { "type": "dc-auto" } }, "control-plane": { "queries": { "system": { "timeout": { "client-side-ms": 5000 } } }, "schema": { "agreement": { "timeout-ms": 10000 } } }, "query": { "defaults": { "page": { "size": 5000 }, "consistency": "LOCAL_ONE", "serial-consistency": "SERIAL", "idempotence": false, "client-timestamps": true, "request": { "timeout-ms": 2000 } }, "retry": { "policy": { "type": "standard-error-aware" } }, "load-balancing": { "policy": { "type": "token-aware", "load-distribution": "shuffle", "fallback-to-non-preferred-nodes": false, "adaptive-ordering": { "signals": [ "response-rate", "in-flight-requests", "recovery-state" ] } }, "node-preference": { "type": "dc-auto" } } } }Verified on the wire by a
tsharkcapture of theSTARTUPframes against a single-node CCMScyllaDB (protocol v4, default config):
SESSION_IDon every connection,DRIVER_CONFIGonly on thecontrol connection, and gone when the flag is off. Verified end to end through
system.clients.client_options(ScyllaDB 2026.1) andsystem_views.clients(Cassandra 4.1),untruncated, with the one backend-conditional key differing as it should.
Invariants 🔒
SESSION_IDis still emitted and only the config blob is dropped.
RuntimeExceptiononly — deliberately notbare
Error, soOutOfMemoryError/StackOverflowErrorstill surface. The oneErrorthis classcan provoke is the
InternalErrorthatgetClass().getSimpleName()raises for certain syntheticclasses; that is caught at the call site, behind a package-private seam so the branch stays
testable.
manual/core/integrationdocuments that the driver "can operatenormally without" Jackson, and
DefaultDriverContextalready honours that for Insights by checkingDefaultDependencyChecker.isPresent(JACKSON)before building the listener. Reporting was a thirdJackson user with no such check, and it is default-on and on the connection-init path — so on that
classpath linking
DefaultDriverConfigReporterraisedNoClassDefFoundError, anErrorraisedwhile resolving the class rather than from any method it declares. Neither the
try/catchabovenor
ProtocolInitHandlercould contain it: every control connection failed and the session couldnot be built. Now the implementation is chosen up front, falling back to a
NoopDriverConfigReporterthat names no Jackson type anywhere (one reference would make loading it fail for exactly the
deployments it exists to serve). Logged unconditionally, unlike Insights: nobody opted in to
reporting, so nobody would think to look for a message saying it is off. Verified both ways against
core's real runtime classpath with its Jackson jars removed.
DefaultSession.init()already forces eagerly. It was the one component on the reporting path left lazy, which made a
Netty event loop the first thread to load it and Jackson — jar reads, mid-
STARTUP. This is alsowhat makes the ordering
buildJson()'s javadoc relies on true by construction.STARTUPoptionvalues go through
ByteBufPrimitiveCodec.writeString, which writes a 16-bit length prefix viaByteBuf.writeShortwith no bounds check, so a value over 65535 bytes silently truncates theprefix modulo 65536 while still appending the whole body — a corrupt frame and a failed handshake,
and not something the
try/catchcan save, since nothing throws. Parts of the report areuser-supplied and unbounded (DC/rack names, consistency levels, custom policy class names), so
without this the "reporting must never prevent a connection" invariant simply wasn't true. Measured
on the UTF-8 bytes, since that is what the prefix counts.
as
null. Same where an optional key's configured value is outside what the schema can express(a disabled request timeout, a disabled
SO_LINGER, an unbounded page size), and same where theanswer is genuinely unknown — the two cases the schema made optional for exactly that purpose.
no-fallback getters, which throw on an absent option, so a config source omitting any one of them
dropped all ~34 fields behind a single WARN. Every read now either sits behind
isDefinedorpasses an explicit fallback, and the schema picks which: an optional field falls back to the same
"disabled" sentinel that already omits it, a required one to the value
reference.confdocuments.Design decisions worth questioning 📐
instanceof— so a user subclass of abuilt-in falls through to
{type:"custom", name:<class>}instead of being misreported as theunmodified built-in.
connection.reconnection.policyand
query.speculative-execution.policydescribe the policy that is actually reconnecting andspeculating. The built-ins latch these numbers into final fields when the context builds them, and
advanced.speculative-execution-policyis documented as not modifiable at runtime, so areloaded profile can carry values no request executes with — and, unlike a constructor, admits
values the schema rejects: a negative
delay-ms, or amax-executionsof 1 that would drop thewhole group while the policy still speculates. Reading the instance makes those ranges hold by
construction. Two policy-derived values still read the profile —
adaptive-orderingandfallback-to-non-preferred-nodes— because those policies expose no accessor for what they latched;the class javadoc names them rather than leaving it implied.
control-plane.queries.system.timeout.server-side-msis ScyllaDB-only.CassandraSchemaQueriesadds a
USING TIMEOUTclause to schema queries there, makingadvanced.metadata.schema.request-timeouta genuine server-side timeout on that backend; omittedon generic Cassandra. Driven by a
scyllaDbsignal derived on the control connection from thefeature store's sharding info — the same proxy check
shouldApplyUsingTimeout()already uses.getSslHandlerFactory(), notgetSslEngineFactory()(per@sylwiaszunejko). The handler factory is the reference
ChannelFactoryinstalls the SSL handlerfrom, and
buildSslHandlerFactory()is the documented expert extension point (e.g. Netty's nativeOpenSSL): an override supplies no engine factory, so reading the engine factory reported such a
session as plaintext when it is in fact encrypted. Host name validation is then read off the engine
factory the active handler actually wraps — never through the context, which can name a different,
unused one and whose
LazyReferencethe reporter would be the first to force (keystore reads on aNetty event loop, mid-
STARTUP).SslEngineFactory.isHostnameValidationRequired()andTimestampGenerator.isClientSide()returnOptional<Boolean>, empty by default. Host namevalidation is a property of the JDK
SSLEngine, unreadable through an opaque handler factory; anda custom
TimestampGeneratoris free to returnStatement.NO_DEFAULT_TIMESTAMPand delegate tothe coordinator, which no class check can detect and which calling
next()to find out would haveside effects. Both keys are now optional in the schema with absence defined as unknown, so an
implementation that cannot answer is reported by omission rather than by a guessed boolean — which
for these two fields would misdescribe a security control and a write-timestamp source. Both
methods are
default, so existing implementations keep compiling.holds these options as
Durationand schedules several in nanoseconds, so truncating a 500 µstimeout to
0would report a live timeout as the very value the field defines as off. Applies toschema.agreement.timeout-ms,queries.system.timeout.client-side-ms,query.defaults.request.timeout-msandreconnection.policy.delay-ms. Three fields aredeliberately exempt, because
0is what they really mean there:connection.connect.timeout-ms(Netty's
CONNECT_TIMEOUT_MILLIStruncates identically, and 0 disables it),...server-side-ms(the value goes on the wire as a
USING TIMEOUTmillisecond argument, so sub-millisecond reallyis
0msserver-side) andspeculative-execution.policy.delay-ms(reference.confdocumentssub-millisecond delays as equivalent to 0).
connection.requests.orphaned.maxis the effective threshold, not the configured one.ChannelFactoryrequiresmax-orphan-requeststo stay belowmax-requests-per-connectionandsilently substitutes a quarter of the latter otherwise. Reporting the configured value would
describe a threshold no connection was built with, so the correction lives in one place —
ChannelFactory.effectiveMaxOrphanRequests(), which the channel setup itself calls.node-preferenceslots are filled differently, because in Java they mean differentthings.
computeNodeDistancederives node distance from the local DC alone — a node outside it isIGNORED, and anIGNOREDnode gets no pool — so the datacenter genuinely scopes which nodes areconnected to, and goes under
connection.node-preference. The rack never reaches that method: itonly reorders replicas at the head of a query plan, with connections still held across the whole
local DC. So the full preference (rack included) goes under
query.load-balancing.node-preference,and the connection group carries the datacenter half alone. Emitting the same object in both would
claim a rack-scoped connection pool that does not exist.
load-distributionisshuffleandadaptive-orderingmaps to slow-replica avoidance. Thebuilt-ins shuffle the replica head of every query plan unconditionally
(
BasicLoadBalancingPolicy.shuffleHead, no config to disable), soround-robinwould describe onlythe non-replica tail and
replica-setwould claim the order is untouched (see A1 for the one casethis misses). Java has no latency-percentile ordering, so
adaptive-orderingmaps to the one realmechanism,
DefaultLoadBalancingPolicy's slow-replica avoidance, with its signals read offavoidSlowReplicasrather than guessed — andlatencydeliberately absent, since those samplesrecord when responses arrived, not how long they took. Its presence is also now the only thing
distinguishing
BasicLoadBalancingPolicyin the report, which has no such mechanism at all.Spec conformance 🔍
The v1 schema is shipped verbatim as a test resource, byte-identical to the design document's
schema block, and every representative report is validated against it in
DefaultDriverConfigReporterTest— enforced, not asserted. A negative test confirms the validatoractually rejects an out-of-schema document.
Every report a stock configuration can produce validates. Two required fields are constrained more
tightly than the option behind them, but only one is reachable through a running driver. Both are
reported truthfully and pinned by tests that assert the violation:
query.defaults.consistencyis a closed enum whilebasic.request.consistencyis an unvalidatedstring. The built-in load balancing policies resolve it through the
ConsistencyLevelRegistryintheir constructor, so an unknown name fails the session before any report exists — reaching this
needs a custom registry defining extra names, which is the case CodeRabbit raised. This is the
one real gap.
connection.requests.in-flight.maxmust be positive, and nothing validatesadvanced.connection.max-requests-per-connectionagainst that —ChannelFactoryhands the valuestraight to
StreamIdGenerator, which does not range-check it. An earlier revision of thisdescription claimed such a setting starts a session; it does not. The connection fails first: a
negative value makes
StreamIdGenerator'sBitSetthrow whileChannelFactoryis still buildingthe channel, and
0leaves no stream id for the control connection's ownOPTIONS, whichChannelHandlerRequestfails onpreAcquireIdbeforeSTARTUPis composed. So this is unreachableby construction, not a live exposure. The value is still passed through and still pinned, so the
behaviour stays defined if the driver ever stops failing that early. (The same setting would also
drive
orphaned.maxnegative — a second reason to read it as one unreachable shape rather than onefield's gap.)
A third shape was reachable until this branch's latest push:
query.speculative-execution.policytook both its numbers from the profile, so a reload could put a negative
delay-ms— whichnonNegativeIntegerrejects — into an otherwise valid document, or drop the group while the policystill speculated. Both now come off the policy, whose constructor admits neither.
Fabricating an admissible value would misreport a setting an operator may have chosen deliberately,
and dropping the whole report would punish every other group for one field.
Approximations, flagged not changed⚠️
load-balancing.policy.load-distributionshufflenewQueryPlanPreserveReplicas, which never shuffles —replica-setin schema terms — anddefault-lwt-request-routing-methodships asPRESERVE_REPLICA_ORDER. So every LWT statement on a default config is distributed the way the report says it is not. No single enum value is honest.load-balancing.policy.fallback-to-non-preferred-nodesmax-nodes-per-remote-dc > 0and a datacenter preference existsmaybeAddDcFailoveralso consultsisDcFailoverAllowedForRequest, false for a DC-local consistency whileallow-for-local-consistency-levelsis off — and both of those ship as the default, so on a config that changes nothing butmax-nodes-per-remote-dcthe report saystruewhile no ordinary statement fails over. Note the "it's per-request, a statement can override it" argument does not carry on its own:query.defaults.consistencyis published under the same caveat. The real cost is that closing it needsConsistencyLevelRegistryresolution of a string this report deliberately passes through unvalidated. A schema value meaning "conditional" is the honest fix.connection.socket.keep-alive,.reuse-addressfalsewhen unsetStandardSocketOptionsdocuments as system dependent.falseholds for JDK NIO on Linux; unverified for the native transports. Both keys are required, so omission is not available.control-plane.queries.system.timeout.client-side-msCONTROL_CONNECTION_TIMEOUTMETADATA_SCHEMA_REQUEST_TIMEOUT, so the two siblings do not describe the same query — an operator debugging a slow schema query reads the wrong number. Already on the thread with @dkropachev; the fix is aqueries.schemasibling, blocked today byadditionalProperties:false.node-preferencedatacenter / rack valuesOptionalLocalDcHelper/OptionalLocalRackHelperpass the configured string to the policy verbatim and match withObjects.equals, so a configured" dc1 "is reported asdc1while the running policy matches no node. Kept and test-pinned:nonEmptyStringleaves no way to report"", andtype:"dc"with the key omitted is invalid too.query.load-balancing.node-preferencetype:"rack"DefaultLoadBalancingPolicy;BasicLoadBalancingPolicynever readslocalRack, andPRESERVE_REPLICA_ORDERignores it as well. Kept — the value is configured, and hiding a real setting is the worse failure mode.node-preferenceslots, for a custom load balancing policyconnection's says the DC decides which nodes hold a pool, which holds becauseBasicLoadBalancingPolicy#computeNodeDistancemakes an out-of-DC nodeIGNORED;query.load-balancing's says it scopes routing. A custom policy computes distance itself and need not readlocal-datacenterorwithLocalDatacenterat all. Kept on A6's grounds. Deliberately asymmetric with the no-DC case, where the group is omitted rather than reporting adc-autothe SPI never promises: nothing is inferred on a custom policy's behalf, while what was configured is passed through.Two cosmetic ones, noted for completeness: a negative
schema.agreement.timeout-msnormalizes to0(same outcome as
0, one extra round trip, and the schema cannot say "negative"); andconnection.connect.timeout-msis reported as a fulllongwhileDefaultNettyOptionsnarrows itwith
intValue(), so a connect timeout past ~24.8 days wraps in Netty.Follow-up ⏭️
For the schema owner — all for the document rather than here:
describe fields the schema no longer has, and both sample payloads still show the pre-restructure
flat envelope, so they fail validation against the document's own schema.
$idandversionstill say v1 /const: 1although earlier revisions removed a requiredtop-level group and renamed load-balancing fields. By the schema's own versioning rule that is a
major bump; harmless while every implementation is unreleased, but a v1 consumer cannot tell the
shapes apart.
dc-autocarries the inferred value in plainlocal-dcwhilerack-autouses an explicitinferred-prefix. Implemented as specified; the asymmetry is easy to misread.node-location-preferencehas no "no preference" variant (raised by @dkropachev). Omitting theoptional group is the schema-valid answer and is what this PR does, but a
nonetype would say itpositively.
query.defaults.consistencyneeds either a wider type or a documented rule for names outside itsenum — the one conformance gap a running Java driver can still produce. (An earlier revision of
this list also asked the spec to define consumer behaviour for a non-positive
in-flight.max;withdrawn — see Spec conformance, no session can reach it.)
control-plane.queries.system.timeoutgroupsclient-side-msandserver-side-msas two views ofone timeout. For Java they are not — see A4; a
queries.schemasibling would let each class ofquery carry an honest pair.
speculative-execution.policy.percentileisexclusiveMinimum: 0, while 3.x'sPercentileSpeculativeExecutionPolicyaccepts0.0— so an accurate report of that configurationis out of schema (raised by @dkropachev on Client config reporting (3.x) — stage 2: full DRIVER_CONFIG report #974). Unreachable from Java 4.x, which has no percentile
policy at all, but the schema is shared.
Other:
restructure. The sub-millisecond reconnection floor does not carry over: 3.x's
ConstantReconnectionPolicyholds along delayMs, so there is no sub-millisecond value totruncate. Separate PR, separate branch. Traffic goes the other way too: the
speculative-execution source-of-truth fix in this push was raised there first, and 3.x additionally
had to stop reporting both built-ins as
custom, which this branch never did.default-on flip is intentional; reasoning is on the threads.
DriverBlockHoundIntegrationITis JDK 14+ only and was not run locally. With reporting on bydefault the report is built on a Netty event loop; reasoned safe (no SSL factory resolution or IO
with the default config, and Jackson is in-memory), but worth watching in CI. The larger half of
that risk is gone: the reporter is now resolved during session init, so the event loop is no longer
the first thread to load it and Jackson.
here.
manual/render with a spurious#prefix (href="#../configuration/reference/") — a site-wideMyST artifact affecting pre-existing links too, so it wants its own issue rather than a partial fix
here. The one link this PR would have added was dropped for that reason.
🤖 Generated with Claude Code