Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2acdd7d
refactor: send SESSION_ID from StartupOptionsBuilder on every connection
nikagra Aug 3, 2026
a72f5a4
refactor: extract the orphan-request correction and the sharding-info…
nikagra Aug 3, 2026
4a73f5e
feat(ssl): expose whether an SslEngineFactory validates host names
nikagra Aug 3, 2026
2a4f03d
feat(time): expose whether a TimestampGenerator assigns timestamps cl…
nikagra Aug 4, 2026
3701629
test: ship the normative v1 driver-config schema and its validator
nikagra Aug 3, 2026
f445494
feat: report the full driver configuration to the cluster at connecti…
nikagra Aug 3, 2026
4cee5aa
feat: report the driver configuration by default
nikagra Aug 3, 2026
0c393fd
feat: expose the latched policy state the config report needs to read
nikagra Aug 5, 2026
fb21cfc
feat: report the driver configuration in the revised v1 schema shape
nikagra Aug 5, 2026
b911b0b
feat: let retry policies report a configured retry limit
nikagra Aug 5, 2026
dd32273
fix: correct three values the DRIVER_CONFIG report got wrong
nikagra Aug 6, 2026
769d791
test: resync the vendored v1 schema with the latest revision
nikagra Aug 6, 2026
ad487cd
feat(ssl): let an SslEngineFactory report unknown host name validation
nikagra Aug 6, 2026
d8077a2
feat(time): let a TimestampGenerator report unknown timestamp assignment
nikagra Aug 6, 2026
921f8fb
fix: never lose the whole report to one missing config option
nikagra Aug 6, 2026
911df4f
fix: report DC failover as off when there is no datacenter preference
nikagra Aug 6, 2026
9299572
fix: keep the config report off the connection path when Jackson is a…
nikagra Aug 6, 2026
e8d8e8f
fix(session): resolve the driver config reporter during session init
nikagra Aug 6, 2026
b6584b6
docs: the non-positive in-flight.max gap is unreachable, not live
nikagra Aug 6, 2026
2d062a2
docs: flag node-preference as an approximation for custom policies
nikagra Aug 6, 2026
ef6b7e6
test: validate a custom speculative-execution policy against the schema
nikagra Aug 6, 2026
06a4270
refactor: drop an unrelated ChannelPool extraction
nikagra Aug 6, 2026
1474db3
docs: trim rationale duplicated between tls() and buildJson()
nikagra Aug 6, 2026
4ddf4ca
fix: report the speculative execution parameters the policy is runnin…
nikagra Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1175,16 +1175,21 @@ public enum DefaultDriverOption implements DriverOption {
*/
ADDRESS_TRANSLATOR_RESOLVE_ADDRESSES("advanced.address-translator.resolve-addresses"),
/**
* Whether the driver reports its effective configuration to ScyllaDB at connection time.
*
* <p>When {@code true}, the driver adds two entries to the CQL {@code STARTUP} options, which
* ScyllaDB stores in {@code system.clients.client_options} so operators can inspect driver
* settings while investigating incidents: a {@code SESSION_ID} on every connection (so the server
* can group a session's connections) and a compact JSON payload under the {@code DRIVER_CONFIG}
* key on the control connection only. At this stage the {@code DRIVER_CONFIG} payload carries
* only schema-version metadata (<code>{"version":1}</code>); reporting of the effective
* configuration fields is planned for a later stage. When {@code false}, neither entry is sent
* and there is no change on the wire.
* Whether the driver reports its effective configuration to the cluster at connection time.
* Defaults to {@code true}.
*
* <p>When {@code true}, the control connection adds a compact JSON payload under the {@code
* DRIVER_CONFIG} key to its CQL {@code STARTUP} options, which the server stores in its
* client-connection system table ({@code system.clients} on ScyllaDB, {@code
* system_views.clients} on Cassandra 4.1+) so operators can inspect driver settings while
* investigating incidents. It describes the effective configuration of the driver's default
* execution profile (connection/socket settings, timeouts, retry/reconnection/
* speculative-execution/load-balancing policies, connection pooling, query defaults, and TLS).
* Only the control connection sends it, since it describes the whole session. When {@code false},
* {@code DRIVER_CONFIG} is not sent.
*
* <p>Reporting is best-effort: if the report cannot be built, or would exceed 32 KiB, it is
* skipped (with a warning) rather than allowed to interfere with connecting.
*
* <p>Value type: boolean
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ protected static void fillWithDriverDefaults(OptionsMap map) {
// values) with no sensible scalar default, analogous to how CONFIG_RELOAD_INTERVAL is omitted.
map.put(TypedDriverOption.CLIENT_ROUTES_NATIVE_TRANSPORT_PORT, 9042);
map.put(TypedDriverOption.CLIENT_ROUTES_SHARD_AWARENESS_ENABLED, false);
map.put(TypedDriverOption.DRIVER_CONFIG_REPORTING_ENABLED, false);
map.put(TypedDriverOption.DRIVER_CONFIG_REPORTING_ENABLED, true);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

@Immutable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ public String toString() {
new TypedDriverOption<>(
DefaultDriverOption.CLIENT_ROUTES_SHARD_AWARENESS_ENABLED, GenericType.BOOLEAN);

/** Whether the driver reports its configuration to ScyllaDB at connection time. */
/** Whether the driver reports its configuration to the cluster at connection time. */
public static final TypedDriverOption<Boolean> DRIVER_CONFIG_REPORTING_ENABLED =
new TypedDriverOption<>(
DefaultDriverOption.DRIVER_CONFIG_REPORTING_ENABLED, GenericType.BOOLEAN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import edu.umd.cs.findbugs.annotations.Nullable;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Optional;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
Expand Down Expand Up @@ -133,6 +134,12 @@ public SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint) {
return engine;
}

@NonNull
@Override
public Optional<Boolean> isHostnameValidationRequired() {
return Optional.of(requireHostnameValidation);
}

@Override
public void close() {
// nothing to do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.datastax.oss.driver.api.core.metadata.EndPoint;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Optional;
import javax.net.ssl.SSLEngine;

/**
Expand All @@ -37,4 +38,26 @@ public interface SslEngineFactory extends AutoCloseable {
*/
@NonNull
SSLEngine newSslEngine(@NonNull EndPoint remoteEndpoint);

/**
* Whether this factory validates the server certificate against the node's host name, or {@link
* Optional#empty()} if that is not known.
*
* <p>This is a diagnostic accessor (reported in the driver-configuration blob sent to the server
* at connection time); it does not affect how {@link #newSslEngine} behaves.
*
* <p>The driver's built-in factories override this to return their real value. It is a {@code
* default} method so that existing implementations keep compiling, and the default is empty
* because the driver can neither assume nor rule out that an arbitrary custom factory performs
* host name validation: reporting either boolean would misdescribe a security control. An empty
* result is reported by leaving the corresponding field out of the configuration blob altogether,
* which is what the cross-driver schema asks for. Custom factories that know should override this
* to report accurately.
*
* @since 4.19.2.1
*/
@NonNull
default Optional<Boolean> isHostnameValidationRequired() {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package com.datastax.oss.driver.api.core.time;

import com.datastax.oss.driver.api.core.cql.Statement;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Optional;

/**
* Generates client-side, microsecond-precision query timestamps.
Expand All @@ -40,4 +42,28 @@ public interface TimestampGenerator extends AutoCloseable {
* timestamp).
*/
long next();

/**
* Whether this generator assigns the write timestamp client-side, as opposed to leaving it to the
* coordinator, or {@link Optional#empty()} if that is not known.
*
* <p>This is a diagnostic accessor (reported in the driver-configuration blob sent to the server
* at connection time); it does not affect {@link #next()}.
*
* <p>The driver's built-in generators override this to return their real value. It is a {@code
* default} method so that existing implementations keep compiling, and the default is empty
* because assigning timestamps client-side is only this interface's <em>usual</em> contract: an
* implementation is free to return {@link Statement#NO_DEFAULT_TIMESTAMP} from {@link #next()}
* and leave the timestamp to the coordinator, which no inspection short of calling {@link
* #next()} — and thereby consuming a timestamp — could detect. An empty result is reported by
* leaving the corresponding field out of the configuration blob altogether, which is what the
* cross-driver schema asks for. Implementations that know should override this to report
* accurately.
*
* @since 4.19.2.1
*/
@NonNull
default Optional<Boolean> isClientSide() {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ public class ChannelFactory {
public static final String INFLIGHT_HANDLER_NAME = "inflight";
public static final String INIT_HANDLER_NAME = "init";

/**
* The number of orphaned requests a connection is actually built with, which is not always the
* configured {@code advanced.connection.max-orphan-requests}: that option has to stay below
* {@code advanced.connection.max-requests-per-connection}, and a value that does not is silently
* corrected to a quarter of it (the caller logs a warning when that happens).
*
* <p>Shared with {@code DefaultDriverConfigReporter}, which reports this number as {@code
* connection.requests.orphaned.max}: one implementation means the report cannot claim a limit the
* connection was not built with.
*
* @param maxRequestsPerConnection the configured {@code max-requests-per-connection}.
* @param maxOrphanRequests the configured {@code max-orphan-requests}.
*/
public static int effectiveMaxOrphanRequests(
int maxRequestsPerConnection, int maxOrphanRequests) {
return (maxOrphanRequests >= maxRequestsPerConnection)
? maxRequestsPerConnection / 4
: maxOrphanRequests;
}

private final String logPrefix;
protected final InternalDriverContext context;

Expand Down Expand Up @@ -377,20 +397,21 @@ protected void initChannel(Channel channel) {
(int) defaultConfig.getBytes(DefaultDriverOption.PROTOCOL_MAX_FRAME_LENGTH);
int maxRequestsPerConnection =
defaultConfig.getInt(DefaultDriverOption.CONNECTION_MAX_REQUESTS);
int maxOrphanRequests =
int configuredMaxOrphanRequests =
defaultConfig.getInt(DefaultDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS);
if (maxOrphanRequests >= maxRequestsPerConnection) {
int maxOrphanRequests =
effectiveMaxOrphanRequests(maxRequestsPerConnection, configuredMaxOrphanRequests);
if (configuredMaxOrphanRequests >= maxRequestsPerConnection) {
if (LOGGED_ORPHAN_WARNING.compareAndSet(false, true)) {
LOG.warn(
"[{}] Invalid value for {}: {}. It must be lower than {}. "
+ "Defaulting to {} (1/4 of max-requests) instead.",
logPrefix,
DefaultDriverOption.CONNECTION_MAX_ORPHAN_REQUESTS.getPath(),
maxOrphanRequests,
configuredMaxOrphanRequests,
DefaultDriverOption.CONNECTION_MAX_REQUESTS.getPath(),
maxRequestsPerConnection / 4);
maxOrphanRequests);
}
maxOrphanRequests = maxRequestsPerConnection / 4;
}

InFlightHandler inFlightHandler =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,7 @@ public int getShardId() {
}

public ShardingInfo getShardingInfo() {
ConnectionShardingInfo info = getSupportedFeatures().getShardingInfo();
return info != null ? info.shardingInfo : null;
return getSupportedFeatures().getNodeShardingInfo();
}

public LwtInfo getLwtInfo() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,24 @@ Message getRequest() {
return request = Options.INSTANCE;
case STARTUP:
Map<String, String> startupOptions = new HashMap<>(context.getStartupOptions());
if (featureStore != null) {
featureStore.populateStartupOptions(startupOptions);
featureStore.populateStartupOptions(startupOptions);
// The DRIVER_CONFIG blob describes the whole session, so only the control connection
// carries it (options.reportConfig); the other connections are correlated to it by the
// SESSION_ID that every connection already carries from context.getStartupOptions().
// No-op when driver config reporting is disabled.
if (options.reportConfig) {
// Sharding info is the backend-conditional signal the report needs: only its
// presence is read, since non-null is the driver's own proxy check for "this is
// ScyllaDB" (also used, independently, by
// CassandraSchemaQueries.shouldApplyUsingTimeout()), which gates ScyllaDB-only
// server-side behavior such as the USING TIMEOUT clause on schema queries. It is
// only populated once the OPTIONS/SUPPORTED handshake has run, which
// ChannelFactory always requests.
context
.getDriverConfigReporter()
.populateControlConnectionOptions(
startupOptions, featureStore.getNodeShardingInfo());
}
// Adds SESSION_ID on every connection and DRIVER_CONFIG on the control connection
// (options.reportConfig); no-op when driver config reporting is disabled.
context
.getDriverConfigReporter()
.populateStartupOptions(startupOptions, options.reportConfig);
return request = new Startup(startupOptions);
case GET_CLUSTER_NAME:
return request = CLUSTER_NAME_QUERY;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class ConstantReconnectionPolicy implements ReconnectionPolicy {
private static final Logger LOG = LoggerFactory.getLogger(ConstantReconnectionPolicy.class);

private final String logPrefix;
private final Duration delay;
private final ReconnectionSchedule schedule;

/** Builds a new instance. */
Expand All @@ -64,9 +65,21 @@ public ConstantReconnectionPolicy(DriverContext context) {
+ " (got %d)",
delay));
}
this.delay = delay;
this.schedule = () -> delay;
}

/**
* The fixed delay between reconnection attempts that this instance was built with.
*
* <p>Read from the configuration once, at construction: a later configuration reload does not
* affect an already-running policy. Exposed so that diagnostics can describe the delay actually
* in force rather than whatever the profile currently says.
*/
public Duration getDelay() {
return delay;
}

@NonNull
@Override
public ReconnectionSchedule newNodeSchedule(@NonNull Node node) {
Expand Down
Loading