Skip to content

Commit c72422c

Browse files
committed
feat: add ClientRoutesConfig and ClientRoutesEndpoint for PrivateLink deployments
1 parent c70f53c commit c72422c

File tree

12 files changed

+897
-2
lines changed

12 files changed

+897
-2
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package com.datastax.oss.driver.api.core.config;
19+
20+
import com.datastax.oss.driver.api.core.session.SessionBuilder;
21+
import edu.umd.cs.findbugs.annotations.NonNull;
22+
import edu.umd.cs.findbugs.annotations.Nullable;
23+
import java.util.ArrayList;
24+
import java.util.Collections;
25+
import java.util.List;
26+
import java.util.Objects;
27+
import net.jcip.annotations.Immutable;
28+
29+
/**
30+
* Configuration for client routes, used in PrivateLink-style deployments.
31+
*
32+
* <p>Client routes enable the driver to discover and connect to nodes through a load balancer (such
33+
* as AWS PrivateLink) by reading endpoint mappings from the {@code system.client_routes} table.
34+
* Each endpoint is identified by a connection ID and maps to specific node addresses.
35+
*
36+
* <p>This configuration is mutually exclusive with a user-provided {@link
37+
* com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator}. If client routes are
38+
* configured, the driver will use its internal client routes handler for address translation.
39+
*
40+
* <p>Example usage:
41+
*
42+
* <pre>{@code
43+
* ClientRoutesConfig config = ClientRoutesConfig.builder()
44+
* .addEndpoint(new ClientRoutesEndpoint(
45+
* UUID.fromString("12345678-1234-1234-1234-123456789012"),
46+
* "my-privatelink.us-east-1.aws.scylladb.com:9042"))
47+
* .build();
48+
*
49+
* CqlSession session = CqlSession.builder()
50+
* .withClientRoutesConfig(config)
51+
* .build();
52+
* }</pre>
53+
*
54+
* @see SessionBuilder#withClientRoutesConfig(ClientRoutesConfig)
55+
* @see ClientRoutesEndpoint
56+
*/
57+
@Immutable
58+
public class ClientRoutesConfig {
59+
60+
private final List<ClientRoutesEndpoint> endpoints;
61+
private final String tableName;
62+
63+
private ClientRoutesConfig(List<ClientRoutesEndpoint> endpoints, String tableName) {
64+
if (endpoints == null || endpoints.isEmpty()) {
65+
throw new IllegalArgumentException("At least one endpoint must be specified");
66+
}
67+
this.endpoints = Collections.unmodifiableList(new ArrayList<>(endpoints));
68+
this.tableName = tableName;
69+
}
70+
71+
/**
72+
* Returns the list of configured endpoints.
73+
*
74+
* @return an immutable list of endpoints.
75+
*/
76+
@NonNull
77+
public List<ClientRoutesEndpoint> getEndpoints() {
78+
return endpoints;
79+
}
80+
81+
/**
82+
* Returns the name of the system table to query for client routes.
83+
*
84+
* @return the table name, or null to use the default ({@code system.client_routes}).
85+
*/
86+
@Nullable
87+
public String getTableName() {
88+
return tableName;
89+
}
90+
91+
/**
92+
* Creates a new builder for constructing a {@link ClientRoutesConfig}.
93+
*
94+
* @return a new builder instance.
95+
*/
96+
@NonNull
97+
public static Builder builder() {
98+
return new Builder();
99+
}
100+
101+
@Override
102+
public boolean equals(Object o) {
103+
if (this == o) {
104+
return true;
105+
}
106+
if (!(o instanceof ClientRoutesConfig)) {
107+
return false;
108+
}
109+
ClientRoutesConfig that = (ClientRoutesConfig) o;
110+
return endpoints.equals(that.endpoints) && Objects.equals(tableName, that.tableName);
111+
}
112+
113+
@Override
114+
public int hashCode() {
115+
return Objects.hash(endpoints, tableName);
116+
}
117+
118+
@Override
119+
public String toString() {
120+
return "ClientRoutesConfig{"
121+
+ "endpoints="
122+
+ endpoints
123+
+ ", tableName='"
124+
+ tableName
125+
+ '\''
126+
+ '}';
127+
}
128+
129+
/** Builder for {@link ClientRoutesConfig}. */
130+
public static class Builder {
131+
private final List<ClientRoutesEndpoint> endpoints = new ArrayList<>();
132+
private String tableName;
133+
134+
/**
135+
* Adds an endpoint to the configuration.
136+
*
137+
* @param endpoint the endpoint to add (must not be null).
138+
* @return this builder.
139+
*/
140+
@NonNull
141+
public Builder addEndpoint(@NonNull ClientRoutesEndpoint endpoint) {
142+
this.endpoints.add(Objects.requireNonNull(endpoint, "endpoint must not be null"));
143+
return this;
144+
}
145+
146+
/**
147+
* Sets the endpoints for the configuration, replacing any previously added endpoints.
148+
*
149+
* @param endpoints the endpoints to set (must not be null or empty).
150+
* @return this builder.
151+
*/
152+
@NonNull
153+
public Builder withEndpoints(@NonNull List<ClientRoutesEndpoint> endpoints) {
154+
Objects.requireNonNull(endpoints, "endpoints must not be null");
155+
this.endpoints.clear();
156+
this.endpoints.addAll(endpoints);
157+
return this;
158+
}
159+
160+
/**
161+
* Sets the name of the system table to query for client routes.
162+
*
163+
* <p>This is primarily useful for testing. If not set, the driver will use the default table
164+
* name from the configuration ({@code system.client_routes}).
165+
*
166+
* @param tableName the table name to use.
167+
* @return this builder.
168+
*/
169+
@NonNull
170+
public Builder withTableName(@Nullable String tableName) {
171+
this.tableName = tableName;
172+
return this;
173+
}
174+
175+
/**
176+
* Builds the {@link ClientRoutesConfig} with the configured endpoints and table name.
177+
*
178+
* @return the new configuration instance.
179+
* @throws IllegalArgumentException if no endpoints have been added.
180+
*/
181+
@NonNull
182+
public ClientRoutesConfig build() {
183+
return new ClientRoutesConfig(endpoints, tableName);
184+
}
185+
}
186+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package com.datastax.oss.driver.api.core.config;
19+
20+
import edu.umd.cs.findbugs.annotations.NonNull;
21+
import edu.umd.cs.findbugs.annotations.Nullable;
22+
import java.util.Objects;
23+
import java.util.UUID;
24+
import net.jcip.annotations.Immutable;
25+
26+
/**
27+
* Represents a client routes endpoint for PrivateLink-style deployments.
28+
*
29+
* <p>Each endpoint corresponds to a connection ID in the {@code system.client_routes} table, with
30+
* an optional connection address that can be used as a seed host for initial connection.
31+
*/
32+
@Immutable
33+
public class ClientRoutesEndpoint {
34+
35+
private final UUID connectionId;
36+
private final String connectionAddr;
37+
38+
/**
39+
* Creates a new endpoint with the given connection ID and no connection address.
40+
*
41+
* @param connectionId the connection ID (must not be null).
42+
*/
43+
public ClientRoutesEndpoint(@NonNull UUID connectionId) {
44+
this(connectionId, null);
45+
}
46+
47+
/**
48+
* Creates a new endpoint with the given connection ID and connection address.
49+
*
50+
* @param connectionId the connection ID (must not be null).
51+
* @param connectionAddr the connection address to use as a seed host (may be null).
52+
*/
53+
public ClientRoutesEndpoint(@NonNull UUID connectionId, @Nullable String connectionAddr) {
54+
this.connectionId = Objects.requireNonNull(connectionId, "connectionId must not be null");
55+
this.connectionAddr = connectionAddr;
56+
}
57+
58+
/** Returns the connection ID for this endpoint. */
59+
@NonNull
60+
public UUID getConnectionId() {
61+
return connectionId;
62+
}
63+
64+
/**
65+
* Returns the connection address for this endpoint, or null if not specified.
66+
*
67+
* <p>When provided and no explicit contact points are given to the session builder, this address
68+
* will be used as a seed host for the initial connection.
69+
*/
70+
@Nullable
71+
public String getConnectionAddr() {
72+
return connectionAddr;
73+
}
74+
75+
@Override
76+
public boolean equals(Object o) {
77+
if (this == o) {
78+
return true;
79+
}
80+
if (!(o instanceof ClientRoutesEndpoint)) {
81+
return false;
82+
}
83+
ClientRoutesEndpoint that = (ClientRoutesEndpoint) o;
84+
return connectionId.equals(that.connectionId)
85+
&& Objects.equals(connectionAddr, that.connectionAddr);
86+
}
87+
88+
@Override
89+
public int hashCode() {
90+
return Objects.hash(connectionId, connectionAddr);
91+
}
92+
93+
@Override
94+
public String toString() {
95+
return "ClientRoutesEndpoint{"
96+
+ "connectionId="
97+
+ connectionId
98+
+ ", connectionAddr='"
99+
+ connectionAddr
100+
+ '\''
101+
+ '}';
102+
}
103+
}

core/src/main/java/com/datastax/oss/driver/api/core/config/DefaultDriverOption.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,17 @@ public enum DefaultDriverOption implements DriverOption {
449449
*/
450450
ADDRESS_TRANSLATOR_CLASS("advanced.address-translator.class"),
451451

452+
/**
453+
* The name of the system table to query for client routes information.
454+
*
455+
* <p>This is used when client routes are configured programmatically via {@link
456+
* com.datastax.oss.driver.api.core.session.SessionBuilder#withClientRoutesConfig}. The default
457+
* value is {@code system.client_routes}.
458+
*
459+
* <p>Value-type: {@link String}
460+
*/
461+
CLIENT_ROUTES_TABLE_NAME("advanced.client-routes.table-name"),
462+
452463
/**
453464
* The native protocol version to use.
454465
*

core/src/main/java/com/datastax/oss/driver/api/core/config/OptionsMap.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ protected static void fillWithDriverDefaults(OptionsMap map) {
396396
map.put(
397397
TypedDriverOption.LOAD_BALANCING_DEFAULT_LWT_REQUEST_ROUTING_METHOD,
398398
"PRESERVE_REPLICA_ORDER");
399+
map.put(TypedDriverOption.CLIENT_ROUTES_TABLE_NAME, "system.client_routes");
399400
}
400401

401402
@Immutable

core/src/main/java/com/datastax/oss/driver/api/core/config/TypedDriverOption.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,9 @@ public String toString() {
939939
DefaultDriverOption.LOAD_BALANCING_DEFAULT_LWT_REQUEST_ROUTING_METHOD,
940940
GenericType.STRING);
941941

942+
public static final TypedDriverOption<String> CLIENT_ROUTES_TABLE_NAME =
943+
new TypedDriverOption<>(DefaultDriverOption.CLIENT_ROUTES_TABLE_NAME, GenericType.STRING);
944+
942945
private static Iterable<TypedDriverOption<?>> introspectBuiltInValues() {
943946
try {
944947
ImmutableList.Builder<TypedDriverOption<?>> result = ImmutableList.builder();

0 commit comments

Comments
 (0)