-
-
-
- ZooKeeper 3.0.0 Release Notes
-
-
-
- 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.
-
-
-
-
-These release notes include new developer and user facing incompatibilities, features, and major improvements.
-
-
-
- Migration Instructions
- Changes
-
-
-
-Migration Instructions when Upgrading to 3.0.0
-
-
-You should only have to read this section if you are upgrading from a previous version of ZooKeeper to version 3.0.0, otw skip down to changes
-
-
-
-A small number of changes in this release have resulted in non-backward compatible Zookeeper client user code and server instance data. The following instructions provide details on how to migrate code and date from version 2.2.1 to version 3.0.0.
-
-
-
-Note: ZooKeeper increments the major version number (major.minor.fix) when backward incompatible changes are made to the source base. As part of the migration from SourceForge we changed the package structure (com.yahoo.zookeeper.* to org.apache.zookeeper.*) and felt it was a good time to incorporate some changes that we had been withholding. As a result the following will be required when migrating from 2.2.1 to 3.0.0 version of ZooKeeper.
-
-
-
- Migrating Client Code
- Migrating Server Data
- Migrating Server Configuration
-
-
-
-Migrating Client Code
-
-
- The underlying client-server protocol has changed in version 3.0.0
- of ZooKeeper. As a result clients must be upgraded along with
- serving clusters to ensure proper operation of the system (old
- pre-3.0.0 clients are not guaranteed to operate against upgraded
- 3.0.0 servers and vice-versa).
-
-
-
-Watch Management
-
-
-In previous releases of ZooKeeper any watches registered by clients were lost if the client lost a connection to a ZooKeeper server.
-This meant that developers had to track watches they were interested in and reregister them if a session disconnect event was recieved.
-In this release the client library tracks watches that a client has registered and reregisters the watches when a connection is made to a new server.
-Applications that still manually reregister interest should continue working properly as long as they are able to handle unsolicited watches.
-For example, an old application may register a watch for /foo and /goo, lose the connection, and reregister only /goo.
-As long as the application is able to recieve a notification for /foo, (probably ignoring it) the applications does not to be changes.
-One caveat to the watch management: it is possible to miss an event for the creation and deletion of a znode if watching for creation and both the create and delete happens while the client is disconnected from ZooKeeper.
-
-
-
-This release also allows clients to specify call specific watch functions.
-This gives the developer the ability to modularize logic in different watch functions rather than cramming everything in the watch function attached to the ZooKeeper handle.
-Call specific watch functions receive all session events for as long as they are active, but will only receive the watch callbacks for which they are registered.
-
-
-
-
-Java API
-
-
- The java package structure has changed from com.yahoo.zookeeper* to org.apache.zookeeper*. This will probably effect all of your java code which makes use of ZooKeeper APIs (typically import statements)
- A number of constants used in the client ZooKeeper API were re-specified using enums (rather than ints). See ZOOKEEPER-7, ZOOKEEPER-132 and ZOOKEEPER-139 for full details
- ZOOKEEPER-18 removed KeeperStateChanged, use KeeperStateDisconnected instead
-
-
-
-Also see the current java API
-
-
-
-
-C API
-
-
- A number of constants used in the client ZooKeeper API were renamed in order to reduce namespace collision, see ZOOKEEPER-6 for full details
-
-
-
-
-
-
-Migrating Server Data
-
-
-The following issues resulted in changes to the on-disk data format (the snapshot and transaction log files contained within the ZK data directory) and require a migration utility to be run.
-
-
-
- ZOOKEEPER-27 Unique DB identifiers for servers and clients
- ZOOKEEPER-32 CRCs for ZooKeeper data
- ZOOKEEPER-33 Better ACL management
- ZOOKEEPER-38 headers (version+) in log/snap files
-
-
-
- The following must be run once, and only once, when upgrading the ZooKeeper server instances to version 3.0.0.
-
-
-
-
- The <dataLogDir> and <dataDir> directories referenced
- below are specified by the dataLogDir
- and dataDir specification in your
- ZooKeeper config file
- respectively. dataLogDir defaults to the
- value of dataDir if not specified explicitly
- in the ZooKeeper server config file (in which case provide the
- same directory for both parameters to the upgrade utility).
-
-
-
-
- Shutdown the ZooKeeper server cluster.
- Backup your <dataLogDir> and <dataDir> directories
- Run upgrade using
-
- bin/zkServer.sh upgrade <dataLogDir> <dataDir>
-
- or
-
- java -classpath pathtolog4j:pathtozookeeper.jar UpgradeMain <dataLogDir> <dataDir>
-
- where <dataLogDir> is the directory where all transaction logs (log.*) are stored. <dataDir> is the directory where all the snapshots (snapshot.*) are stored.
-
- Restart the cluster.
-
-
- If you have any failure during the upgrade procedure keep reading to sanitize your database.
-
-This is how upgrade works in ZooKeeper. This will help you troubleshoot in case you have problems while upgrading
-
-Upgrade moves files from <dataLogDir> and <dataDir> to <dataLogDir>/version-1/ and <dataDir>/version-1 respectively (version-1 sub-directory is created by the upgrade utility).
- Upgrade creates a new version sub-directory <dataDir>/version-2 and <dataLogDir>/version-2
- Upgrade reads the old database from <dataDir>/version-1 and <dataLogDir>/version-1 into the memory and creates a new upgraded snapshot.
- Upgrade writes the new database in <dataDir>/version-2.
-
-
- Troubleshooting.
-
-
- In case you start ZooKeeper 3.0 without upgrading from 2.0 on a 2.0 database - the servers will start up with an empty database.
- This is because the servers assume that <dataDir>/version-2 and <dataLogDir>/version-2 will have the database to start with. Since this will be empty
- in case of no upgrade, the servers will start with an empty database. In such a case, shutdown the ZooKeeper servers, remove the version-2 directory (remember
- this will lead to loss of updates after you started 3.0.)
- and then start the upgrade procedure.
- If the upgrade fails while trying to rename files into the version-1 directory, you should try and move all the files under <dataDir>/version-1
- and <dataLogDir>/version-1 to <dataDir> and <dataLogDir> respectively. Then try upgrade again.
-
-
- If you do not wish to run with ZooKeeper 3.0 and prefer to run with ZooKeeper 2.0 and have already upgraded - you can run ZooKeeper 2 with
- the <dataDir> and <dataLogDir> directories changed to <dataDir>/version-1 and <dataLogDir>/version-1. Remember that you will lose all the updates that you made after the upgrade.
-
-
-
-
-
-
-Migrating Server Configuration
-
-
-There is a significant change to the ZooKeeper server configuration file.
-
-
-The default election algorithm, specified by
- the electionAlg configuration attribute, has
- changed from a default of 0 to a default
- of 3. See
- Cluster
- Options section of the administrators guide, specifically
- the electionAlg
- and server.X properties.
-
-
-
- You will either need to explicitly
- set electionAlg to it's previous default value
- of 0 or change
- your server.X options to include the leader
- election port.
-
-
-
-
-
-
-
-Changes Since ZooKeeper 2.2.1
-
-
-Version 2.2.1 code, documentation, binaries, etc... are still accessible on SourceForge
-
-
-
-Changes Since ZooKeeper 2.2.1
-
-
-
-
- Issue
- Notes
-
-
-
-
-
-
- ZOOKEEPER-43
-
-
- Server side of auto reset watches.
-
-
-
-
-
- ZOOKEEPER-132
-
-
- Create Enum to replace CreateFlag in ZooKepper.create method
-
-
-
-
-
- ZOOKEEPER-139
-
-
- Create Enums for WatcherEvent's KeeperState and EventType
-
-
-
-
-
- ZOOKEEPER-18
-
-
- keeper state inconsistency
-
-
-
-
-
- ZOOKEEPER-38
-
-
- headers in log/snap files
-
-
-
-
-
- ZOOKEEPER-8
-
-
- Stat enchaned to include num of children and size
-
-
-
-
-
- ZOOKEEPER-6
-
-
- List of problem identifiers in zookeeper.h
-
-
-
-
-
- ZOOKEEPER-7
-
-
- Use enums rather than ints for types and state
-
-
-
-
-
- ZOOKEEPER-27
-
-
- Unique DB identifiers for servers and clients
-
-
-
-
-
- ZOOKEEPER-32
-
-
- CRCs for ZooKeeper data
-
-
-
-
-
- ZOOKEEPER-33
-
-
- Better ACL management
-
-
-
-
-
- ZOOKEEPER-203
-
-
- fix datadir typo in releasenotes
-
-
-
-
-
- ZOOKEEPER-145
-
-
- write detailed release notes for users migrating from 2.x to 3.0
-
-
-
-
-
- ZOOKEEPER-23
-
-
- Auto reset of watches on reconnect
-
-
-
-
-
- ZOOKEEPER-191
-
-
- forrest docs for upgrade.
-
-
-
-
-
- ZOOKEEPER-201
-
-
- validate magic number when reading snapshot and transaction logs
-
-
-
-
-
- ZOOKEEPER-200
-
-
- the magic number for snapshot and log must be different
-
-
-
-
-
- ZOOKEEPER-199
-
-
- fix log messages in persistence code
-
-
-
-
-
- ZOOKEEPER-197
-
-
- create checksums for snapshots
-
-
-
-
-
- ZOOKEEPER-198
-
-
- apache license header missing from FollowerSyncRequest.java
-
-
-
-
-
- ZOOKEEPER-5
-
-
- Upgrade Feature in Zookeeper server.
-
-
-
-
-
- ZOOKEEPER-194
-
-
- Fix terminology in zookeeperAdmin.xml
-
-
-
-
-
- ZOOKEEPER-151
-
-
- Document change to server configuration
-
-
-
-
-
- ZOOKEEPER-193
-
-
- update java example doc to compile with latest zookeeper
-
-
-
-
-
- ZOOKEEPER-187
-
-
- CreateMode api docs missing
-
-
-
-
-
- ZOOKEEPER-186
-
-
- add new "releasenotes.xml" to forrest documentation
-
-
-
-
-
- ZOOKEEPER-190
-
-
- Reorg links to docs and navs to docs into related sections
-
-
-
-
-
- ZOOKEEPER-189
-
-
- forrest build not validated xml of input documents
-
-
-
-
-
- ZOOKEEPER-188
-
-
- Check that election port is present for all servers
-
-
-
-
-
- ZOOKEEPER-185
-
-
- Improved version of FLETest
-
-
-
-
-
- ZOOKEEPER-184
-
-
- tests: An explicit include derective is needed for the usage of memcpy functions
-
-
-
-
-
- ZOOKEEPER-183
-
-
- Array subscript is above array bounds in od_completion, src/cli.c.
-
-
-
-
-
- ZOOKEEPER-182
-
-
- zookeeper_init accepts empty host-port string and returns valid pointer to zhandle_t.
-
-
-
-
-
- ZOOKEEPER-17
-
-
- zookeeper_init doc needs clarification
-
-
-
-
-
- ZOOKEEPER-181
-
-
- Some Source Forge Documents did not get moved over: javaExample, zookeeperTutorial, zookeeperInternals
-
-
-
-
-
- ZOOKEEPER-180
-
-
- Placeholder sections needed in document for new topics that the umbrella jira discusses
-
-
-
-
-
- ZOOKEEPER-179
-
-
- Programmer's Guide "Basic Operations" section is missing content
-
-
-
-
-
- ZOOKEEPER-178
-
-
- FLE test.
-
-
-
-
-
- ZOOKEEPER-159
-
-
- Cover two corner cases of leader election
-
-
-
-
-
- ZOOKEEPER-156
-
-
- update programmer guide with acl details from old wiki page
-
-
-
-
-
- ZOOKEEPER-154
-
-
- reliability graph diagram in overview doc needs context
-
-
-
-
-
- ZOOKEEPER-157
-
-
- Peer can't find existing leader
-
-
-
-
-
- ZOOKEEPER-155
-
-
- improve "the zookeeper project" section of overview doc
-
-
-
-
-
- ZOOKEEPER-140
-
-
- Deadlock in QuorumCnxManager
-
-
-
-
-
- ZOOKEEPER-147
-
-
- This is version of the documents with most of the [tbd...] scrubbed out
-
-
-
-
-
- ZOOKEEPER-150
-
-
- zookeeper build broken
-
-
-
-
-
- ZOOKEEPER-136
-
-
- sync causes hang in all followers of quorum.
-
-
-
-
-
- ZOOKEEPER-134
-
-
- findbugs cleanup
-
-
-
-
-
- ZOOKEEPER-133
-
-
- hudson tests failing intermittently
-
-
-
-
-
- ZOOKEEPER-144
-
-
- add tostring support for watcher event, and enums for event type/state
-
-
-
-
-
- ZOOKEEPER-21
-
-
- Improve zk ctor/watcher
-
-
-
-
-
- ZOOKEEPER-142
-
-
- Provide Javadoc as to the maximum size of the data byte array that may be stored within a znode
-
-
-
-
-
- ZOOKEEPER-93
-
-
- Create Documentation for Zookeeper
-
-
-
-
-
- ZOOKEEPER-117
-
-
- threading issues in Leader election
-
-
-
-
-
- ZOOKEEPER-137
-
-
- client watcher objects can lose events
-
-
-
-
-
- ZOOKEEPER-131
-
-
- Old leader election can elect a dead leader over and over again
-
-
-
-
-
- ZOOKEEPER-130
-
-
- update build.xml to support apache release process
-
-
-
-
-
- ZOOKEEPER-118
-
-
- findbugs flagged switch statement in followerrequestprocessor.run
-
-
-
-
-
- ZOOKEEPER-115
-
-
- Potential NPE in QuorumCnxManager
-
-
-
-
-
- ZOOKEEPER-114
-
-
- cleanup ugly event messages in zookeeper client
-
-
-
-
-
- ZOOKEEPER-112
-
-
- src/java/main ZooKeeper.java has test code embedded into it.
-
-
-
-
-
- ZOOKEEPER-39
-
-
- Use Watcher objects rather than boolean on read operations.
-
-
-
-
-
- ZOOKEEPER-97
-
-
- supports optional output directory in code generator.
-
-
-
-
-
- ZOOKEEPER-101
-
-
- Integrate ZooKeeper with "violations" feature on hudson
-
-
-
-
-
- ZOOKEEPER-105
-
-
- Catch Zookeeper exceptions and print on the stderr.
-
-
-
-
-
- ZOOKEEPER-42
-
-
- Change Leader Election to fast tcp.
-
-
-
-
-
- ZOOKEEPER-48
-
-
- auth_id now handled correctly when no auth ids present
-
-
-
-
-
- ZOOKEEPER-44
-
-
- Create sequence flag children with prefixes of 0's so that they can be lexicographically sorted.
-
-
-
-
-
- ZOOKEEPER-108
-
-
- Fix sync operation reordering on a Quorum.
-
-
-
-
-
- ZOOKEEPER-25
-
-
- Fuse module for Zookeeper.
-
-
-
-
-
- ZOOKEEPER-58
-
-
- Race condition on ClientCnxn.java
-
-
-
-
-
- ZOOKEEPER-56
-
-
- Add clover support to build.xml.
-
-
-
-
-
- ZOOKEEPER-75
-
-
- register the ZooKeeper mailing lists with nabble.com
-
-
-
-
-
- ZOOKEEPER-54
-
-
- remove sleeps in the tests.
-
-
-
-
-
- ZOOKEEPER-55
-
-
- build.xml failes to retrieve a release number from SVN and the ant target "dist" fails
-
-
-
-
-
- ZOOKEEPER-89
-
-
- invoke WhenOwnerListener.whenNotOwner when the ZK connection fails
-
-
-
-
-
- ZOOKEEPER-90
-
-
- invoke WhenOwnerListener.whenNotOwner when the ZK session expires and the znode is the leader
-
-
-
-
-
- ZOOKEEPER-82
-
-
- Make the ZooKeeperServer more DI friendly.
-
-
-
-
-
- ZOOKEEPER-110
-
-
- Build script relies on svnant, which is not compatible with subversion 1.5 working copies
-
-
-
-
-
- ZOOKEEPER-111
-
-
- Significant cleanup of existing tests.
-
-
-
-
-
- ZOOKEEPER-122
-
-
- Fix NPE in jute's Utils.toCSVString.
-
-
-
-
-
- ZOOKEEPER-123
-
-
- Fix the wrong class is specified for the logger.
-
-
-
-
-
- ZOOKEEPER-2
-
-
- Fix synchronization issues in QuorumPeer and FastLeader election.
-
-
-
-
-
- ZOOKEEPER-125
-
-
- Remove unwanted class declaration in FastLeaderElection.
-
-
-
-
-
- ZOOKEEPER-61
-
-
- Address in client/server test cases.
-
-
-
-
-
- ZOOKEEPER-75
-
-
- cleanup the library directory
-
-
-
-
-
- ZOOKEEPER-109
-
-
- cleanup of NPE and Resource issue nits found by static analysis
-
-
-
-
-
- ZOOKEEPER-76
-
-
- Commit 677109 removed the cobertura library, but not the build targets.
-
-
-
-
-
- ZOOKEEPER-63
-
-
- Race condition in client close
-
-
-
-
-
- ZOOKEEPER-70
-
-
- Add skeleton forrest doc structure for ZooKeeper
-
-
-
-
-
- ZOOKEEPER-79
-
-
- Document jacob's leader election on the wiki recipes page
-
-
-
-
-
- ZOOKEEPER-73
-
-
- Move ZK wiki from SourceForge to Apache
-
-
-
-
-
- ZOOKEEPER-72
-
-
- Initial creation/setup of ZooKeeper ASF site.
-
-
-
-
-
- ZOOKEEPER-71
-
-
- Determine what to do re ZooKeeper Changelog
-
-
-
-
-
- ZOOKEEPER-68
-
-
- parseACLs in ZooKeeper.java fails to parse elements of ACL, should be lastIndexOf rather than IndexOf
-
-
-
-
-
- ZOOKEEPER-130
-
-
- update build.xml to support apache release process.
-
-
-
-
-
- ZOOKEEPER-131
-
-
- Fix Old leader election can elect a dead leader over and over again.
-
-
-
-
-
- ZOOKEEPER-137
-
-
- client watcher objects can lose events
-
-
-
-
-
- ZOOKEEPER-117
-
-
- threading issues in Leader election
-
-
-
-
-
- ZOOKEEPER-128
-
-
- test coverage on async client operations needs to be improved
-
-
-
-
-
- ZOOKEEPER-127
-
-
- Use of non-standard election ports in config breaks services
-
-
-
-
-
- ZOOKEEPER-53
-
-
- tests failing on solaris.
-
-
-
-
-
- ZOOKEEPER-172
-
-
- FLE Test
-
-
-
-
-
- ZOOKEEPER-41
-
-
- Sample startup script
-
-
-
-
-
- ZOOKEEPER-33
-
-
- Better ACL management
-
-
-
-
-
- ZOOKEEPER-49
-
-
- SetACL does not work
-
-
-
-
-
- ZOOKEEPER-20
-
-
- Child watches are not triggered when the node is deleted
-
-
-
-
-
- ZOOKEEPER-15
-
-
- handle failure better in build.xml:test
-
-
-
-
-
- ZOOKEEPER-11
-
-
- ArrayList is used instead of List
-
-
-
-
-
- ZOOKEEPER-45
-
-
- Restructure the SVN repository after initial import
-
-
-
-
-
- ZOOKEEPER-1
-
-
- Initial ZooKeeper code contribution from Yahoo!
-
-
-
-
-
-
-
-
diff --git a/src/docs/src/documentation/content/xdocs/tabs.xml b/src/docs/src/documentation/content/xdocs/tabs.xml
index aef7e59b083..90bbf99bbcd 100644
--- a/src/docs/src/documentation/content/xdocs/tabs.xml
+++ b/src/docs/src/documentation/content/xdocs/tabs.xml
@@ -31,6 +31,6 @@
-
+
From a8dd2d16fa5cdeda013dc4537c0156131f4e5f75 Mon Sep 17 00:00:00 2001
From: "Patrick D. Hunt"
Date: Fri, 1 Aug 2014 22:14:48 +0000
Subject: [PATCH 003/279] Preparing for release 3.5.0
git-svn-id: https://svn.apache.org/repos/asf/zookeeper/branches/branch-3.5@1615252 13f79535-47bb-0310-9956-ffa450edef68
---
build.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build.xml b/build.xml
index e3f9794dc2b..517966afe02 100644
--- a/build.xml
+++ b/build.xml
@@ -32,7 +32,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant">
-
+
From 7ad0ebde95d0f02fb821e7f0b8396545934a12fb Mon Sep 17 00:00:00 2001
From: "Patrick D. Hunt"
Date: Wed, 6 Aug 2014 04:29:48 +0000
Subject: [PATCH 004/279] Setup for 3.5.1 development activities
git-svn-id: https://svn.apache.org/repos/asf/zookeeper/branches/branch-3.5@1616091 13f79535-47bb-0310-9956-ffa450edef68
---
CHANGES.txt | 9 +++++++++
build.xml | 4 ++--
src/c/configure.ac | 2 +-
src/c/include/zookeeper_version.h | 2 +-
4 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 2f7418a9d8c..ed9822e9747 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,12 @@
+Unreleased
+
+NEW FEATURES:
+
+BUGFIXES:
+
+IMPROVEMENTS:
+
+
Release 3.5.0 - 8/4/2014
NEW FEATURES:
diff --git a/build.xml b/build.xml
index 517966afe02..e06b1ba571a 100644
--- a/build.xml
+++ b/build.xml
@@ -31,8 +31,8 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant">
-
-
+
+
diff --git a/src/c/configure.ac b/src/c/configure.ac
index 350746f4f8b..6291430fa0c 100644
--- a/src/c/configure.ac
+++ b/src/c/configure.ac
@@ -3,7 +3,7 @@
AC_PREREQ(2.59)
-AC_INIT([zookeeper C client],3.5.0,[user@zookeeper.apache.org],[zookeeper])
+AC_INIT([zookeeper C client],3.5.1,[user@zookeeper.apache.org],[zookeeper])
AC_CONFIG_SRCDIR([src/zookeeper.c])
# Save initial CFLAGS and CXXFLAGS values before AC_PROG_CC and AC_PROG_CXX
diff --git a/src/c/include/zookeeper_version.h b/src/c/include/zookeeper_version.h
index 74102463b7e..dd6fd27c406 100644
--- a/src/c/include/zookeeper_version.h
+++ b/src/c/include/zookeeper_version.h
@@ -24,7 +24,7 @@ extern "C" {
#define ZOO_MAJOR_VERSION 3
#define ZOO_MINOR_VERSION 5
-#define ZOO_PATCH_VERSION 0
+#define ZOO_PATCH_VERSION 1
#ifdef __cplusplus
}
From b7efd9790b5305b88b41718f21cead2ab060e1d6 Mon Sep 17 00:00:00 2001
From: Alexander Shraer
Date: Thu, 14 Aug 2014 06:14:38 +0000
Subject: [PATCH 005/279] ZOOKEEPER-1994. Auto-backup configuration files;
config version becomes part of filename (Hongchao Deng via shralex)
git-svn-id: https://svn.apache.org/repos/asf/zookeeper/branches/branch-3.5@1617887 13f79535-47bb-0310-9956-ffa450edef68
---
CHANGES.txt | 2 +
.../zookeeper/server/quorum/QuorumPeer.java | 118 +++---
.../server/quorum/QuorumPeerConfig.java | 168 ++++++---
.../server/quorum/QuorumPeerMain.java | 2 -
.../server/quorum/QuorumPeerTestBase.java | 80 +++-
.../server/quorum/ReconfigBackupTest.java | 345 ++++++++++++++++++
.../server/quorum/ReconfigLegacyTest.java | 13 +-
.../server/quorum/ReconfigRecoveryTest.java | 70 ++--
.../zookeeper/test/LENonTerminateTest.java | 2 +-
9 files changed, 609 insertions(+), 191 deletions(-)
create mode 100644 src/java/test/org/apache/zookeeper/server/quorum/ReconfigBackupTest.java
diff --git a/CHANGES.txt b/CHANGES.txt
index ed9822e9747..b82d8861dbc 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -10,6 +10,8 @@ IMPROVEMENTS:
Release 3.5.0 - 8/4/2014
NEW FEATURES:
+ ZOOKEEPER-1994. Auto-backup configuration files; config version becomes part of filename (Hongchao Deng via shralex)
+
ZOOKEEPER-1355. Add zk.updateServerList(newServerList) (Alex Shraer, Marshall McMullen via fpj)
ZOOKEEPER-1572. Add an async (Java) interface for multi request (Sijie Guo via camille)
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
index 76f0afcbcbe..388ceeb45bd 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
@@ -18,16 +18,12 @@
package org.apache.zookeeper.server.quorum;
import java.io.BufferedReader;
-import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
-import java.io.OutputStreamWriter;
-import java.io.StringReader;
-import java.io.StringWriter;
import java.io.Writer;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
@@ -45,15 +41,12 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.zookeeper.KeeperException.NoNodeException;
-import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.common.AtomicFileWritingIdiom;
import org.apache.zookeeper.common.AtomicFileWritingIdiom.WriterStatement;
import org.apache.zookeeper.common.HostNameUtils;
import org.apache.zookeeper.common.PathUtils;
import org.apache.zookeeper.jmx.MBeanRegistry;
import org.apache.zookeeper.jmx.ZKMBeanInfo;
-import org.apache.zookeeper.server.DataNode;
import org.apache.zookeeper.server.ServerCnxnFactory;
import org.apache.zookeeper.server.ZKDatabase;
import org.apache.zookeeper.server.ZooKeeperServer;
@@ -317,32 +310,11 @@ public void setLearnerType(LearnerType p) {
learnerType = p;
}
-
- protected synchronized void setDynamicConfigFilename(String s) {
- dynamicConfigFilename = PathUtils.normalizeFileSystemPath(s);
- }
-
- protected synchronized String getDynamicConfigFilename() {
- return dynamicConfigFilename;
- }
-
protected synchronized void setConfigFileName(String s) {
configFilename = s;
}
- protected synchronized void setConfigBackwardCompatibility(boolean bc) {
- configBackwardCompatibility = bc;
- }
-
- protected synchronized boolean getConfigBackwardCompatibility() {
- return configBackwardCompatibility;
- }
-
- private String dynamicConfigFilename = null;
-
private String configFilename = null;
-
- private boolean configBackwardCompatibility = false;
public int getQuorumSize(){
return getVotingView().size();
@@ -606,9 +578,9 @@ public QuorumPeer(Map quorumPeers, File dataDir,
File dataLogDir, int electionType,
long myid, int tickTime, int initLimit, int syncLimit,
ServerCnxnFactory cnxnFactory) throws IOException {
- this(quorumPeers, dataDir, dataLogDir, electionType, myid, tickTime,
- initLimit, syncLimit, false, cnxnFactory,
- new QuorumMaj(quorumPeers), null);
+ this(quorumPeers, dataDir, dataLogDir, electionType, myid, tickTime,
+ initLimit, syncLimit, false, cnxnFactory,
+ new QuorumMaj(quorumPeers));
}
public QuorumPeer(Map quorumPeers, File dataDir,
@@ -616,7 +588,7 @@ public QuorumPeer(Map quorumPeers, File dataDir,
long myid, int tickTime, int initLimit, int syncLimit,
boolean quorumListenOnAllIPs,
ServerCnxnFactory cnxnFactory,
- QuorumVerifier quorumConfig, String memFilename) throws IOException {
+ QuorumVerifier quorumConfig) throws IOException {
this();
this.cnxnFactory = cnxnFactory;
this.electionType = electionType;
@@ -627,7 +599,6 @@ public QuorumPeer(Map quorumPeers, File dataDir,
this.quorumListenOnAllIPs = quorumListenOnAllIPs;
this.logFactory = new FileTxnSnapLog(dataLogDir, dataDir);
this.zkDb = new ZKDatabase(this.logFactory);
- this.dynamicConfigFilename = (memFilename != null) ? memFilename : "zoo_replicated" + myid + ".dynamic";
if(quorumConfig == null) quorumConfig = new QuorumMaj(quorumPeers);
setQuorumVerifier(quorumConfig, false);
adminServer = AdminServerFactory.createAdminServer();
@@ -757,7 +728,7 @@ public QuorumPeer(Map quorumPeers, File snapDir,
this(quorumPeers, snapDir, logDir, electionAlg,
myid,tickTime, initLimit,syncLimit, false,
ServerCnxnFactory.createFactory(new InetSocketAddress(clientPort), -1),
- new QuorumMaj(quorumPeers), null);
+ new QuorumMaj(quorumPeers));
}
/**
@@ -773,7 +744,7 @@ public QuorumPeer(Map quorumPeers, File snapDir,
this(quorumPeers, snapDir, logDir, electionAlg,
myid,tickTime, initLimit,syncLimit, false,
ServerCnxnFactory.createFactory(new InetSocketAddress(clientPort), -1),
- quorumConfig, null);
+ quorumConfig);
}
/**
@@ -1289,7 +1260,7 @@ public synchronized void connectNewPeers(){
}
}
}
-
+
public synchronized void restartLeaderElection(QuorumVerifier qvOLD, QuorumVerifier qvNEW){
if (qvOLD == null || !qvOLD.equals(qvNEW)) {
LOG.warn("Restarting Leader Election");
@@ -1298,6 +1269,10 @@ public synchronized void restartLeaderElection(QuorumVerifier qvOLD, QuorumVerif
startLeaderElection();
}
}
+
+ public String getNextDynamicConfigFilename() {
+ return configFilename + QuorumPeerConfig.nextDynamicConfigFileSuffix;
+ }
public synchronized void setLastSeenQuorumVerifier(QuorumVerifier qv, boolean writeToDisk){
if (lastSeenQuorumVerifier!=null && lastSeenQuorumVerifier.getVersion() > qv.getVersion()) {
@@ -1315,9 +1290,10 @@ public synchronized void setLastSeenQuorumVerifier(QuorumVerifier qv, boolean wr
connectNewPeers();
if (writeToDisk) {
try {
- QuorumPeerConfig.writeDynamicConfig(dynamicConfigFilename + ".next", null, false, qv, false);
+ QuorumPeerConfig.writeDynamicConfig(
+ getNextDynamicConfigFilename(), qv, true);
} catch(IOException e){
- LOG.error("Error closing file: ", e.getMessage());
+ LOG.error("Error writing next dynamic config file to disk: ", e.getMessage());
}
}
@@ -1332,50 +1308,48 @@ public synchronized QuorumVerifier setQuorumVerifier(QuorumVerifier qv, boolean
return quorumVerifier;
}
QuorumVerifier prevQV = quorumVerifier;
- quorumVerifier = qv;
- if (lastSeenQuorumVerifier == null || (qv.getVersion() > lastSeenQuorumVerifier.getVersion()))
- lastSeenQuorumVerifier = qv;
+ quorumVerifier = qv;
+ if (lastSeenQuorumVerifier == null || (qv.getVersion() > lastSeenQuorumVerifier.getVersion()))
+ lastSeenQuorumVerifier = qv;
+
if (writeToDisk) {
- // we need to write the dynamic config file. Either it already exists
- // or we have the old-style config file and we're in the backward compatibility mode,
- // so we'll create the dynamic config file for the first time now
- if (dynamicConfigFilename !=null || (configFilename !=null && configBackwardCompatibility)) {
+ // some tests initialize QuorumPeer without a static config file
+ if (configFilename != null) {
try {
- if (configBackwardCompatibility) {
- setDynamicConfigFilename(configFilename + ".dynamic");
- }
- QuorumPeerConfig.writeDynamicConfig(dynamicConfigFilename, configFilename,
- configBackwardCompatibility, qv,
- needEraseClientInfoFromStaticConfig(prevQV, qv));
- configBackwardCompatibility = false;
- } catch(IOException e){
- LOG.error("Error closing file: ", e.getMessage());
+ String dynamicConfigFilename = makeDynamicConfigFilename(
+ qv.getVersion());
+ QuorumPeerConfig.writeDynamicConfig(
+ dynamicConfigFilename, qv, false);
+ QuorumPeerConfig.editStaticConfig(configFilename,
+ dynamicConfigFilename,
+ needEraseClientInfoFromStaticConfig());
+ } catch (IOException e) {
+ LOG.error("Error closing file: ", e.getMessage());
}
} else {
- LOG.error("writeToDisk == true but dynamicConfigFilename == null, configFilename "
- + (configFilename == null ? "== null": "!=null")
- + " and configBackwardCompatibility == " + configBackwardCompatibility);
+ LOG.error("writeToDisk == true but configFilename == null");
}
}
if (qv.getVersion() == lastSeenQuorumVerifier.getVersion()){
- QuorumPeerConfig.deleteFile(dynamicConfigFilename + ".next");
- }
- QuorumServer qs = qv.getAllMembers().get(getId());
- if (qs!=null){
- setQuorumAddress(qs.addr);
- setElectionAddress(qs.electionAddr);
- setClientAddress(qs.clientAddr);
- }
- return prevQV;
+ QuorumPeerConfig.deleteFile( getNextDynamicConfigFilename() );
+ }
+ QuorumServer qs = qv.getAllMembers().get(getId());
+ if (qs!=null){
+ setQuorumAddress(qs.addr);
+ setElectionAddress(qs.electionAddr);
+ setClientAddress(qs.clientAddr);
+ }
+ return prevQV;
+ }
+
+ private String makeDynamicConfigFilename(long version) {
+ return configFilename + ".dynamic." + Long.toHexString(version);
}
- private boolean needEraseClientInfoFromStaticConfig(QuorumVerifier oldQV,
- QuorumVerifier newQV) {
- QuorumServer myOldSpec = oldQV.getAllMembers().get(getId());
- QuorumServer myNewSpec = newQV.getAllMembers().get(getId());
- return (myNewSpec != null && myNewSpec.clientAddr != null
- && (myOldSpec == null || myOldSpec.clientAddr == null));
+ private boolean needEraseClientInfoFromStaticConfig() {
+ QuorumServer server = quorumVerifier.getAllMembers().get(getId());
+ return (server != null && server.clientAddr != null);
}
/**
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
index c4397a1f80d..02edcf07efb 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
@@ -19,22 +19,24 @@
package org.apache.zookeeper.server.quorum;
import java.io.BufferedReader;
-import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.FileReader;
-import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
+import java.io.StringReader;
import java.io.Writer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
+import org.apache.zookeeper.common.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
@@ -54,10 +56,11 @@ public class QuorumPeerConfig {
private static final Logger LOG = LoggerFactory.getLogger(QuorumPeerConfig.class);
private static boolean standaloneEnabled = true;
+ public static final String nextDynamicConfigFileSuffix = ".dynamic.next";
+
protected InetSocketAddress clientPortAddress;
protected File dataDir;
protected File dataLogDir;
- protected boolean configBackwardCompatibilityMode = false;
protected String dynamicConfigFileStr = null;
protected String configFileStr = null;
protected int tickTime = ZooKeeperServer.DEFAULT_TICK_TIME;
@@ -136,10 +139,20 @@ public void parse(String path) throws ConfigException {
FileInputStream inConfig = new FileInputStream(dynamicConfigFileStr);
try {
dynamicCfg.load(inConfig);
+ if (dynamicCfg.getProperty("version") != null) {
+ throw new ConfigException("dynamic file shouldn't have version inside");
+ }
+
+ String version = getVersionFromFilename(dynamicConfigFileStr);
+ // If there isn't any version associated with the filename,
+ // the default version is 0.
+ if (version != null) {
+ dynamicCfg.setProperty("version", version);
+ }
} finally {
inConfig.close();
}
- quorumVerifier = parseDynamicConfig(dynamicCfg, electionAlg, true, configBackwardCompatibilityMode);
+ quorumVerifier = parseDynamicConfig(dynamicCfg, electionAlg, true, false);
checkValidity();
} catch (IOException e) {
@@ -147,7 +160,7 @@ public void parse(String path) throws ConfigException {
} catch (IllegalArgumentException e) {
throw new ConfigException("Error processing " + dynamicConfigFileStr, e);
}
- File nextDynamicConfigFile = new File(dynamicConfigFileStr + ".next");
+ File nextDynamicConfigFile = new File(configFileStr + nextDynamicConfigFileSuffix);
if (nextDynamicConfigFile.exists()) {
try {
Properties dynamicConfigNextCfg = new Properties();
@@ -165,7 +178,7 @@ public void parse(String path) throws ConfigException {
break;
}
}
- lastSeenQuorumVerifier = createQuorumVerifier(dynamicConfigNextCfg, isHierarchical);
+ lastSeenQuorumVerifier = createQuorumVerifier(dynamicConfigNextCfg, isHierarchical);
} catch (IOException e) {
LOG.warn("NextQuorumVerifier is initiated to null");
}
@@ -173,6 +186,25 @@ public void parse(String path) throws ConfigException {
}
}
+ // This method gets the version from the end of dynamic file name.
+ // For example, "zoo.cfg.dynamic.0" returns initial version "0".
+ // "zoo.cfg.dynamic.1001" returns version of hex number "0x1001".
+ // If a dynamic file name doesn't have any version at the end of file,
+ // e.g. "zoo.cfg.dynamic", it returns null.
+ public static String getVersionFromFilename(String filename) {
+ int i = filename.lastIndexOf('.');
+ if(i < 0 || i >= filename.length())
+ return null;
+
+ String hexVersion = filename.substring(i + 1);
+ try {
+ long version = Long.parseLong(hexVersion, 16);
+ return Long.toHexString(version);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
/**
* Parse config from a Properties.
* @param zkProp Properties to parse from.
@@ -284,53 +316,83 @@ public void parseProperties(Properties zkProp)
}
// backward compatibility - dynamic configuration in the same file as
- // static configuration params see writeDynamicConfig() - we change the
- // config file to new format if reconfig happens
+ // static configuration params see writeDynamicConfig()
if (dynamicConfigFileStr == null) {
- configBackwardCompatibilityMode = true;
- quorumVerifier = parseDynamicConfig(zkProp, electionAlg, true,
- configBackwardCompatibilityMode);
+ backupOldConfig();
+ quorumVerifier = parseDynamicConfig(zkProp, electionAlg, true, true);
checkValidity();
}
}
-
+
/**
- * Writes dynamic configuration file, updates static config file if needed.
- * @param dynamicConfigFilename
- * @param configFileStr
- * @param configBackwardCompatibilityMode
- * @param qv
- * @param needEraseStaticClientInfo indicates whether we need to erase the clientPort
- * and clientPortAddress from static config file.
+ * Backward compatibility -- It would backup static config file on bootup
+ * if users write dynamic configuration in "zoo.cfg".
*/
- public static void writeDynamicConfig(String dynamicConfigFilename, String configFileStr,
- final boolean configBackwardCompatibilityMode, final QuorumVerifier qv,
- final boolean needEraseStaticClientInfo) throws IOException {
-
- final String actualDynamicConfigFilename = dynamicConfigFilename;
- new AtomicFileWritingIdiom(new File(actualDynamicConfigFilename), new OutputStreamStatement() {
+ private void backupOldConfig() throws IOException {
+ new AtomicFileWritingIdiom(new File(configFileStr + ".bak"), new OutputStreamStatement() {
@Override
- public void write(OutputStream outConfig) throws IOException {
- byte b[] = qv.toString().getBytes();
- outConfig.write(b);
+ public void write(OutputStream output) throws IOException {
+ InputStream input = null;
+ try {
+ input = new FileInputStream(new File(configFileStr));
+ byte[] buf = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = input.read(buf)) > 0) {
+ output.write(buf, 0, bytesRead);
+ }
+ } finally {
+ if( input != null) {
+ input.close();
+ }
+ }
}
});
- // the following is for users who run without a dynamic config file (old config file)
- // we create a dynamic config file, remove all the dynamic definitions from the config file and add a pointer
- // to the config file. The dynamic config file's name will be the same as the config file's
- // with ".dynamic" appended to it
+ }
- if (!configBackwardCompatibilityMode && !needEraseStaticClientInfo)
- return;
+ /**
+ * Writes dynamic configuration file
+ */
+ public static void writeDynamicConfig(final String dynamicConfigFilename,
+ final QuorumVerifier qv,
+ final boolean needKeepVersion)
+ throws IOException {
+
+ new AtomicFileWritingIdiom(new File(dynamicConfigFilename), new WriterStatement() {
+ @Override
+ public void write(Writer out) throws IOException {
+ Properties cfg = new Properties();
+ cfg.load( new StringReader(
+ qv.toString()));
- editStaticConfig(configFileStr, actualDynamicConfigFilename,
- configBackwardCompatibilityMode, needEraseStaticClientInfo);
+ List servers = new ArrayList();
+ for (Entry
Most of information in this document is written to be accessible as
stand-alone reference material. However, before starting your first
- ZooKeeper application, you should probably at least read the chaptes on
+ ZooKeeper application, you should probably at least read the chapters on
the ZooKeeper Data Model and ZooKeeper Basic Operations. Also,
the Simple Programmming
Example[tbd] is helpful for understanding the basic
@@ -434,7 +434,7 @@
The ZooKeeper Data Model
data associated with it as well as children. It is like having a file
system that allows a file to also be a directory. Paths to nodes are
always expressed as canonical, absolute, slash-separated paths; there are
- no relative reference. Any unicode character can be used in a path subject
+ no relative references. Any unicode character can be used in a path subject
to the following constraints:
From 4f1bb9368e824a3e17c369451fbd0937c0645c7f Mon Sep 17 00:00:00 2001
From: Michi Mutsuzaki
Date: Thu, 21 Aug 2014 01:36:47 +0000
Subject: [PATCH 010/279] ZOOKEEPER-2000. Fix ReconfigTest.testPortChange
(Alexander Shraer via michim)
git-svn-id: https://svn.apache.org/repos/asf/zookeeper/branches/branch-3.5@1619278 13f79535-47bb-0310-9956-ffa450edef68
---
CHANGES.txt | 2 +
.../apache/zookeeper/test/ReconfigTest.java | 86 +++++++++----------
2 files changed, 42 insertions(+), 46 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index a23ea39ecc9..91ba15e9c1e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -762,6 +762,8 @@ BUGFIXES:
ZOOKEEPER-1988. new test patch to verify dynamic reconfig backward
compatibility (Alexander Shraer via rakeshr)
+ ZOOKEEPER-2000. Fix ReconfigTest.testPortChange (Alexander Shraer via michim)
+
IMPROVEMENTS:
ZOOKEEPER-1170. Fix compiler (eclipse) warnings: unused imports,
diff --git a/src/java/test/org/apache/zookeeper/test/ReconfigTest.java b/src/java/test/org/apache/zookeeper/test/ReconfigTest.java
index 775f4a22100..0bcd4d9b93e 100644
--- a/src/java/test/org/apache/zookeeper/test/ReconfigTest.java
+++ b/src/java/test/org/apache/zookeeper/test/ReconfigTest.java
@@ -571,65 +571,62 @@ public void testPortChange() throws Exception {
int leaderIndex = getLeaderId(qu);
int followerIndex = leaderIndex == 1 ? 2 : 1;
- // change leader into observer, and modify all its ports at the same
- // time
- int observerIndex = leaderIndex;
+ // modify follower's client port
- // new ports
- int port1 = PortAssignment.unique();
- int port2 = PortAssignment.unique();
- int port3 = PortAssignment.unique();
- joiningServers.add("server." + observerIndex + "=localhost:" + port1
- + ":" + port2 + ":observer;localhost:" + port3);
+ int quorumPort = qu.getPeer(followerIndex).peer.getQuorumAddress().getPort();
+ int electionPort = qu.getPeer(followerIndex).peer.getElectionAddress().getPort();
+ int oldClientPort = qu.getPeer(followerIndex).peer.getClientPort();
+ int newClientPort = PortAssignment.unique();
+ joiningServers.add("server." + followerIndex + "=localhost:" + quorumPort
+ + ":" + electionPort + ":participant;localhost:" + newClientPort);
// create a /test znode and check that read/write works before
// any reconfig is invoked
- testNormalOperation(zkArr[observerIndex], zkArr[followerIndex]);
+ testNormalOperation(zkArr[followerIndex], zkArr[leaderIndex]);
reconfig(zkArr[followerIndex], joiningServers, null, null, -1);
- // the change of port may not be immediate -- we repeatedly
- // invoke an operation expecting it to eventually fail once
- // the client port of observerIndex changes. If it didn't
- // change -- that's an error.
try {
- for (int i=0; i < 30; i++) {
+ for (int i=0; i < 20; i++) {
Thread.sleep(1000);
- zkArr[observerIndex].setData("/test", "teststr".getBytes(), -1);
+ zkArr[followerIndex].setData("/test", "teststr".getBytes(), -1);
}
- Assert.fail("client port didn't change");
} catch (KeeperException.ConnectionLossException e) {
- zkArr[observerIndex] = new ZooKeeper("127.0.0.1:"
- + qu.getPeer(observerIndex).peer.getClientPort(),
- ClientBase.CONNECTION_TIMEOUT, new Watcher() {
- public void process(WatchedEvent event) {}});
+ Assert.fail("Existing client disconnected when client port changed!");
}
- leaderIndex = getLeaderId(qu);
-
- followerIndex = 1;
- while (followerIndex == leaderIndex || followerIndex == observerIndex)
- followerIndex++;
-
- testNormalOperation(zkArr[observerIndex], zkArr[followerIndex]);
+ zkArr[followerIndex].close();
+ zkArr[followerIndex] = new ZooKeeper("127.0.0.1:"
+ + oldClientPort,
+ ClientBase.CONNECTION_TIMEOUT, new Watcher() {
+ public void process(WatchedEvent event) {}});
+ for (int i = 0; i < 10; i++) {
+ try {
+ Thread.sleep(1000);
+ zkArr[followerIndex].setData("/test", "teststr".getBytes(), -1);
+ Assert.fail("New client connected to old client port!");
+ } catch (KeeperException.ConnectionLossException e) {
+ }
+ }
- testServerHasConfig(zkArr[observerIndex], joiningServers, null);
+ zkArr[followerIndex].close();
+ zkArr[followerIndex] = new ZooKeeper("127.0.0.1:"
+ + newClientPort,
+ ClientBase.CONNECTION_TIMEOUT, new Watcher() {
+ public void process(WatchedEvent event) {}});
- Assert.assertTrue(qu.getPeer(observerIndex).peer.getQuorumAddress()
- .getPort() == port1);
- Assert.assertTrue(qu.getPeer(observerIndex).peer.getElectionAddress()
- .getPort() == port2);
- Assert.assertTrue(qu.getPeer(observerIndex).peer.getClientPort() == port3);
- Assert.assertTrue(qu.getPeer(observerIndex).peer.getPeerState() == ServerState.OBSERVING);
- Assert.assertTrue(qu.getPeer(observerIndex).peer.getName()
- .endsWith(String.format(":%d", port3)));
+ testNormalOperation(zkArr[followerIndex], zkArr[leaderIndex]);
+ testServerHasConfig(zkArr[followerIndex], joiningServers, null);
+ Assert.assertTrue(qu.getPeer(followerIndex).peer.getName()
+ .endsWith(String.format(":%d", newClientPort)));
joiningServers.clear();
// change leader's leading port - should renounce leadership
- port1 = PortAssignment.unique();
- joiningServers.add("server." + leaderIndex + "=localhost:" + port1
+ int newQuorumPort = PortAssignment.unique();
+ joiningServers.add("server." + leaderIndex + "=localhost:"
+ + newQuorumPort
+ ":"
+ qu.getPeer(leaderIndex).peer.getElectionAddress().getPort()
+ ":participant;localhost:"
@@ -637,18 +634,15 @@ ClientBase.CONNECTION_TIMEOUT, new Watcher() {
reconfig(zkArr[followerIndex], joiningServers, null, null, -1);
- testNormalOperation(zkArr[observerIndex], zkArr[followerIndex]);
+ testNormalOperation(zkArr[followerIndex], zkArr[leaderIndex]);
Assert.assertTrue(qu.getPeer(leaderIndex).peer.getQuorumAddress()
- .getPort() == port1);
- Assert.assertTrue(qu.getPeer(leaderIndex).peer.leader == null
- && qu.getPeer(leaderIndex).peer.follower != null);
- Assert.assertTrue(qu.getPeer(followerIndex).peer.leader != null
- && qu.getPeer(followerIndex).peer.follower == null);
+ .getPort() == newQuorumPort);
+ Assert.assertTrue(getLeaderId(qu) != leaderIndex); // the leader changed
joiningServers.clear();
- // change in leader election port
+ // change everyone's leader election port
for (int i = 1; i <= 3; i++) {
joiningServers.add("server." + i + "=localhost:"
From bfeda16cf01da437282fe4a710deb5bb198c2a60 Mon Sep 17 00:00:00 2001
From: Michi Mutsuzaki
Date: Sun, 24 Aug 2014 05:44:25 +0000
Subject: [PATCH 011/279] ZOOKEEPER-2017 New tests for reconfig failure cases
(Alexander Shraer and Hongchao Deng via michim)
git-svn-id: https://svn.apache.org/repos/asf/zookeeper/branches/branch-3.5@1620112 13f79535-47bb-0310-9956-ffa450edef68
---
CHANGES.txt | 3 +
.../server/quorum/ReconfigFailureCases.java | 269 ++++++++++++++++++
.../server/quorum/ReconfigRecoveryTest.java | 4 +-
.../server/quorum/StandaloneDisabledTest.java | 16 +-
.../apache/zookeeper/test/ReconfigTest.java | 41 +--
5 files changed, 290 insertions(+), 43 deletions(-)
create mode 100644 src/java/test/org/apache/zookeeper/server/quorum/ReconfigFailureCases.java
diff --git a/CHANGES.txt b/CHANGES.txt
index 91ba15e9c1e..6f02f13ffd2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1055,6 +1055,9 @@ IMPROVEMENTS:
ZOOKEEPER-1986. refactor log trace on touchSession
(Hongchao Deng via phunt)
+ ZOOKEEPER-2017 New tests for reconfig failure cases (Alexander Shraer and
+ Hongchao Deng via michim)
+
headers
Release 3.4.0 -
diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigFailureCases.java b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigFailureCases.java
new file mode 100644
index 00000000000..d6fcdc9cf7f
--- /dev/null
+++ b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigFailureCases.java
@@ -0,0 +1,269 @@
+/**
+ * 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.server.quorum;
+
+import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.KeeperException.NewConfigNoQuorum;
+import org.apache.zookeeper.PortAssignment;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.data.Stat;
+import org.apache.zookeeper.test.ClientBase;
+import org.apache.zookeeper.test.QuorumUtil;
+import org.apache.zookeeper.test.ReconfigTest;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ReconfigFailureCases extends QuorumPeerTestBase {
+
+ private QuorumUtil qu;
+
+ @After
+ public void tearDown() throws Exception {
+ if (qu != null) {
+ qu.tearDown();
+ }
+ }
+
+ /*
+ * Tests that an incremental reconfig fails if the current config is hiearchical.
+ */
+ @Test
+ public void testIncrementalReconfigInvokedOnHiearchicalQS() throws Exception {
+ qu = new QuorumUtil(2); // create 5 servers
+ qu.disableJMXTest = true;
+ qu.startAll();
+ ZooKeeper[] zkArr = ReconfigTest.createHandles(qu);
+
+ ArrayList members = new ArrayList();
+ members.add("group.1=3:4:5");
+ members.add("group.2=1:2");
+ members.add("weight.1=0");
+ members.add("weight.2=0");
+ members.add("weight.3=1");
+ members.add("weight.4=1");
+ members.add("weight.5=1");
+
+ for (int i = 1; i <= 5; i++) {
+ members.add("server." + i + "=127.0.0.1:"
+ + qu.getPeer(i).peer.getQuorumAddress().getPort() + ":"
+ + qu.getPeer(i).peer.getElectionAddress().getPort() + ";"
+ + "127.0.0.1:" + qu.getPeer(i).peer.getClientPort());
+ }
+
+ // Change the quorum system from majority to hierarchical.
+ ReconfigTest.reconfig(zkArr[1], null, null, members, -1);
+ ReconfigTest.testNormalOperation(zkArr[1], zkArr[2]);
+
+ // Attempt an incremental reconfig.
+ List leavingServers = new ArrayList();
+ leavingServers.add("3");
+ try {
+ zkArr[1].reconfig(null, leavingServers, null, -1, null);
+ Assert.fail("Reconfig should have failed since the current config isn't Majority QS");
+ } catch (KeeperException.BadArgumentsException e) {
+ // We expect this to happen.
+ } catch (Exception e) {
+ Assert.fail("Should have been BadArgumentsException!");
+ }
+
+ ReconfigTest.closeAllHandles(zkArr);
+ }
+
+ /*
+ * Test that a reconfiguration fails if the proposed change would leave the
+ * cluster with less than 2 participants (StandaloneEnabled = true).
+ * StandaloneDisabledTest.java (startSingleServerTest) checks that if
+ * StandaloneEnabled = false its legal to remove all but one remaining
+ * server.
+ */
+ @Test
+ public void testTooFewRemainingPariticipants() throws Exception {
+ qu = new QuorumUtil(1); // create 3 servers
+ qu.disableJMXTest = true;
+ qu.startAll();
+ ZooKeeper[] zkArr = ReconfigTest.createHandles(qu);
+
+ List leavingServers = new ArrayList();
+ leavingServers.add("2");
+ leavingServers.add("3");
+ try {
+ zkArr[1].reconfig(null, leavingServers, null, -1, null);
+ Assert.fail("Reconfig should have failed since the current config version is not 8");
+ } catch (KeeperException.BadArgumentsException e) {
+ // We expect this to happen.
+ } catch (Exception e) {
+ Assert.fail("Should have been BadArgumentsException!");
+ }
+
+ ReconfigTest.closeAllHandles(zkArr);
+ }
+
+ /*
+ * Tests that a conditional reconfig fails if the specified version doesn't correspond
+ * to the version of the current config.
+ */
+ @Test
+ public void testReconfigVersionConditionFails() throws Exception {
+ qu = new QuorumUtil(1); // create 3 servers
+ qu.disableJMXTest = true;
+ qu.startAll();
+ ZooKeeper[] zkArr = ReconfigTest.createHandles(qu);
+
+ List leavingServers = new ArrayList();
+ leavingServers.add("3");
+ try {
+ zkArr[1].reconfig(null, leavingServers, null, 8, null);
+ Assert.fail("Reconfig should have failed since the current config version is not 8");
+ } catch (KeeperException.BadVersionException e) {
+ // We expect this to happen.
+ } catch (Exception e) {
+ Assert.fail("Should have been BadVersionException!");
+ }
+
+ ReconfigTest.closeAllHandles(zkArr);
+ }
+
+ /*
+ * Tests that if a quorum of a new config is synced with the leader and a reconfig
+ * is allowed to start but then the new quorum is lost, the leader will time out and
+ * we go to leader election.
+ */
+ @Test
+ public void testLeaderTimesoutOnNewQuorum() throws Exception {
+ qu = new QuorumUtil(1); // create 3 servers
+ qu.disableJMXTest = true;
+ qu.startAll();
+ ZooKeeper[] zkArr = ReconfigTest.createHandles(qu);
+
+ List leavingServers = new ArrayList();
+ leavingServers.add("3");
+ qu.shutdown(2);
+ try {
+ // Since we just shut down server 2, its still considered "synced"
+ // by the leader, which allows us to start the reconfig
+ // (PrepRequestProcessor checks that a quorum of the new
+ // config is synced before starting a reconfig).
+ // We try to remove server 3, which requires a quorum of {1,2,3}
+ // (we have that) and of {1,2}, but 2 is down so we won't get a
+ // quorum of new config ACKs.
+ zkArr[1].reconfig(null, leavingServers, null, -1, null);
+ Assert.fail("Reconfig should have failed since we don't have quorum of new config");
+ } catch (KeeperException.ConnectionLossException e) {
+ // We expect leader to lose quorum of proposed config and time out
+ } catch (Exception e) {
+ Assert.fail("Should have been ConnectionLossException!");
+ }
+
+ // The leader should time out and remaining servers should go into
+ // LOOKING state. A new leader won't be established since that
+ // would require completing the reconfig, which is not possible while
+ // 2 is down.
+ Assert.assertEquals(QuorumStats.Provider.LOOKING_STATE,
+ qu.getPeer(1).peer.getServerState());
+ Assert.assertEquals(QuorumStats.Provider.LOOKING_STATE,
+ qu.getPeer(3).peer.getServerState());
+ ReconfigTest.closeAllHandles(zkArr);
+ }
+
+ /*
+ * Converting an observer into a participant may sometimes fail with a
+ * NewConfigNoQuorum exception. This test-case demonstrates the scenario.
+ * Current configuration is (A, B, C, D), where A, B and C are participant
+ * and D is an observer. Suppose that B has crashed (or never booted). If a
+ * reconfiguration is submitted where D is said to become a participant, it
+ * will fail with NewConfigNoQuorum since in this configuration, a majority
+ * of voters in the new configuration (any 3 voters), must be connected and
+ * up-to-date with the leader. An observer cannot acknowledge the history
+ * prefix sent during reconfiguration, and therefore it does not count towards
+ * these 3 required servers and the reconfiguration will be aborted. In case
+ * this happens, a client can achieve the same task by two reconfig commands:
+ * first invoke a reconfig to remove D from the configuration and then invoke a
+ * second command to add it back as a participant (follower). During the
+ * intermediate state D is a non-voting follower and can ACK the state
+ * transfer performed during the second reconfig command.
+ */
+ @Test
+ public void testObserverToParticipantConversionFails() throws Exception {
+ ClientBase.setupTestEnv();
+
+ final int SERVER_COUNT = 4;
+ int[][] ports = ReconfigRecoveryTest.generatePorts(SERVER_COUNT);
+
+ // generate old config string
+ HashSet observers = new HashSet();
+ observers.add(3);
+ StringBuilder sb = ReconfigRecoveryTest.generateConfig(SERVER_COUNT, ports, observers);
+ String currentQuorumCfgSection = sb.toString();
+ String nextQuorumCfgSection = currentQuorumCfgSection.replace("observer", "participant");
+
+ MainThread mt[] = new MainThread[SERVER_COUNT];
+ ZooKeeper zk[] = new ZooKeeper[SERVER_COUNT];
+
+ // Server 0 stays down
+ for (int i = 1; i < SERVER_COUNT; i++) {
+ mt[i] = new MainThread(i, ports[i][2], currentQuorumCfgSection,
+ true, "100000000");
+ mt[i].start();
+ zk[i] = new ZooKeeper("127.0.0.1:" + ports[i][2],
+ ClientBase.CONNECTION_TIMEOUT, this);
+ }
+
+ for (int i = 1; i < SERVER_COUNT; i++) {
+ Assert.assertTrue("waiting for server " + i + " being up",
+ ClientBase.waitForServerUp("127.0.0.1:" + ports[i][2],
+ CONNECTION_TIMEOUT * 2));
+ }
+
+ try {
+ zk[1].reconfig("", "", nextQuorumCfgSection, -1, new Stat());
+ Assert.fail("Reconfig should have failed with NewConfigNoQuorum");
+ } catch (NewConfigNoQuorum e) {
+ // This is expected case since server 0 is down and 3 can't vote
+ // (observer in current role) and we need 3 votes from 0, 1, 2, 3,
+ } catch (Exception e) {
+ Assert.fail("Reconfig should have failed with NewConfigNoQuorum");
+ }
+ // In this scenario to change 3's role to participant we need to remove it first
+ ArrayList leavingServers = new ArrayList();
+ leavingServers.add("3");
+ ReconfigTest.reconfig(zk[1], null, leavingServers, null, -1);
+ ReconfigTest.testNormalOperation(zk[2], zk[3]);
+ ReconfigTest.testServerHasConfig(zk[3], null, leavingServers);
+
+ // Now we're adding it back as a participant and everything should work.
+ List newMembers = Arrays.asList(nextQuorumCfgSection.split("\n"));
+ ReconfigTest.reconfig(zk[1], null, null, newMembers, -1);
+ ReconfigTest.testNormalOperation(zk[2], zk[3]);
+ for (int i = 1; i < SERVER_COUNT; i++) {
+ ReconfigTest.testServerHasConfig(zk[i], newMembers, null);
+ }
+ for (int i = 1; i < SERVER_COUNT; i++) {
+ zk[i].close();
+ mt[i].shutdown();
+ }
+ }
+}
diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRecoveryTest.java b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRecoveryTest.java
index 8df184eebf7..790f244a089 100644
--- a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRecoveryTest.java
+++ b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRecoveryTest.java
@@ -548,7 +548,7 @@ public void testCurrentObserverIsParticipantInNewConfig() throws Exception {
/*
* Generates 3 ports per server
*/
- private int[][] generatePorts(int numServers) {
+ public static int[][] generatePorts(int numServers) {
int[][] ports = new int[numServers][];
for (int i = 0; i < numServers; i++) {
ports[i] = new int[3];
@@ -563,7 +563,7 @@ private int[][] generatePorts(int numServers) {
* Creates a configuration string for servers 0..numServers-1 Ids in
* observerIds correspond to observers, other ids are for participants.
*/
- private StringBuilder generateConfig(int numServers, int[][] ports,
+ public static StringBuilder generateConfig(int numServers, int[][] ports,
HashSet observerIds) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < numServers; i++) {
diff --git a/src/java/test/org/apache/zookeeper/server/quorum/StandaloneDisabledTest.java b/src/java/test/org/apache/zookeeper/server/quorum/StandaloneDisabledTest.java
index 06aa2299d24..4cc71b12758 100644
--- a/src/java/test/org/apache/zookeeper/server/quorum/StandaloneDisabledTest.java
+++ b/src/java/test/org/apache/zookeeper/server/quorum/StandaloneDisabledTest.java
@@ -70,7 +70,7 @@ public void startSingleServerTest() throws Exception {
LOG.info("Configuration after adding 2 followers:\n"
+ new String(zkHandles[leaderId].getConfig(this, new Stat())));
- //shutdown leader- quorum should still exist
+ //shutdown leader- quorum should still exist
shutDownServer(leaderId);
ReconfigTest.testNormalOperation(zkHandles[follower1], zkHandles[follower2]);
@@ -92,6 +92,18 @@ public void startSingleServerTest() throws Exception {
LOG.info("Configuration after removing leader and follower 1:\n"
+ new String(zkHandles[follower2].getConfig(this, new Stat())));
+ // Try to remove follower2, which is the only remaining server. This should fail.
+ reconfigServers.clear();
+ reconfigServers.add(Integer.toString(follower2));
+ try {
+ zkHandles[follower2].reconfig(null, reconfigServers, null, -1, new Stat());
+ Assert.fail("reconfig completed successfully even though there is no quorum up in new config!");
+ } catch (KeeperException.BadArgumentsException e) {
+ // This is expected.
+ } catch (Exception e) {
+ Assert.fail("Should have been BadArgumentsException!");
+ }
+
//Add two participants and change them to observers to check
//that we can reconfigure down to one participant with observers.
ArrayList observerStrings = new ArrayList();
@@ -240,4 +252,4 @@ public void startObserver() throws Exception {
Assert.assertFalse("Observer was able to start by itself!",
ClientBase.waitForServerUp("127.0.0.1:" + clientPort, CONNECTION_TIMEOUT));
}
-}
\ No newline at end of file
+}
diff --git a/src/java/test/org/apache/zookeeper/test/ReconfigTest.java b/src/java/test/org/apache/zookeeper/test/ReconfigTest.java
index 0bcd4d9b93e..8b238ee7463 100644
--- a/src/java/test/org/apache/zookeeper/test/ReconfigTest.java
+++ b/src/java/test/org/apache/zookeeper/test/ReconfigTest.java
@@ -171,7 +171,7 @@ private int getLeaderId(QuorumUtil qu) {
return leaderId;
}
- private ZooKeeper[] createHandles(QuorumUtil qu) throws IOException {
+ public static ZooKeeper[] createHandles(QuorumUtil qu) throws IOException {
// create an extra handle, so we can index the handles from 1 to qu.ALL
// using the server id.
ZooKeeper[] zkArr = new ZooKeeper[qu.ALL + 1];
@@ -187,7 +187,7 @@ public void process(WatchedEvent event) {
return zkArr;
}
- private void closeAllHandles(ZooKeeper[] zkArr) throws InterruptedException {
+ public static void closeAllHandles(ZooKeeper[] zkArr) throws InterruptedException {
for (ZooKeeper zk : zkArr)
if (zk != null)
zk.close();
@@ -408,43 +408,6 @@ public void testBulkReconfig() throws Exception {
closeAllHandles(zkArr);
}
- @Test
- public void testLeaderTimesoutOnNewQuorum() throws Exception {
- qu = new QuorumUtil(1); // create 3 servers
- qu.disableJMXTest = true;
- qu.startAll();
- ZooKeeper[] zkArr = createHandles(qu);
-
- List leavingServers = new ArrayList();
- leavingServers.add("3");
- qu.shutdown(2);
- try {
- // Since we just shut down server 2, its still considered "synced"
- // by the leader, which allows us to start the reconfig
- // (PrepRequestProcessor checks that a quorum of the new
- // config is synced before starting a reconfig).
- // We try to remove server 3, which requires a quorum of {1,2,3}
- // (we have that) and of {1,2}, but 2 is down so we won't get a
- // quorum of new config ACKs.
- zkArr[1].reconfig(null, leavingServers, null, -1, null);
- Assert.fail("Reconfig should have failed since we don't have quorum of new config");
- } catch (KeeperException.ConnectionLossException e) {
- // We expect leader to loose quorum of proposed config and time out
- } catch (Exception e) {
- Assert.fail("Should have been ConnectionLossException!");
- }
-
- // The leader should time out and remaining servers should go into
- // LOOKING state. A new leader won't be established since that
- // would require completing the reconfig, which is not possible while
- // 2 is down.
- Assert.assertEquals(QuorumStats.Provider.LOOKING_STATE,
- qu.getPeer(1).peer.getServerState());
- Assert.assertEquals(QuorumStats.Provider.LOOKING_STATE,
- qu.getPeer(3).peer.getServerState());
- closeAllHandles(zkArr);
- }
-
@Test
public void testRemoveOneAsynchronous() throws Exception {
qu = new QuorumUtil(2);
From dcd2d5b1ca17c5fcb84165b8f7eed247dd0fe45f Mon Sep 17 00:00:00 2001
From: Alexander Shraer
Date: Fri, 29 Aug 2014 14:36:46 +0000
Subject: [PATCH 012/279] ZOOKEEPER-1660 Documentation for Dynamic
Reconfiguration (Reed Wanderman-Milne via shralex)
git-svn-id: https://svn.apache.org/repos/asf/zookeeper/branches/branch-3.5@1621314 13f79535-47bb-0310-9956-ffa450edef68
---
CHANGES.txt | 2 +-
docs/index.html | 8 +-
docs/index.pdf | Bin 12376 -> 12664 bytes
docs/zookeeperAdmin.html | 17 +-
docs/zookeeperAdmin.pdf | Bin 81761 -> 82351 bytes
docs/zookeeperReconfig.html | 1098 +++++++++++++++++
docs/zookeeperReconfig.pdf | Bin 0 -> 53699 bytes
.../src/documentation/content/xdocs/index.xml | 1 +
.../src/documentation/content/xdocs/site.xml | 1 +
.../content/xdocs/zookeeperAdmin.xml | 8 +
.../content/xdocs/zookeeperReconfig.xml | 745 +++++++++++
11 files changed, 1877 insertions(+), 3 deletions(-)
create mode 100644 docs/zookeeperReconfig.html
create mode 100644 docs/zookeeperReconfig.pdf
create mode 100644 src/docs/src/documentation/content/xdocs/zookeeperReconfig.xml
diff --git a/CHANGES.txt b/CHANGES.txt
index 6f02f13ffd2..d424ff584cd 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -5,7 +5,7 @@ NEW FEATURES:
BUGFIXES:
IMPROVEMENTS:
-
+ ZOOKEEPER-1660 Documentation for Dynamic Reconfiguration (Reed Wanderman-Milne via shralex)
Release 3.5.0 - 8/4/2014
diff --git a/docs/index.html b/docs/index.html
index d3fe5aa1e3f..837fb551417 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -67,7 +67,7 @@
Wiki
Prior to the 3.5.0 release, the membership and all other configuration
+ parameters of Zookeeper were static - loaded during boot and immutable at
+ runtime. Operators resorted to ''rolling restarts'' - a manually intensive
+ and error-prone method of changing the configuration that has caused data
+ loss and inconsistency in production.
+
Starting with 3.5.0, “rolling restarts” are no longer needed!
+ ZooKeeper comes with full support for automated configuration changes: the
+ set of Zookeeper servers, their roles (participant / observer), all ports,
+ and even the quorum system can be changed dynamically, without service
+ interruption and while maintaining data consistency. Reconfigurations are
+ performed immediately, just like other operations in ZooKeeper. Multiple
+ changes can be done using a single reconfiguration command. The dynamic
+ reconfiguration functionality does not limit operation concurrency, does
+ not require client operations to be stopped during reconfigurations, has a
+ very simple interface for administrators and no added complexity to other
+ client operations.
+
New client-side features allow clients to find out about configuration
+ changes and to update the connection string (list of servers and their
+ client ports) stored in their ZooKeeper handle. A probabilistic algorithm
+ is used to rebalance clients across the new configuration servers while
+ keeping the extent of client migrations proportional to the change in
+ ensemble membership.
+
This document provides the administrator manual for reconfiguration.
+ For a detailed description of the reconfiguration algorithms, performance
+ measurements, and more, please see our paper:
+
+
+
+Shraer, A., Reed, B., Malkhi, D., Junqueira, F. Dynamic
+ Reconfiguration of Primary/Backup Clusters. In USENIX Annual
+ Technical Conference (ATC) (2012), 425-437
+
A client port of a server is the port on which the server accepts
+ client connection requests. Starting with 3.5.0 the
+ clientPort and clientPortAddress
+ configuration parameters should no longer be used. Instead,
+ this information is now part of the server keyword specification, which
+ becomes as follows:
+
+server.<positive id> = <address1>:<port1>:<port2>[:role];[<client port address>:]<client port>
+
+
The client port specification is to the right of the semicolon. The
+ client port address is optional, and if not specified it defaults to
+ "0.0.0.0". As usual, role is also optional, it can be
+ participant or observer
+ (participant by default).
Prior to 3.5.0, one could run ZooKeeper in Standalone mode or in a
+ Distributed mode. These are separate implementation stacks, and
+ switching between them during run time is not possible. By default (for
+ backward compatibility) standaloneEnabled is set to
+ true. The consequence of using this default is that
+ if started with a single server the ensemble will not be allowed to
+ grow, and if started with more than one server it will not be allowed to
+ shrink to contain fewer than two participants.
+
Setting the flag to false instructs the system
+ to run the Distributed software stack even if there is only a single
+ participant in the ensemble. To achieve this the (static) configuration
+ file should contain:
+
+standaloneEnabled=false
+
+
With this setting it is possible to start a ZooKeeper ensemble
+ containing a single participant and to dynamically grow it by adding
+ more servers. Similarly, it is possible to shrink an ensemble so that
+ just a single participant remains, by removing servers.
+
Since running the Distributed mode allows more flexibility, we
+ recommend setting the flag to false. We expect that
+ the legacy Standalone mode will be deprecated in the future.
+
+
Dynamic configuration file
+
Starting with 3.5.0 we're distinguishing between dynamic
+ configuration parameters, which can be changed during runtime, and
+ static configuration parameters, which are read from a configuration
+ file when a server boots and don't change during its execution. For now,
+ the following configuration keywords are considered part of the dynamic
+ configuration: server, group
+ and weight.
+
Dynamic configuration parameters are stored in a separate file on
+ the server (which we call the dynamic configuration file). This file is
+ linked from the static config file using the new
+ dynamicConfigFile keyword.
When the ensemble configuration changes, the static configuration
+ parameters remain the same. The dynamic parameters are pushed by
+ ZooKeeper and overwrite the dynamic configuration files on all servers.
+ Thus, the dynamic configuration files on the different servers are
+ usually identical (they can only differ momentarily when a
+ reconfiguration is in progress, or if a new configuration hasn't
+ propagated yet to some of the servers). Once created, the dynamic
+ configuration file should not be manually altered. Changed are only made
+ through the new reconfiguration commands outlined below. Note that
+ changing the config of an offline cluster could result in an
+ inconsistency with respect to configuration information stored in the
+ ZooKeeper log (and the special configuration znode, populated from the
+ log) and is therefore highly discouraged.
+
+Example 2
+
+
Users may prefer to initially specify a single configuration file.
+ The following is thus also legal:
+
+
zoo_replicated1.cfg
+
+
+zoo_replicated1.cfg
+
+
tickTime=2000
+dataDir=/zookeeper/data/zookeeper1
+initLimit=5
+syncLimit=2
+clientPort=2791 // note that this line is now redundant and therefore not recommended
+server.1=125.23.63.23:2780:2783:participant;2791
+server.2=125.23.63.24:2781:2784:participant;2792
+server.3=125.23.63.25:2782:2785:participant;2793
+
+
+
+
The configuration files on each server will be automatically split
+ into dynamic and static files, if they are not already in this format.
+ So the configuration file above will be automatically transformed into
+ the two files in Example 1. Note that the clientPort and
+ clientPortAddress lines (if specified) will be automatically removed
+ during this process, if they are redundant (as in the example above).
+ The original static configuration file is backed up (in a .bak
+ file).
+
+
Backward compatibility
+
We still support the old configuration format. For example, the
+ following configuration file is acceptable (but not recommended):
During boot, a dynamic configuration file is created and contains
+ the dynamic part of the configuration as explained earlier. In this
+ case, however, the line "clientPort=2791" will remain in the static
+ configuration file of server 1 since it is not redundant -- it was not
+ specified as part of the "server.1=..." using the format explained in
+ the section Changes to Configuration Format. If a reconfiguration
+ is invoked that sets the client port of server 1, we remove
+ "clientPort=2791" from the static configuration file (the dynamic file
+ now contain this information as part of the specification of server
+ 1).
+
+
+
+
Upgrading to 3.5.0
+
+
Upgrading a running ZooKeeper ensemble to 3.5.0 should be done only
+ after upgrading your ensemble to the 3.4.6 release. Note that this is only
+ necessary for rolling upgrades (if you're fine with shutting down the
+ system completely, you don't have to go through 3.4.6). If you attempt a
+ rolling upgrade without going through 3.4.6 (for example from 3.4.5), you
+ may get the following error:
+
2013-01-30 11:32:10,663 [myid:2] - INFO [localhost/127.0.0.1:2784:QuorumCnxManager$Listener@498] - Received connection request /127.0.0.1:60876
+2013-01-30 11:32:10,663 [myid:2] - WARN [localhost/127.0.0.1:2784:QuorumCnxManager@349] - Invalid server id: -65536
+
During a rolling upgrade, each server is taken down in turn and
+ rebooted with the new 3.5.0 binaries. Before starting the server with
+ 3.5.0 binaries, we highly recommend updating the configuration file so
+ that all server statements "server.x=..." contain client ports (see the
+ section Specifying the client port). As explained earlier
+ you may leave the configuration in a single file, as well as leave the
+ clientPort/clientPortAddress statements (although if you specify client
+ ports in the new format, these statements are now redundant).
+
+
+
+
Dynamic Reconfiguration of the ZooKeeper Ensemble
+
+
The ZooKeeper Java and C API were extended with getConfig and reconfig
+ commands that facilitate reconfiguration. Both commands have a synchronous
+ (blocking) variant and an asynchronous one. We demonstrate these commands
+ here using the Java CLI, but note that you can similarly use the C CLI or
+ invoke the commands directly from a program just like any other ZooKeeper
+ command.
+
+
Retrieving the current dynamic configuration
+
The dynamic configuration is stored in a special znode
+ ZooDefs.CONFIG_NODE = /zookeeper/config. The new
+ config CLI command reads this znode (currently it is
+ simply a wrapper to get /zookeeper/config). As with
+ normal reads, to retrieve the latest committed value you should do a
+ sync first.
Notice the last line of the output. This is the configuration
+ version. The version equals to the zxid of the reconfiguration command
+ which created this configuration. The version of the first established
+ configuration equals to the zxid of the NEWLEADER message sent by the
+ first successfully established leader. When a configuration is written
+ to a dynamic configuration file, the version automatically becomes part
+ of the filename and the static configuration file is updated with the
+ path to the new dynamic configuration file. Configuration files
+ corresponding to earlier versions are retained for backup
+ purposes.
+
During boot time the version (if it exists) is extracted from the
+ filename. The version should never be altered manually by users or the
+ system administrator. It is used by the system to know which
+ configuration is most up-to-date. Manipulating it manually can result in
+ data loss and inconsistency.
+
Just like a get command, the
+ config CLI command accepts the -w
+ flag for setting a watch on the znode, and -s flag for
+ displaying the Stats of the znode. It additionally accepts a new flag
+ -c which outputs only the version and the client
+ connection string corresponding to the current configuration. For
+ example, for the configuration above we would get:
Note that when using the API directly, this command is called
+ getConfig.
+
As any read command it returns the configuration known to the
+ follower to which your client is connected, which may be slightly
+ out-of-date. One can use the sync command for
+ stronger guarantees. For example using the Java API:
Note: in 3.5.0 it doesn't really matter which path is passed to the
+ sync() command as all the server's state is brought
+ up to date with the leader (so one could use a different path instead of
+ ZooDefs.CONFIG_NODE). However, this may change in the future.
+
+
Modifying the current dynamic configuration
+
Modifying the configuration is done through the
+ reconfig command. There are two modes of
+ reconfiguration: incremental and non-incremental (bulk). The
+ non-incremental simply specifies the new dynamic configuration of the
+ system. The incremental specifies changes to the current configuration.
+ The reconfig command returns the new
+ configuration.
+
A few examples are in: ReconfigTest.java,
+ ReconfigRecoveryTest.java and
+ TestReconfigServer.cc.
+
+
General
+
+Removing servers: Any server can
+ be removed, including the leader (although removing the leader will
+ result in a short unavailability, see Figures 6 and 8 in the paper). The server will not be shut-down automatically.
+ Instead, it becomes a "non-voting follower". This is somewhat similar
+ to an observer in that its votes don't count towards the Quorum of
+ votes necessary to commit operations. However, unlike a non-voting
+ follower, an observer doesn't actually see any operation proposals and
+ does not ACK them. Thus a non-voting follower has a more significant
+ negative effect on system throughput compared to an observer.
+ Non-voting follower mode should only be used as a temporary mode,
+ before shutting the server down, or adding it as a follower or as an
+ observer to the ensemble. We do not shut the server down automatically
+ for two main reasons. The first reason is that we do not want all the
+ clients connected to this server to be immediately disconnected,
+ causing a flood of connection requests to other servers. Instead, it
+ is better if each client decides when to migrate independently. The
+ second reason is that removing a server may sometimes (rarely) be
+ necessary in order to change it from "observer" to "participant" (this
+ is explained in the section Additional comments).
+
Note that the new configuration should have some minimal number of
+ participants in order to be considered legal. If the proposed change
+ would leave the cluster with less than 2 participants and standalone
+ mode is enabled (standaloneEnabled=true, see the section The standaloneEnabled flag), the reconfig will not be
+ processed (BadArgumentsException). If standalone mode is disabled
+ (standaloneEnabled=false) then its legal to remain with 1 or more
+ participants.
+
+Adding servers: Before a
+ reconfiguration is invoked, the administrator must make sure that a
+ quorum (majority) of participants from the new configuration are
+ already connected and synced with the current leader. To achieve this
+ we need to connect a new joining server to the leader before it is
+ officially part of the ensemble. This is done by starting the joining
+ server using an initial list of servers which is technically not a
+ legal configuration of the system but (a) contains the joiner, and (b)
+ gives sufficient information to the joiner in order for it to find and
+ connect to the current leader. We list a few different options of
+ doing this safely.
+
+
+
+
+
Initial configuration of joiners is comprised of servers in
+ the last committed configuration and one or more joiners, where
+ joiners are listed as observers.
+ For example, if servers D and E are added at the same time to (A,
+ B, C) and server C is being removed, the initial configuration of
+ D could be (A, B, C, D) or (A, B, C, D, E), where D and E are
+ listed as observers. Similarly, the configuration of E could be
+ (A, B, C, E) or (A, B, C, D, E), where D and E are listed as
+ observers. Note that listing the joiners as
+ observers will not actually make them observers - it will only
+ prevent them from accidentally forming a quorum with other
+ joiners. Instead, they will contact the servers in the
+ current configuration and adopt the last committed configuration
+ (A, B, C), where the joiners are absent. Configuration files of
+ joiners are backed up and replaced automatically as this happens.
+ After connecting to the current leader, joiners become non-voting
+ followers until the system is reconfigured and they are added to
+ the ensemble (as participant or observer, as appropriate).
+
+
+
+
+
+
Initial configuration of each joiner is comprised of servers
+ in the last committed configuration + the
+ joiner itself, listed as a participant. For example, to
+ add a new server D to a configuration consisting of servers (A, B,
+ C), the administrator can start D using an initial configuration
+ file consisting of servers (A, B, C, D). If both D and E are added
+ at the same time to (A, B, C), the initial configuration of D
+ could be (A, B, C, D) and the configuration of E could be (A, B,
+ C, E). Similarly, if D is added and C is removed at the same time,
+ the initial configuration of D could be (A, B, C, D). Never list
+ more than one joiner as participant in the initial configuration
+ (see warning below).
+
+
+
+
+
+
Whether listing the joiner as an observer or as participant,
+ it is also fine not to list all the current configuration servers,
+ as long as the current leader is in the list. For example, when
+ adding D we could start D with a configuration file consisting of
+ just (A, D) if A is the current leader. however this is more
+ fragile since if A fails before D officially joins the ensemble, D
+ doesn’t know anyone else and therefore the administrator will have
+ to intervene and restart D with another server list.
+
+
+
+
+
+
Warning
+
+
+Warning
+
+
Never specify more than one joining server in the same initial
+ configuration as participants. Currently, the joining servers don’t
+ know that they are joining an existing ensemble; if multiple joiners
+ are listed as participants they may form an independent quorum
+ creating a split-brain situation such as processing operations
+ independently from your main ensemble. It is OK to list multiple
+ joiners as observers in an initial config.
+
+
+
+
Finally, note that once connected to the leader, a joiner adopts
+ the last committed configuration, in which it is absent (the initial
+ config of the joiner is backed up before being rewritten). If the
+ joiner restarts in this state, it will not be able to boot since it is
+ absent from its configuration file. In order to start it you’ll once
+ again have to specify an initial configuration.
+
+Modifying server parameters: One
+ can modify any of the ports of a server, or its role
+ (participant/observer) by adding it to the ensemble with different
+ parameters. This works in both the incremental and the bulk
+ reconfiguration modes. It is not necessary to remove the server and
+ then add it back; just specify the new parameters as if the server is
+ not yet in the system. The server will detect the configuration change
+ and perform the necessary adjustments. See an example in the section
+ Incremental mode and an exception to this
+ rule in the section Additional comments.
+
It is also possible to change the Quorum System used by the
+ ensemble (for example, change the Majority Quorum System to a
+ Hierarchical Quorum System on the fly). This, however, is only allowed
+ using the bulk (non-incremental) reconfiguration mode. In general,
+ incremental reconfiguration only works with the Majority Quorum
+ System. Bulk reconfiguration works with both Hierarchical and Majority
+ Quorum Systems.
+
+Performance Impact: There is
+ practically no performance impact when removing a follower, since it
+ is not being automatically shut down (the effect of removal is that
+ the server's votes are no longer being counted). When adding a server,
+ there is no leader change and no noticeable performance disruption.
+ For details and graphs please see Figures 6, 7 and 8 in the paper.
+
The most significant disruption will happen when a leader change
+ is caused, in one of the following cases:
+
+
+
+
+
Leader is removed from the ensemble.
+
+
+
+
+
+
Leader's role is changed from participant to observer.
+
+
+
+
+
+
The port used by the leader to send transactions to others
+ (quorum port) is modified.
+
+
+
+
+
In these cases we perform a leader hand-off where the old leader
+ nominates a new leader. The resulting unavailability is usually
+ shorter than when a leader crashes since detecting leader failure is
+ unnecessary and electing a new leader can usually be avoided during a
+ hand-off (see Figures 6 and 8 in the paper).
+
When the client port of a server is modified, it does not drop
+ existing client connections. New connections to the server will have
+ to use the new client port.
+
+Progress guarantees: Up to the
+ invocation of the reconfig operation, a quorum of the old
+ configuration is required to be available and connected for ZooKeeper
+ to be able to make progress. Once reconfig is invoked, a quorum of
+ both the old and of the new configurations must be available. The
+ final transition happens once (a) the new configuration is activated,
+ and (b) all operations scheduled before the new configuration is
+ activated by the leader are committed. Once (a) and (b) happen, only a
+ quorum of the new configuration is required. Note, however, that
+ neither (a) nor (b) are visible to a client. Specifically, when a
+ reconfiguration operation commits, it only means that an activation
+ message was sent out by the leader. It does not necessarily mean that
+ a quorum of the new configuration got this message (which is required
+ in order to activate it) or that (b) has happened. If one wants to
+ make sure that both (a) and (b) has already occurred (for example, in
+ order to know that it is safe to shut down old servers that were
+ removed), one can simply invoke an update
+ (set-data, or some other quorum operation, but not
+ a sync) and wait for it to commit. An alternative
+ way to achieve this was to introduce another round to the
+ reconfiguration protocol (which, for simplicity and compatibility with
+ Zab, we decided to avoid).
+
+
Incremental mode
+
The incremental mode allows adding and removing servers to the
+ current configuration. Multiple changes are allowed. For
+ example:
The format of the server statement is exactly the same as
+ described in the section Specifying the client port and
+ includes the client port. Notice that here instead of "server.5=" you
+ can just say "5=". In the example above, if server 5 is already in the
+ system, but has different ports or is not an observer, it is updated
+ and once the configuration commits becomes an observer and starts
+ using these new ports. This is an easy way to turn participants into
+ observers and vise versa or change any of their ports, without
+ rebooting the server.
+
ZooKeeper supports two types of Quorum Systems – the simple
+ Majority system (where the leader commits operations after receiving
+ ACKs from a majority of voters) and a more complex Hierarchical
+ system, where votes of different servers have different weights and
+ servers are divided into voting groups. Currently, incremental
+ reconfiguration is allowed only if the last proposed configuration
+ known to the leader uses a Majority Quorum System
+ (BadArgumentsException is thrown otherwise).
+
Incremental mode - examples using the Java API:
+
List<String> leavingServers = new ArrayList<String>();
+leavingServers.add("1");
+leavingServers.add("2");
+byte[] config = zk.reconfig(null, leavingServers, null, -1, new Stat());
+
List<String> leavingServers = new ArrayList<String>();
+List<String> joiningServers = new ArrayList<String>();
+leavingServers.add("1");
+joiningServers.add("server.4=localhost:1234:1235;1236");
+byte[] config = zk.reconfig(joiningServers, leavingServers, null, -1, new Stat());
+
+String configStr = new String(config);
+System.out.println(configStr);
+
There is also an asynchronous API, and an API accepting comma
+ separated Strings instead of List<String>. See
+ src/java/main/org/apache/zookeeper/ZooKeeper.java.
+
+
Non-incremental mode
+
The second mode of reconfiguration is non-incremental, whereby a
+ client gives a complete specification of the new dynamic system
+ configuration. The new configuration can either be given in place or
+ read from a file:
+
+> reconfig -file newconfig.cfg
+ //newconfig.cfg is a dynamic config file, see Dynamic configuration file
+
The new configuration may use a different Quorum System. For
+ example, you may specify a Hierarchical Quorum System even if the
+ current ensemble uses a Majority Quorum System.
+
Bulk mode - example using the Java API:
+
ArrayList<String> newMembers = new ArrayList<String>();
+newMembers.add("server.1=1111:1234:1235;1236");
+newMembers.add("server.2=1112:1237:1238;1239");
+newMembers.add("server.3=1114:1240:1241:observer;1242");
+
+byte[] config = zk.reconfig(null, null, newMembers, -1, new Stat());
+
+String configStr = new String(config);
+System.out.println(configStr);
+
There is also an asynchronous API, and an API accepting comma
+ separated String containing the new members instead of
+ List<String>. See
+ src/java/main/org/apache/zookeeper/ZooKeeper.java.
+
+
Conditional reconfig
+
Sometimes (especially in non-incremental mode) a new proposed
+ configuration depends on what the client "believes" to be the current
+ configuration, and should be applied only to that configuration.
+ Specifically, the reconfig succeeds only if the
+ last configuration at the leader has the specified version.
+
+> reconfig -file <filename> -v <version>
+
+
In the previously listed Java examples, instead of -1 one could
+ specify a configuration version to condition the
+ reconfiguration.
+
+
Error conditions
+
In addition to normal ZooKeeper error conditions, a
+ reconfiguration may fail for the following reasons:
+
+
+
+
+
another reconfig is currently in progress
+ (ReconfigInProgress)
+
+
+
+
+
+
the proposed change would leave the cluster with less than 2
+ participants, in case standalone mode is enabled, or, if
+ standalone mode is disabled then its legal to remain with 1 or
+ more participants (BadArgumentsException)
+
+
+
+
+
+
no quorum of the new configuration was connected and
+ up-to-date with the leader when the reconfiguration processing
+ began (NewConfigNoQuorum)
+
+
+
+
+
+
+-v x was specified, but the version
+ y of the latest configuration is not
+ x (BadVersionException)
+
+
+
+
+
+
an incremental reconfiguration was requested but the last
+ configuration at the leader uses a Quorum System which is
+ different from the Majority system (BadArgumentsException)
+
+
+
+
+
+
syntax error (BadArgumentsException)
+
+
+
+
+
+
I/O exception when reading the configuration from a file
+ (BadArgumentsException)
+
+
+
+
+
Most of these are illustrated by test-cases in
+ ReconfigFailureCases.java.
+
+
Additional comments
+
+Liveness: To better understand
+ the difference between incremental and non-incremental
+ reconfiguration, suppose that client C1 adds server D to the system
+ while a different client C2 adds server E. With the non-incremental
+ mode, each client would first invoke config to find
+ out the current configuration, and then locally create a new list of
+ servers by adding its own suggested server. The new configuration can
+ then be submitted using the non-incremental
+ reconfig command. After both reconfigurations
+ complete, only one of E or D will be added (not both), depending on
+ which client's request arrives second to the leader, overwriting the
+ previous configuration. The other client can repeat the process until
+ its change takes effect. This method guarantees system-wide progress
+ (i.e., for one of the clients), but does not ensure that every client
+ succeeds. To have more control C2 may request to only execute the
+ reconfiguration in case the version of the current configuration
+ hasn't changed, as explained in the section Conditional reconfig. In this way it may avoid blindly
+ overwriting the configuration of C1 if C1's configuration reached the
+ leader first.
+
With incremental reconfiguration, both changes will take effect as
+ they are simply applied by the leader one after the other to the
+ current configuration, whatever that is (assuming that the second
+ reconfig request reaches the leader after it sends a commit message
+ for the first reconfig request -- currently the leader will refuse to
+ propose a reconfiguration if another one is already pending). Since
+ both clients are guaranteed to make progress, this method guarantees
+ stronger liveness. In practice, multiple concurrent reconfigurations
+ are probably rare. Non-incremental reconfiguration is currently the
+ only way to dynamically change the Quorum System. Incremental
+ configuration is currently only allowed with the Majority Quorum
+ System.
+
+Changing an observer into a
+ follower: Clearly, changing a server that participates in
+ voting into an observer may fail if error (2) occurs, i.e., if fewer
+ than the minimal allowed number of participants would remain. However,
+ converting an observer into a participant may sometimes fail for a
+ more subtle reason: Suppose, for example, that the current
+ configuration is (A, B, C, D), where A is the leader, B and C are
+ followers and D is an observer. In addition, suppose that B has
+ crashed. If a reconfiguration is submitted where D is said to become a
+ follower, it will fail with error (3) since in this configuration, a
+ majority of voters in the new configuration (any 3 voters), must be
+ connected and up-to-date with the leader. An observer cannot
+ acknowledge the history prefix sent during reconfiguration, and
+ therefore it does not count towards these 3 required servers and the
+ reconfiguration will be aborted. In case this happens, a client can
+ achieve the same task by two reconfig commands: first invoke a
+ reconfig to remove D from the configuration and then invoke a second
+ command to add it back as a participant (follower). During the
+ intermediate state D is a non-voting follower and can ACK the state
+ transfer performed during the second reconfig comand.
+
+
+
+
Rebalancing Client Connections
+
+
When a ZooKeeper cluster is started, if each client is given the same
+ connection string (list of servers), the client will randomly choose a
+ server in the list to connect to, which makes the expected number of
+ client connections per server the same for each of the servers. We
+ implemented a method that preserves this property when the set of servers
+ changes through reconfiguration. See Sections 4 and 5.1 in the paper.
+
In order for the method to work, all clients must subscribe to
+ configuration changes (by setting a watch on /zookeeper/config either
+ directly or through the getConfig API command). When
+ the watch is triggered, the client should read the new configuration by
+ invoking sync and getConfig and if
+ the configuration is indeed new invoke the
+ updateServerList API command. To avoid mass client
+ migration at the same time, it is better to have each client sleep a
+ random short period of time before invoking
+ updateServerList.
+
A few examples can be found in:
+ StaticHostProviderTest.java and
+ TestReconfig.cc
+
+
Example (this is not a recipe, but a simplified example just to
+ explain the general idea):
+
+public void process(WatchedEvent event) {
+ synchronized (this) {
+ if (event.getType() == EventType.None) {
+ connected = (event.getState() == KeeperState.SyncConnected);
+ notifyAll();
+ } else if (event.getPath()!=null && event.getPath().equals(ZooDefs.CONFIG_NODE)) {
+ // in prod code never block the event thread!
+ zk.sync(ZooDefs.CONFIG_NODE, this, null);
+ zk.getConfig(this, this, null);
+ }
+ }
+}
+public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
+ if (path!=null && path.equals(ZooDefs.CONFIG_NODE)) {
+ String config[] = ConfigUtils.getClientConfigStr(new String(data)).split(" "); // similar to config -c
+ long version = Long.parseLong(config[0], 16);
+ if (this.configVersion == null){
+ this.configVersion = version;
+ } else if (version > this.configVersion) {
+ hostList = config[1];
+ try {
+ // the following command is not blocking but may cause the client to close the socket and
+ // migrate to a different server. In practice its better to wait a short period of time, chosen
+ // randomly, so that different clients migrate at different times
+ zk.updateServerList(hostList);
+ } catch (IOException e) {
+ System.err.println("Error updating server list");
+ e.printStackTrace();
+ }
+ this.configVersion = version;
+} } }