tolbertam commented on code in PR #2013: URL: https://github.com/apache/cassandra-java-driver/pull/2013#discussion_r1949652849
########## core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java: ########## @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + * <p>The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + /** + * A comma separated list with Cassandra node subnets (CIDR notations) to target addresses in a + * format `<subnet>:<hostname>:<port>`, for example: + * `100.64.0.0/15:cassandra.datacenter1.com:9042,100.66.0.0/15:cassandra.datacenter1.com:9042`. If + * configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_SUBNET_ADDRESSES = + "advanced.address-translator.subnet-addresses"; + /** + * A default address to fallback to if Cassandra node IP isn't contained in any of the configured + * subnets. If configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_DEFAULT_ADDRESS = + "advanced.address-translator.default-address"; + + public static DriverOption ADDRESS_TRANSLATOR_SUBNET_ADDRESSES_OPTION = + new DriverOption() { + @NonNull + @Override + public String getPath() { + return ADDRESS_TRANSLATOR_SUBNET_ADDRESSES; + } + }; + public static DriverOption ADDRESS_TRANSLATOR_DEFAULT_ADDRESS_OPTION = + new DriverOption() { + @NonNull + @Override + public String getPath() { + return ADDRESS_TRANSLATOR_DEFAULT_ADDRESS; + } + }; + + private static final String DELIMITER = ":"; + private static final int DEFAULT_PORT = 9042; + + private final List<SubnetAddress> subnetAddresses; + private final Optional<InetSocketAddress> defaultAddress; + private final String logPrefix; + + public SubnetAddressTranslator(@NonNull DriverContext context) { + logPrefix = context.getSessionName(); + this.subnetAddresses = + context.getConfig().getDefaultProfile() + .getStringList(ADDRESS_TRANSLATOR_SUBNET_ADDRESSES_OPTION).stream() Review Comment: From what I can tell this would work this way: ```hocon advanced.address-translator { subnet-addresses = ["100.64.0.0/15:cassandra.datacenter1.com:9042","100.66.0.0/15:cassandra.datacenter1.com:9042"] } ``` One concern I have is placing multiple property values in the same string, which implies some kind of parsing. I suppose that was already the case with address:port, but would be nice to make this structured. Using `:` as a delimiter would also cause some problems with IPv6. could we consider using a string map instead, e.g.: ```hocon advanced.address-translator { subnet-addresses { 100.64.0.0/15 = "cassandra.datacenter1.com:9042" 100.66.0.0/15 = "cassandra.datacenter1.com:9042" } } ``` although i suspect this might break parsing some how with a `/` in a key name, but I think doing something like this could make the config more human/machine readable. ########## core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java: ########## @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + * <p>The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + /** + * A comma separated list with Cassandra node subnets (CIDR notations) to target addresses in a + * format `<subnet>:<hostname>:<port>`, for example: + * `100.64.0.0/15:cassandra.datacenter1.com:9042,100.66.0.0/15:cassandra.datacenter1.com:9042`. If Review Comment: This expects a list, not a comma separated string right? ########## core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java: ########## @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + * <p>The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + /** + * A comma separated list with Cassandra node subnets (CIDR notations) to target addresses in a + * format `<subnet>:<hostname>:<port>`, for example: + * `100.64.0.0/15:cassandra.datacenter1.com:9042,100.66.0.0/15:cassandra.datacenter1.com:9042`. If + * configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_SUBNET_ADDRESSES = + "advanced.address-translator.subnet-addresses"; + /** + * A default address to fallback to if Cassandra node IP isn't contained in any of the configured + * subnets. If configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_DEFAULT_ADDRESS = + "advanced.address-translator.default-address"; Review Comment: this might be considered property overloading, but wonder if we should just fallback on `advertised-hostname` that `FixedHostNameAddressTranslator` uses? ########## core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java: ########## @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + * <p>The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + /** + * A comma separated list with Cassandra node subnets (CIDR notations) to target addresses in a + * format `<subnet>:<hostname>:<port>`, for example: + * `100.64.0.0/15:cassandra.datacenter1.com:9042,100.66.0.0/15:cassandra.datacenter1.com:9042`. If + * configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_SUBNET_ADDRESSES = + "advanced.address-translator.subnet-addresses"; + /** + * A default address to fallback to if Cassandra node IP isn't contained in any of the configured + * subnets. If configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_DEFAULT_ADDRESS = + "advanced.address-translator.default-address"; + + public static DriverOption ADDRESS_TRANSLATOR_SUBNET_ADDRESSES_OPTION = + new DriverOption() { + @NonNull + @Override + public String getPath() { + return ADDRESS_TRANSLATOR_SUBNET_ADDRESSES; + } + }; + public static DriverOption ADDRESS_TRANSLATOR_DEFAULT_ADDRESS_OPTION = + new DriverOption() { + @NonNull + @Override + public String getPath() { + return ADDRESS_TRANSLATOR_DEFAULT_ADDRESS; + } + }; + + private static final String DELIMITER = ":"; + private static final int DEFAULT_PORT = 9042; + + private final List<SubnetAddress> subnetAddresses; + private final Optional<InetSocketAddress> defaultAddress; + private final String logPrefix; + + public SubnetAddressTranslator(@NonNull DriverContext context) { + logPrefix = context.getSessionName(); + this.subnetAddresses = + context.getConfig().getDefaultProfile() + .getStringList(ADDRESS_TRANSLATOR_SUBNET_ADDRESSES_OPTION).stream() + .map(SubnetAddress::fromString) + .collect(Collectors.toList()); + this.defaultAddress = + Optional.ofNullable( + context + .getConfig() + .getDefaultProfile() + .getString(ADDRESS_TRANSLATOR_DEFAULT_ADDRESS_OPTION, null)) + .map(SubnetAddressTranslator::parseAddress); + SubnetAddressTranslator.validateSubnetsAreNotOverlapping(this.subnetAddresses); + } + + @NonNull + @Override + public InetSocketAddress translate(@NonNull InetSocketAddress address) { + InetSocketAddress translatedAddress = null; + for (SubnetAddress subnetAddress : subnetAddresses) { + if (subnetAddress.contains(address)) { + translatedAddress = subnetAddress.address; + } + } + if (translatedAddress == null && defaultAddress.isPresent()) { + translatedAddress = defaultAddress.get(); + } + if (translatedAddress == null) { + translatedAddress = address; + } + LOG.debug("[{}] Resolved {} to {}", logPrefix, address, translatedAddress); + return translatedAddress; + } + + @Override + public void close() {} + + private static InetSocketAddress parseAddress(String address) { + String[] addressTuple = address.split(DELIMITER); Review Comment: Noticed that the two uses of `split` and `getClass` in equals causes some error prone warnings (that would prevent maven compiling): ``` [WARNING] /home/tolbertam/Documents/Projects/cassandra-java-driver/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java:[126,42] [StringSplitter] String.split(String) has surprising behavior (see https://errorprone.info/bugpattern/StringSplitter) Did you mean 'List<String> addressTuple = Splitter.onPattern(DELIMITER).splitToList(address);'? [WARNING] /home/tolbertam/Documents/Projects/cassandra-java-driver/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java:[177,20] [EqualsGetClass] Overriding Object#equals in a non-final class by using getClass rather than instanceof breaks substitutability of subclasses. (see https://errorprone.info/bugpattern/EqualsGetClass) Did you mean 'if (!(other instanceof SubnetAddress)) return false;'? [WARNING] /home/tolbertam/Documents/Projects/cassandra-java-driver/core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java:[199,56] [StringSplitter] String.split(String) has surprising behavior (see https://errorprone.info/bugpattern/StringSplitter) Did you mean 'List<String> subnetAddressTuple = Splitter.onPattern(DELIMITER).splitToList(subnetAddress);'? ``` ########## core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java: ########## @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + * <p>The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + /** + * A comma separated list with Cassandra node subnets (CIDR notations) to target addresses in a + * format `<subnet>:<hostname>:<port>`, for example: + * `100.64.0.0/15:cassandra.datacenter1.com:9042,100.66.0.0/15:cassandra.datacenter1.com:9042`. If + * configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_SUBNET_ADDRESSES = + "advanced.address-translator.subnet-addresses"; + /** + * A default address to fallback to if Cassandra node IP isn't contained in any of the configured + * subnets. If configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_DEFAULT_ADDRESS = + "advanced.address-translator.default-address"; + + public static DriverOption ADDRESS_TRANSLATOR_SUBNET_ADDRESSES_OPTION = + new DriverOption() { + @NonNull + @Override + public String getPath() { + return ADDRESS_TRANSLATOR_SUBNET_ADDRESSES; + } + }; + public static DriverOption ADDRESS_TRANSLATOR_DEFAULT_ADDRESS_OPTION = + new DriverOption() { + @NonNull + @Override + public String getPath() { + return ADDRESS_TRANSLATOR_DEFAULT_ADDRESS; + } + }; + + private static final String DELIMITER = ":"; + private static final int DEFAULT_PORT = 9042; + + private final List<SubnetAddress> subnetAddresses; + private final Optional<InetSocketAddress> defaultAddress; + private final String logPrefix; + + public SubnetAddressTranslator(@NonNull DriverContext context) { + logPrefix = context.getSessionName(); + this.subnetAddresses = + context.getConfig().getDefaultProfile() + .getStringList(ADDRESS_TRANSLATOR_SUBNET_ADDRESSES_OPTION).stream() + .map(SubnetAddress::fromString) + .collect(Collectors.toList()); + this.defaultAddress = + Optional.ofNullable( + context + .getConfig() + .getDefaultProfile() + .getString(ADDRESS_TRANSLATOR_DEFAULT_ADDRESS_OPTION, null)) + .map(SubnetAddressTranslator::parseAddress); + SubnetAddressTranslator.validateSubnetsAreNotOverlapping(this.subnetAddresses); + } + + @NonNull + @Override + public InetSocketAddress translate(@NonNull InetSocketAddress address) { + InetSocketAddress translatedAddress = null; + for (SubnetAddress subnetAddress : subnetAddresses) { + if (subnetAddress.contains(address)) { + translatedAddress = subnetAddress.address; + } + } + if (translatedAddress == null && defaultAddress.isPresent()) { + translatedAddress = defaultAddress.get(); + } + if (translatedAddress == null) { + translatedAddress = address; + } + LOG.debug("[{}] Resolved {} to {}", logPrefix, address, translatedAddress); + return translatedAddress; + } + + @Override + public void close() {} + + private static InetSocketAddress parseAddress(String address) { + String[] addressTuple = address.split(DELIMITER); + if (addressTuple.length == 2) { + return new InetSocketAddress(addressTuple[0], Integer.parseInt(addressTuple[1])); + } + if (addressTuple.length == 1) { + return new InetSocketAddress(addressTuple[0], DEFAULT_PORT); + } + throw new IllegalArgumentException("Invalid default address: " + address); + } + + private static void validateSubnetsAreNotOverlapping(List<SubnetAddress> subnetAddresses) { + for (int i = 0; i < subnetAddresses.size() - 1; i++) { Review Comment: This is good to have :+1:. I imagine without this, there could be some unpredictable behavior if subnets overlap (unclear which endpoint would ultimately be used). ########## core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java: ########## @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + * <p>The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + /** + * A comma separated list with Cassandra node subnets (CIDR notations) to target addresses in a + * format `<subnet>:<hostname>:<port>`, for example: + * `100.64.0.0/15:cassandra.datacenter1.com:9042,100.66.0.0/15:cassandra.datacenter1.com:9042`. If Review Comment: Should also use `cassandra.datacenter2.com:9042` for the second entry just to disambiguate the two configs. ########## core/src/main/resources/reference.conf: ########## @@ -1020,8 +1020,9 @@ datastax-java-driver { # the package com.datastax.oss.driver.internal.core.addresstranslation. # # The driver provides the following implementations out of the box: - # - PassThroughAddressTranslator: returns all addresses unchanged + # - PassThroughAddressTranslator: returns all addresses unchanged. Review Comment: In regards to your question around additional documentation needed, it would be nice to add something documenting `SubnetAddressTranslator` to the [Address resolution docs](https://github.com/apache/cassandra-java-driver/tree/4.x/manual/core/address_resolution) below the `EC2 Multi region` section. Could be good to explain the use case for why this may be considered. If you are feeling adventurous it'd be nice to add a blurb on `FixedHostNameAddressTranslator` as well (I could also make an attempt at this later) ########## core/pom.xml: ########## @@ -116,6 +116,10 @@ <groupId>org.reactivestreams</groupId> <artifactId>reactive-streams</artifactId> </dependency> + <dependency> + <groupId>commons-net</groupId> + <artifactId>commons-net</artifactId> + </dependency> Review Comment: I think if we add any dependencies for this, we should make them optional, as I expect folks will commonly not use an address translator, so we should instead have users who desire to use this feature add this as a dependency in their project (which we could document the requirement for somewhere). Also I think it would be nice to consider using https://github.com/seancfoley/IPAddress instead, as [C* also uses this](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/config/SubnetGroups.java) and it carries the benefits of having no other dependencies and it also supports IPv6. ########## core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java: ########## @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + * <p>The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + /** + * A comma separated list with Cassandra node subnets (CIDR notations) to target addresses in a + * format `<subnet>:<hostname>:<port>`, for example: + * `100.64.0.0/15:cassandra.datacenter1.com:9042,100.66.0.0/15:cassandra.datacenter1.com:9042`. If + * configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_SUBNET_ADDRESSES = + "advanced.address-translator.subnet-addresses"; + /** Review Comment: small nit: add newline between field and the next field doc. ########## core/src/main/resources/reference.conf: ########## @@ -1020,8 +1020,9 @@ datastax-java-driver { # the package com.datastax.oss.driver.internal.core.addresstranslation. # # The driver provides the following implementations out of the box: - # - PassThroughAddressTranslator: returns all addresses unchanged + # - PassThroughAddressTranslator: returns all addresses unchanged. # - FixedHostNameAddressTranslator: translates all addresses to a specific hostname. + # - SubnetAddressTranslator: translates addresses to hostname based on the subnet matches. Review Comment: Like `advertised-hostname` below, we should also add a commented out example of configuring `subnet-addresses` for `SubnetAddressTranslator` ########## core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java: ########## @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + * <p>The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + /** + * A comma separated list with Cassandra node subnets (CIDR notations) to target addresses in a + * format `<subnet>:<hostname>:<port>`, for example: + * `100.64.0.0/15:cassandra.datacenter1.com:9042,100.66.0.0/15:cassandra.datacenter1.com:9042`. If + * configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_SUBNET_ADDRESSES = + "advanced.address-translator.subnet-addresses"; + /** + * A default address to fallback to if Cassandra node IP isn't contained in any of the configured + * subnets. If configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_DEFAULT_ADDRESS = + "advanced.address-translator.default-address"; + + public static DriverOption ADDRESS_TRANSLATOR_SUBNET_ADDRESSES_OPTION = + new DriverOption() { + @NonNull + @Override + public String getPath() { + return ADDRESS_TRANSLATOR_SUBNET_ADDRESSES; + } + }; + public static DriverOption ADDRESS_TRANSLATOR_DEFAULT_ADDRESS_OPTION = Review Comment: small nit: add newline between field and the next field when multi-line. ########## core/src/main/java/com/datastax/oss/driver/internal/core/addresstranslation/SubnetAddressTranslator.java: ########## @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.datastax.oss.driver.internal.core.addresstranslation; + +import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator; +import com.datastax.oss.driver.api.core.config.DriverOption; +import com.datastax.oss.driver.api.core.context.DriverContext; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.net.util.SubnetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This translator returns the proxy address of the private subnet containing the Cassandra node IP, + * or default address if no matching subnets, or passes through the original node address if no + * default configured. + * + * <p>The translator can be used for scenarios when all nodes are behind some kind of proxy, and + * that proxy is different for nodes located in different subnets (eg. when Cassandra is deployed in + * multiple datacenters/regions). One can use this, for example, for Cassandra on Kubernetes with + * different Cassandra datacenters deployed to different Kubernetes clusters. + */ +public class SubnetAddressTranslator implements AddressTranslator { + + private static final Logger LOG = LoggerFactory.getLogger(SubnetAddressTranslator.class); + + /** + * A comma separated list with Cassandra node subnets (CIDR notations) to target addresses in a + * format `<subnet>:<hostname>:<port>`, for example: + * `100.64.0.0/15:cassandra.datacenter1.com:9042,100.66.0.0/15:cassandra.datacenter1.com:9042`. If + * configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_SUBNET_ADDRESSES = + "advanced.address-translator.subnet-addresses"; + /** + * A default address to fallback to if Cassandra node IP isn't contained in any of the configured + * subnets. If configured without port, the default 9042 will be used. + */ + public static final String ADDRESS_TRANSLATOR_DEFAULT_ADDRESS = + "advanced.address-translator.default-address"; Review Comment: Actually, I just realized `FixedHostNameAddressTranslator` currently does not offer the capability of providing a port, where this implementation does (either you can or can't). So I do think perhaps having a separate property is ok. Maybe we should revisit the capability in `FixedHostNameAddressTranslator` later. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: pr-unsubscr...@cassandra.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: pr-unsubscr...@cassandra.apache.org For additional commands, e-mail: pr-h...@cassandra.apache.org