Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,14 @@ of servers -- that is, when deploying clusters of servers.
leader election. If you want to test multiple servers on a single machine, then
different ports can be used for each server.

* *hostProvider.dnsSrvRefreshIntervalMs* :
(Java system property: **zookeeper.hostProvider.dnsSrvRefreshIntervalMs**)
**New in 3.10.0:**
The refresh interval in milliseconds for DNS SRV record lookups when using DnsSrvHostProvider.
This property controls how frequently the DNS SRV records are queried to update the server list.
A value of 0 disables periodic refresh.

The default value is 60000 (60 seconds).

<a name="id_multi_address"></a>
Since ZooKeeper 3.6.0 it is possible to specify **multiple addresses** for each
Expand Down
5 changes: 5 additions & 0 deletions zookeeper-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@
<artifactId>commons-io</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dnsjava</groupId>
<artifactId>dnsjava</artifactId>
<version>3.5.1</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
import org.apache.zookeeper.Watcher.WatcherType;
import org.apache.zookeeper.client.Chroot;
import org.apache.zookeeper.client.ConnectStringParser;
import org.apache.zookeeper.client.ConnectionType;
import org.apache.zookeeper.client.HostProvider;
import org.apache.zookeeper.client.HostProviderFactory;
import org.apache.zookeeper.client.StaticHostProvider;
import org.apache.zookeeper.client.ZKClientConfig;
import org.apache.zookeeper.client.ZooKeeperBuilder;
Expand Down Expand Up @@ -1142,9 +1144,10 @@ public ZooKeeper(ZooKeeperOptions options) throws IOException {
if (options.getHostProvider() != null) {
hostProvider = options.getHostProvider().apply(connectStringParser.getServerAddresses());
} else {
hostProvider = new StaticHostProvider(connectStringParser.getServerAddresses(), clientConfig);
hostProvider = HostProviderFactory.createHostProvider(connectString, clientConfig);
}
this.hostProvider = hostProvider;
validateHostProviderCompatibility(connectString, hostProvider);

chroot = Chroot.ofNullable(connectStringParser.getChrootPath());
cnxn = createConnection(
Expand Down Expand Up @@ -1332,6 +1335,11 @@ public synchronized void close() throws InterruptedException {
LOG.debug("Ignoring unexpected exception during close", e);
}

// Close the host provider to release any resources
if (hostProvider != null) {
hostProvider.close();
}

LOG.info("Session: 0x{} closed", Long.toHexString(getSessionId()));
}

Expand Down Expand Up @@ -3202,4 +3210,26 @@ public synchronized List<ClientInfo> whoAmI() throws InterruptedException {
return response.getClientInfo();
}

/**
* Validates compatibility between connectString and hostProvider.
*
* @param connectString the connection string provided by user
* @param hostProvider the host provider provided by user
* @throws IllegalArgumentException if incompatible combination is detected
*/
private static void validateHostProviderCompatibility(final String connectString, final HostProvider hostProvider) {
final ConnectionType connectStringType = ConnectStringParser.getConnectionType(connectString);
final ConnectionType supportedConnectStringType = hostProvider.getSupportedConnectionType();

if (connectStringType != supportedConnectStringType) {
final String hostProviderName = hostProvider.getClass().getSimpleName();

LOG.error("Connection string type {} is incompatible with host provider type {}: connectString={}, hostProvider={}",
connectStringType.getName(), supportedConnectStringType.getName(), connectString, hostProviderName);
throw new IllegalArgumentException(
String.format("Connection string type %s is incompatible with host provider type %s",
connectStringType.getName(), hostProviderName));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.List;
import org.apache.zookeeper.common.NetUtils;
import org.apache.zookeeper.common.PathUtils;
import org.apache.zookeeper.common.StringUtils;

/**
* A parser for ZooKeeper Client connect strings.
Expand All @@ -38,9 +39,9 @@
public final class ConnectStringParser {

private static final int DEFAULT_PORT = 2181;
private static final String DNS_SRV_PREFIX = "dns-srv://";

private final String chrootPath;

private String chrootPath;
private final ArrayList<InetSocketAddress> serverAddresses = new ArrayList<>();

/**
Expand All @@ -49,27 +50,71 @@ public final class ConnectStringParser {
* @throws IllegalArgumentException
* for an invalid chroot path.
*/
public ConnectStringParser(String connectString) {
// parse out chroot, if any
int off = connectString.indexOf('/');
if (off >= 0) {
String chrootPath = connectString.substring(off);
// ignore "/" chroot spec, same as null
if (chrootPath.length() == 1) {
this.chrootPath = null;
} else {
PathUtils.validatePath(chrootPath);
this.chrootPath = chrootPath;
}
connectString = connectString.substring(0, off);
public ConnectStringParser(final String connectString) {
if (StringUtils.isBlank(connectString)) {
throw new IllegalArgumentException("Connect string cannot be null or empty");
}

if (connectString.startsWith(DNS_SRV_PREFIX)) {
parseDnsSrvFormat(connectString);
} else {
this.chrootPath = null;
parseHostPortFormat(connectString);
}
}

List<String> hostsList = split(connectString, ",");
public String getChrootPath() {
return chrootPath;
}

public ArrayList<InetSocketAddress> getServerAddresses() {
return serverAddresses;
}


/**
* Gets the connection type for the given connect string.
*
* @param connectString the connection string to analyze
* @return ConnectionType.DNS_SRV if it's a DNS SRV connect string, ConnectionType.HOST_PORT otherwise
*/
public static ConnectionType getConnectionType(final String connectString) {
if (connectString == null) {
throw new IllegalArgumentException("connect string cannot be null");
}
return connectString.startsWith(DNS_SRV_PREFIX)
? ConnectionType.DNS_SRV : ConnectionType.HOST_PORT;
}

/**
* Parse DNS SRV connection string format: dns-srv://service.domain.com/chroot
* @throws IllegalArgumentException for an invalid chroot path.
*/
private void parseDnsSrvFormat(final String connectString) {
final String dnsName = connectString.substring(DNS_SRV_PREFIX.length());

final String[] parts = extractChrootPath(dnsName);
final String dnsServiceName = parts[0];

chrootPath = parts[1];
// The DNS service name is stored as a placeholder address
// The actual resolution will be handled by DnsSrvHostProvider
serverAddresses.add(InetSocketAddress.createUnresolved(dnsServiceName, DEFAULT_PORT));
}

/**
* Parse host and port by splitting client connectString
* with support for IPv6 literals
* @throws IllegalArgumentException for an invalid chroot path.
*/
private void parseHostPortFormat(String connectString) {
final String[] parts = extractChrootPath(connectString);
final String serversPart = parts[0];
chrootPath = parts[1];

final List<String> hostsList = split(serversPart, ",");
for (String host : hostsList) {
int port = DEFAULT_PORT;
String[] hostAndPort = NetUtils.getIPV6HostAndPort(host);
final String[] hostAndPort = NetUtils.getIPV6HostAndPort(host);
if (hostAndPort.length != 0) {
host = hostAndPort[0];
if (hostAndPort.length == 2) {
Expand All @@ -89,12 +134,30 @@ public ConnectStringParser(String connectString) {
}
}

public String getChrootPath() {
return chrootPath;
}
/**
* Extract chroot path from a connection string.
*
* @param connectionString the connection string that may contain a chroot path
* @return array where [0] is the server part (before chroot) and [1] is the chroot path (or null)
* @throws IllegalArgumentException for an invalid chroot path
*/
private String[] extractChrootPath(final String connectionString) {
String serverPart = connectionString;
String chrootPath = null;

public ArrayList<InetSocketAddress> getServerAddresses() {
return serverAddresses;
// parse out chroot, if any
final int chrootIndex = connectionString.indexOf('/');
if (chrootIndex >= 0) {
chrootPath = connectionString.substring(chrootIndex);
// ignore "/" chroot spec, same as null
if (chrootPath.length() == 1) {
chrootPath = null;
} else {
PathUtils.validatePath(chrootPath);
}
serverPart = connectionString.substring(0, chrootIndex);
}
return new String[]{serverPart, chrootPath};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 org.apache.zookeeper.client;

import org.apache.yetus.audience.InterfaceAudience;

/**
* Represents the type of connection resolution used by ZooKeeper clients.
* This is an internal enum used for connection string and provider validation.
*/
@InterfaceAudience.Private
public enum ConnectionType {
/**
* Traditional host:port static server list.
*/
HOST_PORT("Host:Port"),

/**
* DNS SRV record-based service discovery.
*/
DNS_SRV("DNS SRV");

private final String name;

ConnectionType(final String name) {
this.name = name;
}

/**
* Returns the name for this connection type.
* @return the name
*/
public String getName() {
return name;
}
}
Loading