Restore Cassandra port option

This commit restores the port option that was removed in an earlier
milestone. Contact points that do not define a port already are
automatically transformed to include the one configured, with a default
matching Cassandra's default port.

This makes upgrades easier in the case a cluster uses consistent ports
everywhere.

Closes gh-19672
This commit is contained in:
Stephane Nicoll 2020-03-27 12:20:40 +01:00
parent a4b3d9a800
commit 7a64b3f761
4 changed files with 85 additions and 19 deletions

View File

@ -21,6 +21,8 @@ import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.net.ssl.SSLContext;
@ -114,7 +116,7 @@ public class CassandraAutoConfiguration {
mapQueryOptions(properties, options);
mapSocketOptions(properties, options);
mapPoolingOptions(properties, options);
map.from(properties::getContactPoints)
map.from(mapContactPoints(properties))
.to((contactPoints) -> options.add(DefaultDriverOption.CONTACT_POINTS, contactPoints));
map.from(properties.getLocalDatacenter()).to(
(localDatacenter) -> options.add(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, localDatacenter));
@ -152,6 +154,29 @@ public class CassandraAutoConfiguration {
.to((maxQueueSize) -> options.add(DefaultDriverOption.REQUEST_THROTTLER_MAX_QUEUE_SIZE, maxQueueSize));
}
private List<String> mapContactPoints(CassandraProperties properties) {
return properties.getContactPoints().stream()
.map((candidate) -> formatContactPoint(candidate, properties.getPort())).collect(Collectors.toList());
}
private String formatContactPoint(String candidate, int port) {
int i = candidate.lastIndexOf(':');
if (i == -1 || !isPort(() -> candidate.substring(i + 1))) {
return String.format("%s:%s", candidate, port);
}
return candidate;
}
private boolean isPort(Supplier<String> value) {
try {
int i = Integer.parseInt(value.get());
return i > 0 && i < 65535;
}
catch (Exception ex) {
return false;
}
}
private static class CassandraDriverOptions {
private final Map<String, String> options = new LinkedHashMap<>();

View File

@ -51,10 +51,16 @@ public class CassandraProperties {
private String sessionName;
/**
* Cluster node addresses in the form 'host:port'.
* Cluster node addresses in the form 'host:port', or a simple 'host' to use the
* configured port.
*/
private final List<String> contactPoints = new ArrayList<>(Collections.singleton("127.0.0.1:9042"));
/**
* Port to use if a contact point does not specify one.
*/
private int port = 9042;
/**
* Datacenter that is considered "local". Contact points should be from this
* datacenter.
@ -147,6 +153,14 @@ public class CassandraProperties {
return this.contactPoints;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
public String getLocalDatacenter() {
return this.localDatacenter;
}

View File

@ -21,9 +21,6 @@ import com.datastax.oss.driver.api.core.CqlSessionBuilder;
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.impl.Parseable;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -78,6 +75,38 @@ class CassandraAutoConfigurationTests {
});
}
@Test
void driverConfigLoaderWithContactPointAndNoPort() {
this.contextRunner
.withPropertyValues("spring.data.cassandra.contact-points=cluster.example.com,another.example.com:9041",
"spring.data.cassandra.local-datacenter=cassandra-eu1")
.run((context) -> {
assertThat(context).hasSingleBean(DriverConfigLoader.class);
DriverExecutionProfile configuration = context.getBean(DriverConfigLoader.class).getInitialConfig()
.getDefaultProfile();
assertThat(configuration.getStringList(DefaultDriverOption.CONTACT_POINTS))
.containsOnly("cluster.example.com:9042", "another.example.com:9041");
assertThat(configuration.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER))
.isEqualTo("cassandra-eu1");
});
}
@Test
void driverConfigLoaderWithContactPointAndNoPortAndCustomPort() {
this.contextRunner
.withPropertyValues("spring.data.cassandra.contact-points=cluster.example.com:9041,another.example.com",
"spring.data.cassandra.port=9043", "spring.data.cassandra.local-datacenter=cassandra-eu1")
.run((context) -> {
assertThat(context).hasSingleBean(DriverConfigLoader.class);
DriverExecutionProfile configuration = context.getBean(DriverConfigLoader.class).getInitialConfig()
.getDefaultProfile();
assertThat(configuration.getStringList(DefaultDriverOption.CONTACT_POINTS))
.containsOnly("cluster.example.com:9041", "another.example.com:9043");
assertThat(configuration.getString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER))
.isEqualTo("cassandra-eu1");
});
}
@Test
void driverConfigLoaderWithCustomSessionName() {
this.contextRunner.withPropertyValues("spring.data.cassandra.session-name=testcluster").run((context) -> {
@ -97,16 +126,6 @@ class CassandraAutoConfigurationTests {
});
}
@Test
void driverConfigLoaderApplyConsistentDefaults() {
this.contextRunner.run((context) -> {
Config defaultConfig = defaultConfig();
DriverExecutionProfile config = context.getBean(DriverConfigLoader.class).getInitialConfig()
.getDefaultProfile();
// TODO
});
}
@Test
void driverConfigLoaderCustomizePoolOptions() {
this.contextRunner.withPropertyValues("spring.data.cassandra.pool.idle-timeout=42",
@ -120,10 +139,6 @@ class CassandraAutoConfigurationTests {
});
}
private static Config defaultConfig() {
return Parseable.newResources("reference.conf", ConfigParseOptions.defaults()).parse().toConfig();
}
@Configuration(proxyBeanMethods = false)
static class SimpleDriverConfigLoaderBuilderCustomizerConfig {

View File

@ -4518,6 +4518,18 @@ Generally, you provide `keyspace-name` and `contact-points` as well the local da
spring.data.cassandra.local-datacenter=datacenter1
----
If the port is the same for all your contact points you can use a shortcut and only specify the host names, as shown in the following example:
[source,properties,indent=0,configprops]
----
spring.data.cassandra.keyspace-name=mykeyspace
spring.data.cassandra.contact-points=cassandrahost1,cassandrahost2
spring.data.cassandra.local-datacenter=datacenter1
----
TIP: Those two examples are identical as the port default to `9042`.
If you need to configure the port, use `spring.data.cassandra.port`.
You can also register an arbitrary number of beans that implement `DriverConfigLoaderBuilderCustomizer` for more advanced driver customizations.
The `CqlSession` can be customized with a bean of type `CqlSessionBuilderCustomizer`.