Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.datastax.oss.driver.api.core.config;

import com.datastax.oss.driver.api.core.session.SessionBuilder;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import net.jcip.annotations.Immutable;

/**
* Configuration for client routes, used in PrivateLink-style deployments.
*
* <p>Client routes enable the driver to discover and connect to nodes through a load balancer (such
* as AWS PrivateLink) by reading endpoint mappings from the {@code system.client_routes} table.
* Each endpoint is identified by a connection ID and maps to specific node addresses.
*
* <p>This configuration is mutually exclusive with a user-provided {@link
* com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator}. If client routes are
* configured, the driver will use its internal client routes handler for address translation.
*
* <p>Example usage:
*
* <pre>{@code
* ClientRoutesConfig config = ClientRoutesConfig.builder()
* .addEndpoint(new ClientRoutesEndpoint(
* UUID.fromString("12345678-1234-1234-1234-123456789012"),
* "my-privatelink.us-east-1.aws.scylladb.com:9042"))
* .withDnsCacheDuration(1000L) // Cache DNS for 1 second (default: 500ms)
* .build();
*
* CqlSession session = CqlSession.builder()
* .withClientRoutesConfig(config)
* .build();
* }</pre>
*
* @see SessionBuilder#withClientRoutesConfig(ClientRoutesConfig)
* @see ClientRoutesEndpoint
*/
@Immutable
public class ClientRoutesConfig {

private static final long DEFAULT_DNS_CACHE_DURATION_MILLIS = 500L;

private final List<ClientRoutesEndpoint> endpoints;
private final String tableName;
private final long dnsCacheDurationMillis;

private ClientRoutesConfig(
List<ClientRoutesEndpoint> endpoints, String tableName, long dnsCacheDurationMillis) {
if (endpoints == null || endpoints.isEmpty()) {
throw new IllegalArgumentException("At least one endpoint must be specified");
}
if (dnsCacheDurationMillis < 0) {
throw new IllegalArgumentException("DNS cache duration must be non-negative");
}
this.endpoints = Collections.unmodifiableList(new ArrayList<>(endpoints));
this.tableName = tableName;
this.dnsCacheDurationMillis = dnsCacheDurationMillis;
}

/**
* Returns the list of configured endpoints.
*
* @return an immutable list of endpoints.
*/
@NonNull
public List<ClientRoutesEndpoint> getEndpoints() {
return endpoints;
}

/**
* Returns the name of the system table to query for client routes.
*
* @return the table name, or null to use the default ({@code system.client_routes}).
*/
@Nullable
public String getTableName() {
return tableName;
}

/**
* Returns the DNS cache duration in milliseconds.
*
* <p>This controls how long resolved DNS entries are cached before being re-resolved. A shorter
* duration is appropriate for dynamic environments where DNS mappings change frequently, while a
* longer duration can reduce DNS lookup overhead in stable environments.
*
* @return the DNS cache duration in milliseconds (default: 500ms).
*/
public long getDnsCacheDurationMillis() {
return dnsCacheDurationMillis;
}

/**
* Creates a new builder for constructing a {@link ClientRoutesConfig}.
*
* @return a new builder instance.
*/
@NonNull
public static Builder builder() {
return new Builder();
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ClientRoutesConfig)) {
return false;
}
ClientRoutesConfig that = (ClientRoutesConfig) o;
return dnsCacheDurationMillis == that.dnsCacheDurationMillis
&& endpoints.equals(that.endpoints)
&& Objects.equals(tableName, that.tableName);
}

@Override
public int hashCode() {
return Objects.hash(endpoints, tableName, dnsCacheDurationMillis);
}

@Override
public String toString() {
return "ClientRoutesConfig{"
+ "endpoints="
+ endpoints
+ ", tableName='"
+ tableName
+ '\''
+ ", dnsCacheDurationMillis="
+ dnsCacheDurationMillis
+ '}';
}

/** Builder for {@link ClientRoutesConfig}. */
public static class Builder {
private final List<ClientRoutesEndpoint> endpoints = new ArrayList<>();
private String tableName;
private long dnsCacheDurationMillis = DEFAULT_DNS_CACHE_DURATION_MILLIS;

/**
* Adds an endpoint to the configuration.
*
* @param endpoint the endpoint to add (must not be null).
* @return this builder.
*/
@NonNull
public Builder addEndpoint(@NonNull ClientRoutesEndpoint endpoint) {
this.endpoints.add(Objects.requireNonNull(endpoint, "endpoint must not be null"));
return this;
}

/**
* Sets the endpoints for the configuration, replacing any previously added endpoints.
*
* @param endpoints the endpoints to set (must not be null or empty).
* @return this builder.
*/
@NonNull
public Builder withEndpoints(@NonNull List<ClientRoutesEndpoint> endpoints) {
Objects.requireNonNull(endpoints, "endpoints must not be null");
if (endpoints.isEmpty()) {
throw new IllegalArgumentException("endpoints must not be empty");
}
this.endpoints.clear();
for (ClientRoutesEndpoint endpoint : endpoints) {
addEndpoint(endpoint);
}
return this;
}

/**
* Sets the name of the system table to query for client routes.
*
* <p>This is primarily useful for testing. If not set, the driver will use the default table
* name from the configuration ({@code system.client_routes}).
*
* @param tableName the table name to use.
* @return this builder.
*/
@NonNull
public Builder withTableName(@Nullable String tableName) {
this.tableName = tableName;
return this;
}

/**
* Sets the DNS cache duration in milliseconds.
*
* <p>This controls how long resolved DNS entries are cached before being re-resolved. A shorter
* duration is appropriate for dynamic environments where DNS mappings change frequently (e.g.,
* during rolling updates), while a longer duration can reduce DNS lookup overhead in stable
* environments.
*
* <p>Default: 500ms
*
* @param durationMillis the cache duration in milliseconds (must be non-negative).
* @return this builder.
* @throws IllegalArgumentException if the duration is negative.
*/
@NonNull
public Builder withDnsCacheDuration(long durationMillis) {
if (durationMillis < 0) {
throw new IllegalArgumentException("DNS cache duration must be non-negative");
}
this.dnsCacheDurationMillis = durationMillis;
return this;
}

/**
* Builds the {@link ClientRoutesConfig} with the configured endpoints and table name.
*
* @return the new configuration instance.
* @throws IllegalArgumentException if no endpoints have been added.
*/
@NonNull
public ClientRoutesConfig build() {
return new ClientRoutesConfig(endpoints, tableName, dnsCacheDurationMillis);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.datastax.oss.driver.api.core.config;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.Objects;
import java.util.UUID;
import net.jcip.annotations.Immutable;

/**
* Represents a client routes endpoint for PrivateLink-style deployments.
*
* <p>Each endpoint corresponds to a connection ID in the {@code system.client_routes} table, with
* an optional connection address that can be used as a seed host for initial connection.
*/
@Immutable
public class ClientRoutesEndpoint {

private final UUID connectionId;
private final String connectionAddr;

/**
* Creates a new endpoint with the given connection ID and no connection address.
*
* @param connectionId the connection ID (must not be null).
*/
public ClientRoutesEndpoint(@NonNull UUID connectionId) {
this(connectionId, null);
}

/**
* Creates a new endpoint with the given connection ID and connection address.
*
* @param connectionId the connection ID (must not be null).
* @param connectionAddr the connection address to use as a seed host (may be null).
*/
public ClientRoutesEndpoint(@NonNull UUID connectionId, @Nullable String connectionAddr) {
this.connectionId = Objects.requireNonNull(connectionId, "connectionId must not be null");
this.connectionAddr = connectionAddr;
}

/** Returns the connection ID for this endpoint. */
@NonNull
public UUID getConnectionId() {
return connectionId;
}

/**
* Returns the connection address for this endpoint, or null if not specified.
*
* <p>When provided and no explicit contact points are given to the session builder, this address
* will be used as a seed host for the initial connection.
*/
@Nullable
public String getConnectionAddr() {
return connectionAddr;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ClientRoutesEndpoint)) {
return false;
}
ClientRoutesEndpoint that = (ClientRoutesEndpoint) o;
return connectionId.equals(that.connectionId)
&& Objects.equals(connectionAddr, that.connectionAddr);
}

@Override
public int hashCode() {
return Objects.hash(connectionId, connectionAddr);
}

@Override
public String toString() {
return "ClientRoutesEndpoint{"
+ "connectionId="
+ connectionId
+ ", connectionAddr='"
+ connectionAddr
+ '\''
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,17 @@ public enum DefaultDriverOption implements DriverOption {
*/
ADDRESS_TRANSLATOR_CLASS("advanced.address-translator.class"),

/**
* The name of the system table to query for client routes information.
*
* <p>This is used when client routes are configured programmatically via {@link
* com.datastax.oss.driver.api.core.session.SessionBuilder#withClientRoutesConfig}. The default
* value is {@code system.client_routes}.
*
* <p>Value-type: {@link String}
*/
CLIENT_ROUTES_TABLE_NAME("advanced.client-routes.table-name"),

/**
* The native protocol version to use.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ protected static void fillWithDriverDefaults(OptionsMap map) {
map.put(
TypedDriverOption.LOAD_BALANCING_DEFAULT_LWT_REQUEST_ROUTING_METHOD,
"PRESERVE_REPLICA_ORDER");
map.put(TypedDriverOption.CLIENT_ROUTES_TABLE_NAME, "system.client_routes");
}

@Immutable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,9 @@ public String toString() {
DefaultDriverOption.LOAD_BALANCING_DEFAULT_LWT_REQUEST_ROUTING_METHOD,
GenericType.STRING);

public static final TypedDriverOption<String> CLIENT_ROUTES_TABLE_NAME =
new TypedDriverOption<>(DefaultDriverOption.CLIENT_ROUTES_TABLE_NAME, GenericType.STRING);

private static Iterable<TypedDriverOption<?>> introspectBuiltInValues() {
try {
ImmutableList.Builder<TypedDriverOption<?>> result = ImmutableList.builder();
Expand Down
Loading
Loading