From 057370dc7dd737cebfd229ef72062b7f201d34dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Sun, 11 Dec 2022 21:30:14 +0100 Subject: [PATCH 01/12] Test what happens with double setup python action --- .github/workflows/tests@v1.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/tests@v1.yml b/.github/workflows/tests@v1.yml index 80e12c610a7..03657ee761d 100644 --- a/.github/workflows/tests@v1.yml +++ b/.github/workflows/tests@v1.yml @@ -99,6 +99,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: '3.x' + + - name: Setup Python 2 (setup it) + uses: actions/setup-python@v2 + with: + python-version: '2.x' - name: Fetch Scylla and Cassandra versions id: fetch-versions @@ -136,6 +141,11 @@ jobs: with: python-version: '3.x' + - name: Setup Python 2 (cas it) + uses: actions/setup-python@v2 + with: + python-version: '2.x' + - name: Setup environment run: | pip3 install https://github.com/scylladb/scylla-ccm/archive/master.zip @@ -187,6 +197,11 @@ jobs: uses: actions/setup-python@v2 with: python-version: '3.x' + + - name: Setup Python 2 (scylla test) + uses: actions/setup-python@v2 + with: + python-version: '2.x' - name: Setup environment run: | From f86d43c1f5e92362620d7a232fc5a21c207245d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Sun, 11 Dec 2022 21:58:30 +0100 Subject: [PATCH 02/12] output python versions --- .github/workflows/tests@v1.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests@v1.yml b/.github/workflows/tests@v1.yml index 03657ee761d..5ab04396186 100644 --- a/.github/workflows/tests@v1.yml +++ b/.github/workflows/tests@v1.yml @@ -172,6 +172,12 @@ jobs: name: ccm-logs-cassandra-${{ matrix.cassandra-version }} path: /tmp/ccm*/ccm*/node*/logs/* + - name: Check python versions + run: | + python --version + python2 --version + python3 --version + scylla-integration-tests: name: Scylla ITs runs-on: ubuntu-latest @@ -228,4 +234,10 @@ jobs: if: ${{ failure() }} with: name: ccm-logs-scylla-${{ matrix.scylla-version }} - path: /tmp/ccm*/ccm*/node*/logs/* \ No newline at end of file + path: /tmp/ccm*/ccm*/node*/logs/* + + - name: Check python versions + run: | + python --version + python2 --version + python3 --version \ No newline at end of file From e49c9df7073c30243e852de80f0f84b0c553bc34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Sun, 11 Dec 2022 23:14:51 +0100 Subject: [PATCH 03/12] reverse order --- .github/workflows/tests@v1.yml | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tests@v1.yml b/.github/workflows/tests@v1.yml index 5ab04396186..de9f873f582 100644 --- a/.github/workflows/tests@v1.yml +++ b/.github/workflows/tests@v1.yml @@ -94,16 +94,16 @@ jobs: steps: - name: Checkout source uses: actions/checkout@v2 + + - name: Setup Python 2 (setup it) + uses: actions/setup-python@v2 + with: + python-version: '2.x' - name: Setup Python 3 uses: actions/setup-python@v2 with: python-version: '3.x' - - - name: Setup Python 2 (setup it) - uses: actions/setup-python@v2 - with: - python-version: '2.x' - name: Fetch Scylla and Cassandra versions id: fetch-versions @@ -136,15 +136,15 @@ jobs: java-version: '8' distribution: 'adopt' - - name: Setup Python 3 - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Setup Python 2 (cas it) uses: actions/setup-python@v2 with: python-version: '2.x' + + - name: Setup Python 3 + uses: actions/setup-python@v2 + with: + python-version: '3.x' - name: Setup environment run: | @@ -175,6 +175,7 @@ jobs: - name: Check python versions run: | python --version + echo "aaa" python2 --version python3 --version @@ -199,16 +200,16 @@ jobs: java-version: '8' distribution: 'adopt' - - name: Setup Python 3 - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Setup Python 2 (scylla test) uses: actions/setup-python@v2 with: python-version: '2.x' + - name: Setup Python 3 + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Setup environment run: | pip3 install https://github.com/scylladb/scylla-ccm/archive/master.zip @@ -239,5 +240,6 @@ jobs: - name: Check python versions run: | python --version + echo "aaa" python2 --version python3 --version \ No newline at end of file From 5da0fcde8166848702b2ddc571625375a01b9167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Tue, 6 Dec 2022 16:06:43 +0100 Subject: [PATCH 04/12] Add Scylla Cloud configuration classes Similarly as in java driver 3.x adds classes representing Scylla Cloud configuration details along with methods that allow for using them in driver code. Adds necessary dependencies to pom files. --- core/pom.xml | 12 + .../scyllacloud/ConfigurationBundle.java | 108 ++++++++ .../core/config/scyllacloud/Parameters.java | 21 ++ .../scyllacloud/ScyllaCloudAuthInfo.java | 80 ++++++ .../ScyllaCloudConnectionConfig.java | 262 ++++++++++++++++++ .../scyllacloud/ScyllaCloudContext.java | 23 ++ .../scyllacloud/ScyllaCloudDatacenter.java | 167 +++++++++++ pom.xml | 16 ++ 8 files changed, 689 insertions(+) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ConfigurationBundle.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/Parameters.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudAuthInfo.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudConnectionConfig.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudContext.java create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudDatacenter.java diff --git a/core/pom.xml b/core/pom.xml index c47e2bfcc95..853dc5c572d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -115,6 +115,10 @@ com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + org.reactivestreams reactive-streams @@ -192,6 +196,14 @@ wiremock test + + org.bouncycastle + bcprov-jdk18on + + + org.bouncycastle + bcpkix-jdk18on + diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ConfigurationBundle.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ConfigurationBundle.java new file mode 100644 index 00000000000..485d0a5a2ea --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ConfigurationBundle.java @@ -0,0 +1,108 @@ +package com.datastax.oss.driver.internal.core.config.scyllacloud; + +import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; +import com.datastax.oss.driver.internal.core.ssl.SniSslEngineFactory; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +public class ConfigurationBundle { + private final KeyStore identity; + private final KeyStore trustStore; + + public ConfigurationBundle(KeyStore identity, KeyStore trustStore) { + this.identity = identity; + this.trustStore = trustStore; + } + + public KeyStore getIdentity() { + return identity; + } + + public KeyStore getTrustStore() { + return trustStore; + } + + private void writeKeystore(String path, KeyStore ks, char[] password) + throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException { + File file = new File(path); + OutputStream os = new FileOutputStream(file); + ks.store(os, password); + os.close(); + } + + public void writeIdentity(String path, char[] password) + throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { + writeKeystore(path, identity, password); + } + + public void writeTrustStore(String path, char[] password) + throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { + writeKeystore(path, trustStore, password); + } + + protected SSLContext getSSLContext() throws IOException, GeneralSecurityException { + KeyManagerFactory kmf = createKeyManagerFactory(identity); + TrustManagerFactory tmf = createTrustManagerFactory(trustStore); + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); + return sslContext; + } + + protected SSLContext getInsecureSSLContext() throws IOException, GeneralSecurityException { + KeyManagerFactory kmf = createKeyManagerFactory(identity); + SSLContext sslContext = SSLContext.getInstance("SSL"); + TrustManager[] trustManager = + new TrustManager[] { + new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException {} + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) + throws CertificateException {} + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + }; + + sslContext.init(kmf.getKeyManagers(), trustManager, new SecureRandom()); + return sslContext; + } + + protected KeyManagerFactory createKeyManagerFactory(KeyStore ks) + throws IOException, GeneralSecurityException { + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, "cassandra".toCharArray()); + return kmf; + } + + protected TrustManagerFactory createTrustManagerFactory(KeyStore ts) + throws IOException, GeneralSecurityException { + TrustManagerFactory tmf = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ts); + return tmf; + } + + public SslEngineFactory getSSLEngineFactory() throws GeneralSecurityException, IOException { + return new SniSslEngineFactory(getSSLContext()); + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/Parameters.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/Parameters.java new file mode 100644 index 00000000000..72706c22e87 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/Parameters.java @@ -0,0 +1,21 @@ +package com.datastax.oss.driver.internal.core.config.scyllacloud; + +import com.datastax.oss.driver.api.core.ConsistencyLevel; +import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +@SuppressWarnings("unused") +public class Parameters { + private final ConsistencyLevel defaultConsistency; + private final ConsistencyLevel defaultSerialConsistency; + + @JsonCreator + public Parameters( + @JsonProperty(value = "defaultConsistency") DefaultConsistencyLevel defaultConsistency, + @JsonProperty(value = "defaultSerialConsistency") + DefaultConsistencyLevel defaultSerialConsistency) { + this.defaultConsistency = defaultConsistency; + this.defaultSerialConsistency = defaultSerialConsistency; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudAuthInfo.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudAuthInfo.java new file mode 100644 index 00000000000..94b266b0c73 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudAuthInfo.java @@ -0,0 +1,80 @@ +package com.datastax.oss.driver.internal.core.config.scyllacloud; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.File; + +public class ScyllaCloudAuthInfo { + private final byte[] clientCertificateData; + private final String clientCertificatePath; + private final byte[] clientKeyData; + private final String clientKeyPath; + private final String username; + private final String password; + + @JsonCreator + public ScyllaCloudAuthInfo( + @JsonProperty(value = "clientCertificateData") byte[] clientCertificateData, + @JsonProperty(value = "clientCertificatePath") String clientCertificatePath, + @JsonProperty(value = "clientKeyData") byte[] clientKeyData, + @JsonProperty(value = "clientKeyPath") String clientKeyPath, + @JsonProperty(value = "username") String username, + @JsonProperty(value = "password") String password) { + this.clientCertificateData = clientCertificateData; + this.clientCertificatePath = clientCertificatePath; + this.clientKeyData = clientKeyData; + this.clientKeyPath = clientKeyPath; + this.username = username; + this.password = password; + } + + public void validate() { + if (clientCertificateData == null) { + if (clientCertificatePath == null) { + throw new IllegalArgumentException( + "Either clientCertificateData or clientCertificatePath has to be provided for authInfo."); + } + File file = new File(clientCertificatePath); + if (!file.canRead()) { + throw new IllegalArgumentException( + "Cannot read file at given clientCertificatePath (" + clientCertificatePath + ")."); + } + } + + if (clientKeyData == null) { + if (clientKeyPath == null) { + throw new IllegalArgumentException( + "Either clientKeyData or clientKeyPath has to be provided for authInfo."); + } + File file = new File(clientKeyPath); + if (!file.canRead()) { + throw new IllegalArgumentException( + "Cannot read file at given clientKeyPath (" + clientKeyPath + ")."); + } + } + } + + public byte[] getClientCertificateData() { + return clientCertificateData; + } + + public String getClientCertificatePath() { + return clientCertificatePath; + } + + public byte[] getClientKeyData() { + return clientKeyData; + } + + public String getClientKeyPath() { + return clientKeyPath; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudConnectionConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudConnectionConfig.java new file mode 100644 index 00000000000..6d27088d4b7 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudConnectionConfig.java @@ -0,0 +1,262 @@ +package com.datastax.oss.driver.internal.core.config.scyllacloud; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.spec.InvalidKeySpecException; +import java.util.Map; +import java.util.NoSuchElementException; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; + +public class ScyllaCloudConnectionConfig { + private final String kind; + private final String apiVersion; + private final Map datacenters; + private final Map authInfos; + private final Map contexts; + private final String currentContext; + private final Parameters parameters; + + @JsonCreator + public ScyllaCloudConnectionConfig( + @JsonProperty(value = "kind") String kind, + @JsonProperty(value = "apiVersion") String apiVersion, + @JsonProperty(value = "datacenters", required = true) + Map datacenters, + @JsonProperty(value = "authInfos", required = true) + Map authInfos, + @JsonProperty(value = "contexts", required = true) Map contexts, + @JsonProperty(value = "currentContext", required = true) String currentContext, + @JsonProperty(value = "parameters") Parameters parameters) { + this.kind = kind; + this.apiVersion = apiVersion; + this.datacenters = datacenters; + this.authInfos = authInfos; + this.contexts = contexts; + this.currentContext = currentContext; + this.parameters = parameters; + } + + public static ScyllaCloudConnectionConfig fromInputStream(InputStream inputStream) + throws IOException { + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + ScyllaCloudConnectionConfig scyllaCloudConnectionConfig = + mapper.readValue(inputStream, ScyllaCloudConnectionConfig.class); + scyllaCloudConnectionConfig.validate(); + return scyllaCloudConnectionConfig; + } + + public void validate() { + if (this.datacenters == null) { + throw new IllegalArgumentException( + "Please provide datacenters (datacenters:) in the configuration yaml."); + } + for (ScyllaCloudDatacenter datacenter : datacenters.values()) { + datacenter.validate(); + } + + if (this.authInfos == null) { + throw new IllegalArgumentException( + "Please provide any authentication config (authInfos:) in the configuration yaml."); + } + for (ScyllaCloudAuthInfo authInfo : authInfos.values()) { + authInfo.validate(); + } + + if (this.contexts == null) { + throw new IllegalArgumentException( + "Please provide any configuration (contexts:) context in the configuration yaml."); + } + + if (this.currentContext == null) { + throw new IllegalArgumentException( + "Please set default context (currentContext:) in the configuration yaml."); + } + } + + public ConfigurationBundle createBundle() + throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, + InvalidKeySpecException { + this.validate(); + KeyStore identity = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + identity.load(null, null); + trustStore.load(null, null); + for (Map.Entry datacenterEntry : datacenters.entrySet()) { + ScyllaCloudDatacenter datacenter = datacenterEntry.getValue(); + InputStream certificateDataStream; + if (datacenter.getCertificateAuthorityData() != null) { + certificateDataStream = new ByteArrayInputStream(datacenter.getCertificateAuthorityData()); + } else if (datacenter.getCertificateAuthorityPath() != null) { + certificateDataStream = new FileInputStream(datacenter.getCertificateAuthorityPath()); + } else { + // impossible + throw new IllegalStateException( + "Neither CertificateAuthorityPath nor CertificateAuthorityData are set in this Datacenter object. " + + "Validation should have prevented this."); + } + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate cert = cf.generateCertificate(certificateDataStream); + trustStore.setCertificateEntry(datacenterEntry.getKey(), cert); + } + + for (Map.Entry authInfoEntry : authInfos.entrySet()) { + ScyllaCloudAuthInfo authInfo = authInfoEntry.getValue(); + InputStream certificateDataStream; + String keyString; + + if (authInfo.getClientCertificateData() != null) { + certificateDataStream = new ByteArrayInputStream(authInfo.getClientCertificateData()); + } else if (authInfo.getClientCertificatePath() != null) { + certificateDataStream = new FileInputStream(authInfo.getClientCertificatePath()); + } else { + // impossible + throw new RuntimeException( + "Neither CertificateAuthorityPath nor CertificateAuthorityData are set in this AuthInfo object. " + + "Validation should have prevented this."); + } + + if (authInfo.getClientKeyData() != null) { + keyString = new String(authInfo.getClientKeyData(), Charset.defaultCharset()); + } else if (authInfo.getClientKeyPath() != null) { + BufferedReader br = + Files.newBufferedReader( + Paths.get(authInfo.getClientKeyPath()), Charset.defaultCharset()); + StringBuilder sb = new StringBuilder(); + String line = br.readLine(); + while (line != null) { + sb.append(line); + line = br.readLine(); + } + keyString = sb.toString(); + } else { + // impossible + throw new RuntimeException( + "Neither ClientKeyData nor ClientKeyPath are set in this AuthInfo object. " + + "Validation should have prevented this."); + } + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate cert = cf.generateCertificate(certificateDataStream); + + Certificate[] certArr = new Certificate[1]; + certArr[0] = cert; + + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + PEMParser pemParser = new PEMParser(new StringReader(keyString)); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + Object object = pemParser.readObject(); + PrivateKey privateKey; + if (object instanceof PrivateKeyInfo) { + privateKey = converter.getPrivateKey((PrivateKeyInfo) object); + } else if (object instanceof PEMKeyPair) { + KeyPair kp = converter.getKeyPair((PEMKeyPair) object); + privateKey = kp.getPrivate(); + } else if (object == null) { + // Should not ever happen + throw new IllegalStateException( + "Error parsing authInfo " + + authInfoEntry.getKey() + + ". " + + "Somehow no objects are left in the stream. Is passed Client Key empty?"); + } else { + throw new InvalidKeySpecException( + "Error parsing authInfo " + + authInfoEntry.getKey() + + ". " + + "Make sure provided key signature is either 'RSA PRIVATE KEY' or 'PRIVATE KEY'"); + } + + identity.setKeyEntry(authInfoEntry.getKey(), privateKey, "cassandra".toCharArray(), certArr); + } + + return new ConfigurationBundle(identity, trustStore); + } + + public ScyllaCloudDatacenter getCurrentDatacenter() { + return getDatacenter(getCurrentContext().getDatacenterName()); + } + + public ScyllaCloudAuthInfo getCurrentAuthInfo() { + return getAuthInfo(getCurrentContext().getAuthInfoName()); + } + + public String getKind() { + return kind; + } + + public String getApiVersion() { + return apiVersion; + } + + public Map getDatacenters() { + return datacenters; + } + + public Map getAuthInfos() { + return authInfos; + } + + public Map getContexts() { + return contexts; + } + + public ScyllaCloudContext getContext(String ctx) { + if (!contexts.containsKey(ctx)) { + throw new NoSuchElementException( + String.format( + "There is no context named %s. Check your Scylla Cloud configuration file.", ctx)); + } + return contexts.get(ctx); + } + + public ScyllaCloudAuthInfo getAuthInfo(String authInfo) { + if (!authInfos.containsKey(authInfo)) { + throw new NoSuchElementException( + String.format( + "There is no authInfo named %s. Check your Scylla Cloud configuration file.", + authInfo)); + } + return authInfos.get(authInfo); + } + + public ScyllaCloudDatacenter getDatacenter(String datacenter) { + if (!datacenters.containsKey(datacenter)) { + throw new NoSuchElementException( + String.format( + "There is no datacenter named %s. Check your Scylla Cloud configuration file.", + datacenter)); + } + return datacenters.get(datacenter); + } + + public ScyllaCloudContext getCurrentContext() { + return getContext(currentContext); + } + + public Parameters getParameters() { + return parameters; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudContext.java new file mode 100644 index 00000000000..2d94635cdc1 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudContext.java @@ -0,0 +1,23 @@ +package com.datastax.oss.driver.internal.core.config.scyllacloud; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ScyllaCloudContext { + private final String datacenterName; + private final String authInfoName; + + public ScyllaCloudContext( + @JsonProperty(value = "datacenterName", required = true) String datacenterName, + @JsonProperty(value = "authInfoName", required = true) String authInfoName) { + this.datacenterName = datacenterName; + this.authInfoName = authInfoName; + } + + public String getDatacenterName() { + return datacenterName; + } + + public String getAuthInfoName() { + return authInfoName; + } +} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudDatacenter.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudDatacenter.java new file mode 100644 index 00000000000..66c824edc71 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/config/scyllacloud/ScyllaCloudDatacenter.java @@ -0,0 +1,167 @@ +package com.datastax.oss.driver.internal.core.config.scyllacloud; + +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; +import com.datastax.oss.driver.shaded.guava.common.net.HostAndPort; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.File; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +public class ScyllaCloudDatacenter { + private final String certificateAuthorityPath; + private final byte[] certificateAuthorityData; + private final String server; + private final String tlsServerName; + private final String nodeDomain; + private final String proxyURL; + + private final boolean insecureSkipTlsVerify; + + // Full hostname has limit of 255 chars. + // Host UUID takes 32 chars for hex digits and 4 dashes. Additional 1 is for separator dot before + // nodeDomain + private static final int NODE_DOMAIN_MAX_LENGTH = 255 - 32 - 4 - 1; + + @JsonCreator + public ScyllaCloudDatacenter( + @JsonProperty(value = "certificateAuthorityPath") String certificateAuthorityPath, + @JsonProperty(value = "certificateAuthorityData") byte[] certificateAuthorityData, + @JsonProperty(value = "server") String server, + @JsonProperty(value = "tlsServerName") String tlsServerName, + @JsonProperty(value = "nodeDomain") String nodeDomain, + @JsonProperty(value = "proxyURL") String proxyURL, + @JsonProperty(value = "insecureSkipTlsVerify", defaultValue = "false") + boolean insecureSkipTlsVerify) { + this.certificateAuthorityPath = certificateAuthorityPath; + this.certificateAuthorityData = certificateAuthorityData; + this.server = server; + this.tlsServerName = tlsServerName; + this.nodeDomain = nodeDomain; + this.proxyURL = proxyURL; + this.insecureSkipTlsVerify = insecureSkipTlsVerify; + } + + public void validate() { + if (certificateAuthorityData == null) { + if (certificateAuthorityPath == null) { + throw new IllegalArgumentException( + "Either certificateAuthorityData or certificateAuthorityPath must be provided for datacenter description."); + } + File file = new File(certificateAuthorityPath); + if (!file.canRead()) { + throw new IllegalArgumentException( + "Cannot read file at given certificateAuthorityPath (" + + certificateAuthorityPath + + ")."); + } + } + validateServer(); + validateNodeDomain(); + } + + public String getCertificateAuthorityPath() { + return certificateAuthorityPath; + } + + public byte[] getCertificateAuthorityData() { + return certificateAuthorityData; + } + + public InetSocketAddress getServer() { + HostAndPort parsedServer = HostAndPort.fromString(server); + return InetSocketAddress.createUnresolved(parsedServer.getHost(), parsedServer.getPort()); + } + + public String getNodeDomain() { + return nodeDomain; + } + + public String getTlsServerName() { + return tlsServerName; + } + + public String getProxyURL() { + return proxyURL; + } + + public boolean isInsecureSkipTlsVerify() { + return insecureSkipTlsVerify; + } + + // Using parts relevant to hostnames as we're dealing with a part of hostname + // RFC-1123 Section 2.1 and RFC-952 1. + private void validateNodeDomain() { + if (nodeDomain == null || nodeDomain.length() == 0) { + throw new IllegalArgumentException( + "nodeDomain property is required in datacenter description."); + } else { + if (nodeDomain.length() > NODE_DOMAIN_MAX_LENGTH) { + // Should be shorter because it is not the whole hostname + throw new IllegalArgumentException( + "Subdomain name too long (max " + NODE_DOMAIN_MAX_LENGTH + "): " + nodeDomain); + } + if (nodeDomain.contains(" ")) { + throw new IllegalArgumentException( + "nodeDomain '" + nodeDomain + "' cannot contain spaces."); + } + if (nodeDomain.startsWith(".") || nodeDomain.endsWith(".")) { + throw new IllegalArgumentException( + "nodeDomain '" + nodeDomain + "' cannot start or end with a dot."); + } + if (nodeDomain.endsWith("-")) { + throw new IllegalArgumentException( + "nodeDomain '" + nodeDomain + "' cannot end with a minus sign."); + } + } + + List components = ImmutableList.copyOf(nodeDomain.split("\\.")); + for (String component : components) { + if (component.length() == 0) { + throw new IllegalArgumentException( + "nodeDomain '" + nodeDomain + "' cannot have empty components between dots."); + } + + for (int index = 0; index < component.length(); index++) { + if (!Character.isLetterOrDigit(component.charAt(index))) { + if (component.charAt(index) == '-') { + if (index == 0 || index == component.length() - 1) { + throw new IllegalArgumentException( + "nodeDomain '" + + nodeDomain + + "' components can have minus sign only as interior character: " + + component.charAt(index)); + } + } else { + throw new IllegalArgumentException( + "nodeDomain '" + + nodeDomain + + "' contains illegal character: " + + component.charAt(index)); + } + } + } + } + } + + private void validateServer() { + if (server == null) { + throw new IllegalArgumentException("server property is required in datacenter description."); + } else { + try { + // Property 'server' is not a true URL because it does not contain protocol prefix + // We're adding prefix just to satisfy that part of validation + URL url = new URL("http://" + server); + if (url.getPort() == -1) { + throw new IllegalArgumentException( + "server property '" + server + "' does not contain a port."); + } + } catch (MalformedURLException e) { + throw new IllegalArgumentException( + "server property '" + server + "' is not a valid URL", e); + } + } + } +} diff --git a/pom.xml b/pom.xml index d137664037b..5602ea9d068 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,7 @@ 2.13.2 2.13.2.2 1.9.12 + 1.72 1.1.7.3 1.7.1 @@ -342,6 +343,11 @@ jackson-databind ${jackson-databind.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + com.google.testing.compile compile-testing @@ -445,6 +451,16 @@ blockhound-junit-platform 1.0.4.RELEASE + + org.bouncycastle + bcprov-jdk18on + ${bouncycastle.version} + + + org.bouncycastle + bcpkix-jdk18on + ${bouncycastle.version} + From 962e7bc45e7c9948e9e9a3af866fe762461fcca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Tue, 6 Dec 2022 16:11:05 +0100 Subject: [PATCH 05/12] Add Scylla Cloud yaml configuration parsing test --- .../CloudConfigYamlParsingTest.java | 31 +++++++++++++++++++ .../config/scyllacloud/incompleteConf.yaml | 21 +++++++++++++ .../config/scyllacloud/testConf.yaml | 23 ++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/config/scyllacloud/CloudConfigYamlParsingTest.java create mode 100644 core/src/test/resources/config/scyllacloud/incompleteConf.yaml create mode 100644 core/src/test/resources/config/scyllacloud/testConf.yaml diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/scyllacloud/CloudConfigYamlParsingTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/scyllacloud/CloudConfigYamlParsingTest.java new file mode 100644 index 00000000000..73818df524c --- /dev/null +++ b/core/src/test/java/com/datastax/oss/driver/internal/core/config/scyllacloud/CloudConfigYamlParsingTest.java @@ -0,0 +1,31 @@ +package com.datastax.oss.driver.internal.core.config.scyllacloud; + +import java.io.IOException; +import java.net.URL; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import org.junit.Test; + +public class CloudConfigYamlParsingTest { + @Test + public void read_simple_config_and_create_bundle() + throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, + InvalidKeySpecException { + final String CONFIG_PATH = "/config/scyllacloud/testConf.yaml"; + URL url = getClass().getResource(CONFIG_PATH); + ScyllaCloudConnectionConfig scyllaCloudConnectionConfig = + ScyllaCloudConnectionConfig.fromInputStream(url.openStream()); + scyllaCloudConnectionConfig.validate(); + scyllaCloudConnectionConfig.createBundle(); + } + + @Test(expected = IllegalArgumentException.class) + public void read_incomplete_config() throws IOException { + // This config does not contain certificates which are required + final String CONFIG_PATH = "/config/scyllacloud/incompleteConf.yaml"; + URL url = getClass().getResource(CONFIG_PATH); + ScyllaCloudConnectionConfig.fromInputStream(url.openStream()); + } +} diff --git a/core/src/test/resources/config/scyllacloud/incompleteConf.yaml b/core/src/test/resources/config/scyllacloud/incompleteConf.yaml new file mode 100644 index 00000000000..b2fa018b1ef --- /dev/null +++ b/core/src/test/resources/config/scyllacloud/incompleteConf.yaml @@ -0,0 +1,21 @@ +datacenters: + eu-west-123: + server: redacted.com.anskjfbsdiubg:1234 + nodeDomain: redacted.com.anskjfbsdiubg + +authInfos: + default: + clientKeyData: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb0FJQkFBS0NBUUVBbTBnNWRiSGM4cEJEbFRTNDJqY3BmdlVyMTU3QnhsK2JIUFI5ZEZSWXNPTm5DR1NkCldGdWxqaHp1YkVxWE9lYmZhOTdpYmpIK0ZqbkZ4UEJmS0xzUi9nV2VORjNNVFE2MzZqa1YrVTNINDFEalRDT1AKdyt6ZTBDREM1S2huNEhZK0YxMFJDT1BVVWFNZ09nZi9IbXp5aTRiY0xxTUtJR2xEQXpSNHpQMHFnT1Jnemd0Sgo1bnV6SWtCakJtR29nQXdFNGhybnVxWVVjam5aYkZ0a0xZcnl5R2RmMjRSSHpSakw2ZnF5QnF1U2xBMHRqUFU0ClhyNlpQTStsUlhBSjZzNGNRUFZ3UzRHaTk1aWhwM1FmZ3piRlBFcHAzeFJ3Mkd1TXBXNFBlekcyc1dKdTdhb0YKQ294L2htVS9SU21lNllwVHF3Ym94WWF0MW9GT0pnTXFadFJub3dJREFRQUJBb0gvUUV1bmZ4UW1hRWxUc25RaQpGampBOWRDT1ZybGxncjRUZStuQUNHcmtUbG5hVDU5Wmh6eHJVR3lxVEN5V2NXTW5INE1xUG5aMHZyOHRKRjVqCkNIcHMrTUZhd0ZRV1E5SFVWU2ROOGV4QzE5eW1RT0I0cHFkdG1yLzE4cmZJU3lpcWpRSDhDN0pLTjROVDFMTjYKN3g0dFQ1aUhrc2Z2YVh3c1F5ZDIzV24yTDJlM1BuYVUwZkR6RzNYaGxLa3hOYXNpalF2dWNjNDhlYVJ4UlM5WgpFeHdLOC93SGh6WllBa1h4QndZaWNsaG91V1R5b2Jqd2UwVGJHQlJaRCtHWmwxdHU0TlRmb3E1cDR3V1VaUEJhCm1RUXVmbUtWMEZBUkRhYmNpa0lSRXIvelVWUjVzN3daaldzWSs0MUFGc3lUY2dCa2lack1ZVkdGbjMvaUl3R2kKWjRJUkFvR0JBUHFGZ1ZUZ2NCYnpQTjRLbmRMVWVmT2FnZkFSOEoxYjRVRUNpc21rOVhPTlJvRWJPSGpVa3JUMQpCK01lNmVyNXFjSEdSdjlHYVZwWWQ0Y1VyWlhiOGRsZUVSQ3ppYlY4OXpYVzBMZTk2Qk9ra09YK2VyNHh5US8xCnJUWnluZkZBUmRhK3B6VWdqZFFHRnJZREpsZmp2MXRjQU94WEFzWGF5UEZjbEp6SlBNanRBb0dCQUo2dGltRUUKeUlzcE1XOGw3WENNZ3I2aTdyN3M5M0dVT1dYb1RMSU91WENXMWd0bVdwdTRQY0d6MHdWUTNKR3pBNXdEMnJzRQpnbEZXQW9XeW53c1JZUWhiQWZRem5Ba1dqMk9RbC9BaW1oZzF3SkhqUXdzZ0VjbUJrK2tqbGlFTmFtQ1BHM0VJCkk3M2FneHNIN2xuQXhvZnBtSDdpS0Y1MjFva2pEb0NNVjdEUEFvR0FmUCs0TkYxNEVEdDFsMlM4c3NHSng5N0UKRHNFa3laOWFtVkZuWm8yRVd3K1dxanlteE1Oc0lCWlN4U1JibXY3UGtQd1oySzJOUzZMd29OblVjdjIzZ3JuSQowZ2lESWFja3doeFpNQlQxZ3plTmhQU2cwZDJOY1FVb1ZBNkVlQ2VWc1R5WHVZNXd0ZVlEMXZWallGOG16N0xzClV3Qm5SY21Ra2IyYitVNy9vVkVDZ1lCSStQQUpmQVNxRXRDY0Yrb2c5MDF0VkVyTlhQYlNzZUxQbmN3Zm1xdm4KUGtiRFNWZmtBdy9MaytJNHNKNHZGdzlTNFdibTJNVUJtTGRpT3VudlVoZTRtdm5FRHpQejdmOFZQN3JRQVdteQpObzRQeVY3Y3IrdmVLb3dXREhxUFNyY2dIcy8wNUZSampDajg5bUhEdnViT1BEd1lKZk9BdGRBbGt3eXBTMkZNCmV3S0JnRjFBZU1WRXpoTGkzV0RvTktieTE0RFFLVFh1akJjVFR3dkJZTk5OcXU2ZnVhYWJyU3I1azhBR3BaR00KdGgxTUZ0K2N1MnoyZUcvZ2c3enIzU002WVhqQldqNmJpa1NnRnA0TitjQ0JwZ1hYOExLVklua3huY0tEQk5OOQpqdXRPUlQrL1JVcDRDWEhvRWxhRWN1R3VCTVVDNnFEVjdZOWpOWGVDRmZIMHRqc1gKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K + username: cassandra + password: cassandra + +contexts: + default: + datacenterName: eu-west-123 + authInfoName: default + +currentContext: default + +parameters: + defaultConsistency: LOCAL_QUORUM + defaultSerialConsistency: LOCAL_SERIAL diff --git a/core/src/test/resources/config/scyllacloud/testConf.yaml b/core/src/test/resources/config/scyllacloud/testConf.yaml new file mode 100644 index 00000000000..785f79b94cf --- /dev/null +++ b/core/src/test/resources/config/scyllacloud/testConf.yaml @@ -0,0 +1,23 @@ +datacenters: + eu-west-123: + certificateAuthorityData: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVGRENDQXZ5Z0F3SUJBZ0lFSmVQTDBqQU5CZ2txaGtpRzl3MEJBUXNGQURCTE1Rc3dDUVlEVlFRR0V3SlYNClV6RVJNQThHQTFVRUNoTUlSR0YwWVZOMFlYZ3hFREFPQmdOVkJBc1RCME5EVFc1dlpHVXhGekFWQmdOVkJBTVQNCkRrTmhjM05oYm1SeVlTQk9iMlJsTUI0WERUSXlNVEV3TnpFd016RTBOVm9YRFRJek1URXdOekV3TXpFME5Wb3cNClN6RUxNQWtHQTFVRUJoTUNWVk14RVRBUEJnTlZCQW9UQ0VSaGRHRlRkR0Y0TVJBd0RnWURWUVFMRXdkRFEwMXUNCmIyUmxNUmN3RlFZRFZRUURFdzVEWVhOellXNWtjbUVnVG05a1pUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQUQNCmdnRVBBRENDQVFvQ2dnRUJBSnRJT1hXeDNQS1FRNVUwdU5vM0tYNzFLOWVld2NaZm14ejBmWFJVV0xEalp3aGsNCm5WaGJwWTRjN214S2x6bm0zMnZlNG00eC9oWTV4Y1R3WHlpN0VmNEZualJkekUwT3QrbzVGZmxOeCtOUTQwd2oNCmo4UHMzdEFnd3VTb1orQjJQaGRkRVFqajFGR2pJRG9IL3g1czhvdUczQzZqQ2lCcFF3TTBlTXo5S29Ea1lNNEwNClNlWjdzeUpBWXdaaHFJQU1CT0lhNTdxbUZISTUyV3hiWkMySzhzaG5YOXVFUjgwWXkrbjZzZ2Fya3BRTkxZejENCk9GNittVHpQcFVWd0Nlck9IRUQxY0V1Qm92ZVlvYWQwSDRNMnhUeEthZDhVY05ocmpLVnVEM3N4dHJGaWJ1MnENCkJRcU1mNFpsUDBVcG51bUtVNnNHNk1XR3JkYUJUaVlES21iVVo2TUNBd0VBQWFPQi96Q0IvRENCMmdZRFZSMFINCkJJSFNNSUhQZ2hsaGJua3VZMngxYzNSbGNpMXBaQzV6WTNsc2JHRXVZMjl0Z2pvMk5UWTVPVFJtTkMxa09HRTMNCkxUUmtOREl0WW1ObE9DMHdaVGxtWWpJNFl6Z3haalF1WTJ4MWMzUmxjaTFwWkM1elkzbHNiR0V1WTI5dGdqbzUNCk0ySmlZV0V6TXkxbU5tRTNMVFJtTkRJdFlXRmtaUzFqWkRJNU0yTmhOR0ZtT0RJdVkyeDFjM1JsY2kxcFpDNXoNClkzbHNiR0V1WTI5dGdqcG1Nakk0TkRSaFlTMDBPR0ZsTFRSbE5UY3RPR1UxWWkxbE1HSTVPRE5rT0RaallqSXUNClkyeDFjM1JsY2kxcFpDNXpZM2xzYkdFdVkyOXRNQjBHQTFVZERnUVdCQlNuMDFzZEM1QXYzVk1vTGtEeWViajMNCjJqalIwREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBZlBRK1hEdysxMlI0cVZlcnQ1ekVCYVFhNFYyZW11SWMNCks0QkRyQ3lPdFh0eEV1eUhsb0NWS2FzR0FJWi95aTYvZ3p1NnVobDRYcjhrZ2tDaElDcDYwOWlNVFFtL0RnRm8NClQ4TFNlY0oxSjFYa0t5NXFUdXAyL2ROTW5CcXF6SGxMV1FXajNLTDQwMXgzbng4L2lPODJJTTNmZFV3Ri9LZ0QNClRpRVBtNFlydXdaTURqSVl0YVkzV290Y2x6WVcvb1Y1bmRXL1BwemxUNWdqNTFyK2t1bFMvcm5lYmM4cG1tbGwNCmpzMDZrMVEvN1Y3eTRMamNQWk80SG5HbEN1dHhDM1pKT2d2c0pwTTc3ZFdDbkhoNW9qdFJHRkdaUUd3dHBTMXENCjhndUhmMFdIUDdMTTY1dDVCKy8wOVIvdDFsRU1wajQrb3hiTWpkRUNKR0hMOWRQY1N2SlVIZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: redacted.com.anskjfbsdiubg:1234 + nodeDomain: redacted.com.anskjfbsdiubg + +authInfos: + default: + clientCertificateData: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVGRENDQXZ5Z0F3SUJBZ0lFSmVQTDBqQU5CZ2txaGtpRzl3MEJBUXNGQURCTE1Rc3dDUVlEVlFRR0V3SlYNClV6RVJNQThHQTFVRUNoTUlSR0YwWVZOMFlYZ3hFREFPQmdOVkJBc1RCME5EVFc1dlpHVXhGekFWQmdOVkJBTVQNCkRrTmhjM05oYm1SeVlTQk9iMlJsTUI0WERUSXlNVEV3TnpFd016RTBOVm9YRFRJek1URXdOekV3TXpFME5Wb3cNClN6RUxNQWtHQTFVRUJoTUNWVk14RVRBUEJnTlZCQW9UQ0VSaGRHRlRkR0Y0TVJBd0RnWURWUVFMRXdkRFEwMXUNCmIyUmxNUmN3RlFZRFZRUURFdzVEWVhOellXNWtjbUVnVG05a1pUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQUQNCmdnRVBBRENDQVFvQ2dnRUJBSnRJT1hXeDNQS1FRNVUwdU5vM0tYNzFLOWVld2NaZm14ejBmWFJVV0xEalp3aGsNCm5WaGJwWTRjN214S2x6bm0zMnZlNG00eC9oWTV4Y1R3WHlpN0VmNEZualJkekUwT3QrbzVGZmxOeCtOUTQwd2oNCmo4UHMzdEFnd3VTb1orQjJQaGRkRVFqajFGR2pJRG9IL3g1czhvdUczQzZqQ2lCcFF3TTBlTXo5S29Ea1lNNEwNClNlWjdzeUpBWXdaaHFJQU1CT0lhNTdxbUZISTUyV3hiWkMySzhzaG5YOXVFUjgwWXkrbjZzZ2Fya3BRTkxZejENCk9GNittVHpQcFVWd0Nlck9IRUQxY0V1Qm92ZVlvYWQwSDRNMnhUeEthZDhVY05ocmpLVnVEM3N4dHJGaWJ1MnENCkJRcU1mNFpsUDBVcG51bUtVNnNHNk1XR3JkYUJUaVlES21iVVo2TUNBd0VBQWFPQi96Q0IvRENCMmdZRFZSMFINCkJJSFNNSUhQZ2hsaGJua3VZMngxYzNSbGNpMXBaQzV6WTNsc2JHRXVZMjl0Z2pvMk5UWTVPVFJtTkMxa09HRTMNCkxUUmtOREl0WW1ObE9DMHdaVGxtWWpJNFl6Z3haalF1WTJ4MWMzUmxjaTFwWkM1elkzbHNiR0V1WTI5dGdqbzUNCk0ySmlZV0V6TXkxbU5tRTNMVFJtTkRJdFlXRmtaUzFqWkRJNU0yTmhOR0ZtT0RJdVkyeDFjM1JsY2kxcFpDNXoNClkzbHNiR0V1WTI5dGdqcG1Nakk0TkRSaFlTMDBPR0ZsTFRSbE5UY3RPR1UxWWkxbE1HSTVPRE5rT0RaallqSXUNClkyeDFjM1JsY2kxcFpDNXpZM2xzYkdFdVkyOXRNQjBHQTFVZERnUVdCQlNuMDFzZEM1QXYzVk1vTGtEeWViajMNCjJqalIwREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBZlBRK1hEdysxMlI0cVZlcnQ1ekVCYVFhNFYyZW11SWMNCks0QkRyQ3lPdFh0eEV1eUhsb0NWS2FzR0FJWi95aTYvZ3p1NnVobDRYcjhrZ2tDaElDcDYwOWlNVFFtL0RnRm8NClQ4TFNlY0oxSjFYa0t5NXFUdXAyL2ROTW5CcXF6SGxMV1FXajNLTDQwMXgzbng4L2lPODJJTTNmZFV3Ri9LZ0QNClRpRVBtNFlydXdaTURqSVl0YVkzV290Y2x6WVcvb1Y1bmRXL1BwemxUNWdqNTFyK2t1bFMvcm5lYmM4cG1tbGwNCmpzMDZrMVEvN1Y3eTRMamNQWk80SG5HbEN1dHhDM1pKT2d2c0pwTTc3ZFdDbkhoNW9qdFJHRkdaUUd3dHBTMXENCjhndUhmMFdIUDdMTTY1dDVCKy8wOVIvdDFsRU1wajQrb3hiTWpkRUNKR0hMOWRQY1N2SlVIZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + clientKeyData: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb0FJQkFBS0NBUUVBbTBnNWRiSGM4cEJEbFRTNDJqY3BmdlVyMTU3QnhsK2JIUFI5ZEZSWXNPTm5DR1NkCldGdWxqaHp1YkVxWE9lYmZhOTdpYmpIK0ZqbkZ4UEJmS0xzUi9nV2VORjNNVFE2MzZqa1YrVTNINDFEalRDT1AKdyt6ZTBDREM1S2huNEhZK0YxMFJDT1BVVWFNZ09nZi9IbXp5aTRiY0xxTUtJR2xEQXpSNHpQMHFnT1Jnemd0Sgo1bnV6SWtCakJtR29nQXdFNGhybnVxWVVjam5aYkZ0a0xZcnl5R2RmMjRSSHpSakw2ZnF5QnF1U2xBMHRqUFU0ClhyNlpQTStsUlhBSjZzNGNRUFZ3UzRHaTk1aWhwM1FmZ3piRlBFcHAzeFJ3Mkd1TXBXNFBlekcyc1dKdTdhb0YKQ294L2htVS9SU21lNllwVHF3Ym94WWF0MW9GT0pnTXFadFJub3dJREFRQUJBb0gvUUV1bmZ4UW1hRWxUc25RaQpGampBOWRDT1ZybGxncjRUZStuQUNHcmtUbG5hVDU5Wmh6eHJVR3lxVEN5V2NXTW5INE1xUG5aMHZyOHRKRjVqCkNIcHMrTUZhd0ZRV1E5SFVWU2ROOGV4QzE5eW1RT0I0cHFkdG1yLzE4cmZJU3lpcWpRSDhDN0pLTjROVDFMTjYKN3g0dFQ1aUhrc2Z2YVh3c1F5ZDIzV24yTDJlM1BuYVUwZkR6RzNYaGxLa3hOYXNpalF2dWNjNDhlYVJ4UlM5WgpFeHdLOC93SGh6WllBa1h4QndZaWNsaG91V1R5b2Jqd2UwVGJHQlJaRCtHWmwxdHU0TlRmb3E1cDR3V1VaUEJhCm1RUXVmbUtWMEZBUkRhYmNpa0lSRXIvelVWUjVzN3daaldzWSs0MUFGc3lUY2dCa2lack1ZVkdGbjMvaUl3R2kKWjRJUkFvR0JBUHFGZ1ZUZ2NCYnpQTjRLbmRMVWVmT2FnZkFSOEoxYjRVRUNpc21rOVhPTlJvRWJPSGpVa3JUMQpCK01lNmVyNXFjSEdSdjlHYVZwWWQ0Y1VyWlhiOGRsZUVSQ3ppYlY4OXpYVzBMZTk2Qk9ra09YK2VyNHh5US8xCnJUWnluZkZBUmRhK3B6VWdqZFFHRnJZREpsZmp2MXRjQU94WEFzWGF5UEZjbEp6SlBNanRBb0dCQUo2dGltRUUKeUlzcE1XOGw3WENNZ3I2aTdyN3M5M0dVT1dYb1RMSU91WENXMWd0bVdwdTRQY0d6MHdWUTNKR3pBNXdEMnJzRQpnbEZXQW9XeW53c1JZUWhiQWZRem5Ba1dqMk9RbC9BaW1oZzF3SkhqUXdzZ0VjbUJrK2tqbGlFTmFtQ1BHM0VJCkk3M2FneHNIN2xuQXhvZnBtSDdpS0Y1MjFva2pEb0NNVjdEUEFvR0FmUCs0TkYxNEVEdDFsMlM4c3NHSng5N0UKRHNFa3laOWFtVkZuWm8yRVd3K1dxanlteE1Oc0lCWlN4U1JibXY3UGtQd1oySzJOUzZMd29OblVjdjIzZ3JuSQowZ2lESWFja3doeFpNQlQxZ3plTmhQU2cwZDJOY1FVb1ZBNkVlQ2VWc1R5WHVZNXd0ZVlEMXZWallGOG16N0xzClV3Qm5SY21Ra2IyYitVNy9vVkVDZ1lCSStQQUpmQVNxRXRDY0Yrb2c5MDF0VkVyTlhQYlNzZUxQbmN3Zm1xdm4KUGtiRFNWZmtBdy9MaytJNHNKNHZGdzlTNFdibTJNVUJtTGRpT3VudlVoZTRtdm5FRHpQejdmOFZQN3JRQVdteQpObzRQeVY3Y3IrdmVLb3dXREhxUFNyY2dIcy8wNUZSampDajg5bUhEdnViT1BEd1lKZk9BdGRBbGt3eXBTMkZNCmV3S0JnRjFBZU1WRXpoTGkzV0RvTktieTE0RFFLVFh1akJjVFR3dkJZTk5OcXU2ZnVhYWJyU3I1azhBR3BaR00KdGgxTUZ0K2N1MnoyZUcvZ2c3enIzU002WVhqQldqNmJpa1NnRnA0TitjQ0JwZ1hYOExLVklua3huY0tEQk5OOQpqdXRPUlQrL1JVcDRDWEhvRWxhRWN1R3VCTVVDNnFEVjdZOWpOWGVDRmZIMHRqc1gKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K + username: cassandra + password: cassandra + +contexts: + default: + datacenterName: eu-west-123 + authInfoName: default + +currentContext: default + +parameters: + defaultConsistency: LOCAL_QUORUM + defaultSerialConsistency: LOCAL_SERIAL From 0e506cbc1ac0340ef738e8c2e272ad807d3e9e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Tue, 6 Dec 2022 16:15:05 +0100 Subject: [PATCH 06/12] Add support for connecting with Scylla Cloud clusters Allows driver to build sessions with Scylla Cloud yaml configuration files and to establish connection with Scylla Cloud clusters. Takes care of control connection random node issue by replacing the endpoint after the first connection with one that points to a specific node in a slightly hacky way. --- .../core/session/ProgrammaticArguments.java | 18 ++++ .../api/core/session/SessionBuilder.java | 90 ++++++++++--------- .../internal/core/channel/DriverChannel.java | 7 +- .../core/context/DefaultDriverContext.java | 6 ++ .../core/metadata/DefaultTopologyMonitor.java | 8 +- .../metadata/ScyllaCloudTopologyMonitor.java | 82 +++++++++++++++++ 6 files changed, 166 insertions(+), 45 deletions(-) create mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/ScyllaCloudTopologyMonitor.java diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/ProgrammaticArguments.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/ProgrammaticArguments.java index b8b2bd8b723..168c4f8edff 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/ProgrammaticArguments.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/ProgrammaticArguments.java @@ -64,6 +64,7 @@ public static Builder builder() { private final AuthProvider authProvider; private final SslEngineFactory sslEngineFactory; private final InetSocketAddress cloudProxyAddress; + private final String scyllaCloudNodeDomain; private final UUID startupClientId; private final String startupApplicationName; private final String startupApplicationVersion; @@ -82,6 +83,7 @@ private ProgrammaticArguments( @Nullable AuthProvider authProvider, @Nullable SslEngineFactory sslEngineFactory, @Nullable InetSocketAddress cloudProxyAddress, + @Nullable String scyllaCloudNodeDomain, @Nullable UUID startupClientId, @Nullable String startupApplicationName, @Nullable String startupApplicationVersion, @@ -99,6 +101,7 @@ private ProgrammaticArguments( this.authProvider = authProvider; this.sslEngineFactory = sslEngineFactory; this.cloudProxyAddress = cloudProxyAddress; + this.scyllaCloudNodeDomain = scyllaCloudNodeDomain; this.startupClientId = startupClientId; this.startupApplicationName = startupApplicationName; this.startupApplicationVersion = startupApplicationVersion; @@ -163,6 +166,11 @@ public InetSocketAddress getCloudProxyAddress() { return cloudProxyAddress; } + @Nullable + public String getScyllaCloudNodeDomain() { + return scyllaCloudNodeDomain; + } + @Nullable public UUID getStartupClientId() { return startupClientId; @@ -203,6 +211,7 @@ public static class Builder { private AuthProvider authProvider; private SslEngineFactory sslEngineFactory; private InetSocketAddress cloudProxyAddress; + private String scyllaCloudNodeDomain; private UUID startupClientId; private String startupApplicationName; private String startupApplicationVersion; @@ -366,6 +375,14 @@ public Builder withCloudProxyAddress(@Nullable InetSocketAddress cloudAddress) { return this; } + @NonNull + public Builder withScyllaCloudProxyAddress( + @Nullable InetSocketAddress cloudAddress, String scyllaCloudNodeDomain) { + this.cloudProxyAddress = cloudAddress; + this.scyllaCloudNodeDomain = scyllaCloudNodeDomain; + return this; + } + @NonNull public Builder withAuthProvider(@Nullable AuthProvider authProvider) { this.authProvider = authProvider; @@ -422,6 +439,7 @@ public ProgrammaticArguments build() { authProvider, sslEngineFactory, cloudProxyAddress, + scyllaCloudNodeDomain, startupClientId, startupApplicationName, startupApplicationVersion, diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index fba54c29905..2b0c9577f28 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -44,15 +44,17 @@ import com.datastax.oss.driver.api.core.type.codec.registry.MutableCodecRegistry; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.internal.core.ContactPoints; -import com.datastax.oss.driver.internal.core.config.cloud.CloudConfig; -import com.datastax.oss.driver.internal.core.config.cloud.CloudConfigFactory; +import com.datastax.oss.driver.internal.core.config.scyllacloud.ConfigurationBundle; +import com.datastax.oss.driver.internal.core.config.scyllacloud.ScyllaCloudConnectionConfig; import com.datastax.oss.driver.internal.core.config.typesafe.DefaultDriverConfigLoader; import com.datastax.oss.driver.internal.core.context.DefaultDriverContext; import com.datastax.oss.driver.internal.core.context.InternalDriverContext; import com.datastax.oss.driver.internal.core.metadata.DefaultEndPoint; +import com.datastax.oss.driver.internal.core.metadata.SniEndPoint; import com.datastax.oss.driver.internal.core.session.DefaultSession; import com.datastax.oss.driver.internal.core.util.concurrent.BlockingOperation; import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.InputStream; @@ -60,7 +62,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -96,6 +97,7 @@ public abstract class SessionBuilder { protected Set programmaticContactPoints = new HashSet<>(); protected CqlIdentifier keyspace; protected Callable cloudConfigInputStream; + protected Callable scyllaCloudConfigInputStream; protected ProgrammaticArguments.Builder programmaticArgumentsBuilder = ProgrammaticArguments.builder(); @@ -655,6 +657,17 @@ public SelfT withCloudSecureConnectBundle(@NonNull Path cloudConfigPath) { return self; } + @NonNull + public SelfT withScyllaCloudSecureConnectBundle(@NonNull Path cloudConfigPath) { + try { + URL cloudConfigUrl = cloudConfigPath.toAbsolutePath().normalize().toUri().toURL(); + this.scyllaCloudConfigInputStream = cloudConfigUrl::openStream; + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Incorrect format of cloudConfigPath", e); + } + return self; + } + /** * Registers a CodecRegistry to use for the session. * @@ -687,6 +700,12 @@ public SelfT withCloudSecureConnectBundle(@NonNull URL cloudConfigUrl) { return self; } + @NonNull + public SelfT withScyllaCloudSecureConnectBundle(@NonNull URL cloudConfigUrl) { + this.scyllaCloudConfigInputStream = cloudConfigUrl::openStream; + return self; + } + /** * Configures this SessionBuilder for Cloud deployments by retrieving connection information from * the provided {@link InputStream}. @@ -711,6 +730,12 @@ public SelfT withCloudSecureConnectBundle(@NonNull InputStream cloudConfigInputS return self; } + @NonNull + public SelfT withScyllaCloudSecureConnectBundle(@NonNull InputStream cloudConfigInputStream) { + this.scyllaCloudConfigInputStream = () -> cloudConfigInputStream; + return self; + } + /** * Configures this SessionBuilder to use the provided Cloud proxy endpoint. * @@ -733,6 +758,13 @@ public SelfT withCloudProxyAddress(@Nullable InetSocketAddress cloudProxyAddress return self; } + @NonNull + public SelfT withScyllaCloudProxyAddress( + @Nullable InetSocketAddress cloudProxyAddress, String nodeDomain) { + this.programmaticArgumentsBuilder.withScyllaCloudProxyAddress(cloudProxyAddress, nodeDomain); + return self; + } + /** * A unique identifier for the created session. * @@ -857,16 +889,9 @@ protected final CompletionStage buildDefaultSessionAsync() { : defaultConfigLoader(programmaticArguments.getClassLoader()); DriverExecutionProfile defaultConfig = configLoader.getInitialConfig().getDefaultProfile(); - if (cloudConfigInputStream == null) { - String configUrlString = - defaultConfig.getString(DefaultDriverOption.CLOUD_SECURE_CONNECT_BUNDLE, null); - if (configUrlString != null) { - cloudConfigInputStream = () -> getURL(configUrlString).openStream(); - } - } List configContactPoints = defaultConfig.getStringList(DefaultDriverOption.CONTACT_POINTS, Collections.emptyList()); - if (cloudConfigInputStream != null) { + if (scyllaCloudConfigInputStream != null) { if (!programmaticContactPoints.isEmpty() || !configContactPoints.isEmpty()) { LOG.info( "Both a secure connect bundle and contact points were provided. These are mutually exclusive. The contact points from the secure bundle will have priority."); @@ -880,20 +905,27 @@ protected final CompletionStage buildDefaultSessionAsync() { LOG.info( "Both a secure connect bundle and SSL options were provided. They are mutually exclusive. The SSL options from the secure bundle will have priority."); } - CloudConfig cloudConfig = - new CloudConfigFactory().createCloudConfig(cloudConfigInputStream.call()); - addContactEndPoints(cloudConfig.getEndPoints()); + ScyllaCloudConnectionConfig cloudConfig = + ScyllaCloudConnectionConfig.fromInputStream(scyllaCloudConfigInputStream.call()); + InetSocketAddress proxyAddress = cloudConfig.getCurrentDatacenter().getServer(); + addContactEndPoints( + ImmutableList.of( + new SniEndPoint(proxyAddress, cloudConfig.getCurrentDatacenter().getNodeDomain()))); boolean localDataCenterDefined = anyProfileHasDatacenterDefined(configLoader.getInitialConfig()); if (programmaticLocalDatacenter || localDataCenterDefined) { LOG.info( - "Both a secure connect bundle and a local datacenter were provided. They are mutually exclusive. The local datacenter from the secure bundle will have priority."); + "Both a secure connect bundle and a local datacenter were provided. They are mutually exclusive. The currentContext datacenter name from the secure bundle will be ignored."); + } else { programmaticArgumentsBuilder.clearDatacenters(); + withLocalDatacenter(cloudConfig.getCurrentContext().getDatacenterName()); } - withLocalDatacenter(cloudConfig.getLocalDatacenter()); - withSslEngineFactory(cloudConfig.getSslEngineFactory()); - withCloudProxyAddress(cloudConfig.getProxyAddress()); + ConfigurationBundle bundle = cloudConfig.createBundle(); + withSslEngineFactory(bundle.getSSLEngineFactory()); + withScyllaCloudProxyAddress( + proxyAddress, cloudConfig.getCurrentDatacenter().getNodeDomain()); + programmaticArguments = programmaticArgumentsBuilder.build(); } @@ -912,7 +944,6 @@ protected final CompletionStage buildDefaultSessionAsync() { (InternalDriverContext) buildContext(configLoader, programmaticArguments), contactPoints, keyspace); - } catch (Throwable t) { // We construct the session synchronously (until the init() call), but async clients expect a // failed future if anything goes wrong. So wrap any error from that synchronous part. @@ -929,27 +960,6 @@ private boolean anyProfileHasDatacenterDefined(DriverConfig driverConfig) { return false; } - /** - * Returns URL based on the configUrl setting. If the configUrl has no protocol provided, the - * method will fallback to file:// protocol and return URL that has file protocol specified. - * - * @param configUrl url to config secure bundle - * @return URL with file protocol if there was not explicit protocol provided in the configUrl - * setting - */ - private URL getURL(String configUrl) throws MalformedURLException { - try { - return new URL(configUrl); - } catch (MalformedURLException e1) { - try { - return Paths.get(configUrl).toAbsolutePath().normalize().toUri().toURL(); - } catch (MalformedURLException e2) { - e2.addSuppressed(e1); - throw e2; - } - } - } - /** * This must return an instance of {@code InternalDriverContext} (it's not expressed * directly in the signature to avoid leaking that type through the protected API). diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java index e1b1005cc14..7c69c987e0d 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/channel/DriverChannel.java @@ -69,7 +69,7 @@ public class DriverChannel { @SuppressWarnings("RedundantStringConstructorCall") static final Object FORCEFUL_CLOSE_MESSAGE = new String("FORCEFUL_CLOSE_MESSAGE"); - private final EndPoint endPoint; + private EndPoint endPoint; private final Channel channel; private final InFlightHandler inFlightHandler; private final WriteCoalescer writeCoalescer; @@ -326,4 +326,9 @@ public SetKeyspaceEvent(CqlIdentifier keyspaceName, Promise promise) { this.promise = promise; } } + + // Necessary for swapping ControlConnection endpoint when connecting with serverless clusters + public void setEndPoint(EndPoint endPoint) { + this.endPoint = endPoint; + } } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index 2dc0e45d7b8..b412e55a412 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -60,6 +60,7 @@ import com.datastax.oss.driver.internal.core.metadata.MetadataManager; import com.datastax.oss.driver.internal.core.metadata.MultiplexingNodeStateListener; import com.datastax.oss.driver.internal.core.metadata.NoopNodeStateListener; +import com.datastax.oss.driver.internal.core.metadata.ScyllaCloudTopologyMonitor; import com.datastax.oss.driver.internal.core.metadata.TopologyMonitor; import com.datastax.oss.driver.internal.core.metadata.schema.MultiplexingSchemaChangeListener; import com.datastax.oss.driver.internal.core.metadata.schema.NoopSchemaChangeListener; @@ -236,6 +237,7 @@ public class DefaultDriverContext implements InternalDriverContext { private final Map nodeDistanceEvaluatorsFromBuilder; private final ClassLoader classLoader; private final InetSocketAddress cloudProxyAddress; + private final String scyllaCloudNodeDomain; private final LazyReference requestLogFormatterRef = new LazyReference<>("requestLogFormatter", this::buildRequestLogFormatter, cycleDetector); private final UUID startupClientId; @@ -291,6 +293,7 @@ public DefaultDriverContext( this.nodeDistanceEvaluatorsFromBuilder = programmaticArguments.getNodeDistanceEvaluators(); this.classLoader = programmaticArguments.getClassLoader(); this.cloudProxyAddress = programmaticArguments.getCloudProxyAddress(); + this.scyllaCloudNodeDomain = programmaticArguments.getScyllaCloudNodeDomain(); this.startupClientId = programmaticArguments.getStartupClientId(); this.startupApplicationName = programmaticArguments.getStartupApplicationName(); this.startupApplicationVersion = programmaticArguments.getStartupApplicationVersion(); @@ -492,6 +495,9 @@ protected TopologyMonitor buildTopologyMonitor() { if (cloudProxyAddress == null) { return new DefaultTopologyMonitor(this); } + if (scyllaCloudNodeDomain != null) { + return new ScyllaCloudTopologyMonitor(this, cloudProxyAddress, scyllaCloudNodeDomain); + } return new CloudTopologyMonitor(this, cloudProxyAddress); } diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java index da5fc2115eb..6716e1458b5 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/DefaultTopologyMonitor.java @@ -68,11 +68,11 @@ public class DefaultTopologyMonitor implements TopologyMonitor { private static final int INFINITE_PAGE_SIZE = -1; private final String logPrefix; - private final InternalDriverContext context; - private final ControlConnection controlConnection; + protected final InternalDriverContext context; + protected final ControlConnection controlConnection; private final Duration timeout; - private final boolean reconnectOnInit; - private final CompletableFuture closeFuture; + protected final boolean reconnectOnInit; + protected final CompletableFuture closeFuture; @VisibleForTesting volatile boolean isSchemaV2; @VisibleForTesting volatile int port = -1; diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/ScyllaCloudTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/ScyllaCloudTopologyMonitor.java new file mode 100644 index 00000000000..b7e1ce690b2 --- /dev/null +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/ScyllaCloudTopologyMonitor.java @@ -0,0 +1,82 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed 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.internal.core.metadata; + +import com.datastax.oss.driver.api.core.metadata.EndPoint; +import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; +import com.datastax.oss.driver.internal.core.context.InternalDriverContext; +import com.datastax.oss.driver.internal.core.util.concurrent.CompletableFutures; +import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletionStage; + +public class ScyllaCloudTopologyMonitor extends DefaultTopologyMonitor { + + private final InetSocketAddress cloudProxyAddress; + private final String nodeDomain; + + public ScyllaCloudTopologyMonitor( + InternalDriverContext context, InetSocketAddress cloudProxyAddress, String nodeDomain) { + super(context); + this.cloudProxyAddress = cloudProxyAddress; + this.nodeDomain = nodeDomain; + } + + @NonNull + @Override + protected EndPoint buildNodeEndPoint( + @NonNull AdminRow row, + @Nullable InetSocketAddress broadcastRpcAddress, + @NonNull EndPoint localEndPoint) { + UUID hostId = Objects.requireNonNull(row.getUuid("host_id")); + return new SniEndPoint(cloudProxyAddress, hostId + "." + nodeDomain); + } + + // Perform usual init with extra steps. After establishing connection we need to replace + // endpoint that randomizes target node with concrete endpoint to the specific node. + @Override + public CompletionStage init() { + if (closeFuture.isDone()) { + return CompletableFutures.failedFuture(new IllegalStateException("closed")); + } + return controlConnection + .init(true, reconnectOnInit, true) + .thenCompose( + v -> { + return query( + controlConnection.channel(), + "SELECT host_id FROM system.local", + Collections.emptyMap()) + .toCompletableFuture(); + }) + .thenApply( + adminResult -> { + AdminRow localRow = adminResult.iterator().next(); + UUID hostId = localRow.getUuid("host_id"); + EndPoint newEndpoint = new SniEndPoint(cloudProxyAddress, hostId + "." + nodeDomain); + // Replace initial contact point with specified endpoint, so that reconnections won't + // choose different random node + context.getMetadataManager().addContactPoints(ImmutableSet.of(newEndpoint)); + controlConnection.channel().setEndPoint(newEndpoint); + return null; + }); + } +} From 0bc0f08772101b73095b68135d83e49ad1009027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Tue, 6 Dec 2022 16:25:33 +0100 Subject: [PATCH 07/12] Add test support for ccm Scylla Cloud clusters This amounts to enabling support for using "--sni-proxy" and "--sni-port" flags and probing for path to generated Scylla Cloud configuration file. --- .../driver/api/testinfra/ccm/CcmBridge.java | 35 +++++++++++++++++-- .../api/testinfra/ccm/CustomCcmRule.java | 5 +++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index 41801e52bcc..099e6ef3153 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -23,6 +23,7 @@ import com.datastax.oss.driver.api.core.Version; import com.datastax.oss.driver.shaded.guava.common.base.Joiner; +import com.datastax.oss.driver.shaded.guava.common.base.Preconditions; import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap; import com.datastax.oss.driver.shaded.guava.common.collect.Maps; import com.datastax.oss.driver.shaded.guava.common.io.Resources; @@ -179,6 +180,8 @@ public class CcmBridge implements AutoCloseable { private final List dseWorkloads; private final String jvmArgs; + private final int sniProxyPort; + private CcmBridge( Path configDirectory, int[] nodes, @@ -188,7 +191,8 @@ private CcmBridge( List dseConfigurationRawYaml, List createOptions, Collection jvmArgs, - List dseWorkloads) { + List dseWorkloads, + int sniProxyPort) { this.configDirectory = configDirectory; if (nodes.length == 1) { // Hack to ensure that the default DC is always called 'dc1': pass a list ('-nX:0') even if @@ -216,6 +220,7 @@ private CcmBridge( } this.jvmArgs = allJvmArgs.toString(); this.dseWorkloads = dseWorkloads; + this.sniProxyPort = sniProxyPort; } // Copied from Netty's PlatformDependent to avoid the dependency on Netty @@ -290,6 +295,14 @@ private String getCcmVersionString(Version version) { return version.toString(); } + public String getScyllaCloudConfigPathString() { + return configDirectory.toFile().getAbsolutePath() + "/" + CLUSTER_NAME + "/config_data.yaml"; + } + + public String getIpPrefix() { + return ipPrefix; + } + public void create() { if (created.compareAndSet(false, true)) { if (INSTALL_DIRECTORY != null) { @@ -359,7 +372,12 @@ public void reloadCore(int node, String keyspace, String table, boolean reindex) public void start() { if (started.compareAndSet(false, true)) { try { - execute("start", jvmArgs, "--wait-for-binary-proto"); + execute( + "start", + jvmArgs, + "--wait-for-binary-proto", + (sniProxyPort >= 0 ? "--sni-proxy" : ""), + (sniProxyPort > 0 ? "--sni-port=" + sniProxyPort : "")); } catch (RuntimeException re) { // if something went wrong starting CCM, see if we can also dump the error executeCheckLogError(); @@ -510,6 +528,8 @@ public static class Builder { private final Path configDirectory; + private int sniProxyPort = -1; + private Builder() { try { this.configDirectory = Files.createTempDirectory("ccm"); @@ -614,6 +634,14 @@ public Builder withDseWorkloads(String... workloads) { return this; } + /** Enable SNI proxy and use given port number. Port 0 means any port. */ + public Builder withSniProxy(int port) { + Preconditions.checkArgument(port >= 0); + Preconditions.checkArgument(port <= 65535); + this.sniProxyPort = port; + return this; + } + public CcmBridge build() { return new CcmBridge( configDirectory, @@ -624,7 +652,8 @@ public CcmBridge build() { dseRawYaml, createOptions, jvmArgs, - dseWorkloads); + dseWorkloads, + sniProxyPort); } } } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java index 4ea1b3843f3..e6b0dd0a71b 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CustomCcmRule.java @@ -112,6 +112,11 @@ public Builder withSslAuth() { return this; } + public Builder withSniProxy(int port) { + bridgeBuilder.withSniProxy(port); + return this; + } + public CustomCcmRule build() { return new CustomCcmRule(bridgeBuilder.build()); } From 656575ca041e1e937d306ec034bc6bbe91ef59c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Tue, 6 Dec 2022 16:30:47 +0100 Subject: [PATCH 08/12] Add test using local SNI proxy enabled Scylla cluster Adds a single test method configured to connect to local 3 node ccm cluster with SNI proxy enabled and perform some basic operations. --- .../core/cloud/ScyllaCloudMultiNodeIT.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/ScyllaCloudMultiNodeIT.java diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/ScyllaCloudMultiNodeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/ScyllaCloudMultiNodeIT.java new file mode 100644 index 00000000000..f1449ea68ca --- /dev/null +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/ScyllaCloudMultiNodeIT.java @@ -0,0 +1,88 @@ +package com.datastax.oss.driver.api.core.cloud; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; +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.metadata.EndPoint; +import com.datastax.oss.driver.api.core.metadata.Node; +import com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; +import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListenerBase; +import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; +import com.datastax.oss.driver.api.testinfra.CassandraSkip; +import com.datastax.oss.driver.api.testinfra.ccm.CustomCcmRule; +import com.datastax.oss.driver.categories.IsolatedTests; +import com.datastax.oss.driver.internal.core.config.scyllacloud.ScyllaCloudConnectionConfig; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Map; +import java.util.UUID; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.mockito.Mockito; + +@Category(IsolatedTests.class) +@CassandraSkip +public class ScyllaCloudMultiNodeIT { + + private static final int NUMBER_OF_NODES = 3; + private static final int SNI_PORT = 0; // Let CCM pick + + @ClassRule + public static CustomCcmRule CCM_RULE = + CustomCcmRule.builder().withNodes(NUMBER_OF_NODES).withSniProxy(SNI_PORT).build(); + + @Test + public void connect_w_simple_operations_protocol_v4() { + String configPath = CCM_RULE.getCcmBridge().getScyllaCloudConfigPathString(); + File configFile = new File(configPath); + DriverConfigLoader loader = + DriverConfigLoader.programmaticBuilder() + .withString(DefaultDriverOption.PROTOCOL_VERSION, DefaultProtocolVersion.V4.toString()) + .build(); + SchemaChangeListener mockListener = Mockito.mock(SchemaChangeListenerBase.class); + try (CqlSession session = + CqlSession.builder() + .withConfigLoader(loader) + .withScyllaCloudSecureConnectBundle(configFile.toPath()) + // Currently ccm produces cloud config with eu-west-1 dc name but uses dc1 + .withLocalDatacenter("dc1") + .withSchemaChangeListener(mockListener) + .build()) { + + session.execute( + String.format( + "CREATE KEYSPACE %s " + + "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': %d}", + "testks", NUMBER_OF_NODES)); + session.execute("CREATE TABLE testks.testtab (a int PRIMARY KEY, b int);"); + + verify(mockListener, times(1)).onTableCreated(any(TableMetadata.class)); + verify(mockListener, times(1)).onKeyspaceCreated(any(KeyspaceMetadata.class)); + + int sniPort = + ScyllaCloudConnectionConfig.fromInputStream(Files.newInputStream(configFile.toPath())) + .getCurrentDatacenter() + .getServer() + .getPort(); + Map map = session.getMetadata().getNodes(); + assertThat(map.size()).isEqualTo(NUMBER_OF_NODES); + String expectedEndpointPrefix = CCM_RULE.getCcmBridge().getIpPrefix() + "1:" + sniPort + ":"; + for (Map.Entry entry : map.entrySet()) { + EndPoint endPoint = entry.getValue().getEndPoint(); + assertThat(endPoint.toString()).startsWith(expectedEndpointPrefix); + assertThat(endPoint.toString()).contains(entry.getKey().toString()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} From 529f1963179f242c0b1cd567f2f12c74bbfa6094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Tue, 6 Dec 2022 17:06:26 +0100 Subject: [PATCH 09/12] Remove code and references to Datastax Astra cloud Having both at the same time can be confusing for the users and future contributors. --- .../api/core/session/SessionBuilder.java | 92 ---- .../core/config/cloud/CloudConfig.java | 64 --- .../core/config/cloud/CloudConfigFactory.java | 295 ----------- .../core/context/DefaultDriverContext.java | 8 +- .../core/metadata/CloudTopologyMonitor.java | 45 -- .../config/cloud/CloudConfigFactoryTest.java | 233 -------- .../src/test/resources/config/cloud/creds.zip | Bin 9805 -> 0 bytes .../test/resources/config/cloud/identity.jks | Bin 2493 -> 0 bytes .../test/resources/config/cloud/metadata.json | 1 - .../resources/config/cloud/trustStore.jks | Bin 956 -> 0 bytes .../astra/AstraReadCassandraVersion.java | 79 --- .../oss/driver/api/core/cloud/CloudIT.java | 498 ------------------ .../driver/api/core/cloud/SniProxyRule.java | 41 -- .../driver/api/core/cloud/SniProxyServer.java | 148 ------ .../ScyllaCloudMultiNodeIT.java | 2 +- 15 files changed, 3 insertions(+), 1503 deletions(-) delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfig.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfigFactory.java delete mode 100644 core/src/main/java/com/datastax/oss/driver/internal/core/metadata/CloudTopologyMonitor.java delete mode 100644 core/src/test/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfigFactoryTest.java delete mode 100644 core/src/test/resources/config/cloud/creds.zip delete mode 100644 core/src/test/resources/config/cloud/identity.jks delete mode 100644 core/src/test/resources/config/cloud/metadata.json delete mode 100644 core/src/test/resources/config/cloud/trustStore.jks delete mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/astra/AstraReadCassandraVersion.java delete mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/CloudIT.java delete mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/SniProxyRule.java delete mode 100644 integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/SniProxyServer.java rename integration-tests/src/test/java/com/datastax/oss/driver/api/core/{cloud => scyllacloud}/ScyllaCloudMultiNodeIT.java (98%) diff --git a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java index 2b0c9577f28..4a73de40c3f 100644 --- a/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java +++ b/core/src/main/java/com/datastax/oss/driver/api/core/session/SessionBuilder.java @@ -96,7 +96,6 @@ public abstract class SessionBuilder { protected DriverConfigLoader configLoader; protected Set programmaticContactPoints = new HashSet<>(); protected CqlIdentifier keyspace; - protected Callable cloudConfigInputStream; protected Callable scyllaCloudConfigInputStream; protected ProgrammaticArguments.Builder programmaticArgumentsBuilder = @@ -632,31 +631,6 @@ public SelfT withClassLoader(@Nullable ClassLoader classLoader) { return self; } - /** - * Configures this SessionBuilder for Cloud deployments by retrieving connection information from - * the provided {@link Path}. - * - *

To connect to a Cloud database, you must first download the secure database bundle from the - * DataStax Astra console that contains the connection information, then instruct the driver to - * read its contents using either this method or one if its variants. - * - *

For more information, please refer to the DataStax Astra documentation. - * - * @param cloudConfigPath Path to the secure connect bundle zip file. - * @see #withCloudSecureConnectBundle(URL) - * @see #withCloudSecureConnectBundle(InputStream) - */ - @NonNull - public SelfT withCloudSecureConnectBundle(@NonNull Path cloudConfigPath) { - try { - URL cloudConfigUrl = cloudConfigPath.toAbsolutePath().normalize().toUri().toURL(); - this.cloudConfigInputStream = cloudConfigUrl::openStream; - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Incorrect format of cloudConfigPath", e); - } - return self; - } - @NonNull public SelfT withScyllaCloudSecureConnectBundle(@NonNull Path cloudConfigPath) { try { @@ -680,84 +654,18 @@ public SelfT withCodecRegistry(@Nullable MutableCodecRegistry codecRegistry) { return self; } - /** - * Configures this SessionBuilder for Cloud deployments by retrieving connection information from - * the provided {@link URL}. - * - *

To connect to a Cloud database, you must first download the secure database bundle from the - * DataStax Astra console that contains the connection information, then instruct the driver to - * read its contents using either this method or one if its variants. - * - *

For more information, please refer to the DataStax Astra documentation. - * - * @param cloudConfigUrl URL to the secure connect bundle zip file. - * @see #withCloudSecureConnectBundle(Path) - * @see #withCloudSecureConnectBundle(InputStream) - */ - @NonNull - public SelfT withCloudSecureConnectBundle(@NonNull URL cloudConfigUrl) { - this.cloudConfigInputStream = cloudConfigUrl::openStream; - return self; - } - @NonNull public SelfT withScyllaCloudSecureConnectBundle(@NonNull URL cloudConfigUrl) { this.scyllaCloudConfigInputStream = cloudConfigUrl::openStream; return self; } - /** - * Configures this SessionBuilder for Cloud deployments by retrieving connection information from - * the provided {@link InputStream}. - * - *

To connect to a Cloud database, you must first download the secure database bundle from the - * DataStax Astra console that contains the connection information, then instruct the driver to - * read its contents using either this method or one if its variants. - * - *

For more information, please refer to the DataStax Astra documentation. - * - *

Note that the provided stream will be consumed and closed when either {@link - * #build()} or {@link #buildAsync()} are called; attempting to reuse it afterwards will result in - * an error being thrown. - * - * @param cloudConfigInputStream A stream containing the secure connect bundle zip file. - * @see #withCloudSecureConnectBundle(Path) - * @see #withCloudSecureConnectBundle(URL) - */ - @NonNull - public SelfT withCloudSecureConnectBundle(@NonNull InputStream cloudConfigInputStream) { - this.cloudConfigInputStream = () -> cloudConfigInputStream; - return self; - } - @NonNull public SelfT withScyllaCloudSecureConnectBundle(@NonNull InputStream cloudConfigInputStream) { this.scyllaCloudConfigInputStream = () -> cloudConfigInputStream; return self; } - /** - * Configures this SessionBuilder to use the provided Cloud proxy endpoint. - * - *

Normally, this method should not be called directly; the normal and easiest way to configure - * the driver for Cloud deployments is through a {@linkplain #withCloudSecureConnectBundle(URL) - * secure connect bundle}. - * - *

Setting this option to any non-null address will make the driver use a special topology - * monitor tailored for Cloud deployments. This topology monitor assumes that the target cluster - * should be contacted through the proxy specified here, using SNI routing. - * - *

For more information, please refer to the DataStax Astra documentation. - * - * @param cloudProxyAddress The address of the Cloud proxy to use. - * @see Server Name Indication - */ - @NonNull - public SelfT withCloudProxyAddress(@Nullable InetSocketAddress cloudProxyAddress) { - this.programmaticArgumentsBuilder.withCloudProxyAddress(cloudProxyAddress); - return self; - } - @NonNull public SelfT withScyllaCloudProxyAddress( @Nullable InetSocketAddress cloudProxyAddress, String nodeDomain) { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfig.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfig.java deleted file mode 100644 index e2207e3db95..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfig.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed 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.internal.core.config.cloud; - -import com.datastax.oss.driver.api.core.metadata.EndPoint; -import com.datastax.oss.driver.api.core.ssl.SslEngineFactory; -import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.net.InetSocketAddress; -import java.util.List; -import net.jcip.annotations.ThreadSafe; - -@ThreadSafe -public class CloudConfig { - - private final InetSocketAddress proxyAddress; - private final List endPoints; - private final String localDatacenter; - private final SslEngineFactory sslEngineFactory; - - CloudConfig( - @NonNull InetSocketAddress proxyAddress, - @NonNull List endPoints, - @NonNull String localDatacenter, - @NonNull SslEngineFactory sslEngineFactory) { - this.proxyAddress = proxyAddress; - this.endPoints = ImmutableList.copyOf(endPoints); - this.localDatacenter = localDatacenter; - this.sslEngineFactory = sslEngineFactory; - } - - @NonNull - public InetSocketAddress getProxyAddress() { - return proxyAddress; - } - - @NonNull - public List getEndPoints() { - return endPoints; - } - - @NonNull - public String getLocalDatacenter() { - return localDatacenter; - } - - @NonNull - public SslEngineFactory getSslEngineFactory() { - return sslEngineFactory; - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfigFactory.java b/core/src/main/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfigFactory.java deleted file mode 100644 index f7386dcc390..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfigFactory.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed 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.internal.core.config.cloud; - -import com.datastax.oss.driver.api.core.metadata.EndPoint; -import com.datastax.oss.driver.internal.core.metadata.SniEndPoint; -import com.datastax.oss.driver.internal.core.ssl.SniSslEngineFactory; -import com.datastax.oss.driver.shaded.guava.common.io.ByteStreams; -import com.datastax.oss.driver.shaded.guava.common.net.HostAndPort; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.ConnectException; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import net.jcip.annotations.ThreadSafe; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@ThreadSafe -public class CloudConfigFactory { - private static final Logger LOG = LoggerFactory.getLogger(CloudConfigFactory.class); - /** - * Creates a {@link CloudConfig} with information fetched from the specified Cloud configuration - * URL. - * - *

The target URL must point to a valid secure connect bundle archive in ZIP format. - * - * @param cloudConfigUrl the URL to fetch the Cloud configuration from; cannot be null. - * @throws IOException If the Cloud configuration cannot be read. - * @throws GeneralSecurityException If the Cloud SSL context cannot be created. - */ - @NonNull - public CloudConfig createCloudConfig(@NonNull URL cloudConfigUrl) - throws IOException, GeneralSecurityException { - Objects.requireNonNull(cloudConfigUrl, "cloudConfigUrl cannot be null"); - return createCloudConfig(cloudConfigUrl.openStream()); - } - - /** - * Creates a {@link CloudConfig} with information fetched from the specified {@link InputStream}. - * - *

The stream must contain a valid secure connect bundle archive in ZIP format. Note that the - * stream will be closed after a call to that method and cannot be used anymore. - * - * @param cloudConfig the stream to read the Cloud configuration from; cannot be null. - * @throws IOException If the Cloud configuration cannot be read. - * @throws GeneralSecurityException If the Cloud SSL context cannot be created. - */ - @NonNull - public CloudConfig createCloudConfig(@NonNull InputStream cloudConfig) - throws IOException, GeneralSecurityException { - Objects.requireNonNull(cloudConfig, "cloudConfig cannot be null"); - JsonNode configJson = null; - ByteArrayOutputStream keyStoreOutputStream = null; - ByteArrayOutputStream trustStoreOutputStream = null; - ObjectMapper mapper = new ObjectMapper().configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false); - try (ZipInputStream zipInputStream = new ZipInputStream(cloudConfig)) { - ZipEntry entry; - while ((entry = zipInputStream.getNextEntry()) != null) { - String fileName = entry.getName(); - switch (fileName) { - case "config.json": - configJson = mapper.readTree(zipInputStream); - break; - case "identity.jks": - keyStoreOutputStream = new ByteArrayOutputStream(); - ByteStreams.copy(zipInputStream, keyStoreOutputStream); - break; - case "trustStore.jks": - trustStoreOutputStream = new ByteArrayOutputStream(); - ByteStreams.copy(zipInputStream, trustStoreOutputStream); - break; - } - } - } - if (configJson == null) { - throw new IllegalStateException("Invalid bundle: missing file config.json"); - } - if (keyStoreOutputStream == null) { - throw new IllegalStateException("Invalid bundle: missing file identity.jks"); - } - if (trustStoreOutputStream == null) { - throw new IllegalStateException("Invalid bundle: missing file trustStore.jks"); - } - char[] keyStorePassword = getKeyStorePassword(configJson); - char[] trustStorePassword = getTrustStorePassword(configJson); - ByteArrayInputStream keyStoreInputStream = - new ByteArrayInputStream(keyStoreOutputStream.toByteArray()); - ByteArrayInputStream trustStoreInputStream = - new ByteArrayInputStream(trustStoreOutputStream.toByteArray()); - SSLContext sslContext = - createSslContext( - keyStoreInputStream, keyStorePassword, trustStoreInputStream, trustStorePassword); - URL metadataServiceUrl = getMetadataServiceUrl(configJson); - JsonNode proxyMetadataJson; - try (BufferedReader proxyMetadata = fetchProxyMetadata(metadataServiceUrl, sslContext)) { - proxyMetadataJson = mapper.readTree(proxyMetadata); - } - InetSocketAddress sniProxyAddress = getSniProxyAddress(proxyMetadataJson); - List endPoints = getEndPoints(proxyMetadataJson, sniProxyAddress); - String localDatacenter = getLocalDatacenter(proxyMetadataJson); - SniSslEngineFactory sslEngineFactory = new SniSslEngineFactory(sslContext); - validateIfBundleContainsUsernamePassword(configJson); - return new CloudConfig(sniProxyAddress, endPoints, localDatacenter, sslEngineFactory); - } - - @NonNull - protected char[] getKeyStorePassword(JsonNode configFile) { - if (configFile.has("keyStorePassword")) { - return configFile.get("keyStorePassword").asText().toCharArray(); - } else { - throw new IllegalStateException("Invalid config.json: missing field keyStorePassword"); - } - } - - @NonNull - protected char[] getTrustStorePassword(JsonNode configFile) { - if (configFile.has("trustStorePassword")) { - return configFile.get("trustStorePassword").asText().toCharArray(); - } else { - throw new IllegalStateException("Invalid config.json: missing field trustStorePassword"); - } - } - - @NonNull - protected URL getMetadataServiceUrl(JsonNode configFile) throws MalformedURLException { - if (configFile.has("host")) { - String metadataServiceHost = configFile.get("host").asText(); - if (configFile.has("port")) { - int metadataServicePort = configFile.get("port").asInt(); - return new URL("https", metadataServiceHost, metadataServicePort, "/metadata"); - } else { - throw new IllegalStateException("Invalid config.json: missing field port"); - } - } else { - throw new IllegalStateException("Invalid config.json: missing field host"); - } - } - - protected void validateIfBundleContainsUsernamePassword(JsonNode configFile) { - if (configFile.has("username") || configFile.has("password")) { - LOG.info( - "The bundle contains config.json with username and/or password. Providing it in the bundle is deprecated and ignored."); - } - } - - @NonNull - protected SSLContext createSslContext( - @NonNull ByteArrayInputStream keyStoreInputStream, - @NonNull char[] keyStorePassword, - @NonNull ByteArrayInputStream trustStoreInputStream, - @NonNull char[] trustStorePassword) - throws IOException, GeneralSecurityException { - KeyManagerFactory kmf = createKeyManagerFactory(keyStoreInputStream, keyStorePassword); - TrustManagerFactory tmf = createTrustManagerFactory(trustStoreInputStream, trustStorePassword); - SSLContext sslContext = SSLContext.getInstance("SSL"); - sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); - return sslContext; - } - - @NonNull - protected KeyManagerFactory createKeyManagerFactory( - @NonNull InputStream keyStoreInputStream, @NonNull char[] keyStorePassword) - throws IOException, GeneralSecurityException { - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(keyStoreInputStream, keyStorePassword); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, keyStorePassword); - Arrays.fill(keyStorePassword, (char) 0); - return kmf; - } - - @NonNull - protected TrustManagerFactory createTrustManagerFactory( - @NonNull InputStream trustStoreInputStream, @NonNull char[] trustStorePassword) - throws IOException, GeneralSecurityException { - KeyStore ts = KeyStore.getInstance("JKS"); - ts.load(trustStoreInputStream, trustStorePassword); - TrustManagerFactory tmf = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(ts); - Arrays.fill(trustStorePassword, (char) 0); - return tmf; - } - - @NonNull - protected BufferedReader fetchProxyMetadata( - @NonNull URL metadataServiceUrl, @NonNull SSLContext sslContext) throws IOException { - try { - HttpsURLConnection connection = (HttpsURLConnection) metadataServiceUrl.openConnection(); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setRequestMethod("GET"); - connection.setRequestProperty("host", "localhost"); - return new BufferedReader( - new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)); - } catch (ConnectException e) { - throw new IllegalStateException( - "Unable to connect to cloud metadata service. Please make sure your cluster is not parked or terminated", - e); - } catch (UnknownHostException e) { - throw new IllegalStateException( - "Unable to resolve host for cloud metadata service. Please make sure your cluster is not terminated", - e); - } - } - - @NonNull - protected String getLocalDatacenter(@NonNull JsonNode proxyMetadata) { - JsonNode contactInfo = getContactInfo(proxyMetadata); - if (contactInfo.has("local_dc")) { - return contactInfo.get("local_dc").asText(); - } else { - throw new IllegalStateException("Invalid proxy metadata: missing field local_dc"); - } - } - - @NonNull - protected InetSocketAddress getSniProxyAddress(@NonNull JsonNode proxyMetadata) { - JsonNode contactInfo = getContactInfo(proxyMetadata); - if (contactInfo.has("sni_proxy_address")) { - HostAndPort sniProxyHostAndPort = - HostAndPort.fromString(contactInfo.get("sni_proxy_address").asText()); - if (!sniProxyHostAndPort.hasPort()) { - throw new IllegalStateException( - "Invalid proxy metadata: missing port from field sni_proxy_address"); - } - return InetSocketAddress.createUnresolved( - sniProxyHostAndPort.getHost(), sniProxyHostAndPort.getPort()); - } else { - throw new IllegalStateException("Invalid proxy metadata: missing field sni_proxy_address"); - } - } - - @NonNull - protected List getEndPoints( - @NonNull JsonNode proxyMetadata, @NonNull InetSocketAddress sniProxyAddress) { - JsonNode contactInfo = getContactInfo(proxyMetadata); - if (contactInfo.has("contact_points")) { - List endPoints = new ArrayList<>(); - JsonNode hostIdsJson = contactInfo.get("contact_points"); - for (int i = 0; i < hostIdsJson.size(); i++) { - endPoints.add(new SniEndPoint(sniProxyAddress, hostIdsJson.get(i).asText())); - } - return endPoints; - } else { - throw new IllegalStateException("Invalid proxy metadata: missing field contact_points"); - } - } - - @NonNull - protected JsonNode getContactInfo(@NonNull JsonNode proxyMetadata) { - if (proxyMetadata.has("contact_info")) { - return proxyMetadata.get("contact_info"); - } else { - throw new IllegalStateException("Invalid proxy metadata: missing field contact_info"); - } - } -} diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java index b412e55a412..b7991975190 100644 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java +++ b/core/src/main/java/com/datastax/oss/driver/internal/core/context/DefaultDriverContext.java @@ -54,7 +54,6 @@ import com.datastax.oss.driver.internal.core.channel.DefaultWriteCoalescer; import com.datastax.oss.driver.internal.core.channel.WriteCoalescer; import com.datastax.oss.driver.internal.core.control.ControlConnection; -import com.datastax.oss.driver.internal.core.metadata.CloudTopologyMonitor; import com.datastax.oss.driver.internal.core.metadata.DefaultTopologyMonitor; import com.datastax.oss.driver.internal.core.metadata.LoadBalancingPolicyWrapper; import com.datastax.oss.driver.internal.core.metadata.MetadataManager; @@ -492,13 +491,10 @@ protected ChannelFactory buildChannelFactory() { } protected TopologyMonitor buildTopologyMonitor() { - if (cloudProxyAddress == null) { - return new DefaultTopologyMonitor(this); - } - if (scyllaCloudNodeDomain != null) { + if (cloudProxyAddress != null) { return new ScyllaCloudTopologyMonitor(this, cloudProxyAddress, scyllaCloudNodeDomain); } - return new CloudTopologyMonitor(this, cloudProxyAddress); + return new DefaultTopologyMonitor(this); } protected MetadataManager buildMetadataManager() { diff --git a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/CloudTopologyMonitor.java b/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/CloudTopologyMonitor.java deleted file mode 100644 index 6df0bf3e055..00000000000 --- a/core/src/main/java/com/datastax/oss/driver/internal/core/metadata/CloudTopologyMonitor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed 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.internal.core.metadata; - -import com.datastax.oss.driver.api.core.metadata.EndPoint; -import com.datastax.oss.driver.internal.core.adminrequest.AdminRow; -import com.datastax.oss.driver.internal.core.context.InternalDriverContext; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import java.net.InetSocketAddress; -import java.util.Objects; -import java.util.UUID; - -public class CloudTopologyMonitor extends DefaultTopologyMonitor { - - private final InetSocketAddress cloudProxyAddress; - - public CloudTopologyMonitor(InternalDriverContext context, InetSocketAddress cloudProxyAddress) { - super(context); - this.cloudProxyAddress = cloudProxyAddress; - } - - @NonNull - @Override - protected EndPoint buildNodeEndPoint( - @NonNull AdminRow row, - @Nullable InetSocketAddress broadcastRpcAddress, - @NonNull EndPoint localEndPoint) { - UUID hostId = Objects.requireNonNull(row.getUuid("host_id")); - return new SniEndPoint(cloudProxyAddress, hostId.toString()); - } -} diff --git a/core/src/test/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfigFactoryTest.java b/core/src/test/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfigFactoryTest.java deleted file mode 100644 index 6f54413b601..00000000000 --- a/core/src/test/java/com/datastax/oss/driver/internal/core/config/cloud/CloudConfigFactoryTest.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed 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.internal.core.config.cloud; - -import static com.datastax.oss.driver.Assertions.assertThat; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.any; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.assertj.core.api.Assertions.catchThrowable; - -import com.datastax.oss.driver.internal.core.ssl.SniSslEngineFactory; -import com.fasterxml.jackson.core.JsonParseException; -import com.github.tomakehurst.wiremock.common.JettySettings; -import com.github.tomakehurst.wiremock.core.Options; -import com.github.tomakehurst.wiremock.http.AdminRequestHandler; -import com.github.tomakehurst.wiremock.http.HttpServer; -import com.github.tomakehurst.wiremock.http.HttpServerFactory; -import com.github.tomakehurst.wiremock.http.StubRequestHandler; -import com.github.tomakehurst.wiremock.jetty9.JettyHttpServer; -import com.github.tomakehurst.wiremock.junit.WireMockRule; -import com.google.common.base.Joiner; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import org.eclipse.jetty.io.NetworkTrafficListener; -import org.eclipse.jetty.server.ConnectionFactory; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class CloudConfigFactoryTest { - - private static final String BUNDLE_PATH = "/config/cloud/creds.zip"; - - @Rule - public WireMockRule wireMockRule = - new WireMockRule( - wireMockConfig() - .httpsPort(30443) - .dynamicPort() - .httpServerFactory(new HttpsServerFactory()) - .needClientAuth(true) - .keystorePath(path("/config/cloud/identity.jks").toString()) - .keystorePassword("fakePasswordForTests") - .trustStorePath(path("/config/cloud/trustStore.jks").toString()) - .trustStorePassword("fakePasswordForTests2")); - - public CloudConfigFactoryTest() throws URISyntaxException {} - - @Test - public void should_load_config_from_local_filesystem() throws Exception { - // given - URL configFile = getClass().getResource(BUNDLE_PATH); - mockProxyMetadataService(jsonMetadata()); - // when - CloudConfigFactory cloudConfigFactory = new CloudConfigFactory(); - CloudConfig cloudConfig = cloudConfigFactory.createCloudConfig(configFile); - // then - assertCloudConfig(cloudConfig); - } - - @Test - public void should_load_config_from_external_location() throws Exception { - // given - mockHttpSecureBundle(secureBundle()); - mockProxyMetadataService(jsonMetadata()); - // when - URL configFile = new URL("http", "localhost", wireMockRule.port(), BUNDLE_PATH); - CloudConfigFactory cloudConfigFactory = new CloudConfigFactory(); - CloudConfig cloudConfig = cloudConfigFactory.createCloudConfig(configFile); - // then - assertCloudConfig(cloudConfig); - } - - @Test - public void should_throw_when_bundle_not_found() throws Exception { - // given - stubFor(any(urlEqualTo(BUNDLE_PATH)).willReturn(aResponse().withStatus(404))); - // when - URL configFile = new URL("http", "localhost", wireMockRule.port(), BUNDLE_PATH); - CloudConfigFactory cloudConfigFactory = new CloudConfigFactory(); - Throwable t = catchThrowable(() -> cloudConfigFactory.createCloudConfig(configFile)); - assertThat(t) - .isInstanceOf(FileNotFoundException.class) - .hasMessageContaining(configFile.toExternalForm()); - } - - @Test - public void should_throw_when_bundle_not_readable() throws Exception { - // given - mockHttpSecureBundle("not a zip file".getBytes(StandardCharsets.UTF_8)); - // when - URL configFile = new URL("http", "localhost", wireMockRule.port(), BUNDLE_PATH); - CloudConfigFactory cloudConfigFactory = new CloudConfigFactory(); - Throwable t = catchThrowable(() -> cloudConfigFactory.createCloudConfig(configFile)); - assertThat(t) - .isInstanceOf(IllegalStateException.class) - .hasMessageContaining("Invalid bundle: missing file config.json"); - } - - @Test - public void should_throw_when_metadata_not_found() throws Exception { - // given - mockHttpSecureBundle(secureBundle()); - stubFor(any(urlPathEqualTo("/metadata")).willReturn(aResponse().withStatus(404))); - // when - URL configFile = new URL("http", "localhost", wireMockRule.port(), BUNDLE_PATH); - CloudConfigFactory cloudConfigFactory = new CloudConfigFactory(); - Throwable t = catchThrowable(() -> cloudConfigFactory.createCloudConfig(configFile)); - assertThat(t).isInstanceOf(FileNotFoundException.class).hasMessageContaining("metadata"); - } - - @Test - public void should_throw_when_metadata_not_readable() throws Exception { - // given - mockHttpSecureBundle(secureBundle()); - mockProxyMetadataService("not a valid json payload"); - // when - URL configFile = new URL("http", "localhost", wireMockRule.port(), BUNDLE_PATH); - CloudConfigFactory cloudConfigFactory = new CloudConfigFactory(); - Throwable t = catchThrowable(() -> cloudConfigFactory.createCloudConfig(configFile)); - assertThat(t).isInstanceOf(JsonParseException.class).hasMessageContaining("Unrecognized token"); - } - - private void mockHttpSecureBundle(byte[] body) { - stubFor( - any(urlEqualTo(BUNDLE_PATH)) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/octet-stream") - .withBody(body))); - } - - private void mockProxyMetadataService(String jsonMetadata) { - stubFor( - any(urlPathEqualTo("/metadata")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withBody(jsonMetadata))); - } - - private byte[] secureBundle() throws IOException, URISyntaxException { - return Files.readAllBytes(path(BUNDLE_PATH)); - } - - private String jsonMetadata() throws IOException, URISyntaxException { - return Joiner.on('\n') - .join(Files.readAllLines(path("/config/cloud/metadata.json"), StandardCharsets.UTF_8)); - } - - private Path path(String resource) throws URISyntaxException { - return Paths.get(getClass().getResource(resource).toURI()); - } - - private void assertCloudConfig(CloudConfig config) { - InetSocketAddress expectedProxyAddress = InetSocketAddress.createUnresolved("localhost", 30002); - assertThat(config.getLocalDatacenter()).isEqualTo("dc1"); - assertThat(config.getProxyAddress()).isEqualTo(expectedProxyAddress); - assertThat(config.getEndPoints()).extracting("proxyAddress").containsOnly(expectedProxyAddress); - assertThat(config.getEndPoints()) - .extracting("serverName") - .containsExactly( - "4ac06655-f861-49f9-881e-3fee22e69b94", - "2af7c253-3394-4a0d-bfac-f1ad81b5154d", - "b17b6e2a-3f48-4d6a-81c1-20a0a1f3192a"); - assertThat(config.getSslEngineFactory()).isNotNull().isInstanceOf(SniSslEngineFactory.class); - } - - static { - javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier( - (hostname, sslSession) -> hostname.equals("localhost")); - } - - // see https://github.com/tomakehurst/wiremock/issues/874 - private static class HttpsServerFactory implements HttpServerFactory { - @Override - public HttpServer buildHttpServer( - Options options, - AdminRequestHandler adminRequestHandler, - StubRequestHandler stubRequestHandler) { - return new JettyHttpServer(options, adminRequestHandler, stubRequestHandler) { - @Override - protected ServerConnector createServerConnector( - String bindAddress, - JettySettings jettySettings, - int port, - NetworkTrafficListener listener, - ConnectionFactory... connectionFactories) { - if (port == options.httpsSettings().port()) { - SslConnectionFactory sslConnectionFactory = - (SslConnectionFactory) connectionFactories[0]; - SslContextFactory sslContextFactory = sslConnectionFactory.getSslContextFactory(); - sslContextFactory.setKeyStorePassword(options.httpsSettings().keyStorePassword()); - connectionFactories = - new ConnectionFactory[] {sslConnectionFactory, connectionFactories[1]}; - } - return super.createServerConnector( - bindAddress, jettySettings, port, listener, connectionFactories); - } - }; - } - } -} diff --git a/core/src/test/resources/config/cloud/creds.zip b/core/src/test/resources/config/cloud/creds.zip deleted file mode 100644 index 3b5d1cb1cbd9d1d805eb3aaf06ace9b224d45b87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9805 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 diff --git a/core/src/test/resources/config/cloud/identity.jks b/core/src/test/resources/config/cloud/identity.jks deleted file mode 100644 index bac5bbaa965be8d543497047d5e05c1f3c12238a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2493 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 GqwxSGx?Cdw diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/astra/AstraReadCassandraVersion.java b/examples/src/main/java/com/datastax/oss/driver/examples/astra/AstraReadCassandraVersion.java deleted file mode 100644 index d434665552a..00000000000 --- a/examples/src/main/java/com/datastax/oss/driver/examples/astra/AstraReadCassandraVersion.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed 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.examples.astra; - -import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.cql.ResultSet; -import com.datastax.oss.driver.api.core.cql.Row; -import java.nio.file.Paths; - -/** - * Connects to a DataStax Astra cluster and extracts basic information from it. - * - *

Preconditions: - * - *

    - *
  • A DataStax Astra cluster is running and accessible. - *
  • A DataStax Astra secure connect bundle for the running cluster. - *
- * - *

Side effects: none. - * - * @see - * Creating an Astra Database (GCP) - * @see - * Providing access to Astra databases (GCP) - * @see - * Obtaining Astra secure connect bundle (GCP) - * @see Java driver online - * manual - */ -public class AstraReadCassandraVersion { - - public static void main(String[] args) { - - // The Session is what you use to execute queries. It is thread-safe and should be - // reused. - try (CqlSession session = - CqlSession.builder() - // Change the path here to the secure connect bundle location (see javadocs above) - .withCloudSecureConnectBundle(Paths.get("/path/to/secure-connect-database_name.zip")) - // Change the user_name and password here for the Astra instance - .withAuthCredentials("user_name", "fakePasswordForTests") - // Uncomment the next line to use a specific keyspace - // .withKeyspace("keyspace_name") - .build()) { - - // We use execute to send a query to Cassandra. This returns a ResultSet, which - // is essentially a collection of Row objects. - ResultSet rs = session.execute("select release_version from system.local"); - // Extract the first row (which is the only one in this case). - Row row = rs.one(); - - // Extract the value of the first (and only) column from the row. - assert row != null; - String releaseVersion = row.getString("release_version"); - System.out.printf("Cassandra version is: %s%n", releaseVersion); - } - // The try-with-resources block automatically close the session after we’re done with it. - // This step is important because it frees underlying resources (TCP connections, thread - // pools...). In a real application, you would typically do this at shutdown - // (for example, when undeploying your webapp). - } -} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/CloudIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/CloudIT.java deleted file mode 100644 index 7874c4719d8..00000000000 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/CloudIT.java +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed 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.cloud; - -import static com.datastax.oss.driver.internal.core.util.LoggerTest.setupTestLogger; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.any; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.spi.ILoggingEvent; -import com.datastax.oss.driver.api.core.AllNodesFailedException; -import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.auth.AuthenticationException; -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.cql.ResultSet; -import com.datastax.oss.driver.api.core.session.SessionBuilder; -import com.datastax.oss.driver.api.testinfra.session.SessionUtils; -import com.datastax.oss.driver.categories.IsolatedTests; -import com.datastax.oss.driver.internal.core.config.cloud.CloudConfigFactory; -import com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory; -import com.datastax.oss.driver.internal.core.util.LoggerTest; -import com.github.tomakehurst.wiremock.junit.WireMockRule; -import java.io.IOException; -import java.io.InputStream; -import java.net.InetSocketAddress; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.List; -import javax.net.ssl.SSLContext; -import org.junit.ClassRule; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.experimental.categories.Category; - -@Category(IsolatedTests.class) -@Ignore("Disabled because it is causing trouble in Jenkins CI") -public class CloudIT { - - private static final String BUNDLE_URL_PATH = "/certs/bundles/creds.zip"; - - @ClassRule public static SniProxyRule proxyRule = new SniProxyRule(); - - // Used only to host the secure connect bundle, for tests that require external URLs - @Rule - public WireMockRule wireMockRule = - new WireMockRule(wireMockConfig().dynamicPort().dynamicHttpsPort()); - - @Test - public void should_connect_to_proxy_using_path() { - ResultSet set; - Path bundle = proxyRule.getProxy().getDefaultBundlePath(); - try (CqlSession session = - CqlSession.builder() - .withAuthCredentials("cassandra", "cassandra") - .withCloudSecureConnectBundle(bundle) - .build()) { - set = session.execute("select * from system.local"); - } - assertThat(set).isNotNull(); - } - - @Test - public void should_connect_and_log_info_that_config_json_with_username_password_was_provided() { - ResultSet set; - Path bundle = proxyRule.getProxy().getDefaultBundlePath(); - LoggerTest.LoggerSetup logger = setupTestLogger(CloudConfigFactory.class, Level.INFO); - - try (CqlSession session = - CqlSession.builder() - .withAuthCredentials("cassandra", "cassandra") - .withCloudSecureConnectBundle(bundle) - .build()) { - set = session.execute("select * from system.local"); - verify(logger.appender, timeout(500).atLeast(1)) - .doAppend(logger.loggingEventCaptor.capture()); - assertThat( - logger.loggingEventCaptor.getAllValues().stream() - .map(ILoggingEvent::getFormattedMessage)) - .contains( - "The bundle contains config.json with username and/or password. Providing it in the bundle is deprecated and ignored."); - } - assertThat(set).isNotNull(); - } - - @Test - public void - should_fail_with_auth_error_when_connecting_using_bundle_with_username_password_in_config_json() { - Path bundle = proxyRule.getProxy().getDefaultBundlePath(); - - // fails with auth error because username/password from config.json is ignored - AllNodesFailedException exception = null; - try { - CqlSession.builder().withCloudSecureConnectBundle(bundle).build(); - } catch (AllNodesFailedException ex) { - exception = ex; - } - assertThat(exception).isNotNull(); - List errors = exception.getAllErrors().values().iterator().next(); - Throwable firstError = errors.get(0); - assertThat(firstError).isInstanceOf(AuthenticationException.class); - } - - @Test - public void should_connect_to_proxy_without_credentials() { - ResultSet set; - Path bundle = proxyRule.getProxy().getBundleWithoutCredentialsPath(); - try (CqlSession session = - CqlSession.builder() - .withCloudSecureConnectBundle(bundle) - .withAuthCredentials("cassandra", "cassandra") - .build()) { - set = session.execute("select * from system.local"); - } - assertThat(set).isNotNull(); - } - - @Test - public void should_connect_to_proxy_using_non_normalized_path() { - Path bundle = proxyRule.getProxy().getBundlesRootPath().resolve("../bundles/creds-v1.zip"); - ResultSet set; - try (CqlSession session = - CqlSession.builder() - .withAuthCredentials("cassandra", "cassandra") - .withCloudSecureConnectBundle(bundle) - .build()) { - set = session.execute("select * from system.local"); - } - assertThat(set).isNotNull(); - } - - @Test - public void should_connect_to_proxy_using_input_stream() throws IOException { - InputStream bundle = Files.newInputStream(proxyRule.getProxy().getDefaultBundlePath()); - ResultSet set; - try (CqlSession session = - CqlSession.builder() - .withAuthCredentials("cassandra", "cassandra") - .withCloudSecureConnectBundle(bundle) - .build()) { - set = session.execute("select * from system.local"); - } - assertThat(set).isNotNull(); - } - - @Test - public void should_connect_to_proxy_using_URL() throws IOException { - // given - byte[] bundle = Files.readAllBytes(proxyRule.getProxy().getDefaultBundlePath()); - stubFor( - any(urlEqualTo(BUNDLE_URL_PATH)) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/octet-stream") - .withBody(bundle))); - URL bundleUrl = - new URL(String.format("http://localhost:%d%s", wireMockRule.port(), BUNDLE_URL_PATH)); - - // when - ResultSet set; - try (CqlSession session = - CqlSession.builder() - .withAuthCredentials("cassandra", "cassandra") - .withCloudSecureConnectBundle(bundleUrl) - .build()) { - - // then - set = session.execute("select * from system.local"); - } - assertThat(set).isNotNull(); - } - - @Test - public void should_connect_to_proxy_using_absolute_path_provided_in_the_session_setting() { - // given - String bundle = proxyRule.getProxy().getDefaultBundlePath().toString(); - DriverConfigLoader loader = - DriverConfigLoader.programmaticBuilder() - .withString(DefaultDriverOption.CLOUD_SECURE_CONNECT_BUNDLE, bundle) - .build(); - // when - ResultSet set; - try (CqlSession session = - CqlSession.builder() - .withAuthCredentials("cassandra", "cassandra") - .withConfigLoader(loader) - .build()) { - - // then - set = session.execute("select * from system.local"); - } - assertThat(set).isNotNull(); - } - - @Test - public void should_connect_to_proxy_using_non_normalized_path_provided_in_the_session_setting() { - // given - String bundle = - proxyRule.getProxy().getBundlesRootPath().resolve("../bundles/creds-v1.zip").toString(); - DriverConfigLoader loader = - DriverConfigLoader.programmaticBuilder() - .withString(DefaultDriverOption.CLOUD_SECURE_CONNECT_BUNDLE, bundle) - .build(); - // when - ResultSet set; - try (CqlSession session = - CqlSession.builder() - .withAuthCredentials("cassandra", "cassandra") - .withConfigLoader(loader) - .build()) { - - // then - set = session.execute("select * from system.local"); - } - assertThat(set).isNotNull(); - } - - @Test - public void - should_connect_to_proxy_using_url_with_file_protocol_provided_in_the_session_setting() { - // given - String bundle = proxyRule.getProxy().getDefaultBundlePath().toString(); - DriverConfigLoader loader = - DriverConfigLoader.programmaticBuilder() - .withString(DefaultDriverOption.CLOUD_SECURE_CONNECT_BUNDLE, bundle) - .build(); - // when - ResultSet set; - try (CqlSession session = - CqlSession.builder() - .withAuthCredentials("cassandra", "cassandra") - .withConfigLoader(loader) - .build()) { - - // then - set = session.execute("select * from system.local"); - } - assertThat(set).isNotNull(); - } - - @Test - public void should_connect_to_proxy_using_url_with_http_protocol_provided_in_the_session_setting() - throws IOException { - // given - byte[] bundle = Files.readAllBytes(proxyRule.getProxy().getDefaultBundlePath()); - stubFor( - any(urlEqualTo(BUNDLE_URL_PATH)) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/octet-stream") - .withBody(bundle))); - String bundleUrl = String.format("http://localhost:%d%s", wireMockRule.port(), BUNDLE_URL_PATH); - DriverConfigLoader loader = - DriverConfigLoader.programmaticBuilder() - .withString(DefaultDriverOption.CLOUD_SECURE_CONNECT_BUNDLE, bundleUrl) - .build(); - // when - ResultSet set; - try (CqlSession session = - CqlSession.builder() - .withAuthCredentials("cassandra", "cassandra") - .withConfigLoader(loader) - .build()) { - - // then - set = session.execute("select * from system.local"); - } - assertThat(set).isNotNull(); - } - - @Test - public void - should_connect_and_log_info_when_contact_points_and_secure_bundle_used_programmatic() { - // given - LoggerTest.LoggerSetup logger = setupTestLogger(SessionBuilder.class, Level.INFO); - - Path bundle = proxyRule.getProxy().getBundleWithoutCredentialsPath(); - - try (CqlSession session = - CqlSession.builder() - .withCloudSecureConnectBundle(bundle) - .addContactPoint(new InetSocketAddress("127.0.0.1", 9042)) - .withAuthCredentials("cassandra", "cassandra") - .build(); ) { - - // when - ResultSet set = session.execute("select * from system.local"); - // then - assertThat(set).isNotNull(); - verify(logger.appender, timeout(500).atLeast(1)) - .doAppend(logger.loggingEventCaptor.capture()); - assertThat( - logger.loggingEventCaptor.getAllValues().stream() - .map(ILoggingEvent::getFormattedMessage)) - .contains( - "Both a secure connect bundle and contact points were provided. These are mutually exclusive. The contact points from the secure bundle will have priority."); - - } finally { - logger.close(); - } - } - - @Test - public void should_connect_and_log_info_when_contact_points_and_secure_bundle_used_config() { - // given - LoggerTest.LoggerSetup logger = setupTestLogger(SessionBuilder.class, Level.INFO); - - DriverConfigLoader loader = - SessionUtils.configLoaderBuilder() - .withStringList( - DefaultDriverOption.CONTACT_POINTS, Collections.singletonList("localhost:9042")) - .build(); - - Path bundle = proxyRule.getProxy().getBundleWithoutCredentialsPath(); - - try (CqlSession session = - CqlSession.builder() - .withConfigLoader(loader) - .withCloudSecureConnectBundle(bundle) - .withAuthCredentials("cassandra", "cassandra") - .build(); ) { - - // when - ResultSet set = session.execute("select * from system.local"); - // then - assertThat(set).isNotNull(); - verify(logger.appender, timeout(500).atLeast(1)) - .doAppend(logger.loggingEventCaptor.capture()); - assertThat( - logger.loggingEventCaptor.getAllValues().stream() - .map(ILoggingEvent::getFormattedMessage)) - .contains( - "Both a secure connect bundle and contact points were provided. These are mutually exclusive. The contact points from the secure bundle will have priority."); - - } finally { - logger.close(); - } - } - - @Test - public void should_connect_and_log_info_when_ssl_context_and_secure_bundle_used_programmatic() - throws NoSuchAlgorithmException { - // given - LoggerTest.LoggerSetup logger = setupTestLogger(SessionBuilder.class, Level.INFO); - - Path bundle = proxyRule.getProxy().getBundleWithoutCredentialsPath(); - - try (CqlSession session = - CqlSession.builder() - .withCloudSecureConnectBundle(bundle) - .withAuthCredentials("cassandra", "cassandra") - .withSslContext(SSLContext.getInstance("SSL")) - .build()) { - // when - ResultSet set = session.execute("select * from system.local"); - // then - assertThat(set).isNotNull(); - verify(logger.appender, timeout(500).atLeast(1)) - .doAppend(logger.loggingEventCaptor.capture()); - assertThat( - logger.loggingEventCaptor.getAllValues().stream() - .map(ILoggingEvent::getFormattedMessage)) - .contains( - "Both a secure connect bundle and SSL options were provided. They are mutually exclusive. The SSL options from the secure bundle will have priority."); - } finally { - logger.close(); - } - } - - @Test - public void should_error_when_ssl_context_and_secure_bundle_used_config() - throws NoSuchAlgorithmException { - // given - LoggerTest.LoggerSetup logger = setupTestLogger(SessionBuilder.class, Level.INFO); - - DriverConfigLoader loader = - SessionUtils.configLoaderBuilder() - .withBoolean(DefaultDriverOption.RECONNECT_ON_INIT, true) - .withClass(DefaultDriverOption.SSL_ENGINE_FACTORY_CLASS, DefaultSslEngineFactory.class) - .build(); - - Path bundle = proxyRule.getProxy().getBundleWithoutCredentialsPath(); - - try (CqlSession session = - CqlSession.builder() - .withConfigLoader(loader) - .withCloudSecureConnectBundle(bundle) - .withAuthCredentials("cassandra", "cassandra") - .build()) { - // when - ResultSet set = session.execute("select * from system.local"); - // then - assertThat(set).isNotNull(); - verify(logger.appender, timeout(500).atLeast(1)) - .doAppend(logger.loggingEventCaptor.capture()); - assertThat( - logger.loggingEventCaptor.getAllValues().stream() - .map(ILoggingEvent::getFormattedMessage)) - .contains( - "Both a secure connect bundle and SSL options were provided. They are mutually exclusive. The SSL options from the secure bundle will have priority."); - } finally { - logger.close(); - } - } - - @Test - public void - should_connect_and_log_info_when_local_data_center_and_secure_bundle_used_programmatic() { - // given - LoggerTest.LoggerSetup logger = setupTestLogger(SessionBuilder.class, Level.INFO); - - DriverConfigLoader loader = - SessionUtils.configLoaderBuilder() - .withString(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, "dc-ignore") - .build(); - - Path bundle = proxyRule.getProxy().getBundleWithoutCredentialsPath(); - - try (CqlSession session = - CqlSession.builder() - .withCloudSecureConnectBundle(bundle) - .withConfigLoader(loader) - .withAuthCredentials("cassandra", "cassandra") - .build(); ) { - - // when - ResultSet set = session.execute("select * from system.local"); - // then - assertThat(set).isNotNull(); - verify(logger.appender, timeout(500).atLeast(1)) - .doAppend(logger.loggingEventCaptor.capture()); - assertThat( - logger.loggingEventCaptor.getAllValues().stream() - .map(ILoggingEvent::getFormattedMessage)) - .contains( - "Both a secure connect bundle and a local datacenter were provided. They are mutually exclusive. The local datacenter from the secure bundle will have priority."); - - } finally { - logger.close(); - } - } - - @Test - public void should_connect_and_log_info_when_local_data_center_and_secure_bundle_used_config() { - // given - LoggerTest.LoggerSetup logger = setupTestLogger(SessionBuilder.class, Level.INFO); - - Path bundle = proxyRule.getProxy().getBundleWithoutCredentialsPath(); - - try (CqlSession session = - CqlSession.builder() - .withCloudSecureConnectBundle(bundle) - .withLocalDatacenter("dc-ignored") - .withAuthCredentials("cassandra", "cassandra") - .build(); ) { - - // when - ResultSet set = session.execute("select * from system.local"); - // then - assertThat(set).isNotNull(); - verify(logger.appender, timeout(500).atLeast(1)) - .doAppend(logger.loggingEventCaptor.capture()); - assertThat( - logger.loggingEventCaptor.getAllValues().stream() - .map(ILoggingEvent::getFormattedMessage)) - .contains( - "Both a secure connect bundle and a local datacenter were provided. They are mutually exclusive. The local datacenter from the secure bundle will have priority."); - - } finally { - logger.close(); - } - } -} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/SniProxyRule.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/SniProxyRule.java deleted file mode 100644 index 706f337d39c..00000000000 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/SniProxyRule.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed 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.cloud; - -import org.junit.rules.ExternalResource; - -public class SniProxyRule extends ExternalResource { - - private final SniProxyServer proxy; - - public SniProxyRule() { - proxy = new SniProxyServer(); - } - - @Override - protected void before() { - proxy.startProxy(); - } - - @Override - protected void after() { - proxy.stopProxy(); - } - - public SniProxyServer getProxy() { - return proxy; - } -} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/SniProxyServer.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/SniProxyServer.java deleted file mode 100644 index af137f2bb70..00000000000 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/SniProxyServer.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright DataStax, Inc. - * - * Licensed 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.cloud; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.concurrent.TimeUnit; -import org.apache.commons.exec.CommandLine; -import org.apache.commons.exec.DefaultExecutor; -import org.apache.commons.exec.ExecuteStreamHandler; -import org.apache.commons.exec.ExecuteWatchdog; -import org.apache.commons.exec.Executor; -import org.apache.commons.exec.LogOutputStream; -import org.apache.commons.exec.PumpStreamHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SniProxyServer { - - private static final Logger LOG = LoggerFactory.getLogger(SniProxyServer.class); - - private final Path proxyPath; - private final Path bundlesRootPath; - private final Path defaultBundlePath; - private final Path bundleWithoutCredentialsPath; - private final Path bundleWithoutClientCertificatesPath; - private final Path bundleWithInvalidCAPath; - private final Path bundleWithUnreachableMetadataServicePath; - - private volatile boolean running = false; - - public SniProxyServer() { - this(Paths.get(System.getProperty("proxy.path", "./"))); - } - - public SniProxyServer(Path proxyPath) { - this.proxyPath = proxyPath.normalize().toAbsolutePath(); - bundlesRootPath = proxyPath.resolve("certs/bundles/"); - defaultBundlePath = bundlesRootPath.resolve("creds-v1.zip"); - bundleWithoutCredentialsPath = bundlesRootPath.resolve("creds-v1-wo-creds.zip"); - bundleWithoutClientCertificatesPath = bundlesRootPath.resolve("creds-v1-wo-cert.zip"); - bundleWithInvalidCAPath = bundlesRootPath.resolve("creds-v1-invalid-ca.zip"); - bundleWithUnreachableMetadataServicePath = bundlesRootPath.resolve("creds-v1-unreachable.zip"); - } - - public void startProxy() { - CommandLine run = CommandLine.parse(proxyPath + "/run.sh"); - execute(run); - running = true; - } - - public void stopProxy() { - if (running) { - CommandLine findImageId = - CommandLine.parse("docker ps -a -q --filter ancestor=single_endpoint"); - String id = execute(findImageId); - CommandLine stop = CommandLine.parse("docker kill " + id); - execute(stop); - running = false; - } - } - - /** @return The root folder of the SNI proxy server docker image. */ - public Path getProxyPath() { - return proxyPath; - } - - /** - * @return The root folder where secure connect bundles exposed by this SNI proxy for testing - * purposes can be found. - */ - public Path getBundlesRootPath() { - return bundlesRootPath; - } - - /** - * @return The default secure connect bundle. It contains credentials and all certificates - * required to connect. - */ - public Path getDefaultBundlePath() { - return defaultBundlePath; - } - - /** @return A secure connect bundle without credentials in config.json. */ - public Path getBundleWithoutCredentialsPath() { - return bundleWithoutCredentialsPath; - } - - /** @return A secure connect bundle without client certificates (no identity.jks). */ - public Path getBundleWithoutClientCertificatesPath() { - return bundleWithoutClientCertificatesPath; - } - - /** @return A secure connect bundle with an invalid Certificate Authority. */ - public Path getBundleWithInvalidCAPath() { - return bundleWithInvalidCAPath; - } - - /** @return A secure connect bundle with an invalid address for the Proxy Metadata Service. */ - public Path getBundleWithUnreachableMetadataServicePath() { - return bundleWithUnreachableMetadataServicePath; - } - - private String execute(CommandLine cli) { - LOG.debug("Executing: " + cli); - ExecuteWatchdog watchDog = new ExecuteWatchdog(TimeUnit.MINUTES.toMillis(10)); - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - try (LogOutputStream errStream = - new LogOutputStream() { - @Override - protected void processLine(String line, int logLevel) { - LOG.error("sniendpointerr> {}", line); - } - }) { - Executor executor = new DefaultExecutor(); - ExecuteStreamHandler streamHandler = new PumpStreamHandler(outStream, errStream); - executor.setStreamHandler(streamHandler); - executor.setWatchdog(watchDog); - executor.setWorkingDirectory(proxyPath.toFile()); - int retValue = executor.execute(cli); - if (retValue != 0) { - LOG.error("Non-zero exit code ({}) returned from executing ccm command: {}", retValue, cli); - } - return outStream.toString(); - } catch (IOException ex) { - if (watchDog.killedProcess()) { - throw new RuntimeException("The command '" + cli + "' was killed after 10 minutes"); - } else { - throw new RuntimeException("The command '" + cli + "' failed to execute", ex); - } - } - } -} diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/ScyllaCloudMultiNodeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java similarity index 98% rename from integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/ScyllaCloudMultiNodeIT.java rename to integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java index f1449ea68ca..1eb34bf9ebc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/cloud/ScyllaCloudMultiNodeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java @@ -1,4 +1,4 @@ -package com.datastax.oss.driver.api.core.cloud; +package com.datastax.oss.driver.api.core.scyllacloud; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; From f70605ca0694b0a938682c6d810611b483c14899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Tue, 6 Dec 2022 19:45:33 +0100 Subject: [PATCH 10/12] Add Scylla Cloud example Adds an usage example which is slightly modified ReadCassandraVersion. --- .../scyllacloud/ReadScyllaVersion.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 examples/src/main/java/com/datastax/oss/driver/examples/scyllacloud/ReadScyllaVersion.java diff --git a/examples/src/main/java/com/datastax/oss/driver/examples/scyllacloud/ReadScyllaVersion.java b/examples/src/main/java/com/datastax/oss/driver/examples/scyllacloud/ReadScyllaVersion.java new file mode 100644 index 00000000000..a899edf7fb7 --- /dev/null +++ b/examples/src/main/java/com/datastax/oss/driver/examples/scyllacloud/ReadScyllaVersion.java @@ -0,0 +1,33 @@ +package com.datastax.oss.driver.examples.scyllacloud; + +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.DefaultProtocolVersion; +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.cql.ResultSet; +import com.datastax.oss.driver.api.core.cql.Row; +import java.io.File; + +public class ReadScyllaVersion { + + public static void main(String[] args) { + String configPath = "/path/to/scylla/cloud/conf/file"; + File configFile = new File(configPath); + DriverConfigLoader loader = + DriverConfigLoader.programmaticBuilder() + .withString(DefaultDriverOption.PROTOCOL_VERSION, DefaultProtocolVersion.V4.toString()) + .build(); + + try (CqlSession session = + CqlSession.builder() + .withConfigLoader(loader) + .withScyllaCloudSecureConnectBundle(configFile.toPath()) + .build()) { + ResultSet rs = session.execute("select release_version from system.local"); + Row row = rs.one(); + assert row != null; + String releaseVersion = row.getString("release_version"); + System.out.printf("Scylla version: %s%n", releaseVersion); + } + } +} From bcfa34c654865c1db0c031b6ff059b29fad7e7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Sun, 11 Dec 2022 21:59:37 +0100 Subject: [PATCH 11/12] adding dot to trigger actions --- .../oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java index 1eb34bf9ebc..117db36aa86 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java @@ -34,7 +34,7 @@ public class ScyllaCloudMultiNodeIT { private static final int NUMBER_OF_NODES = 3; - private static final int SNI_PORT = 0; // Let CCM pick + private static final int SNI_PORT = 0; // Let CCM pick. @ClassRule public static CustomCcmRule CCM_RULE = From a4f3f4347eb2e84b539a170c0b31d658c052376d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20B=C4=85czkowski?= Date: Sun, 11 Dec 2022 23:15:26 +0100 Subject: [PATCH 12/12] delete dot to trigger actions --- .../oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java index 117db36aa86..1eb34bf9ebc 100644 --- a/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java +++ b/integration-tests/src/test/java/com/datastax/oss/driver/api/core/scyllacloud/ScyllaCloudMultiNodeIT.java @@ -34,7 +34,7 @@ public class ScyllaCloudMultiNodeIT { private static final int NUMBER_OF_NODES = 3; - private static final int SNI_PORT = 0; // Let CCM pick. + private static final int SNI_PORT = 0; // Let CCM pick @ClassRule public static CustomCcmRule CCM_RULE =