diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..af960483d22 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,19 @@ +# Auto detect text files and perform LF normalization +* text=auto + +*.cs text diff=csharp +*.java text diff=java +*.html text diff=html +*.py text diff=python +*.pl text diff=perl +*.pm text diff=perl +*.css text +*.js text +*.sql text + +*.sh text eol=lf + +*.bat text eol=crlf +*.cmd text eol=crlf +*.csproj text merge=union eol=crlf +*.sln text merge=union eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..0cbf40d712d --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +# Java +hs_err_pid* + +# Git +*.orij +*.rej + +# SVN +.svn +.revision + +# Eclipse +.metadata +.classpath +.eclipse/ +.idea/ +.project +.revision/ +.settings/ +.externalToolBuilders/ +local.properties +.recommenders +*.launch +build/ +target/ +out/ + +# Intellij +*.ipr +*.iws +*.iml + +# NetBeans +*~.nib +nbbuild/ +dist/ +nbdist/ +.ndb-gradle/ +.cproject +.buildpath + +# Other +*~ +logs/ +*.out +*.log +*.bak +*.tmp +tmp/** +tmp/**/* +.DS_Store +.gradle +*.patch +*.swp +generated + +# C +tags +.cproject +.project +obj +src/c/core.* +src/c/TEST-*.txt +src/c/*.la +src/c/*.lo +src/c/*.o +src/c/generated/ +src/java/generated/ +src/java/lib/ant-eclipse-* +src/java/lib/ivy-* +src/c/Makefile.in +src/c/aclocal.m4 +src/c/autom4te.cache/ +src/c/compile +src/c/config.guess +src/c/config.h.in +src/c/config.sub +src/c/configure +src/c/depcomp +src/c/install-sh +src/c/ltmain.sh +src/c/missing diff --git a/CHANGES.txt b/CHANGES.txt deleted file mode 100644 index e391415ddc9..00000000000 --- a/CHANGES.txt +++ /dev/null @@ -1,1675 +0,0 @@ -Trunk - -Non-backward compatible changes: - -BUGFIXES: - -Backward compatible changes: - -BUGFIXES: - - ZOOKEEPER-735. cppunit test testipv6 assumes that the machine is ipv6 - enabled. (mahadev) - - ZOOKEEPER-720. Use zookeeper-{version}-sources.jar instead of - zookeeper-{version}-src.jar to publish sources in the Maven repository - (paolo via phunt) - - ZOOKEEPER-722. zkServer.sh uses sh's builtin echo on BSD, behaves - incorrectly. (Ivan Kelly via phunt) - - ZOOKEEPER-741. root level create on REST proxy fails (phunt) - - ZOOKEEPER-631. zkpython's C code could do with a style clean-up - (henry robinson via phunt) - - ZOOKEEPER-746. learner outputs session id to log in dec (phunt via - henryr) - - ZOOKEEPER-738. zookeeper.jute.h fails to compile with -pedantic - (Jozef Hatala via phunt) - - ZOOKEEPER-734. QuorumPeerTestBase.java and ZooKeeperServerMainTest.java - do not handle windows path correctly (Vishal K via phunt) - - ZOOKEEPER-754. numerous misspellings "succesfully" - (Andrei Savu via phunt) - - ZOOKEEPER-749. OSGi metadata not included in binary only jar (phunt - via henryr) - - ZOOKEEPER-750. move maven artifacts into "dist-maven" subdir of the - release (package target) (phunt via henryr) - - ZOOKEEPER-758. zkpython segfaults on invalid acl with missing key - (Kapil Thangavelu via henryr) - - ZOOKEEPER-737. some 4 letter words may fail with netcat (nc). (mahadev) - - ZOOKEEPER-764. Observer elected leader due to inconsistent voting view - (henry via mahadev) - - ZOOKEEPER-763. Deadlock on close w/ zkpython / c client - (henry via phunt) - - ZOOKEEPER-774. Recipes tests are slightly outdated: they do not compile - against JUnit 4.8 (Sergey Doroshenko via phunt) - - ZOOKEEPER-772. zkpython segfaults when watcher from async get children is - invoked. (henry via phunt) - - ZOOKEEPER-636. configure.ac has instructions which override the contents of - CFLAGS and CXXFLAGS. (Maxim P. Dementiev via phunt) - - ZOOKEEPER-796. zkServer.sh should support an external PIDFILE variable - (Alex Newman via phunt) - - ZOOKEEPER-719. Add throttling to BookKeeper client (fpj via breed) - - ZOOKEEPER-814. monitoring scripts are missing apache license headers - (andrei savu via mahadev) - - ZOOKEEPER-783. committedLog in ZKDatabase is not properly synchronized - (henry via mahadev) - - ZOOKEEPER-790. Last processed zxid set prematurely while establishing - leadership (flavio via mahadev) - - ZOOKEEPER-795. eventThread isn't shutdown after a connection - "session expired" event coming (Sergey Doroshenko and Ben via mahadev) - - ZOOKEEPER-792. zkpython memory leak (Lei Zhang via henryr) - - ZOOKEEPER-854. BookKeeper does not compile due to changes in the ZooKeeper - code (Flavio via mahadev) - - ZOOKEEPER-861. Missing the test SSL certificate used for running junit tests. - (erwin tam via mahadev) - - ZOOKEEPER-867. ClientTest is failing on hudson - fd cleanup (phunt) - - ZOOKEEPER-785. Zookeeper 3.3.1 shouldn't infinite loop if someone creates a - server.0 line (phunt and Andrei Savu via breed) - - ZOOKEEPER-785. Zookeeper 3.3.1 shouldn't infinite loop if someone creates a - server.0 line (part 2) (phunt) - - ZOOKEEPER-870. Zookeeper trunk build broken. (mahadev via phunt) - - ZOOKEEPER-831. BookKeeper: Throttling improved for reads (breed via fpj) - - ZOOKEEPER-846. zookeeper client doesn't shut down cleanly on the close call - (phunt) - - ZOOKEEPER-804. c unit tests failing due to "assertion cptr failed" (michi - mutsuzaki via mahadev) - - ZOOKEEPER-844. handle auth failure in java client - (Camille Fournier via phunt) - - ZOOKEEPER-822. Leader election taking a long time to complete - (Vishal K via phunt) - - ZOOKEEPER-866. Hedwig Server stays in "disconnected" state when - connection to ZK dies but gets reconnected (erwin tam via breed) - - ZOOKEEPER-881. ZooKeeperServer.loadData loads database twice - (jared cantwell via breed) - - ZOOKEEPER-855. clientPortBindAddress should be clientPortAddress - (Jared Cantwell via fpj) - - ZOOKEEPER-888. c-client / zkpython: Double free corruption on - node watcher (Austin Shoemaker via henryr) - - ZOOKEEPER-893. ZooKeeper high cpu usage when invalid requests - (Thijs Terlouw via phunt) - - ZOOKEEPER-804. c unit tests failing due to "assertion cptr failed" - (second try - Jared Cantwell via phunt) - - ZOOKEEPER-820. update c unit tests to ensure "zombie" java server - processes don't cause failure (Michi Mutsuzaki via phunt) - - ZOOKEEPER-794. Callbacks are not invoked when the client is closed - (Alexis Midon via phunt) - - ZOOKEEPER-800. zoo_add_auth returns ZOK if zookeeper handle is in - ZOO_CLOSED_STATE (michi mutsuzaki via mahadev konar) - - ZOOKEEPER-904. super digest is not actually acting as a full superuser - (Camille Fournier via mahadev) - - ZOOKEEPER-897. C Client seg faults during close (jared cantwell via mahadev) - - ZOOKEEPER-898. C Client might not cleanup correctly during close - (jared cantwell via mahadev) - - ZOOKEEPER-907. Spurious "KeeperErrorCode = Session moved" messages (vishal k via breed) - - ZOOKEEPER-884. Remove LedgerSequence references from BookKeeper documentation and comments in tests (fpj via breed) - - ZOOKEEPER-916. Problem receiving messages from subscribed channels in c++ client (ivan via breed) - - ZOOKEEPER-930. Hedwig c++ client uses a non thread safe logging library (ivan via breed) - - ZOOKEEPER-900. FLE implementation should be improved to use non-blocking sockets (vishal via fpj) - - ZOOKEEPER-937. test -e not available on solaris /bin/sh (Erik Hetzner via mahadev) - - ZOOKEEPER-905. enhance zkServer.sh for easier zookeeper automation-izing (Nicholas Harteau via mahadev) - - ZOOKEEPER-913. Version parser fails to parse "3.3.2-dev" from build.xml (Anthony Urso and phunt via breed) - - ZOOKEEPER-957. zkCleanup.sh doesn't do anything (Ted Dunning via mahadev) - - ZOOKEEPER-958. Flag to turn off autoconsume in hedwig c++ client (Ivan Kelly - via mahadev) - - ZOOKEEPER-882. Startup loads last transaction from snapshot (j:ared via fpj) - - ZOOKEEPER-962. leader/follower coherence issue when follower is receiving a DIFF - (camille fournier via breed) - - ZOOKEEPER-902. Fix findbug issue in trunk "Malicious code vulnerability" - (flavio and phunt via phunt) - - ZOOKEEPER-985. Test BookieRecoveryTest fails on trunk. (fpj via breed) - - ZOOKEEPER-983. running zkServer.sh start remotely using ssh hangs (phunt) - - ZOOKEEPER-976. ZooKeeper startup script doesn't use JAVA_HOME (phunt) - - ZOOKEEPER-994 "eclipse" target in the build script doesnot include - libraray required for test classes in the classpath (MIS via phunt) - - ZOOKEEPER-1013 zkServer.sh usage message should mention all startup options - (eugene koontz via mahadev) - - ZOOKEEPER-1007. iarchive leak in C client (jeremy stribling via mahadev) - - ZOOKEEPER-993. Code improvements (MIS via fpj) - - ZOOKEEPER-1012. support distinct JVMFLAGS for zookeeper server in zkServer.sh - and zookeeper client in zkCli.sh (Eugene Koontz via breed) - - ZOOKEEPER-880. QuorumCnxManager$SendWorker grows without bounds (vishal via breed) - - ZOOKEEPER-1018. The connection permutation in get_addrs uses a weak and inefficient - shuffle (Stephen Tyree via breed) - - ZOOKEEPER-1028. In python bindings, zookeeper.set2() should return a stat dict but - instead returns None. (Chris Medaglia and Ivan Kelly via mahadev) - - ZOOKEEPER-975. new peer goes in LEADING state even if ensemble is online. (vishal via fpj) - - ZOOKEEPER-1049. Session expire/close flooding renders heartbeats to delay significantly. - (chang song via mahadev) - - ZOOKEEPER-1033. c client should install includes into INCDIR/zookeeper, not INCDIR/c-client-src - (Nicholas Harteau via mahadev) - - ZOOKEEPER-1061. Zookeeper stop fails if start called twice. (Ted Dunning via mahadev) - - ZOOKEEPER-1059. stat command isses on non-existing node causes NPE. (Bhallamudi Kamesh via mahadev) - - ZOOKEEPER-1058. fix typo in opToString for getData. (camille) - - ZOOKEEPER-1046. Creating a new sequential node results in a ZNODEEXISTS error. (Vishal K via camille) - - ZOOKEEPER-1069. Calling shutdown() on a QuorumPeer too quickly can lead to a corrupt log. (Vishal K via camille) - - ZOOKEEPER-1083. Javadoc for WatchedEvent not being generated. (Ivan Kelly via michim) - - ZOOKEEPER-1086. zookeeper test jar has non mavenised dependency. (Ivan Kelly via michim) - - ZOOKEEPER-335. zookeeper servers should commit the new leader txn to their logs. (breed) - - ZOOKEEPER-1081. modify leader/follower code to correctly deal with new leader (breed) - - ZOOKEEPER-1082. modify leader election to correctly take into account current epoch (fpj via breed) - - ZOOKEEPER-1060. QuorumPeer takes a long time to shutdown (Vishal via fpj) - - ZOOKEEPER-1087. ForceSync VM arguement not working when set to "no" (Nate Putnam via breed) - - ZOOKEEPER-1068. Documentation and default config suggest incorrect - location for Zookeeper state (Roman Shaposhnik via phunt) - - ZOOKEEPER-1103. In QuorumTest, use the same "for ( .. try { break } - catch { } )" pattern in testFollowersStartAfterLeaders as in - testSessionMove. (Eugene Koontz via phunt) - ZOOKEEPER-1046. Creating a new sequential node results in a ZNODEEXISTS error. (breed via camille) - - ZOOKEEPER-1097. Quota is not correctly rehydrated on snapshot reload (camille via henryr) - - ZOOKEEPER-1046. Small fix: Creating a new sequential node results in a ZNODEEXISTS error. (Vishal K via camille) - - ZOOKEEPER-782. Incorrect C API documentation for Watches. (mahadev via breed) - - ZOOKEEPER-1063. Dubious synchronization in Zookeeper and ClientCnxnSocketNIO classes (Yanick Dufresne via breed) - - ZOOKEEPER-1124. Multiop submitted to non-leader always fails due to timeout (Marshall McMullen via breed) - - ZOOKEEPER-1111. JMXEnv uses System.err instead of logging - (Ivan Kelly via phunt) - - ZOOKEEPER-1027. chroot not transparent in zoo_create() (Thijs Terlouw via - mahadev) - - ZOOKEEPER-1109. Zookeeper service is down when SyncRequestProcessor meets - any exception. (Laxman via mahadev) - - ZOOKEEPER-1134. ClientCnxnSocket string comparison using == rather than equals. - (phunt via mahadev) - - ZOOKEEPER-1119. zkServer stop command incorrectly reading comment lines in - zoo.cfg (phunt via mahadev) - - ZOOKEEPER-1090. Race condition while taking snapshot can lead to not restoring data tree correctly (Vishal K via breed) - - ZOOKEEPER-1138. release audit failing for a number of new files. (phunt via mahadev) - - ZOOKEEPER-1139. jenkins is reporting two warnings, fix these (phunt via mahadev) - - ZOOKEEPER-1142. incorrect stat output (phunt via mahadev) - - ZOOKEEPER-1144. ZooKeeperServer not starting on leader due to a race condition (Vishal K via camille) - - ZOOKEEPER-839. deleteRecursive does not belong to the other methods. - (mahadev) - - ZOOKEEPER-1146. significant regression in client (c/python) performance. - (phunt via mahadev) - - ZOOKEEPER-1150. fix for this patch to compile on windows. (dheeraj - via mahadev) - - ZOOKEEPER-1055. check for duplicate ACLs in addACL() and create(). - (Eugene Koontz via mahadev) - - ZOOKEEPER-1141. zkpython fails tests under python 2.4. (phunt via mahadev) - - ZOOKEEPER-1025. zkCli is overly sensitive to to spaces. (Laxman via camille) - - ZOOKEEPER-1117. zookeeper 3.3.3 fails to build with gcc >= 4.6.1 on - Debian/Ubuntu (James Page via mahadev) - - ZOOKEEPER-1140. server shutdown is not stopping threads. (laxman via mahadev) - - ZOOKEEPER-1051. SIGPIPE in Zookeeper 0.3.* when send'ing after - cluster disconnection (Stephen Tyree via mahadev) - - ZOOKEEPER-1168. ZooKeeper fails to run with IKVM (Andrew Finnell via phunt) - - ZOOKEEPER-1165. better eclipse support in tests (Warren Turkal via phunt) - - ZOOKEEPER-1154. Data inconsistency when the node(s) with the highest zxid is not present at the time of leader election. (Vishal Kathuria via camille) - - ZOOKEEPER-1156. Log truncation truncating log too much - can cause data loss. (Vishal Kathuria via camille) - - ZOOKEEPER-1160. test timeouts are too small (breed via phunt) - - ZOOKEEPER-731. Zookeeper#delete , #create - async versions miss a verb in the javadoc. (Thomas Koch via camille) - - ZOOKEEPER-1108. Various bugs in zoo_add_auth in C. (Dheeraj Agrawal via mahadev) - - ZOOKEEPER-981. Hang in zookeeper_close() in the multi-threaded C client. - (Jeremy Stribling via mahadev) - - ZOOKEEPER-1125. Intermittent java core test failures. (Vishar Kher via mahadev) - - ZOOKEEPER-961. Watch recovery after disconnection when connection string contains a prefix. - (Matthias Spycher via mahadev) - - ZOOKEEPER-1136. NEW_LEADER should be queued not sent to match the Zab 1.0 protocol - on the twiki (breed via mahadev) - -IMPROVEMENTS: - ZOOKEEPER-724. Improve junit test integration - log harness information - (phunt via mahadev) - - ZOOKEEPER-766. forrest recipes docs don't mention the lock/queue recipe - implementations available in the release (phunt via mahadev) - - ZOOKEEPER-769: Leader can treat observers as quorum members - (Sergey Doroshenko via henryr) - - ZOOKEEPER-788: Add server id to message logs - (Ivan Kelly via flavio) - - ZOOKEEPER-789. Improve FLE log messages (flavio via phunt) - - ZOOKEEPER-798. Fixup loggraph for FLE changes (Ivan Kelly via phunt) - - ZOOKEEPER-797 c client source with AI_ADDRCONFIG cannot be compiled with - early glibc (Qian Ye via phunt) - - ZOOKEEPER-790. Last processed zxid set prematurely while establishing leadership - (fpj via breed) - - ZOOKEEPER-821. Add ZooKeeper version information to zkpython (Rich - Schumacher via mahadev) - - ZOOKEEPER-765. Add python example script (Travis and Andrei via mahadev) - - ZOOKEEPER-809. Improved REST Interface (Andrei Savu via phunt) - - ZOOKEEPER-733. use netty to handle client connections (breed and phunt) - - ZOOKEEPER-853. Make zookeeper.is_unrecoverable return True or False - in zkpython (Andrei Savu via henryr) - - ZOOKEEPER-864. Hedwig C++ client improvements (Ivan Kelly via breed) - - ZOOKEEPER-862. Hedwig created ledgers with hardcoded Bookkeeper ensemble and - quorum size. Make these a server config parameter instead. (Erwin T via breed) - - ZOOKEEPER-926. Fork Hadoop common's test-patch.sh and modify for Zookeeper. - (nigel) - - ZOOKEEPER-909. Extract NIO specific code from ClientCnxn - (Thomas Koch via phunt) - - ZOOKEEPER-908. Remove code duplication and inconsistent naming in - ClientCnxn.Packet creation (Thomas Koch via phunt) - - ZOOKEEPER-836. hostlist as string. (Thomas Koch via breed) - - ZOOKEEPER-921. zkPython incorrectly checks for existence of required - ACL elements (Nicholas Knight via henryr) - - ZOOKEEPER-963. Make Forrest work with JDK6 (Carl Steinbach via henryr) - - ZOOKEEPER-500. Async methods shouldnt throw exceptions (fpj via breed) - - ZOOKEEPER-977. passing null for path_buffer in zoo_create (breed via mahadev) - - ZOOKEEPER-465. Ledger size in bytes. (flavio via mahadev) - - ZOOKEEPER-980. allow configuration parameters for log4j.properties - (phunt via mahadev) - - ZOOKEEPER-1042. Generate zookeeper test jar for maven installation - (ivan kelly via breed) - - ZOOKEEPER-1030: Increase default for maxClientCnxns - (Todd Lipcon via breed/mahadev/phunt) - - ZOOKEEPER-850: Switch from log4j to slf4j (Olaf Krische via michim) - - ZOOKEEPER-874. FileTxnSnapLog.restore does not call listener (diogo via fpj) - - ZOOKEEPER-1052. Findbugs warning in QuorumPeer.ResponderThread.run() (fpj via michim) - - ZOOKEEPER-1094. Small improvements to LeaderElection and Vote classes (henryr via breed) - - ZOOKEEPER-1074. zkServer.sh is missing nohup/sleep, which are necessary - for remote invocation. (phunt via mahadev) - - ZOOKEEPER-965. Need a multi-update command to allow multiple znodes to be updated safely (Marshall McMullen and Ted Dunning via breed) - - ZOOKEEPER-1073. address a documentation issue in ZOOKEEPER-1030. (phunt via mahadev) - - ZOOKEEPER-1095. Simple leader election recipe - (Eric Sammer via henry and phunt) - - ZOOKEEPER-1076. some quorum tests are unnecessarily extending QuorumBase (phunt via mahadev) - - ZOOKEEPER-1143. quorum send & recv workers are missing thread names - (phunt via mahadev) - - ZOOKEEPER-1104. CLONE - In QuorumTest, use the same "for ( .. try { break } - catch { } )" pattern in testFollowersStartAfterLeaders as in testSessionMove. - (Eugene Koontz via mahadev) - - ZOOKEEPER-1034. perl bindings should automatically find the zookeeper - c-client headers (nicholas harteau via mahadev) - - ZOOKEEPER-1166. Please add a few svn:ignore properties (via phunt) - - ZOOKEEPER-1169. Fix compiler (eclipse) warnings in (generated) jute - code (Thomas Koch via phunt) - - ZOOKEEPER-1171. fix build for java 7 (phunt via mahadev) - - -NEW FEATURES: - ZOOKEEPER-729. Java client API to recursively delete a subtree. - (Kay Kay via henry) - - ZOOKEEPER-747. Add C# generation to Jute (Eric Hauser via phunt) - - ZOOKEEPER-464. Need procedure to garbage collect ledgers - (erwin via fpj) - - ZOOKEEPER-773. Log visualisation (Ivan Kelly via phunt) - - ZOOKEEPER-744. Add monitoring four-letter word (Andrei Savu via phunt) - - ZOOKEEPER-712. Bookie recovery. (erwin tam via breed) - - ZOOKEEPER-799. Add tools and recipes for monitoring as a contrib - (Andrei Savu via phunt) - - ZOOKEEPER-808. Web-based Administrative Interface - (Andrei Savu via phunt) - - ZOOKEEPER-775. A large scale pub/sub system (Erwin, Ivan and Ben via - mahadev) - - ZOOKEEPER-1020. Implement function in C client to determine which host you're currently connected to. (stephen tyree via breed) - - ZOOKEEPER-1038. Move bookkeeper and hedwig code in subversion (breed) - - ZOOKEEPER-784. Server-side functionality for read-only mode (Sergey Doroshenko via henryr) - - ZOOKEEPER-992. MT Native Version of Windows C Client (Dheeraj Agrawal via michim) - - ZOOKEEPER-938. Support Kerberos authentication of clients. (Eugene Koontz - via mahadev) - - ZOOKEEPER-1152. Exceptions thrown from handleAuthentication can cause buffer corruption issues in NIOServer. (camille via breed) - - ZOOKEEPER-999. Create an package integration project (Eric Yang via phunt) - - ZOOKEEPER-1107. automating log and snapshot cleaning (Laxman via phunt) - -DEPRECATION: - ZOOKEEPER-1153. Deprecate AuthFLE and LE. (Flavio Junqueira via mahadev) - -Release 3.3.0 - 2010-03-24 - -Non-backward compatible changes: - -BUGFIXES: - -Backward compatible changes: - -BUGFIXES: - ZOOKEEPER-59. Synchronized block in NIOServerCnxn (fpj via breed) - - ZOOKEEPER-524. DBSizeTest is not really testing anything (breed) - - ZOOKEEPER-469. make sure CPPUNIT_CFLAGS isn't overwritten - (chris via mahadev) - - ZOOKEEPER-471. update zkperl for 3.2.x branch (chris via mahadev) - - ZOOKEEPER-470. include unistd.h for sleep() in c tests (chris via mahadev) - - ZOOKEEPR-460. bad testRetry in cppunit tests (hudson failure) - (giri via mahadev) - - ZOOKEEPER-467. Change log level in BookieHandle. (flavio via mahadev) - - ZOOKEEPER-482. ignore sigpipe in testRetry to avoid silent immediate - failure. (chris via mahadev) - - ZOOKEEPER-487. setdata on root (/) crashes the servers (mahadev via phunt) - - ZOOKEEPER-457. Make ZookeeperMain public, support for HBase (and other) - embedded clients (ryan rawson via phunt) - - ZOOKEEPER-481. Add lastMessageSent to QuorumCnxManager. (flavio via mahadev) - - ZOOKEEPER-479. QuorumHierarchical does not count groups correctly - (flavio via mahadev) - - ZOOKEEPER-466. crash on zookeeper_close() when using auth with empty cert - (Chris Darroch via phunt) - - ZOOKEEPER-480. FLE should perform leader check when node is not leading and - add vote of follower (flavio via mahadev) - - ZOOKEEPER-491. Prevent zero-weight servers from being elected. - (flavio via mahadev) - - ZOOKEEPER-447. zkServer.sh doesn't allow different config files to be - specified on the command line (henry robinson via phunt) - - ZOOKEEPER-493. patch for command line setquota (steve bendiola via phunt) - - ZOOKEEPER-311. handle small path lengths in zoo_create() - (chris barroch via breed) - - ZOOKEEPER-484. Clients get SESSION MOVED exception when switching from - follower to a leader. (mahadev) - - ZOOKEEPER-490. the java docs for session creation are misleading/incomplete - (phunt) - - ZOOKEEPER-501. CnxManagerTest failed on hudson. (flavio via mahadev) - - ZOOKEEPER-499. electionAlg should default to FLE (3) - regression - (phunt via mahadev) - - ZOOKEEPER-477. zkCleanup.sh is flaky (fernando via mahadev) - - ZOOKEEPER-498. Unending Leader Elections : WAN configuration - (flavio via mahadev) - - ZOOKEEPER-508. proposals and commits for DIFF and Truncate messages from the - leader to the followers is buggy. (mahadev and ben via mahadev) - - ZOOKEEPER-518. DEBUG message for outstanding proposals in leader should be - moved to trace. (phunt) - - ZOOKEEPER-533. ant error running clean twice (phunt via mahadev) - - ZOOKEEPER-535. ivy task does not enjoy being defined twice - (build error) (phunt via mahadev) - - ZOOKEEPER-420. build/test should not require install in zkpython - (henry robinson via phunt) - - ZOOKEEPER-538. zookeeper.async causes python to segfault - (henry robinson via phunt) - - ZOOKEEPER-542. c-client can spin when server unresponsive (Christian - Wiedmann via mahadev) - - ZOOKEEEPER-510. zkpython lumps all exceptions as IOError, needs specialized - exceptions for KeeperException types (henry & pat via mahadev) - - ZOOKEEPER-541. zkpython limited to 256 handles (henry robinson via phunt) - - ZOOKEEPER-554. zkpython can segfault when statting a deleted node - (henry robinson via phunt) - - ZOOKEEPER-512. FLE election fails to elect leader (flavio via mahadev) - - ZOOKEEPER-563. ant test for recipes is broken. (mahadev via phunt) - - ZOOKEEPER-562. c client can flood server with pings if tcp send queue - filled. (ben reed via mahadev) - - ZOOKEEPER-537. The zookeeper jar includes the java source files - (Thomas Dudziak via phunt) - - ZOOKEEPER-551. unnecessary SetWatches message on new session. - (phunt via flavio) - - ZOOKEEPER-566. "reqs" four letter word (command port) returns no information - (phunt via breed) - - ZOOKEEPER-567. javadoc for getchildren2 needs to mention "new in 3.3.0" - (phunt via breed) - - ZOOKEEPER-547. Sanity check in QuorumCnxn Manager and quorum communication - port. (mahadev via breed) - - ZOOKEEPER-532. java compiler should be target Java 1.5 - (hiram chirino and phunt via breed) - - ZOOKEEPER-519. Followerhandler should close the socket if it gets an exception - on a write. (mahadev via breed) - - ZOOKEEPER-570. AsyncHammerTest is broken, callbacks need to validate rc - parameter (phunt via breed) - - ZOOKEEPER-3. syncLimit has slightly different comments in the class header, - and > inline with the variable. (mahadev via breed) - - ZOOKEEPER-576. docs need to be updated for session moved exception and how - to handle it (breed via phunt) - - ZOOKEEPER-582. ZooKeeper can revert to old data when a snapshot is created - outside of normal processing (ben reed and mahadev via mahadev) - - ZOOKEEPER-597. ASyncHammerTest is failing intermittently on hudson trunk - (Patrick Hunt via mahadev) - - ZOOKEEPER-598. LearnerHandler is misspelt in the thread's constructor - (Henry Robinson via fpj) - - ZOOKEEPER-597. ASyncHammerTest is failing intermittently on hudson trunk (take 2) - (breed) - - ZOOKEEPER-597. ASyncHammerTest is failing intermittently on hudson trunk - (take 3) (phunt via mahadev) - - ZOOKEEPER-597. ASyncHammerTest is failing intermittently on hudson trunk - (take 4) (breed via mahadev) - - ZOOKEEPER-597. ASyncHammerTest is failing intermittently on hudson trunk - (take 5) (mahadev) - - ZOOKEEPER-611. hudson build failiure (mahadev) - - ZOOKEEPER-611. hudson build failure (take 2) (mahadev) - - ZOOKEEPER-615. wrong javadoc for create with a sequence flag - (mahadev via breed) - - ZOOKEEPER-588. remove unnecessary/annoying log of tostring error in - Request.toString() (phunt via breed) - - ZOOKEEPER-587. client should log timeout negotiated with server - (phunt via mahadev) - - ZOOKEEPER-610. cleanup final fields, esp those used for locking - (phunt via henry) - - ZOOKEEPER-614. Improper synchronisation in getClientCnxnCount - (henry via mahadev) - - ZOOKEEPER-609. ObserverTest failure "zk should not be connected expected not - same" (henry robinson via phunt) - - ZOOKEEPER-630. Trunk has duplicate ObserverTest.java files - (henry robinson via phunt) - - ZOOKEEPER-627. zkpython arbitrarily restricts the size of a 'get' to 512 - bytes (henry robinson via mahadev) - - ZOOKEEPER-534. The test target in contib/bookkeeper does not depend on jar - target. (phunt via mahadev) - - ZOOKEEPER-623. ClientBase in bookkeeper.util requires junit (fpj via breed) - - ZOOKEEPER-600. TODO pondering about allocation behavior in zkpython may be - removed (gustavo via mahadev) - - ZOOKEEPER-596. The last logged zxid calculated by zookeeper servers could - cause problems in leader election if data gets corrupted. (mahadev) - - ZOOKEEPER-637. Trunk build is failing (fpj via breed) - - ZOOKEEPER-637. Trunk build is failing - second patch (breed via fpj) - - ZOOKEEPER-644. Nightly build failed on hudson. (pat via mahadev) - - ZOOKEEPER-651: Log exception trace in QuorumCnxManager.SendWorker - (flavio via henry) - - ZOOKEEPER-608. Receipt of ACK from observer should not be logged as ERROR - (henry via mahadev) - - ZOOKEEPER-647. hudson failure in testLeaderShutdown (flavio via mahadev) - - ZOOKEEPER-574. the documentation on snapcount in the admin guide has the - wrong default (phunt via mahadev) - - ZOOKEEPER-656. SledgeHammer test - thread.run() deprecated (kay kay via mahadev) - - ZOOKEEPER-413. two flaws need addressing in the c tests that can cause false - positive failures (phunt via mahadev) - - ZOOKEEPER-495. c client logs an invalid error when zookeeper_init is called - with chroot (phunt via mahadev) - - ZOOKEEPER-589. When create a znode, a NULL ACL parameter cannot be accepted. - (breed via mahadev) - - ZOOKEEPER-673. Fix observer documentation regarding leader election (flavio - via mahadev) - - ZOOKEEPER-672. typo nits across documentation (Kay Kay via mahadev) - - ZOOKEEPER-668. Close method in LedgerInputStream doesn't do anything (flavio - via mahadev) - - ZOOKEEPER-569. Failure of elected leader can lead to never-ending leader - election (henry via flavio) - - ZOOKEEPER-669. watchedevent tostring should clearly output the - state/type/path (phunt via mahadev) - - ZOOKEEPER-683. LogFormatter fails to parse transactional log files (phunt - via mahadev) - - ZOOKEEPER-682. Event is not processed when the watcher is set to watch "/" - if chrooted (Scott Wang via mahadev) - - ZOOKEEPER-687. LENonterminatetest fails on some machines. (mahadev) - - ZOOKEEPER-681. Minor doc issue re unset maxClientCnxns (phunt via mahadev) - - ZOOKEEPER-622. Test for pending watches in send_set_watches should be moved - (ben and steven via mahadev) - - ZOOKEEPER-689. release build broken - ivysettings.xml not copied during - "package" (phunt via mahadev) - - ZOOKEEPER-59. Synchronized block in NIOServerCnxn (flavio via mahadev) - - ZOOKEEPER-691. Interface changed for NIOServer.Factory (breed via mahadev) - - ZOOKEEPER-685. Race in LENonTerminateTest (henry via breed) - - ZOOKEEPER-677. c client doesn't allow ipv6 numeric connect string - (breed & phunt & mahadev via breed) - - ZOOKEEPER-693. TestObserver stuck in tight notification loop in FLE - (flavio via phunt) - - ZOOKEEPER-696. NPE in the hudson logs, seems nioservercnxn closed twice - (phunt via mahadev) - - ZOOKEEPER-511. bad error handling in FollowerHandler.sendPackets - (mahadev via flavio) - - ZOOKEEPER-604. zk needs to prevent export of any symbol not listed in their - api (mahadev) - - ZOOKEEPER-121. SyncRequestProcessor is not closing log stream during - shutdown (mahadev) - - ZOOKEEPER-698. intermittent JMX test failures due to not verifying QuorumPeer - shutdown (phunt) - - ZOOKEEPER-121_2. SyncRequestProcessor is not closing log stream during - shutdown (breed via mahadev) - - ZOOKEEPER-121_3. SyncRequestProcessor is not closing log stream during - shutdown (mahadev via phunt) - - ZOOKEEPER-121_4. SyncRequestProcessor is not closing log stream during - shutdown (mahadev via breed) - - ZOOKEEPER-586. c client does not compile under cygwin (phunt, mahadev, breed via breed) - - ZOOKEEPER-624. The C Client cause core dump when receive error data from - Zookeeper Server (mahadev) - - ZOOKEEPER-591. The C Client cannot exit properly in some situation (mahadev) - - ZOOKEEPER-591_2. The C Client cannot exit properly in some situation - (mahadev via phunt) - - ZOOKEEPER-709. bookkeeper build failing with missing factory - (phunt) - - ZOOKEEPER-708. zkpython failing due to undefined symbol - deallocate_String_vector (mahadev via phunt) - - ZOOKEEPER-436. Bookies should auto register to ZooKeeper (erwin tam & fpj via breed) - - ZOOKEEPER-710. permanent ZSESSIONMOVED error after client app reconnects to zookeeper cluster (phunt via breed) - - ZOOKEEPER-718. the fatjar is missing libraries (ben via mahadev) - - ZOOKEEPER-717. add a preferred list to the instancemanager (breed via - mahadev) - -IMPROVEMENTS: - ZOOKEEPER-473. cleanup junit tests to eliminate false positives due to - "socket reuse" and failure to close client (phunt via mahadev) - - ZOOKEEPER-488. Fix zkServer.sh to add clover.jar in classpath - (Giridharan Kesavan via gkesavan) - - ZOOKEEPER-516. add support for 10 minute test ie "pre-commit" test (phunt) - - ZOOKEEPER-529. Use Ivy to pull dependencies and also generate pom (phunt - via mahadev) - - ZOOKEEPER-530. Memory corruption: Zookeeper c client IPv6 implementation - does not honor struct sockaddr_in6 size (isabel drost via mahadev) - - ZOOKEEPER-549. Refactor Followers and related classes into a Peer->Follower - hierarchy in preparation for Observers (henry robinson via mahadev) - - ZOOKEEPER-472. Making DataNode not instantiate a HashMap when the node is - ephmeral (Erik Holstad via mahadev) - - ZOOKEEPER-425. Add OSGi metadata to zookeeper.jar (david bosschaert via breed) - - ZOOKEEPER-599. Changes to FLE and QuorumCnxManager to support Observers - (fpj via breed) - - ZOOKEEPER-506. QuorumBase should use default leader election (fpj via breed) - - ZOOKEEPER-633. Fetch netty using ivy for bookkeeper (giri via fpj) - - ZOOKEEPER-544. improve client testability - allow test client to access - connected server location (phunt via breed) - - ZOOKEEPER-426. Windows versions of zookeeper scripts - (David Bosschaert via breed) - - ZOOKEEPER-638. upgrade ivy to 2.1.0 final from 2.1.0 release - candidate (phunt via breed) - - ZOOKEEPER-648. Fix releaseaudit warning count to zero (phunt via henry) - - ZOOKEEPER-626. ensure the c/java cli's print xid/sessionid/etc... in hex - (pat via mahadev) - - ZOOKEEPER-655. StringBuffer -> StringBuilder - conversion of references as - necessary (Kay Kay via henry) - - ZOOKEEPER-612. Make Zookeeper C client can be compiled by gcc of early - version (qian via mahadev) - - ZOOKEEPER-456. CREATOR_ALL_ACL has unnecessary PERMS.ADMIN in the - declartion. (phunt via mahadev) - - ZOOKEEPER-593. java client api does not allow client to access negotiated - session timeout (phunt via mahadev) - - ZOOKEEPER-507. BookKeeper client re-write (Utkarsh and ben via mahadev) - - ZOOKEEPER-665. Add BookKeeper streaming documentation (flavio via mahadev) - - ZOOKEEPER-664. BookKeeper API documentation (flavio via mahadev) - - ZOOKEEPER-607. improve bookkeeper overview (flavio via mahadev) - - ZOOKEEPER-485. Need ops documentation that details supervision of ZK server - processes. (phunt via mahadev) - - ZOOKEEPER-658. update forrest docs - AuthFLE no longer supported (flavio via - mahadev) - - ZOOKEEPER-640. make build.xml more configurable to ease packaging for linux - distros (phunt via mahadev) - - ZOOKEEPER-579. zkpython needs more test coverage for ACL code paths (henry - via mahadev) - - ZOOKEEPER-688. explain session expiration better in the docs & faq (phunt - via mahadev) - - ZOOKEEPER-663. hudson failure in ZKDatabaseCorruptionTest (mahadev via henryr) - - ZOOKEEPER-543. Tests for ZooKeeper examples (steven via mahadev) - - ZOOKEEPER-692. upgrade junit to latest version (4.8.1) (phunt via mahadev) - - ZOOKEEPER-601. allow configuration of session timeout min/max bounds (phunt - via mahadev) - -NEW FEATURES: - ZOOKEEPER-539. generate eclipse project via ant target. (phunt via mahadev) - - ZOOKEEPER-555. Add stat information to GetChildrenResponse. (Arni Jonson and - phunt via mahadev) - - ZOOKEEPER-550. Java Queue Recipe. (steven cheng via mahadev) - - ZOOKEEPER-368. Observers: core functionality (henry robinson via mahadev) - - ZOOKEEPER-496. zookeeper-tree utility for export, import and incremental - updates (anirban roy via breed) - - ZOOKEEPER-572. add ability for operator to examine state of watches - currently registered with a server (phunt via mahadev) - - ZOOKEEPER-678. Gui browser application to view and edit the contents of a - zookeeper instance (Colin Goodheart-Smithe via phunt) - - ZOOKEEPER-635. Server supports listening on a specified network address (phunt via breed) - -Release 3.2.0 - 2009-06-30 - -Non-backward compatible changes: - -BUGFIXES: - ZOOKEEPER-444. perms definition for PERMS_ALL differ in C and java (mahadev) - -Backward compatible changes: - -BUGFIXES: - ZOOKEEPER-303. Bin scripts dont work on a Mac. (tom white via mahadev) - - ZOOKEEPER-330. zookeeper standalone server does not startup with just a - port and datadir. (chris darroch and mahadev) - - ZOOKEEPER-319. add locking around auth info in zhandle_t. - (chris darroch via mahadev) - - ZOOKEEPER-320. call auth completion in free_completions(). - (chris darroch via mahadev) - - ZOOKEEPER-334. bookkeeper benchmark (testclient.java) has compiling errors. - (flavio and mahadev) - - ZOOKEEPER-281. autoreconf fails for /zookeeper-3.0.1/src/c/ (phunt) - - ZOOKEEPER-318. remove locking in zk_hashtable.c or add locking in - collect_keys() (chris darroch via mahadev) - - ZOOKEEPER-333. helgrind thread issues identified in mt c client code - (mahadev via phunt) - - ZOOKEEPER-309. core dump using zoo_get_acl() (mahadev via phunt) - - ZOOKEEPER-341. regression in QuorumPeerMain, - tickTime from config is lost, cannot start quorum (phunt via mahadev) - - ZOOKEEPER-360. WeakHashMap in Bookie.java causes NPE (flavio via mahadev) - - ZOOKEEPER-362. Issues with FLENewEpochTest. (fix bug in Fast leader election) - (flavio via mahadev) - - ZOOKEEPER-363. NPE when recovering ledger with no hint. (flavio via mahadev) - - ZOOKEEPER-370. Fix critical problems reported by findbugs. - (flavio via mahadev) - - ZOOKEEPER-347. zkfuse uses non-standard String. (patrick hunt via mahadev) - - ZOOKEEPER-355. make validatePath non public in Zookeeper client api. - (phunt via mahadev) - - ZOOKEEPER-374. Uninitialized struct variable in C causes warning which - is treated as an error (phunt via mahadev) - - ZOOKEEPER-337. improve logging in leader election lookForLeader method when - address resolution fails (phunt via mahadev) - - ZOOKEEPER-367. RecoveryTest failure - "unreasonable length" IOException - (mahadev via phunt) - - ZOOKEEPER-346. remove the kill command fro mthe client port. - (phunt via mahadev) - - ZOOKEEPER-377. running ant cppunit tests, a failure still results in - BUILD SUCCESSFUL (giri via mahadev) - - ZOOKEEPER-382. zookeeper cpp tests fails on 64 bit machines with gcc 4.1.2 - (mahadev via phunt) - - ZOOKEEPER-365. javadoc is wrong for setLast in LedgerHandle - (flavio via phunt) - - ZOOKEEPER-392. Change log4j properties in bookkeeper. (flavio via mahadev) - - ZOOKEEPER-400. Issues with procedure to close ledger. (flavio) - - ZOOKEEPER-405. nullpointer exception in zookeeper java shell. - (mahadev via breed) - - ZOOKEEPER-410. address all findbugs warnings in client/server classes. - (phunt via breed) - - ZOOKEEPER-403. cleanup javac compiler warnings. (flavio via breed) - - ZOOKEEPER-407. address all findbugs warnings in - org.apache.zookeeper.server.quorum.** packages. - (flavio via breed) - - ZOOKEEPER-411. Building zookeeper fails on RHEL 5 64 bit during test-cppunit - (mahadev via phunt) - - ZOOKEEPER-402. zookeeper c library segfaults on data for a node in zookeeper - being null. (mahadev via phunt) - - ZOOKEEPER-415. zookeeper c tests hang. (mahadev via phunt) - - ZOOKEEPER-385. crctest failed on hudson patch test (mahadev via phunt) - - ZOOKEEPER-192. trailing whitespace in config file can cause number format - exceptions (phunt via breed) - - ZOOKEEPER-409. address all findbugs warnings in jute related classes - (phunt via breed) - - ZOOKEEPER-416. bookkeeper jar includes unnnecessary files. - (flavio via mahadev) - - ZOOKEEPER-419. Reference counting bug in Python bindings causes abort errors - (henry robinson via phunt) - - ZOOKEEPER-421. zkpython run_tests.sh is missing #! - (henry robinson via phunt) - - ZOOKEEPER-406. address all findbugs warnings in persistence classes. - (phunt et al via breed) - - ZOOKEEPER-435. allow "super" admin digest based auth to be configurable - (phunt via breed) - - ZOOKEEPER-375. zoo_add_auth only retains most recent auth on re-sync. - (mahadev) - - ZOOKEEPER-433. getacl on root znode (/) fails. (phunt via mahadev) - - ZOOKEEPER-408. address all findbugs warnings in persistence classes. - (phunt, mahadev, flavio via mahadev) - - ZOOKEEPER-427. ZooKeeper server unexpectedly high CPU utilisation - (Sergey Zhuravlev via breed) - - ZOOKEEPER-446. some traces of the host auth scheme left (breed via mahadev) - - ZOOKEEPER-438. addauth fails to register auth on new client that's not yet - connected (breed via mahadev) - - ZOOKEEPER-448. png files do nto work with forrest. (mahadev) - - ZOOKEEPER-417. stray message problem when changing servers - (breed via mahadev) - - ZOOKEEPER-449. sesssionmoved in java code and ZCLOSING in C have the same - value. (mahadev) - - ZOOKEEPER-452. zookeeper performance graph should have percentage of reads - rather than percentage of writes - zkperfRW-3.2.jpg (mahadev) - - ZOOKEEPER-450. emphemeral cleanup not happening with session timeout. - (breed via mahadev) - - ZOOKEEPER-453. Worker is not removed in QuorumCnxManager upon crash. - (flavio via mahadev) - - ZOOKEEPER-454. allow compilation with jdk1.5 (phunt) - - ZOOKEEPER-455. zookeeper c client crashes with chroot specified in the string. - (phunt via mahadev) - - ZOOKEEPER-468. avoid compile warning in send_auth_info(). - -IMPROVEMENTS: - ZOOKEEPER-308. improve the atomic broadcast performance 3x. - (breed via mahadev) - - ZOOKEEPER-326. standalone server ignores tickTime configuration. - (chris darroch via mahadev) - - ZOOKEEPER-279. Allow specialization of quorum config parsing - (e.g. variable expansion in zoo.cfg) (Jean-Daniel Cryans via phunt) - - ZOOKEEPER-351. to run checkstyle (giridharan kesavan via mahadev) - - ZOOKEEPER-350. to run rats for releaseaudit. - (giridharan kesavan via mahadev) - - ZOOKEEPER-352. to add standard ant targets required by test-patch.sh script - (giridharan kesavan via mahadev) - - ZOOKEEPER-353. javadoc warnings needs to be fixed. - (giridharan kesavan via mahadev) - - ZOOKEEPER-354. to fix javadoc warning in the source files. (mahadev) - - ZOOKEEPER-349. to automate patch testing. (giridharan kesavan via mahadev) - - ZOOKEEPER-288. Cleanup and fixes to BookKeeper (flavio via mahadev) - - ZOOKEEPER-305. Replace timers with semaphores in FLENewEpochTest. - (flavio via mahadev) - - ZOOKEEPER-60. Get cppunit tests running as part of Hudson CI. - (girish via mahadev) - - ZOOKEEPER-343. add tests that specifically verify the zkmain and - qpmain classes. (phunt via mahadev) - - ZOOKEEPER-361. integrate cppunit testing as part of hudson patch process. - (giri via mahadev) - - ZOOKEEPER-373. One thread per bookie (flavio via mahadev) - - ZOOKEEPER-384. keeper exceptions missing path (phunt via mahadev) - - ZOOKEEPER-380. bookkeeper should have a streaming api so that its easier to - store checpoints/snapshots in bookkeeper. (mahadev via flavio) - - ZOOKEEPER-389. add help/usage to the c shell cli.c (phunt via mahadev) - - ZOOKEEPER-376. ant test target re-compiles cppunit code every time - (phunt via mahadev) - - ZOOKEEPER-391. bookeeper mainline code should not be calling - printStackTrace. (flavio via mahadev) - - ZOOKEEPER-300. zk jmx code is calling printStackTrace when creating bean - name (should not be) (phunt via mahadev) - - ZOOKEEPER-94. JMX tests are needed to verify that the JMX MBeans work - properly (phunt via mahadev) - - ZOOKEEPER-404. nightly build failed on hudson. - (henry robinson and pat via mahadev) - - ZOOKEEPER-345. the CLIs should allow addAuth to be invoked. - (henry robinson via breed) - - ZOOKEEPER-292. commit configure scripts (autotools) to svn for c projects and - include in release (phunt via breed) - - ZOOKEEPER-383. Asynchronous version of createLedger(). (flavio via mahadev) - - ZOOKEEPER-358. Throw exception when ledger does not exist. (flavio via breed) - - ZOOKEEPER-431. Expose methods to ease ZK integration. (Jean-Daniel via breed) - - ZOOKEEPER-396. race condition in zookeeper client library between - zookeeper_close and zoo_synchronous api. (mahadev) - - ZOOKEEPER-196. doxygen comment for state argument of watcher_fn typedef and - implementation differ ("...one of the *_STATE constants, otherwise -1") - (breed via mahadev) - - ZOOKEEPER-336. single bad client can cause server to stop accepting - connections (henry robinson via breed) - - ZOOKEEPER-434. the java shell should indicate connection status on command - prompt (henry robinson via breed) - - ZOOKEEPER-437. Variety of Documentation Updates (grant via mahadev) - - ZOOKEEPER-443. trace logging in watch notification not wrapped with - istraceneabled - inefficient (pat via mahadev) - - ZOOKEEPER-432. Various improvements to zkpython bindings. - (henry via mahadev) - - ZOOKEEPER-428. logging should be makred as warn rathen than error in - NIOServerCnxn. (phunt via mahadev) - - ZOOKEEPER-422. Java CLI should support ephemeral and sequential node creation - (henry via breed) - - ZOOKEEPER-315. add forrest docs for bookkeeper. (flavio via mahadev) - - ZOOKEEPER-329. document how to integrate 3rd party authentication into ZK - server ACLs. (breed via mahadev) - - ZOOKEEPER-356. Masking bookie failure during writes to a ledger - (flavio via breed) - - ZOOKEEPER-327. document effects (latency) of storing large amounts of data - in znodes. (breed via mahadev) - - ZOOKEEPER-264. docs should include a state transition diagram for client - state (breed via mahadev) - - ZOOKEEPER-440. update the performance documentation in forrest - (breed via phunt) - -NEW FEATURES: - - ZOOKEEPER-371. jdiff documentation included in build/release (giri via phunt) - - ZOOKEEPER-78. added a high level protocol/feature - for easy Leader - Election or exclusive Write Lock creation (mahadev via phunt) - - ZOOKEEPER-29. Flexible quorums (flavio via mahadev) - - ZOOKEEPER-378. perl binding for zookeeper (chris darroch via mahadev) - - ZOOKEEPER-386. improve java cli shell. (henry robinson via mahadev) - - ZOOKEEPER-36. REST access to ZooKeeper (phunt via mahadev) - - ZOOKEEPER-395. Python bindings. (henry robinson via mahadev) - - ZOOKEEPER-237. Add a Chroot request (phunt and mahadev) - - -Release 3.1.0 - 2009-02-06 - -Non-backward compatible changes: - -BUGFIXES: - - ZOOKEEPER-255. zoo_set() api does not return stat datastructure. - (avery ching via mahadev) - - ZOOKEEPER-246. review error code definition in both source and docs. - (pat via mahadev) - -Backward compatible changes: - -BUGFIXES: - ZOOKEEPER-211. Not all Mock tests are working (ben via phunt) - - ZOOKEEPER-223. change default level in root logger to INFO. - (pat via mahadev) - - ZOOKEEPER-212. fix the snapshot to be asynchronous. (mahadev and ben) - - ZOOKEEPER-213. fix programmer guide C api docs to be in sync with latest - zookeeper.h (pat via mahadev) - - ZOOKEEPER-219. fix events.poll timeout in watcher test to be longer. - (pat via mahadev) - - ZOOKEEPER-217. Fix errors in config to be thrown as Exceptions. (mahadev) - - ZOOKEEPER-228. fix apache header missing in DBTest. (mahadev) - - ZOOKEEPER-218. fix the error in the barrier example code. (pat via mahadev) - - ZOOKEEPER-206. documentation tab should contain the version number and - other small site changes. (pat via mahadev) - - ZOOKEEPER-226. fix exists calls that fail on server if node has null data. - (mahadev) - - ZOOKEEPER-204. SetWatches needs to be the first message after auth - messages to the server (ben via mahadev) - - ZOOKEEPER-208. Zookeeper C client uses API that are not thread safe, - causing crashes when multiple instances are active. - (austin shoemaker, chris daroch and ben reed via mahadev) - - ZOOKEEPER-227. gcc warning from recordio.h (chris darroch via mahadev) - - ZOOKEEPER-232. fix apache licence header in TestableZookeeper (mahadev) - - ZOOKEEPER-249. QuorumPeer.getClientPort() always returns -1. - (nitay joffe via mahadev) - - ZOOKEEPER-248. QuorumPeer should use Map interface instead of HashMap - implementation. (nitay joffe via mahadev) - - ZOOKEEPER-241. Build of a distro fails after clean target is run. - (patrick hunt via mahadev) - - ZOOKEEPER-245. update readme/quickstart to be release tar, rather than - source, based (patrick hunt via mahadev) - - ZOOKEEPER-251. NullPointerException stopping and starting Zookeeper servers - (mahadev via phunt) - - ZOOKEEPER-250. isvalidsnapshot should handle the case of 0 snapshot - files better. (mahadev via phunt) - - ZOOKEEPER-265. remove (deprecate) unused NoSyncConnected from KeeperState. - (phunt via mahadev) - - ZOOKEEPER-273. Zookeeper c client build should not depend on CPPUNIT. (pat -and runping via mahadev) - - ZOOKEEPER-268. tostring on jute generated objects can cause NPE. (pat via mahadev) - - ZOOKEEPER-267. java client incorrectly generating syncdisconnected event when in disconnected state. (pat via breed) - - ZOOKEEPER-263. document connection host:port as comma separated list in forrest docs (pat via breed) - - ZOOKEEPER-275. Bug in FastLeaderElection. (flavio via mahadev) - - ZOOKEEPER-272. getchildren can fail for large number of children. (mahadev) - - ZOOKEEPER-16. Need to do path validation. (pat, mahadev) - - ZOOKEEPER-252. PurgeTxnLog is not handling the new dataDir directory - structure (mahadev via phunt) - - ZOOKEEPER-291. regression for legacy code using KeeperException.Code - constants (due to 246). (pat via mahadev) - - ZOOKEEPER-255. zoo_set() api does not return stat datastructure. - (avery ching via mahadev) - - ZOOKEEPER-293. zoo_set needs to be abi compatible (3.1 changed the -signature), fix this by adding zoo_set2 (pat via mahadev) - - ZOOKEEPER-302. Quote values in JMX objectnames. (tom and pat via mahadev) - -IMPROVEMENTS: - - ZOOKEEPER-64. Log system env information when initializing server and - client (pat via mahadev) - - ZOOKEEPER-243. add SEQUENCE flag documentation to the programming guide. - (patrick hunt via mahadev) - - ZOOKEEPER-161. Content needed: "Designing a ZooKeeper Deployment" - (breed via phunt) - - ZOOKEEPER-247. fix formatting of C API in ACL section of programmer guide. - (patrick hunt via mahadev) - - ZOOKEEPER-230. Improvements to FLE. (Flavio via mahadev) - - ZOOKEEPER-225. c client should log an info message in zookeeper_init - detailing connection parameters. (pat via mahadev) - - ZOOKEEPER-222. print C client log message timestamp in human readable - form. (pat via mahadev) - - ZOOKEEPER-256. support use of JMX to manage log4j configuration at runtime. - (pat via mahadev) - - ZOOKEEPER-214. add new "stat reset" command to server admin port. - (pat via mahadev) - - ZOOKEEPER-258. docs incorrectly state max client timeout as 60 seconds - (it's based on server ticktime). (phunt via mahadev) - - ZOOKEEPER-135. Fat jar build target. (phunt and breed via mahadev) - - ZOOKEEPER-234. Eliminate using statics to initialize the sever. Should - allow server to be more embeddable in OSGi enviorments. (phunt) - - ZOOKEEPER-259. cleanup the logging levels used (use the correct level) - and messages generated. (phunt via breed) - - ZOOKEEPER-210. Require Java 6. (phunt via breed) - - ZOOKEEPER-177. needed: docs for JMX (phunt via mahadev) - - ZOOKEEPER-253. documentation of DataWatcher state transition is misleading -regarding auto watch reset on reconnect. (phunt via mahadev) - - ZOOKEEPER-269. connectionloss- add more documentation to detail. (phunt and -flavio via mahadev) - - ZOOKEEPER-260. document the recommended values for server id's - (mahadev via phunt) - - ZOOKEEPER-215. expand system test environment (breed via phunt) - - ZOOKEEPER-229. improve documentation regarding user's responsibility to - cleanup datadir (snaps/logs) (mahadev via phunt) - - ZOOKEEPER-69. ZooKeeper logo - - ZOOKEEPER-286. Make GenerateLoad use InstanceContainers. (breed via mahadev) - - ZOOKEEPER-220. programming guide watches section should clarify - server/clientlib role in data/child watch maint. (breed via phunt) - - ZOOKEEPER-289. add debug messages to nioserver select loop. (mahadev) - -NEW FEATURES: - - ZOOKEEPER-276. Bookkeeper contribution (Flavio and Luca Telloli via mahadev) - - ZOOKEEPER-231. Quotas in ZooKeeper. (mahadev) - -Release 3.0.0 - 2008-10-21 - -Non-backward compatible changes: - - ZOOKEEPER-43. Server side of auto reset watches. (breed via mahadev) - - ZOOKEEPER-132. Create Enum to replace CreateFlag in ZooKepper.create - method (Jakob Homan via phunt) - - ZOOKEEPER-139. Create Enums for WatcherEvent's KeeperState and EventType - (Jakob Homan via phunt) - - ZOOKEEPER-18. keeper state inconsistency (Jakob Homan via phunt) - - ZOOKEEPER-38. headers (version+) in log/snap files (Andrew Kornev and Mahadev - Konar via breed) - - ZOOKEEPER-8. Stat enchaned to include num of children and size - (phunt) - - ZOOKEEPER-6. List of problem identifiers in zookeeper.h - (phunt) - - ZOOKEEPER-7. Use enums rather than ints for types and state - (Jakob Homan via mahadev) - - ZOOKEEPER-27. Unique DB identifiers for servers and clients - (mahadev) - - ZOOKEEPER-32. CRCs for ZooKeeper data - (mahadev) - - ZOOKEEPER-33. Better ACL management - (mahadev) - -Backward compatible changes: - - BUGFIXES: - - ZOOKEEPER-203. fix datadir typo in releasenotes (phunt) - - ZOOKEEPER-145. write detailed release notes for users migrating from 2.x - to 3.0 (phunt) - - ZOOKEEPER-23. Auto reset of watches on reconnect (breed via phunt) - - ZOOKEEPER-191. forrest docs for upgrade. (mahadev via phunt) - - ZOOKEEPER-201. validate magic number when reading snapshot and transaction - logs (mahadev via phunt) - - ZOOKEEPER-200. the magic number for snapshot and log must be different - (currently same) (phunt) - - ZOOKEEPER-199. fix log messages in persistence code (mahadev via phunt) - - ZOOKEEPER-197. create checksums for snapshots (mahadev via phunt) - - ZOOKEEPER-198. apache license header missing from FollowerSyncRequest.java - (phunt) - - ZOOKEEPER-5. Upgrade Feature in Zookeeper server. (mahadev via phunt) - - ZOOKEEPER-194. Fix terminology in zookeeperAdmin.xml - (Flavio Paiva Junqueira) - - ZOOKEEPER-151. Document change to server configuration - (Flavio Paiva Junqueira) - - ZOOKEEPER-193. update java example doc to compile with latest zookeeper - (phunt) - - ZOOKEEPER-187. CreateMode api docs missing (phunt) - - ZOOKEEPER-186. add new "releasenotes.xml" to forrest documentation - (phunt) - - ZOOKEEPER-190. Reorg links to docs and navs to docs into related sections - (robbie via phunt) - - ZOOKEEPER-189. forrest build not validated xml of input documents - (robbie via phunt) - - ZOOKEEPER-188. Check that election port is present for all servers - (Flavio Paiva Junqueira via phunt) - - ZOOKEEPER-185. Improved version of FLETest (Flavio Paiva Junqueira) - - ZOOKEEPER-184. tests: An explicit include derective is needed for the usage - of memcpy(), memset(), strlen(), strdup() and free() functions - (Maxim P. Dementiev via phunt) - - ZOOKEEPER-183. Array subscript is above array bounds in od_completion(), - src/cli.c. (Maxim P. Dementiev via phunt) - - ZOOKEEPER-182. zookeeper_init accepts empty host-port string and returns - valid pointer to zhandle_t. (Maxim P. Dementiev via phunt) - - ZOOKEEPER-17. zookeeper_init doc needs clarification (phunt) - - ZOOKEEPER-181. Some Source Forge Documents did not get moved over: - javaExample, zookeeperTutorial, zookeeperInternals (robbie via phunt) - - ZOOKEEPER-180. Placeholder sections needed in document for new topics that - the umbrella jira discusses (robbie via phunt) - - ZOOKEEPER-179. Programmer's Guide "Basic Operations" section is missing - content (robbie via phunt) - - ZOOKEEPER-178. FLE test. (Flavio Paiva Junqueira) - - ZOOKEEPER-159. Cover two corner cases of leader election - (Flavio Paiva Junqueira via phunt) - - ZOOKEEPER-156. update programmer guide with acl details from old wiki page - (phunt) - - ZOOKEEPER-154. reliability graph diagram in overview doc needs context - (phunt) - - ZOOKEEPER-157. Peer can't find existing leader (Flavio Paiva Junqueira) - - ZOOKEEPER-155. improve "the zookeeper project" section of overview doc - (phunt) - - ZOOKEEPER-140. Deadlock in QuorumCnxManager (Flavio Paiva Junqueira) - - ZOOKEEPER-147. This is version of the documents with most of the [tbd...] - scrubbed out (robbie via phunt) - - ZOOKEEPER-150. zookeeper build broken (mahadev via phunt) - - ZOOKEEPER-136. sync causes hang in all followers of quorum. (breed) - - ZOOKEEPER-134. findbugs cleanup (phunt) - - ZOOKEEPER-133. hudson tests failing intermittently (phunt) - - ZOOKEEPER-144. add tostring support for watcher event, and enums for event - type/state (Jakob Homan via phunt) - - ZOOKEEPER-21. Improve zk ctor/watcher (state transition) docs (phunt) - - ZOOKEEPER-142. Provide Javadoc as to the maximum size of the data byte - array that may be stored within a znode (Jakob Homan via phunt) - - ZOOKEEPER-93. Create Documentation for Zookeeper (phunt) - - ZOOKEEPER-117. threading issues in Leader election (fpj via breed) - - ZOOKEEPER-137. client watcher objects can lose events (phunt via breed) - - ZOOKEEPER-131. Old leader election can elect a dead leader over and over - again (breed via mahadev) - - ZOOKEEPER-130. update build.xml to support apache release process - (phunt via mahadev) - - ZOOKEEPER-118. findbugs flagged switch statement in - followerrequestprocessor.run() (Flavio Paiva Junqueira via phunt) - - ZOOKEEPER-115. Potential NPE in QuorumCnxManager - (Flavio Paiva Junqueira) - - ZOOKEEPER-114. cleanup ugly event messages in zookeeper client - (Jakob Homan) - - ZOOKEEPER-112. src/java/main ZooKeeper.java has test code embedded into it. - (phunt) - - ZOOKEEPER-39. Use Watcher objects rather than boolean on read operations. - (Andrew Kornev) - - ZOOKEEPER-97. supports optional output directory in code generator. (Hiram - Chirino via phunt) - - ZOOKEEPER-101. Integrate ZooKeeper with "violations" feature on hudson - (phunt) - - ZOOKEEPER-105. Catch Zookeeper exceptions and print on the stderr. - (Anthony Urso via Mahadev) - - ZOOKEEPER-42. Change Leader Election to fast tcp. (Flavio Paiva Junqueira - via phunt) - - ZOOKEEPER-48. auth_id now handled correctly when no auth ids present - (Benjamin Reed via phunt) - - ZOOKEEPER-44. Create sequence flag children with prefixes of 0's so that - they can be lexicographically sorted. (Jakob Homan via mahadev) - - ZOOKEEPER-108. Fix sync operation reordering on a Quorum. - (Flavio Paiva Junqueira via Mahadev) - - ZOOKEEPER-25. Fuse module for Zookeeper. (Swee Lim, Bart, Patrick Hunt and - Andrew Kornev via Mahadev) - - ZOOKEEPER-58. Race condition on ClientCnxn.java (breed) - - ZOOKEEPER-56. Add clover support to build.xml. (Patrick Hunt via mahadev) - - ZOOKEEPER-75. register the ZooKeeper mailing lists with nabble.com (phunt) - - ZOOKEEPER-54. remove sleeps in the tests. (phunt) - - ZOOKEEPER-55. build.xml failes to retrieve a release number from SVN and - the ant target "dist" fails (Andrew Kornev) - - ZOOKEEPER-89. invoke WhenOwnerListener.whenNotOwner() when the ZK - connection fails (james strachan) - - ZOOKEEPER-90. invoke WhenOwnerListener.whenNotOwner() when the ZK - session expires and the znode is the leader (james strachan) - - ZOOKEEPER-82. Make the ZooKeeperServer more DI friendly. (Hiram Chirino via - mahadev) - - ZOOKEEPER-110. Build script relies on svnant, which is not compatible - with subversion 1.5 working copies (Jakob Homan) - - ZOOKEEPER-111. Significant cleanup of existing tests. (Patrick Hunt via - mahadev) - - ZOOKEEPER-122. Fix NPE in jute's Utils.toCSVString. (Anthony Urso via - mahadev) - - ZOOKEEPER-123. Fix the wrong class is specified for the logger. (Jakob Homan - via mahadev) - - ZOOKEEPER-2. Fix synchronization issues in QuorumPeer and FastLeader - election. (Flavio Paiva Junqueira via mahadev) - - ZOOKEEPER-125. Remove unwanted class declaration in FastLeaderElection. - (Flavio Paiva Junqueira via mahadev) - - ZOOKEEPER-61. Address (remove) use of sleep(#) in client/server test cases. - (phunt) - - ZOOKEEPER-75. cleanup the library directory (phunt) - - ZOOKEEPER-109. cleanup of NPE and Resource issue nits found by static - analysis (phunt) - - ZOOKEEPER-76. Commit 677109 removed the cobertura library, but not the - build targets. (phunt) - - ZOOKEEPER-63. Race condition in client close() operation. (phunt via breed) - - ZOOKEEPER-70. Add skeleton forrest doc structure for ZooKeeper (phunt) - - ZOOKEEPER-79. Document jacob's leader election on the wiki recipes page - (Flavio Junqueira) - - ZOOKEEPER-73. Move ZK wiki from SourceForge to Apache (phunt) - - ZOOKEEPER-72. Initial creation/setup of ZooKeeper ASF site. (phunt) - - ZOOKEEPER-71. Determine what to do re ZooKeeper Changelog(s) (mahadev) - - ZOOKEEPER-68. parseACLs in ZooKeeper.java fails to parse elements of ACL, - should be lastIndexOf rather than IndexOf (mahadev) - - ZOOKEEPER-130. update build.xml to support apache release process. - (phunt via mahadev) - - ZOOKEEPER-131. Fix Old leader election can elect a dead leader over and over - again. (breed via mahadev) - - ZOOKEEPER-137. client watcher objects can lose events (Patrick Hunt via breed) - - ZOOKEEPER-117. threading issues in Leader election (Flavio Junqueira and - Patrick Hunt via breed) - - ZOOKEEPER-128. test coverage on async client operations needs to be improved - (phunt) - - ZOOKEEPER-127. Use of non-standard election ports in config breaks services - (Mark Harwood and Flavio Junqueira via breed) - - ZOOKEEPER-53. tests failing on solaris. (phunt) - - ZOOKEEPER-172. FLE Test (Flavio Junqueira via breed) - - ZOOKEEPER-41. Sample startup script (mahadev) - - ZOOKEEPER-33. Better ACL management (Mahadev Konar) - - ZOOKEEPER-49. SetACL does not work (breed) - - ZOOKEEPER-20. Child watches are not triggered when the node is deleted - (phunt) - - ZOOKEEPER-15. handle failure better in build.xml:test (phunt) - - ZOOKEEPER-11. ArrayList is used instead of List (phunt) - - ZOOKEEPER-45. Restructure the SVN repository after initial import (phunt) - - ZOOKEEPER-1. Initial ZooKeeper code contribution from Yahoo! (phunt) diff --git a/NOTICE.txt b/NOTICE.txt index 73580508223..877a3f50637 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache ZooKeeper -Copyright 2009-2011 The Apache Software Foundation +Copyright 2009-2017 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/README.txt b/README.md similarity index 94% rename from README.txt rename to README.md index 3495f47cb41..d056f5afb91 100644 --- a/README.txt +++ b/README.md @@ -1,10 +1,10 @@ For the latest information about ZooKeeper, please visit our website at: - http://hadoop.apache.org/zookeeper/ + http://zookeeper.apache.org/ and our wiki, at: - http://wiki.apache.org/hadoop/ZooKeeper + https://cwiki.apache.org/confluence/display/ZOOKEEPER Full documentation for this release can also be found in docs/index.html diff --git a/bin/README.txt b/bin/README.txt index 7924dd6df9a..e70506dd711 100644 --- a/bin/README.txt +++ b/bin/README.txt @@ -1,6 +1,6 @@ -This directory contain scripts that allow easy access (classpath in particular) -to the ZooKeeper server and command line client. - -Files ending in .sh are unix and cygwin compatible - -Files ending in .cmd are msdos/windows compatible +This directory contain scripts that allow easy access (classpath in particular) +to the ZooKeeper server and command line client. + +Files ending in .sh are unix and cygwin compatible + +Files ending in .cmd are msdos/windows compatible diff --git a/bin/zkCleanup.sh b/bin/zkCleanup.sh index f75731f64bd..38ee2e8be4a 100755 --- a/bin/zkCleanup.sh +++ b/bin/zkCleanup.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -27,8 +27,8 @@ # use POSTIX interface, symlink is followed automatically ZOOBIN="${BASH_SOURCE-$0}" -ZOOBIN=`dirname ${ZOOBIN}` -ZOOBINDIR=`cd ${ZOOBIN}; pwd` +ZOOBIN="$(dirname "${ZOOBIN}")" +ZOOBINDIR="$(cd "${ZOOBIN}"; pwd)" if [ -e "$ZOOBIN/../libexec/zkEnv.sh" ]; then . "$ZOOBINDIR"/../libexec/zkEnv.sh @@ -36,16 +36,16 @@ else . "$ZOOBINDIR"/zkEnv.sh fi -ZOODATADIR=$(grep "^[[:space:]]*dataDir=" "$ZOOCFG" | sed -e 's/.*=//') -ZOODATALOGDIR=$(grep "^[[:space:]]*dataLogDir=" "$ZOOCFG" | sed -e 's/.*=//') +ZOODATADIR="$(grep "^[[:space:]]*dataDir=" "$ZOOCFG" | sed -e 's/.*=//')" +ZOODATALOGDIR="$(grep "^[[:space:]]*dataLogDir=" "$ZOOCFG" | sed -e 's/.*=//')" if [ "x$ZOODATALOGDIR" = "x" ] then -$JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ +"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ -cp "$CLASSPATH" $JVMFLAGS \ org.apache.zookeeper.server.PurgeTxnLog "$ZOODATADIR" $* else -$JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ +"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ -cp "$CLASSPATH" $JVMFLAGS \ org.apache.zookeeper.server.PurgeTxnLog "$ZOODATALOGDIR" "$ZOODATADIR" $* fi diff --git a/bin/zkCli.cmd b/bin/zkCli.cmd index 3ee9d6d569b..0ffa0300e27 100644 --- a/bin/zkCli.cmd +++ b/bin/zkCli.cmd @@ -1,24 +1,24 @@ -@echo off -REM Licensed to the Apache Software Foundation (ASF) under one or more -REM contributor license agreements. See the NOTICE file distributed with -REM this work for additional information regarding copyright ownership. -REM The ASF licenses this file to You under the Apache License, Version 2.0 -REM (the "License"); you may not use this file except in compliance with -REM the License. You may obtain a copy of the License at -REM -REM http://www.apache.org/licenses/LICENSE-2.0 -REM -REM Unless required by applicable law or agreed to in writing, software -REM distributed under the License is distributed on an "AS IS" BASIS, -REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -REM See the License for the specific language governing permissions and -REM limitations under the License. - -setlocal -call "%~dp0zkEnv.cmd" - -set ZOOMAIN=org.apache.zookeeper.ZooKeeperMain -java "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% %* - -endlocal - +@echo off +REM Licensed to the Apache Software Foundation (ASF) under one or more +REM contributor license agreements. See the NOTICE file distributed with +REM this work for additional information regarding copyright ownership. +REM The ASF licenses this file to You under the Apache License, Version 2.0 +REM (the "License"); you may not use this file except in compliance with +REM the License. You may obtain a copy of the License at +REM +REM http://www.apache.org/licenses/LICENSE-2.0 +REM +REM Unless required by applicable law or agreed to in writing, software +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. + +setlocal +call "%~dp0zkEnv.cmd" + +set ZOOMAIN=org.apache.zookeeper.ZooKeeperMain +call %JAVA% "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% %* + +endlocal + diff --git a/bin/zkCli.sh b/bin/zkCli.sh index 27869a6b89d..992a91367d9 100755 --- a/bin/zkCli.sh +++ b/bin/zkCli.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -27,8 +27,8 @@ # use POSTIX interface, symlink is followed automatically ZOOBIN="${BASH_SOURCE-$0}" -ZOOBIN=`dirname ${ZOOBIN}` -ZOOBINDIR=`cd ${ZOOBIN}; pwd` +ZOOBIN="$(dirname "${ZOOBIN}")" +ZOOBINDIR="$(cd "${ZOOBIN}"; pwd)" if [ -e "$ZOOBIN/../libexec/zkEnv.sh" ]; then . "$ZOOBINDIR"/../libexec/zkEnv.sh @@ -36,6 +36,6 @@ else . "$ZOOBINDIR"/zkEnv.sh fi -$JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ +"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ -cp "$CLASSPATH" $CLIENT_JVMFLAGS $JVMFLAGS \ - org.apache.zookeeper.ZooKeeperMain $@ + org.apache.zookeeper.ZooKeeperMain "$@" diff --git a/bin/zkEnv.cmd b/bin/zkEnv.cmd index 5fda353302f..41eed11ed5e 100644 --- a/bin/zkEnv.cmd +++ b/bin/zkEnv.cmd @@ -1,34 +1,49 @@ -@echo off -REM Licensed to the Apache Software Foundation (ASF) under one or more -REM contributor license agreements. See the NOTICE file distributed with -REM this work for additional information regarding copyright ownership. -REM The ASF licenses this file to You under the Apache License, Version 2.0 -REM (the "License"); you may not use this file except in compliance with -REM the License. You may obtain a copy of the License at -REM -REM http://www.apache.org/licenses/LICENSE-2.0 -REM -REM Unless required by applicable law or agreed to in writing, software -REM distributed under the License is distributed on an "AS IS" BASIS, -REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -REM See the License for the specific language governing permissions and -REM limitations under the License. - -set ZOOCFGDIR=%~dp0%..\conf -set ZOO_LOG_DIR=%~dp0%.. -set ZOO_LOG4J_PROP=INFO,CONSOLE - -REM for sanity sake assume Java 1.6 -REM see: http://java.sun.com/javase/6/docs/technotes/tools/windows/java.html - -REM add the zoocfg dir to classpath -set CLASSPATH=%ZOOCFGDIR% - -REM make it work in the release -SET CLASSPATH=%~dp0..\*;%~dp0..\lib\*;%CLASSPATH% - -REM make it work for developers -SET CLASSPATH=%~dp0..\build\classes;%~dp0..\build\lib\*;%CLASSPATH% - -set ZOOCFG=%ZOOCFGDIR%\zoo.cfg - +@echo off +REM Licensed to the Apache Software Foundation (ASF) under one or more +REM contributor license agreements. See the NOTICE file distributed with +REM this work for additional information regarding copyright ownership. +REM The ASF licenses this file to You under the Apache License, Version 2.0 +REM (the "License"); you may not use this file except in compliance with +REM the License. You may obtain a copy of the License at +REM +REM http://www.apache.org/licenses/LICENSE-2.0 +REM +REM Unless required by applicable law or agreed to in writing, software +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. + +set ZOOCFGDIR=%~dp0%..\conf +set ZOO_LOG_DIR=%~dp0%.. +set ZOO_LOG4J_PROP=INFO,CONSOLE + +REM for sanity sake assume Java 1.6 +REM see: http://java.sun.com/javase/6/docs/technotes/tools/windows/java.html + +REM add the zoocfg dir to classpath +set CLASSPATH=%ZOOCFGDIR% + +REM make it work in the release +SET CLASSPATH=%~dp0..\*;%~dp0..\lib\*;%CLASSPATH% + +REM make it work for developers +SET CLASSPATH=%~dp0..\build\classes;%~dp0..\build\lib\*;%CLASSPATH% + +set ZOOCFG=%ZOOCFGDIR%\zoo.cfg + +@REM setup java environment variables + +if not defined JAVA_HOME ( + echo Error: JAVA_HOME is not set. + goto :eof +) + +set JAVA_HOME=%JAVA_HOME:"=% + +if not exist "%JAVA_HOME%"\bin\java.exe ( + echo Error: JAVA_HOME is incorrectly set. + goto :eof +) + +set JAVA="%JAVA_HOME%"\bin\java diff --git a/bin/zkEnv.sh b/bin/zkEnv.sh index c074212a34c..43cbc2235a5 100755 --- a/bin/zkEnv.sh +++ b/bin/zkEnv.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -23,8 +23,8 @@ # or the conf directory that is # a sibling of this script's directory -ZOOBINDIR=${ZOOBINDIR:-/usr/bin} -ZOOKEEPER_PREFIX=${ZOOBINDIR}/.. +ZOOBINDIR="${ZOOBINDIR:-/usr/bin}" +ZOOKEEPER_PREFIX="${ZOOBINDIR}/.." if [ "x$ZOOCFGDIR" = "x" ] then @@ -75,14 +75,20 @@ do CLASSPATH="$i:$CLASSPATH" done -#make it work in the release -if [ -d ${ZOOKEEPER_PREFIX}/share/zookeeper ]; then - LIBPATH="${ZOOKEEPER_PREFIX}"/share/zookeeper/*.jar +#make it work in the binary package +#(use array for LIBPATH to account for spaces within wildcard expansion) +if [ -e "${ZOOKEEPER_PREFIX}"/share/zookeeper/zookeeper-*.jar ]; then + LIBPATH=("${ZOOKEEPER_PREFIX}"/share/zookeeper/*.jar) else - LIBPATH="${ZOOBINDIR}"/../lib/*.jar + #release tarball format + for i in "$ZOOBINDIR"/../zookeeper-*.jar + do + CLASSPATH="$i:$CLASSPATH" + done + LIBPATH=("${ZOOBINDIR}"/../lib/*.jar) fi -for i in ${LIBPATH} +for i in "${LIBPATH[@]}" do CLASSPATH="$i:$CLASSPATH" done @@ -106,4 +112,4 @@ then CLASSPATH=`cygpath -wp "$CLASSPATH"` fi -#echo "CLASSPATH=$CLASSPATH" +#echo "CLASSPATH=$CLASSPATH" \ No newline at end of file diff --git a/bin/zkServer.cmd b/bin/zkServer.cmd index 1c2061f62f7..6b4cf026c12 100644 --- a/bin/zkServer.cmd +++ b/bin/zkServer.cmd @@ -1,25 +1,24 @@ -@echo off -REM Licensed to the Apache Software Foundation (ASF) under one or more -REM contributor license agreements. See the NOTICE file distributed with -REM this work for additional information regarding copyright ownership. -REM The ASF licenses this file to You under the Apache License, Version 2.0 -REM (the "License"); you may not use this file except in compliance with -REM the License. You may obtain a copy of the License at -REM -REM http://www.apache.org/licenses/LICENSE-2.0 -REM -REM Unless required by applicable law or agreed to in writing, software -REM distributed under the License is distributed on an "AS IS" BASIS, -REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -REM See the License for the specific language governing permissions and -REM limitations under the License. - -setlocal -call "%~dp0zkEnv.cmd" - -set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain -echo on -java "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %* - -endlocal - +@echo off +REM Licensed to the Apache Software Foundation (ASF) under one or more +REM contributor license agreements. See the NOTICE file distributed with +REM this work for additional information regarding copyright ownership. +REM The ASF licenses this file to You under the Apache License, Version 2.0 +REM (the "License"); you may not use this file except in compliance with +REM the License. You may obtain a copy of the License at +REM +REM http://www.apache.org/licenses/LICENSE-2.0 +REM +REM Unless required by applicable law or agreed to in writing, software +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. + +setlocal +call "%~dp0zkEnv.cmd" + +set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain +echo on +call %JAVA% "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %* + +endlocal diff --git a/bin/zkServer.sh b/bin/zkServer.sh index d9858d140ce..51dbe9f581e 100755 --- a/bin/zkServer.sh +++ b/bin/zkServer.sh @@ -21,6 +21,19 @@ # relative to the canonical path of this script. # + + +# use POSTIX interface, symlink is followed automatically +ZOOBIN="${BASH_SOURCE-$0}" +ZOOBIN="$(dirname "${ZOOBIN}")" +ZOOBINDIR="$(cd "${ZOOBIN}"; pwd)" + +if [ -e "$ZOOBIN/../libexec/zkEnv.sh" ]; then + . "$ZOOBINDIR/../libexec/zkEnv.sh" +else + . "$ZOOBINDIR/zkEnv.sh" +fi + # See the following page for extensive details on setting # up the JVM to accept JMX remote management: # http://java.sun.com/javase/6/docs/technotes/guides/management/agent.html @@ -30,30 +43,40 @@ then JMXLOCALONLY=false fi -if [ "x$JMXDISABLE" = "x" ] +if [ "x$JMXDISABLE" = "x" ] || [ "$JMXDISABLE" = 'false' ] then - echo "JMX enabled by default" >&2 + echo "ZooKeeper JMX enabled by default" >&2 + if [ "x$JMXPORT" = "x" ] + then # for some reason these two options are necessary on jdk6 on Ubuntu # accord to the docs they are not necessary, but otw jconsole cannot # do a local attach ZOOMAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=$JMXLOCALONLY org.apache.zookeeper.server.quorum.QuorumPeerMain" + else + if [ "x$JMXAUTH" = "x" ] + then + JMXAUTH=false + fi + if [ "x$JMXSSL" = "x" ] + then + JMXSSL=false + fi + if [ "x$JMXLOG4J" = "x" ] + then + JMXLOG4J=true + fi + echo "ZooKeeper remote JMX Port set to $JMXPORT" >&2 + echo "ZooKeeper remote JMX authenticate set to $JMXAUTH" >&2 + echo "ZooKeeper remote JMX ssl set to $JMXSSL" >&2 + echo "ZooKeeper remote JMX log4j set to $JMXLOG4J" >&2 + ZOOMAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=$JMXPORT -Dcom.sun.management.jmxremote.authenticate=$JMXAUTH -Dcom.sun.management.jmxremote.ssl=$JMXSSL -Dzookeeper.jmx.log4j.disable=$JMXLOG4J org.apache.zookeeper.server.quorum.QuorumPeerMain" + fi else echo "JMX disabled by user request" >&2 ZOOMAIN="org.apache.zookeeper.server.quorum.QuorumPeerMain" fi -# use POSTIX interface, symlink is followed automatically -ZOOBIN="${BASH_SOURCE-$0}" -ZOOBIN=`dirname ${ZOOBIN}` -ZOOBINDIR=`cd ${ZOOBIN}; pwd` - -if [ -e "$ZOOBIN/../libexec/zkEnv.sh" ]; then - . "$ZOOBINDIR"/../libexec/zkEnv.sh -else - . "$ZOOBINDIR"/zkEnv.sh -fi - -if [ "x$SERVER_JVMFLAGS" ] +if [ "x$SERVER_JVMFLAGS" != "x" ] then JVMFLAGS="$SERVER_JVMFLAGS $JVMFLAGS" fi @@ -64,7 +87,7 @@ then fi # if we give a more complicated path to the config, don't screw around in $ZOOCFGDIR -if [ "x`dirname $ZOOCFG`" != "x$ZOOCFGDIR" ] +if [ "x$(dirname "$ZOOCFG")" != "x$ZOOCFGDIR" ] then ZOOCFG="$2" fi @@ -80,15 +103,27 @@ fi echo "Using config: $ZOOCFG" >&2 -if [ -z $ZOOPIDFILE ]; then - ZOO_DATADIR=$(grep "^[[:space:]]*dataDir" "$ZOOCFG" | sed -e 's/.*=//') +case "$OSTYPE" in +*solaris*) + GREP=/usr/xpg4/bin/grep + ;; +*) + GREP=grep + ;; +esac +if [ -z "$ZOOPIDFILE" ]; then + ZOO_DATADIR="$($GREP "^[[:space:]]*dataDir" "$ZOOCFG" | sed -e 's/.*=//')" if [ ! -d "$ZOO_DATADIR" ]; then mkdir -p "$ZOO_DATADIR" fi ZOOPIDFILE="$ZOO_DATADIR/zookeeper_server.pid" else # ensure it exists, otw stop will fail - mkdir -p $(dirname "$ZOOPIDFILE") + mkdir -p "$(dirname "$ZOOPIDFILE")" +fi + +if [ ! -w "$ZOO_LOG_DIR" ] ; then +mkdir -p "$ZOO_LOG_DIR" fi _ZOO_DAEMON_OUT="$ZOO_LOG_DIR/zookeeper.out" @@ -96,17 +131,25 @@ _ZOO_DAEMON_OUT="$ZOO_LOG_DIR/zookeeper.out" case $1 in start) echo -n "Starting zookeeper ... " - if [ -f $ZOOPIDFILE ]; then - if kill -0 `cat $ZOOPIDFILE` > /dev/null 2>&1; then - echo $command already running as process `cat $ZOOPIDFILE`. + if [ -f "$ZOOPIDFILE" ]; then + if kill -0 `cat "$ZOOPIDFILE"` > /dev/null 2>&1; then + echo $command already running as process `cat "$ZOOPIDFILE"`. exit 0 fi fi - nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ + nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null & if [ $? -eq 0 ] then - if /bin/echo -n $! > "$ZOOPIDFILE" + case "$OSTYPE" in + *solaris*) + /bin/echo "${!}\\c" > "$ZOOPIDFILE" + ;; + *) + /bin/echo -n $! > "$ZOOPIDFILE" + ;; + esac + if [ $? -eq 0 ]; then sleep 1 echo STARTED @@ -120,29 +163,32 @@ start) fi ;; start-foreground) - $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ + ZOO_CMD=(exec "$JAVA") + if [ "${ZOO_NOEXEC}" != "" ]; then + ZOO_CMD=("$JAVA") + fi + "${ZOO_CMD[@]}" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" ;; print-cmd) - echo "$JAVA -Dzookeeper.log.dir=\"${ZOO_LOG_DIR}\" -Dzookeeper.root.logger=\"${ZOO_LOG4J_PROP}\" -cp \"$CLASSPATH\" $JVMFLAGS $ZOOMAIN \"$ZOOCFG\" > \"$_ZOO_DAEMON_OUT\" 2>&1 < /dev/null" + echo "\"$JAVA\" -Dzookeeper.log.dir=\"${ZOO_LOG_DIR}\" -Dzookeeper.root.logger=\"${ZOO_LOG4J_PROP}\" -cp \"$CLASSPATH\" $JVMFLAGS $ZOOMAIN \"$ZOOCFG\" > \"$_ZOO_DAEMON_OUT\" 2>&1 < /dev/null" ;; stop) echo -n "Stopping zookeeper ... " if [ ! -f "$ZOOPIDFILE" ] then - echo "error: could not find file $ZOOPIDFILE" - exit 1 + echo "no zookeeper to stop (could not find file $ZOOPIDFILE)" else $KILL -9 $(cat "$ZOOPIDFILE") rm "$ZOOPIDFILE" echo STOPPED - exit 0 fi + exit 0 ;; upgrade) shift echo "upgrading the servers to 3.*" - $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ + "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ -cp "$CLASSPATH" $JVMFLAGS org.apache.zookeeper.server.upgrade.UpgradeMain ${@} echo "Upgrading ... " ;; @@ -154,7 +200,16 @@ restart) ;; status) # -q is necessary on some versions of linux where nc returns too quickly, and no stat result is output - STAT=`echo stat | nc -q 1 localhost $(grep "^[[:space:]]*clientPort" "$ZOOCFG" | sed -e 's/.*=//') 2> /dev/null| grep Mode` + clientPortAddress=`$GREP "^[[:space:]]*clientPortAddress[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.*=//'` + if ! [ $clientPortAddress ] + then + clientPortAddress="localhost" + fi + clientPort=`$GREP "^[[:space:]]*clientPort[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.*=//'` + STAT=`"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ + -cp "$CLASSPATH" $JVMFLAGS org.apache.zookeeper.client.FourLetterWordMain \ + $clientPortAddress $clientPort srvr 2> /dev/null \ + | $GREP Mode` if [ "x$STAT" = "x" ] then echo "Error contacting service. It is probably not running." diff --git a/build.xml b/build.xml index 219ce54f576..233bfa15d0b 100644 --- a/build.xml +++ b/build.xml @@ -17,21 +17,28 @@ limitations under the License. --> - + + + + - + - - + + @@ -68,6 +75,8 @@ + + @@ -76,6 +85,7 @@ + @@ -83,7 +93,7 @@ + value="http://docs.oracle.com/javase/6/docs/api/" /> @@ -98,9 +108,9 @@ - + + value="https://repo1.maven.org/maven2/org/apache/ivy/ivy" /> @@ -108,6 +118,15 @@ + + + + + + + + @@ -137,6 +156,7 @@ + @@ -166,6 +186,24 @@ + + + + + + + + + + + + + + @@ -237,7 +275,7 @@ + includes="org/apache/jute/**" debug="on" classpath="${ivy.lib}/audience-annotations-${audience-annotations.version}.jar" /> @@ -276,7 +314,7 @@ includes="org/apache/zookeeper/version/util/**" debug="on" /> - + @@ -291,7 +329,7 @@ - + @@ -304,9 +342,9 @@ - + + target="${javac.target}" source="${javac.source}" debug="on" classpath="${ivy.lib}/audience-annotations-${audience-annotations.version}.jar" /> @@ -328,12 +366,12 @@ - - @@ -344,16 +382,21 @@ - - + + + + @@ -391,7 +434,7 @@ - + @@ -405,7 +448,7 @@ - @@ -457,22 +500,24 @@ windowtitle="${Name} ${version} API" doctitle="${Name} ${version} API" bottom="Copyright &copy; ${year} The Apache Software Foundation" + doclet="org.apache.yetus.audience.tools.IncludePublicAnnotationsStandardDoclet" + docletpath="${ivy.lib}/audience-annotations-${audience-annotations.version}.jar" > - - - - - - - - - - + + + + - - + + + + + + + + @@ -493,7 +538,7 @@ + outputproperty="revision" errorproperty="revision.error" failonerror="true"> @@ -507,7 +552,7 @@ - + @@ -525,8 +570,8 @@ - - + + @@ -548,7 +593,7 @@ - + @@ -566,13 +611,13 @@ - - + + - + @@ -632,11 +677,115 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -661,9 +810,10 @@ distribution directory so contribs know where to install to.--> + - + @@ -689,26 +839,19 @@ + pomfile="${dist.maven.dir}/${name}.pom" + templatefile="${basedir}/src/pom.template"> - - + + - - - - - - - - + tofile="${dist.maven.dir}/${final.name}-tests.jar"/> + + @@ -733,16 +876,19 @@ - - - + + + + + + @@ -811,7 +957,7 @@ - @@ -832,7 +978,7 @@ - + @@ -850,10 +996,21 @@ + + + + + + + + + + @@ -879,7 +1036,7 @@ - + @@ -891,7 +1048,7 @@ - @@ -983,6 +1140,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The version is ${zookeeper-pom.version} + + + + + + + + + + + @@ -1024,6 +1250,8 @@ + + @@ -1036,7 +1264,14 @@ - + + + + + + + + @@ -1095,6 +1330,9 @@ + + + Tests failed! @@ -1155,7 +1393,7 @@ if="cppunit" description="to run cppunit test cases"> - + @@ -1234,12 +1472,14 @@ - + + + - + @@ -1258,7 +1498,11 @@ - + + + + + @@ -1332,7 +1576,7 @@ - Tests failed! + @@ -1347,32 +1591,20 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - @@ -1391,6 +1623,7 @@ + @@ -1469,6 +1702,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -1557,7 +1811,7 @@ description="Create eclipse project files"> - + diff --git a/conf/zoo_sample.cfg b/conf/zoo_sample.cfg index aafb3247239..a5a2c0bbe3e 100644 --- a/conf/zoo_sample.cfg +++ b/conf/zoo_sample.cfg @@ -12,6 +12,9 @@ syncLimit=5 dataDir=/tmp/zookeeper # the port at which the clients will connect clientPort=2181 +# the maximum number of client connections. +# increase this if you need to handle more clients +#maxClientCnxns=60 # # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. diff --git a/docs/bookkeeperConfig.html b/docs/bookkeeperConfig.html index 42a2c0925ff..ff8d4e8fdad 100644 --- a/docs/bookkeeperConfig.html +++ b/docs/bookkeeperConfig.html @@ -20,7 +20,7 @@ |breadtrail +--> @@ -61,10 +61,10 @@ +-->
  • -Project +Project
  • -Wiki +Wiki
  • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
    @@ -374,7 +374,7 @@

    ZooKeeper Metadata

    @@ -61,10 +61,10 @@ +-->
    • -Project +Project
    • -Wiki +Wiki
    • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
      @@ -684,7 +684,7 @@

      Closing out ledgers

      @@ -61,10 +61,10 @@ +-->
      • -Project +Project
      • -Wiki +Wiki
      • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
        @@ -1073,7 +1073,7 @@

        Deleting a ledger

        @@ -61,10 +61,10 @@ +-->
        • -Project +Project
        • -Wiki +Wiki
        • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
          @@ -253,7 +253,7 @@

          Pre-requisites

          Download

          BookKeeper is distributed along with ZooKeeper. To get a ZooKeeper distribution, download a recent - + stable release from one of the Apache Download Mirrors.

          @@ -438,7 +438,7 @@

          Example

          @@ -61,10 +61,10 @@ +-->
          • -Project +Project
          • -Wiki +Wiki
          • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
            @@ -602,7 +602,7 @@

            Reading a stream of bytes

            @@ -61,10 +61,10 @@ +-->
            • -Project +Project
            • -Wiki +Wiki
            • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
              @@ -221,7 +221,7 @@

              ZooKeeper: Because Coordinating Distributed Systems is a Zoo

              The following documents describe concepts and procedures to get you started using ZooKeeper. If you have more questions, please - ask the mailing list or browse the + ask the mailing list or browse the archives.

              @@ -320,11 +320,11 @@

              ZooKeeper: Because Coordinating Distributed Systems is a Zoo

              @@ -380,7 +380,7 @@

              ZooKeeper: Because Coordinating Distributed Systems is a Zoo

              @@ -61,10 +61,10 @@ +-->
              • -Project +Project
              • -Wiki +Wiki
              • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                @@ -898,7 +898,7 @@

                Complete Source Listings

                @@ -61,10 +61,10 @@ +-->
                • -Project +Project
                • -Wiki +Wiki
                • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                  @@ -209,7 +209,7 @@

                  Site Linkmap Table of Contents

                  • -Hadoop  ___________________  site +ZooKeeper  ___________________  site
                    • @@ -380,19 +380,19 @@

                      Site Linkmap Table of Contents

                      • -Wiki  ___________________  wiki +Wiki  ___________________  wiki
                      • -FAQ  ___________________  faq +FAQ  ___________________  faq
                      @@ -424,7 +424,7 @@

                      Site Linkmap Table of Contents

                      @@ -61,10 +61,10 @@ +-->
                      • -Project +Project
                      • -Wiki +Wiki
                      • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                        @@ -454,7 +454,7 @@

                        Double Barriers

                      • if p is the lowest process - node in L, wait on highest process node in P

                        + node in L, wait on highest process node in L

                      • @@ -905,7 +905,7 @@

                        Leader Election

                      • -

                        Watch for changes on "ELECTION/n_j", where j is the smallest +

                        Watch for changes on "ELECTION/n_j", where j is the largest sequence number such that j < i and n_j is a znode in C;

                      • @@ -932,7 +932,7 @@

                        Leader Election

                      • Otherwise, watch for changes on "ELECTION/n_j", where j is the - smallest sequence number such that j < i and n_j is a znode in C; + largest sequence number such that j < i and n_j is a znode in C;

                      • @@ -940,7 +940,7 @@

                        Leader Election

                        Note that the znode having no preceding znode on the list of children does not imply that the creator of this znode is aware that it is - the current leader. Applications may consider creating a separate to znode + the current leader. Applications may consider creating a separate znode to acknowledge that the leader has executed the leader procedure.

                        @@ -964,7 +964,7 @@

                        Leader Election

                        @@ -61,10 +61,10 @@ +-->
                        • -Project +Project
                        • -Wiki +Wiki
                        • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                          @@ -202,1685 +202,1342 @@ PDF -icon
                          PDF
                          -

                          ZooKeeper 3.0.0 Release Notes

                          -
                          -
                          -
                          -
                          - - - - - -

                          -These release notes include new developer and user facing incompatibilities, features, and major improvements. -

                          - - + +

                          Improvement +

                            - -
                          • -

                            -Migration Instructions -

                            +
                          • [ZOOKEEPER-2479] - Add 'electionTimeTaken' value in LeaderMXBean and FollowerMXBean
                          • - -
                          • -

                            -Changes -

                            +
                          • [ZOOKEEPER-2507] - C unit test improvement: line break between 'ZooKeeper server started' and 'Running' +
                          • +
                          • [ZOOKEEPER-2557] - Update gitignore to account for other file extensions +
                          • +
                          • [ZOOKEEPER-2594] - Use TLS for downloading artifacts during build +
                          • +
                          • [ZOOKEEPER-2620] - Add comments to testReadOnlySnapshotDir and testReadOnlyTxnLogDir indicating that the tests will fail when run as root +
                          • +
                          • [ZOOKEEPER-2672] - Remove CHANGE.txt +
                          • +
                          • [ZOOKEEPER-2682] - Make it optional to fail build on test failure
                          • -
                          - - - -

                          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. -

                          + +

                          New Feature +

                          + +

                          Test +

                          +
                            +
                          • [ZOOKEEPER-2502] - Flaky Test: org.apache.zookeeper.server.quorum.CnxManagerTest.testCnxFromFutureVersion
                          • - -
                          • -

                            -Migrating Server Configuration -

                            +
                          • [ZOOKEEPER-2650] - Test Improvement by adding more QuorumPeer Auth related test cases +
                          • +
                          • [ZOOKEEPER-2656] - Fix ServerConfigTest#testValidArguments test case failures +
                          • +
                          • [ZOOKEEPER-2664] - ClientPortBindTest#testBindByAddress may fail due to "No such device" exception +
                          • +
                          • [ZOOKEEPER-2665] - Port QA github pull request build to branch 3.4 and 3.5
                          • +
                          • [ZOOKEEPER-2716] - Flaky Test: org.apache.zookeeper.server.SessionTrackerTest.testAddSessionAfterSessionExpiry +
                          • +
                          + Release Notes - ZooKeeper - Version 3.4.9 + +

                          Sub-task +

                          + - -

                          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

                          -
                            - -
                          1. -

                            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)

                            + +

                            Bug +

                            +
                              +
                            • [ZOOKEEPER-1676] - C client zookeeper_interest returning ZOK on Connection Loss
                            • - -
                            • -

                              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-2133] - zkperl: Segmentation fault if getting a node with null value
                            • - -
                            • -

                              -ZOOKEEPER-18 removed KeeperStateChanged, use KeeperStateDisconnected instead

                              +
                            • [ZOOKEEPER-2141] - ACL cache in DataTree never removes entries
                            • - -
                          -

                          -Also see the current java API - -

                          - -

                          C API

                          -
                            - -
                          1. -

                            A number of constants used in the client ZooKeeper API were renamed in order to reduce namespace collision, see ZOOKEEPER-6 for full details

                            +
                          2. [ZOOKEEPER-2195] - fsync.warningthresholdms in zoo.cfg not working
                          3. - -
                          - -

                          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-2243] - Supported platforms is completely out of date +
                        • +
                        • [ZOOKEEPER-2247] - Zookeeper service becomes unavailable when leader fails to write transaction log +
                        • +
                        • [ZOOKEEPER-2283] - traceFile property is not used in the ZooKeeper, it should be removed from documentation +
                        • +
                        • [ZOOKEEPER-2294] - Ant target generate-clover-reports is broken +
                        • +
                        • [ZOOKEEPER-2375] - Prevent multiple initialization of login object in each ZooKeeperSaslClient instance +
                        • +
                        • [ZOOKEEPER-2379] - recent commit broke findbugs qabot check +
                        • +
                        • [ZOOKEEPER-2385] - Zookeeper trunk build is failing on windows +
                        • +
                        • [ZOOKEEPER-2405] - getTGT() in Login.java mishandles confidential information +
                        • +
                        • [ZOOKEEPER-2450] - Upgrade Netty version due to security vulnerability (CVE-2014-3488) +
                        • +
                        • [ZOOKEEPER-2452] - Back-port ZOOKEEPER-1460 to 3.4 for IPv6 literal address support. +
                        • +
                        • [ZOOKEEPER-2477] - documentation should refer to Java cli shell and not C cli shell +
                        • +
                        • [ZOOKEEPER-2498] - Potential resource leak in C client when processing unexpected / out of order response +
                        • +
                        + +

                        Improvement +

                        -

                        - -The following must be run once, and only once, when upgrading the ZooKeeper server instances to version 3.0.0. -

                        -
                        -
                        Note
                        -
                        - -

                        - 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). -

                        - -
                        -
                        -
                          - -
                        1. -

                          Shutdown the ZooKeeper server cluster.

                          + Release Notes - ZooKeeper - Version 3.4.8 + +

                          Bug +

                          +
                            +
                          • [ZOOKEEPER-1929] - std::length_error on update children
                          • - -
                          • -

                            Backup your <dataLogDir> and <dataDir> directories

                            +
                          • [ZOOKEEPER-2211] - PurgeTxnLog does not correctly purge when snapshots and logs are at different locations
                          • - -
                          • -

                            Run upgrade using

                            +
                          • [ZOOKEEPER-2229] - Several four-letter words are undocumented. +
                          • +
                          • [ZOOKEEPER-2281] - ZK Server startup fails if there are spaces in the JAVA_HOME path +
                          • +
                          • [ZOOKEEPER-2295] - TGT refresh time logic is wrong +
                          • +
                          • [ZOOKEEPER-2311] - assert in setup_random +
                          • +
                          • [ZOOKEEPER-2337] - Fake "invalid" hostnames used in tests are sometimes valid +
                          • +
                          • [ZOOKEEPER-2340] - JMX is disabled even if JMXDISABLE is false +
                          • +
                          • [ZOOKEEPER-2347] - Deadlock shutting down zookeeper +
                          • +
                          • [ZOOKEEPER-2360] - Update commons collections version used by tests/releaseaudit +
                          • +
                          • [ZOOKEEPER-2412] - leader zk out of memory, and leader db lastZxid is not update when process set data. +
                          • +
                          + + Release Notes - ZooKeeper - Version 3.4.7 +

                          Sub-task +

                            - -
                          • -

                            -bin/zkServer.sh upgrade <dataLogDir> <dataDir> -

                            +
                          • [ZOOKEEPER-1866] - ClientBase#createClient is failing frequently +
                          • +
                          • [ZOOKEEPER-1868] - Server not coming back up in QuorumZxidSyncTest +
                          • +
                          • [ZOOKEEPER-1872] - QuorumPeer is not shutdown in few cases +
                          • +
                          • [ZOOKEEPER-1904] - WatcherTest#testWatchAutoResetWithPending is failing +
                          • +
                          • [ZOOKEEPER-1905] - ZKClients are hitting KeeperException$ConnectionLossException due to wrong usage pattern +
                          • +
                          • [ZOOKEEPER-2047] - testTruncationNullLog fails on windows +
                          • +
                          • [ZOOKEEPER-2237] - Port async multi to 3.4 branch
                          • -
                          - -

                          or

                          - + +

                          Bug +

                            - -
                          • -

                            -java -classpath pathtolog4j:pathtozookeeper.jar UpgradeMain <dataLogDir> <dataDir> -

                            +
                          • [ZOOKEEPER-602] - log all exceptions not caught by ZK threads +
                          • +
                          • [ZOOKEEPER-706] - large numbers of watches can cause session re-establishment to fail +
                          • +
                          • [ZOOKEEPER-1002] - The Barrier sample code should create a EPHEMERAL znode instead of EPHEMERAL_SEQUENTIAL znode +
                          • +
                          • [ZOOKEEPER-1029] - C client bug in zookeeper_init (if bad hostname is given) +
                          • +
                          • [ZOOKEEPER-1062] - Net-ZooKeeper: Net::ZooKeeper consumes 100% cpu on wait +
                          • +
                          • [ZOOKEEPER-1077] - C client lib doesn't build on Solaris +
                          • +
                          • [ZOOKEEPER-1222] - getACL should only call DataTree.copyStat when passed in stat is not null +
                          • +
                          • [ZOOKEEPER-1575] - adding .gitattributes to prevent CRLF and LF mismatches for source and text files +
                          • +
                          • [ZOOKEEPER-1797] - PurgeTxnLog may delete data logs during roll +
                          • +
                          • [ZOOKEEPER-1803] - Add description for pzxid in programmer's guide. +
                          • +
                          • [ZOOKEEPER-1833] - fix windows build +
                          • +
                          • [ZOOKEEPER-1853] - zkCli.sh can't issue a CREATE command containing spaces in the data +
                          • +
                          • [ZOOKEEPER-1878] - Inconsistent behavior in autocreation of dataDir and dataLogDir +
                          • +
                          • [ZOOKEEPER-1888] - ZkCli.cmd commands fail with "'java' is not recognized as an internal or external command" +
                          • +
                          • [ZOOKEEPER-1895] - update all notice files, copyright, etc... with the new year - 2014 +
                          • +
                          • [ZOOKEEPER-1897] - ZK Shell/Cli not processing commands +
                          • +
                          • [ZOOKEEPER-1900] - NullPointerException in truncate +
                          • +
                          • [ZOOKEEPER-1901] - [JDK8] Sort children for comparison in AsyncOps tests +
                          • +
                          • [ZOOKEEPER-1906] - zkpython: invalid data in GetData for empty node +
                          • +
                          • [ZOOKEEPER-1911] - REST contrib module does not include all required files when packaged +
                          • +
                          • [ZOOKEEPER-1913] - Invalid manifest files due to bogus revision property value +
                          • +
                          • [ZOOKEEPER-1917] - Apache Zookeeper logs cleartext admin passwords +
                          • +
                          • [ZOOKEEPER-1926] - Unit tests should only use build/test/data for data +
                          • +
                          • [ZOOKEEPER-1927] - zkServer.sh fails to read dataDir (and others) from zoo.cfg on Solaris 10 (grep issue, manifests as FAILED TO WRITE PID). +
                          • +
                          • [ZOOKEEPER-1939] - ReconfigRecoveryTest.testNextConfigUnreachable is failing +
                          • +
                          • [ZOOKEEPER-1943] - "src/contrib/zooinspector/NOTICE.txt" isn't complying to ".gitattributes" in branch-3.4 +
                          • +
                          • [ZOOKEEPER-1945] - deb - zkCli.sh, zkServer.sh and zkEnv.sh regression caused by ZOOKEEPER-1663 +
                          • +
                          • [ZOOKEEPER-1949] - recipes jar not included in the distribution package +
                          • +
                          • [ZOOKEEPER-2026] - Startup order in ServerCnxnFactory-ies is wrong +
                          • +
                          • [ZOOKEEPER-2033] - zookeeper follower fails to start after a restart immediately following a new epoch +
                          • +
                          • [ZOOKEEPER-2039] - Jute compareBytes incorrect comparison index +
                          • +
                          • [ZOOKEEPER-2049] - Yosemite build failure: htonll conflict +
                          • +
                          • [ZOOKEEPER-2052] - Unable to delete a node when the node has no children +
                          • +
                          • [ZOOKEEPER-2056] - Zookeeper 3.4.x and 3.5.0-alpha is not OSGi compliant +
                          • +
                          • [ZOOKEEPER-2060] - Trace bug in NettyServerCnxnFactory +
                          • +
                          • [ZOOKEEPER-2064] - Prevent resource leak in various classes +
                          • +
                          • [ZOOKEEPER-2073] - Memory leak on zookeeper_close +
                          • +
                          • [ZOOKEEPER-2096] - C client builds with incorrect error codes in VisualStudio 2010+ +
                          • +
                          • [ZOOKEEPER-2114] - jute generated allocate_* functions are not externally visible +
                          • +
                          • [ZOOKEEPER-2124] - Allow Zookeeper version string to have underscore '_' +
                          • +
                          • [ZOOKEEPER-2142] - JMX ObjectName is incorrect for observers +
                          • +
                          • [ZOOKEEPER-2146] - BinaryInputArchive readString should check length before allocating memory +
                          • +
                          • [ZOOKEEPER-2174] - JUnit4ZKTestRunner logs test failure for all exceptions even if the test method is annotated with an expected exception. +
                          • +
                          • [ZOOKEEPER-2186] - QuorumCnxManager#receiveConnection may crash with random input +
                          • +
                          • [ZOOKEEPER-2201] - Network issues can cause cluster to hang due to near-deadlock +
                          • +
                          • [ZOOKEEPER-2213] - Empty path in Set crashes server and prevents restart +
                          • +
                          • [ZOOKEEPER-2224] - Four letter command hangs when network is slow +
                          • +
                          • [ZOOKEEPER-2227] - stmk four-letter word fails execution at server while reading trace mask argument. +
                          • +
                          • [ZOOKEEPER-2235] - License update +
                          • +
                          • [ZOOKEEPER-2239] - JMX State from LocalPeerBean incorrect +
                          • +
                          • [ZOOKEEPER-2245] - SimpleSysTest test cases fails +
                          • +
                          • [ZOOKEEPER-2256] - Zookeeper is not using specified JMX port in zkEnv.sh +
                          • +
                          • [ZOOKEEPER-2268] - Zookeeper doc creation fails on windows +
                          • +
                          • [ZOOKEEPER-2279] - QuorumPeer loadDataBase() error message is incorrect +
                          • +
                          • [ZOOKEEPER-2296] - compilation broken for 3.4
                          • -
                          - -

                          where <dataLogDir> is the directory where all transaction logs (log.*) are stored. <dataDir> is the directory where all the snapshots (snapshot.*) are stored.

                          - + +

                          Improvement +

                          +
                            +
                          • [ZOOKEEPER-657] - Cut down the running time of ZKDatabase corruption.
                          • - -
                          • -

                            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

                        -
                          - -
                        1. -

                          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).

                          +
                        2. [ZOOKEEPER-1402] - Upload Zookeeper package to Maven Central
                        3. - -
                        4. -

                          Upgrade creates a new version sub-directory <dataDir>/version-2 and <dataLogDir>/version-2

                          +
                        5. [ZOOKEEPER-1506] - Re-try DNS hostname -> IP resolution if node connection fails
                        6. - -
                        7. -

                          Upgrade reads the old database from <dataDir>/version-1 and <dataLogDir>/version-1 into the memory and creates a new upgraded snapshot.

                          +
                        8. [ZOOKEEPER-1574] - mismatched CR/LF endings in text files
                        9. - -
                        10. -

                          Upgrade writes the new database in <dataDir>/version-2.

                          +
                        11. [ZOOKEEPER-1746] - AsyncCallback.*Callback don't have any Javadoc
                        12. - -
                        -

                        Troubleshooting.

                        -
                          - -
                        1. -

                          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.

                          +
                        2. [ZOOKEEPER-1907] - Improve Thread handling
                        3. - -
                        4. -

                          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. -

                          - +
                        5. [ZOOKEEPER-1948] - Enable JMX remote monitoring
                        6. - -
                        7. -

                          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. -

                          -
                        8. - -
                        - -

                        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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
                      • [ZOOKEEPER-2040] - Server to log underlying cause of SASL connection problems +
                      • +
                      • [ZOOKEEPER-2126] - Improve exit log messsage of EventThread and SendThread by adding SessionId +
                      • +
                      • [ZOOKEEPER-2179] - Typo in Watcher.java +
                      • +
                      • [ZOOKEEPER-2194] - Let DataNode.getChildren() return an unmodifiable view of its children set +
                      • +
                      • [ZOOKEEPER-2205] - Log type of unexpected quorum packet in learner handler loop +
                      • +
                      • [ZOOKEEPER-2315] - Change client connect zk service timeout log level from Info to Warn level +
                      • + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Release Notes - ZooKeeper - Version 3.4.6 + +

                        Sub-task +

                        +
                          +
                        • [ZOOKEEPER-1414] - QuorumPeerMainTest.testQuorum, testBadPackets are failing intermittently +
                        • +
                        • [ZOOKEEPER-1459] - Standalone ZooKeeperServer is not closing the transaction log files on shutdown +
                        • +
                        • [ZOOKEEPER-1558] - Leader should not snapshot uncommitted state +
                        • +
                        • [ZOOKEEPER-1808] - Add version to FLE notifications for 3.4 branch +
                        • +
                        • [ZOOKEEPER-1817] - Fix don't care for b3.4 +
                        • +
                        • [ZOOKEEPER-1834] - Catch IOException in FileTxnLog +
                        • +
                        • [ZOOKEEPER-1837] - Fix JMXEnv checks (potential race conditions) +
                        • +
                        • [ZOOKEEPER-1838] - ZooKeeper shutdown hangs indefinitely at NioServerSocketChannelFactory.releaseExternalResources +
                        • +
                        • [ZOOKEEPER-1841] - problem in QuorumTest +
                        • +
                        • [ZOOKEEPER-1849] - Need to properly tear down tests in various cases +
                        • +
                        • [ZOOKEEPER-1852] - ServerCnxnFactory instance is not properly cleanedup +
                        • +
                        • [ZOOKEEPER-1854] - ClientBase ZooKeeper server clean-up +
                        • +
                        • [ZOOKEEPER-1857] - PrepRequestProcessotTest doesn't shutdown ZooKeeper server +
                        • +
                        • [ZOOKEEPER-1858] - JMX checks - potential race conditions while stopping and starting server +
                        • +
                        • [ZOOKEEPER-1867] - Bug in ZkDatabaseCorruptionTest +
                        • +
                        • [ZOOKEEPER-1873] - Unnecessarily InstanceNotFoundException is coming when unregister failed jmxbeans +
                        • +
                        + +

                        Bug +

                        +
                          +
                        • [ZOOKEEPER-87] - Follower does not shut itself down if its too far behind the leader. +
                        • +
                        • [ZOOKEEPER-732] - Improper translation of error into Python exception +
                        • +
                        • [ZOOKEEPER-753] - update log4j dependency from 1.2.15 to 1.2.16 in branch 3.4 +
                        • +
                        • [ZOOKEEPER-805] - four letter words fail with latest ubuntu nc.openbsd +
                        • +
                        • [ZOOKEEPER-877] - zkpython does not work with python3.1 +
                        • +
                        • [ZOOKEEPER-978] - ZookeeperServer does not close zk database on shutdwon +
                        • +
                        • [ZOOKEEPER-1057] - zookeeper c-client, connection to offline server fails to successfully fallback to second zk host +
                        • +
                        • [ZOOKEEPER-1179] - NettyServerCnxn does not properly close socket on 4 letter word requests +
                        • +
                        • [ZOOKEEPER-1238] - when the linger time was changed for NIO the patch missed Netty +
                        • +
                        • [ZOOKEEPER-1334] - Zookeeper 3.4.x is not OSGi compliant - MANIFEST.MF is flawed +
                        • +
                        • [ZOOKEEPER-1379] - 'printwatches, redo, history and connect '. client commands always print usage. This is not necessary +
                        • +
                        • [ZOOKEEPER-1382] - Zookeeper server holds onto dead/expired session ids in the watch data structures +
                        • +
                        • [ZOOKEEPER-1387] - Wrong epoch file created +
                        • +
                        • [ZOOKEEPER-1388] - Client side 'PathValidation' is missing for the multi-transaction api. +
                        • +
                        • [ZOOKEEPER-1448] - Node+Quota creation in transaction log can crash leader startup +
                        • +
                        • [ZOOKEEPER-1462] - Read-only server does not initialize database properly +
                        • +
                        • [ZOOKEEPER-1474] - Cannot build Zookeeper with IBM Java: use of Sun MXBean classes +
                        • +
                        • [ZOOKEEPER-1478] - Small bug in QuorumTest.testFollowersStartAfterLeader( ) +
                        • +
                        • [ZOOKEEPER-1495] - ZK client hangs when using a function not available on the server. +
                        • +
                        • [ZOOKEEPER-1513] - "Unreasonable length" exception while starting a server. +
                        • +
                        • [ZOOKEEPER-1535] - ZK Shell/Cli re-executes last command on exit +
                        • +
                        • [ZOOKEEPER-1548] - Cluster fails election loop in new and interesting way +
                        • +
                        • [ZOOKEEPER-1551] - Observers ignore txns that come after snapshot and UPTODATE +
                        • +
                        • [ZOOKEEPER-1553] - Findbugs configuration is missing some dependencies +
                        • +
                        • [ZOOKEEPER-1554] - Can't use zookeeper client without SASL +
                        • +
                        • [ZOOKEEPER-1557] - jenkins jdk7 test failure in testBadSaslAuthNotifiesWatch +
                        • +
                        • [ZOOKEEPER-1562] - Memory leaks in zoo_multi API +
                        • +
                        • [ZOOKEEPER-1573] - Unable to load database due to missing parent node +
                        • +
                        • [ZOOKEEPER-1578] - org.apache.zookeeper.server.quorum.Zab1_0Test failed due to hard code with 33556 port +
                        • +
                        • [ZOOKEEPER-1581] - change copyright in notice to 2012 +
                        • +
                        • [ZOOKEEPER-1596] - Zab1_0Test should ensure that the file is closed +
                        • +
                        • [ZOOKEEPER-1597] - Windows build failing +
                        • +
                        • [ZOOKEEPER-1599] - 3.3 server cannot join 3.4 quorum +
                        • +
                        • [ZOOKEEPER-1603] - StaticHostProviderTest testUpdateClientMigrateOrNot hangs +
                        • +
                        • [ZOOKEEPER-1606] - intermittent failures in ZkDatabaseCorruptionTest on jenkins +
                        • +
                        • [ZOOKEEPER-1610] - Some classes are using == or != to compare Long/String objects instead of .equals() +
                        • +
                        • [ZOOKEEPER-1613] - The documentation still points to 2008 in the copyright notice +
                        • +
                        • [ZOOKEEPER-1622] - session ids will be negative in the year 2022 +
                        • +
                        • [ZOOKEEPER-1624] - PrepRequestProcessor abort multi-operation incorrectly +
                        • +
                        • [ZOOKEEPER-1629] - testTransactionLogCorruption occasionally fails +
                        • +
                        • [ZOOKEEPER-1632] - fix memory leaks in cli_st +
                        • +
                        • [ZOOKEEPER-1633] - Introduce a protocol version to connection initiation message +
                        • +
                        • [ZOOKEEPER-1642] - Leader loading database twice +
                        • +
                        • [ZOOKEEPER-1645] - ZooKeeper OSGi package imports not complete +
                        • +
                        • [ZOOKEEPER-1646] - mt c client tests fail on Ubuntu Raring +
                        • +
                        • [ZOOKEEPER-1647] - OSGi package import/export changes not applied to bin-jar +
                        • +
                        • [ZOOKEEPER-1648] - Fix WatcherTest in JDK7 +
                        • +
                        • [ZOOKEEPER-1653] - zookeeper fails to start because of inconsistent epoch +
                        • +
                        • [ZOOKEEPER-1657] - Increased CPU usage by unnecessary SASL checks +
                        • +
                        • [ZOOKEEPER-1663] - scripts don't work when path contains spaces +
                        • +
                        • [ZOOKEEPER-1667] - Watch event isn't handled correctly when a client reestablish to a server +
                        • +
                        • [ZOOKEEPER-1696] - Fail to run zookeeper client on Weblogic application server +
                        • +
                        • [ZOOKEEPER-1697] - large snapshots can cause continuous quorum failure +
                        • +
                        • [ZOOKEEPER-1702] - ZooKeeper client may write operation packets before receiving successful response to connection request, can cause TCP RST +
                        • +
                        • [ZOOKEEPER-1706] - Typo in Double Barriers example +
                        • +
                        • [ZOOKEEPER-1711] - ZooKeeper server binds to all ip addresses for leader election and broadcast +
                        • +
                        • [ZOOKEEPER-1713] - wrong time calculation in zkfuse.cc +
                        • +
                        • [ZOOKEEPER-1714] - perl client segfaults if ZOO_READ_ACL_UNSAFE constant is used +
                        • +
                        • [ZOOKEEPER-1719] - zkCli.sh, zkServer.sh and zkEnv.sh regression caused by ZOOKEEPER-1663 +
                        • +
                        • [ZOOKEEPER-1731] - Unsynchronized access to ServerCnxnFactory.connectionBeans results in deadlock +
                        • +
                        • [ZOOKEEPER-1732] - ZooKeeper server unable to join established ensemble +
                        • +
                        • [ZOOKEEPER-1733] - FLETest#testLE is flaky on windows boxes +
                        • +
                        • [ZOOKEEPER-1744] - clientPortAddress breaks "zkServer.sh status" +
                        • +
                        • [ZOOKEEPER-1745] - Wrong Import-Package in the META-INF/MANIFEST.MF of zookeeper 3.4.5 bundle +
                        • +
                        • [ZOOKEEPER-1750] - Race condition producing NPE in NIOServerCnxn.toString +
                        • +
                        • [ZOOKEEPER-1751] - ClientCnxn#run could miss the second ping or connection get dropped before a ping +
                        • +
                        • [ZOOKEEPER-1753] - ClientCnxn is not properly releasing the resources, which are used to ping RwServer +
                        • +
                        • [ZOOKEEPER-1754] - Read-only server allows to create znode +
                        • +
                        • [ZOOKEEPER-1755] - Concurrent operations of four letter 'dump' ephemeral command and killSession causing NPE +
                        • +
                        • [ZOOKEEPER-1756] - zookeeper_interest() in C client can return a timeval of 0 +
                        • +
                        • [ZOOKEEPER-1764] - ZooKeeper attempts at SASL eventhough it shouldn't +
                        • +
                        • [ZOOKEEPER-1765] - Update code conventions link on "How to contribute" page +
                        • +
                        • [ZOOKEEPER-1770] - NullPointerException in SnapshotFormatter +
                        • +
                        • [ZOOKEEPER-1774] - QuorumPeerMainTest fails consistently with "complains about host" assertion failure +
                        • +
                        • [ZOOKEEPER-1775] - Ephemeral nodes not present in one of the members of the ensemble +
                        • +
                        • [ZOOKEEPER-1776] - Ephemeral nodes not present in one of the members of the ensemble +
                        • +
                        • [ZOOKEEPER-1781] - ZooKeeper Server fails if snapCount is set to 1 +
                        • +
                        • [ZOOKEEPER-1786] - ZooKeeper data model documentation is incorrect +
                        • +
                        • [ZOOKEEPER-1790] - Deal with special ObserverId in QuorumCnxManager.receiveConnection +
                        • +
                        • [ZOOKEEPER-1798] - Fix race condition in testNormalObserverRun +
                        • +
                        • [ZOOKEEPER-1799] - SaslAuthFailDesignatedClientTest.testAuth fails frequently on SUSE +
                        • +
                        • [ZOOKEEPER-1805] - "Don't care" value in ZooKeeper election breaks rolling upgrades +
                        • +
                        • [ZOOKEEPER-1811] - The ZooKeeperSaslClient service name principal is hardcoded to "zookeeper" +
                        • +
                        • [ZOOKEEPER-1812] - ZooInspector reconnection always fails if first connection fails +
                        • +
                        • [ZOOKEEPER-1821] - very ugly warning when compiling load_gen.c +
                        • +
                        • [ZOOKEEPER-1839] - Deadlock in NettyServerCnxn +
                        • +
                        • [ZOOKEEPER-1844] - TruncateTest fails on windows +
                        • +
                        • [ZOOKEEPER-1845] - FLETest.testLE fails on windows +
                        • +
                        • [ZOOKEEPER-1850] - cppunit test testNonexistingHost in TestZookeeperInit is failing on Unbuntu +
                        • +
                        • [ZOOKEEPER-2015] - I found memory leak in zk client for c++ +
                        • +
                        + +

                        Improvement +

                        +
                          +
                        • [ZOOKEEPER-1019] - zkfuse doesn't list dependency on boost in README +
                        • +
                        • [ZOOKEEPER-1096] - Leader communication should listen on specified IP, not wildcard address +
                        • +
                        • [ZOOKEEPER-1324] - Remove Duplicate NEWLEADER packets from the Leader to the Follower. +
                        • +
                        • [ZOOKEEPER-1552] - Enable sync request processor in Observer +
                        • +
                        • [ZOOKEEPER-1564] - Allow JUnit test build with IBM Java +
                        • +
                        • [ZOOKEEPER-1583] - Document maxClientCnxns in conf/zoo_sample.cfg +
                        • +
                        • [ZOOKEEPER-1584] - Adding mvn-install target for deploying the zookeeper artifacts to .m2 repository. +
                        • +
                        • [ZOOKEEPER-1598] - Ability to support more digits in the version string +
                        • +
                        • [ZOOKEEPER-1615] - minor typos in ZooKeeper Programmer's Guide web page +
                        • +
                        • [ZOOKEEPER-1627] - Add org.apache.zookeeper.common to exported packages in OSGi MANIFEST headers +
                        • +
                        • [ZOOKEEPER-1666] - Avoid Reverse DNS lookup if the hostname in connection string is literal IP address. +
                        • +
                        • [ZOOKEEPER-1715] - Upgrade netty version +
                        • +
                        • [ZOOKEEPER-1758] - Add documentation for zookeeper.observer.syncEnabled flag +
                        • +
                        • [ZOOKEEPER-1771] - ZooInspector authentication +
                        • +
                        + +

                        Task +

                        + + +

                        Test +

                        +
                          +
                        • [ZOOKEEPER-1980] - how to draw the figure"ZooKeeper Throughput as the Read-Write Ratio Varies" ? +
                        • +
                        - - - - + Release Notes - ZooKeeper - Version 3.4.5 + +

                        Bug +

                        +
                          +
                        • [ZOOKEEPER-1376] - zkServer.sh does not correctly check for $SERVER_JVMFLAGS +
                        • +
                        • [ZOOKEEPER-1550] - ZooKeeperSaslClient does not finish anonymous login on OpenJDK +
                        • +
                        • [ZOOKEEPER-1560] - Zookeeper client hangs on creation of large nodes +
                        • +
                        • [ZOOKEEPER-1686] - Publish ZK 3.4.5 test jar +
                        • +
                        + +

                        Improvement +

                        + - + Release Notes - ZooKeeper - Version 3.4.4 + +

                        Bug +

                        +
                          +
                        • [ZOOKEEPER-1048] - addauth command does not work in cli_mt/cli_st +
                        • +
                        • [ZOOKEEPER-1163] - Memory leak in zk_hashtable.c:do_insert_watcher_object() +
                        • +
                        • [ZOOKEEPER-1210] - Can't build ZooKeeper RPM with RPM >= 4.6.0 (i.e. on RHEL 6 and Fedora >= 10) +
                        • +
                        • [ZOOKEEPER-1236] - Security uses proprietary Sun APIs +
                        • +
                        • [ZOOKEEPER-1277] - servers stop serving when lower 32bits of zxid roll over +
                        • +
                        • [ZOOKEEPER-1303] - Observer LearnerHandlers are not removed from Leader collection. +
                        • +
                        • [ZOOKEEPER-1307] - zkCli.sh is exiting when an Invalid ACL exception is thrown from setACL command through client +
                        • +
                        • [ZOOKEEPER-1318] - In Python binding, get_children (and get and exists, and probably others) with expired session doesn't raise exception properly +
                        • +
                        • [ZOOKEEPER-1339] - C clien doesn't build with --enable-debug +
                        • +
                        • [ZOOKEEPER-1344] - ZooKeeper client multi-update command is not considering the Chroot request +
                        • +
                        • [ZOOKEEPER-1354] - AuthTest.testBadAuthThenSendOtherCommands fails intermittently +
                        • +
                        • [ZOOKEEPER-1361] - Leader.lead iterates over 'learners' set without proper synchronisation +
                        • +
                        • [ZOOKEEPER-1380] - zkperl: _zk_release_watch doesn't remove items properly from the watch list +
                        • +
                        • [ZOOKEEPER-1384] - test-cppunit overrides LD_LIBRARY_PATH and fails if gcc is in non-standard location +
                        • +
                        • [ZOOKEEPER-1386] - avoid flaky URL redirection in "ant javadoc" : replace "http://java.sun.com/javase/6/docs/api/" with "http://download.oracle.com/javase/6/docs/api/" +
                        • +
                        • [ZOOKEEPER-1395] - node-watcher double-free redux +
                        • +
                        • [ZOOKEEPER-1403] - zkCli.sh script quoting issue +
                        • +
                        • [ZOOKEEPER-1406] - dpkg init scripts don't restart - missing check_priv_sep_dir +
                        • +
                        • [ZOOKEEPER-1412] - java client watches inconsistently triggered on reconnect +
                        • +
                        • [ZOOKEEPER-1419] - Leader election never settles for a 5-node cluster +
                        • +
                        • [ZOOKEEPER-1427] - Writing to local files is done non-atomically +
                        • +
                        • [ZOOKEEPER-1431] - zkpython: async calls leak memory +
                        • +
                        • [ZOOKEEPER-1437] - Client uses session before SASL authentication complete +
                        • +
                        • [ZOOKEEPER-1463] - external inline function is not compatible with C99 +
                        • +
                        • [ZOOKEEPER-1465] - Cluster availability following new leader election takes a long time with large datasets - is correlated to dataset size +
                        • +
                        • [ZOOKEEPER-1466] - QuorumCnxManager.shutdown missing synchronization +
                        • +
                        • [ZOOKEEPER-1471] - Jute generates invalid C++ code +
                        • +
                        • [ZOOKEEPER-1483] - Fix leader election recipe documentation +
                        • +
                        • [ZOOKEEPER-1489] - Data loss after truncate on transaction log +
                        • +
                        • [ZOOKEEPER-1490] - If the configured log directory does not exist zookeeper will not start. Better to create the directory and start +
                        • +
                        • [ZOOKEEPER-1493] - C Client: zookeeper_process doesn't invoke completion callback if zookeeper_close has been called +
                        • +
                        • [ZOOKEEPER-1494] - C client: socket leak after receive timeout in zookeeper_interest() +
                        • +
                        • [ZOOKEEPER-1496] - Ephemeral node not getting cleared even after client has exited +
                        • +
                        • [ZOOKEEPER-1501] - Nagios plugin always returns OK when it cannot connect to zookeeper +
                        • +
                        • [ZOOKEEPER-1514] - FastLeaderElection - leader ignores the round information when joining a quorum +
                        • +
                        • [ZOOKEEPER-1521] - LearnerHandler initLimit/syncLimit problems specifying follower socket timeout limits +
                        • +
                        • [ZOOKEEPER-1522] - intermittent failures in Zab test due to NPE in recursiveDelete test function +
                        • +
                        • [ZOOKEEPER-1536] - c client : memory leak in winport.c +
                        • +
                        • [ZOOKEEPER-1686] - Publish ZK 3.4.5 test jar +
                        • +
                        + +

                        Improvement +

                        +
                          +
                        • [ZOOKEEPER-1321] - Add number of client connections metric in JMX and srvr +
                        • +
                        • [ZOOKEEPER-1377] - add support for dumping a snapshot file content (similar to LogFormatter) +
                        • +
                        • [ZOOKEEPER-1389] - it would be nice if start-foreground used exec $JAVA in order to get rid of the intermediate shell process +
                        • +
                        • [ZOOKEEPER-1390] - some expensive debug code not protected by a check for debug +
                        • +
                        • [ZOOKEEPER-1433] - improve ZxidRolloverTest (test seems flakey) +
                        • +
                        • [ZOOKEEPER-1454] - Document how to run autoreconf if cppunit is installed in a non-standard directory +
                        • +
                        • [ZOOKEEPER-1481] - allow the C cli to run exists with a watcher +
                        • +
                        • [ZOOKEEPER-1497] - Allow server-side SASL login with JAAS configuration to be programmatically set (rather than only by reading JAAS configuration file) +
                        • +
                        • [ZOOKEEPER-1503] - remove redundant JAAS configuration code in SaslAuthTest and SaslAuthFailTest +
                        • +
                        • [ZOOKEEPER-1510] - Should not log SASL errors for non-secure usage +
                        • +
                        • [ZOOKEEPER-1565] - Allow ClientTest.java build with IBM Java +
                        • +
                        • [ZOOKEEPER-1570] - Allow QuorumBase.java build with IBM Java +
                        • +
                        • [ZOOKEEPER-1571] - Allow QuorumUtil.java build with IBM Java +
                        • +
                        + +

                        Task +

                        + - - - - + Release Notes - ZooKeeper - Version 3.4.3 + +

                        Bug +

                        +
                          +
                        • [ZOOKEEPER-973] - bind() could fail on Leader because it does not setReuseAddress on its ServerSocket +
                        • +
                        • [ZOOKEEPER-1089] - zkServer.sh status does not work due to invalid option of nc +
                        • +
                        • [ZOOKEEPER-1327] - there are still remnants of hadoop urls +
                        • +
                        • [ZOOKEEPER-1336] - javadoc for multi is confusing, references functionality that doesn't seem to exist +
                        • +
                        • [ZOOKEEPER-1338] - class cast exceptions may be thrown by multi ErrorResult class (invalid equals) +
                        • +
                        • [ZOOKEEPER-1340] - multi problem - typical user operations are generating ERROR level messages in the server +
                        • +
                        • [ZOOKEEPER-1343] - getEpochToPropose should check if lastAcceptedEpoch is greater or equal than epoch +
                        • +
                        • [ZOOKEEPER-1348] - Zookeeper 3.4.2 C client incorrectly reports string version of 3.4.1 +
                        • +
                        • [ZOOKEEPER-1351] - invalid test verification in MultiTransactionTest +
                        • +
                        • [ZOOKEEPER-1352] - server.InvalidSnapshotTest is using connection timeouts that are too short +
                        • +
                        • [ZOOKEEPER-1353] - C client test suite fails consistently +
                        • +
                        • [ZOOKEEPER-1367] - Data inconsistencies and unexpired ephemeral nodes after cluster restart +
                        • +
                        • [ZOOKEEPER-1370] - Add logging changes in Release Notes needed for clients because of ZOOKEEPER-850. +
                        • +
                        • [ZOOKEEPER-1373] - Hardcoded SASL login context name clashes with Hadoop security configuration override +
                        • +
                        • [ZOOKEEPER-1374] - C client multi-threaded test suite fails to compile on ARM architectures. +
                        • +
                        + +

                        Improvement +

                        +
                          +
                        • [ZOOKEEPER-1322] - Cleanup/fix logging in Quorum code. +
                        • +
                        • [ZOOKEEPER-1345] - Add a .gitignore file with general exclusions and Eclipse project files excluded +
                        • +
                        + +

                        Test +

                        +
                          +
                        • [ZOOKEEPER-1337] - multi's "Transaction" class is missing tests. +
                        • +
                        - + Release Notes - ZooKeeper - Version 3.4.2 + +

                        Bug +

                        + - - - - + Release Notes - ZooKeeper - Version 3.4.1 - +

                        Bug +

                        + - - - - - + Release Notes - ZooKeeper - Version 3.4.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                        Changes Since ZooKeeper 2.2.1
                        IssueNotes
                        - - 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! -
                        -
                        +

                        Sub-task +

                        +
                          +
                        • [ZOOKEEPER-784] - server-side functionality for read-only mode +
                        • +
                        • [ZOOKEEPER-798] - Fixup loggraph for FLE changes +
                        • +
                        • [ZOOKEEPER-839] - deleteRecursive does not belong to the other methods +
                        • +
                        • [ZOOKEEPER-908] - Remove code duplication and inconsistent naming in ClientCnxn.Packet creation +
                        • +
                        • [ZOOKEEPER-909] - Extract NIO specific code from ClientCnxn +
                        • +
                        • [ZOOKEEPER-966] - Client side for multi +
                        • +
                        • [ZOOKEEPER-967] - Server side decoding and function dispatch +
                        • +
                        • [ZOOKEEPER-968] - Database multi-update +
                        • +
                        • [ZOOKEEPER-1042] - Generate zookeeper test jar for maven installation +
                        • +
                        • [ZOOKEEPER-1081] - modify leader/follower code to correctly deal with new leader +
                        • +
                        • [ZOOKEEPER-1082] - modify leader election to correctly take into account current epoch +
                        • +
                        • [ZOOKEEPER-1150] - fix for this patch to compile on windows... +
                        • +
                        • [ZOOKEEPER-1160] - test timeouts are too small +
                        • +
                        • [ZOOKEEPER-1201] - Clean SaslServerCallbackHandler.java +
                        • +
                        • [ZOOKEEPER-1246] - Dead code in PrepRequestProcessor catch Exception block +
                        • +
                        • [ZOOKEEPER-1282] - Learner.java not following Zab 1.0 protocol - setCurrentEpoch should be done upon receipt of NEWLEADER (before acking it) and not upon receipt of UPTODATE +
                        • +
                        • [ZOOKEEPER-1291] - AcceptedEpoch not updated at leader before it proposes the epoch to followers +
                        • +
                        + +

                        Bug +

                        +
                          +
                        • [ZOOKEEPER-335] - zookeeper servers should commit the new leader txn to their logs. +
                        • +
                        • [ZOOKEEPER-418] - Need nifty zookeeper browser +
                        • +
                        • [ZOOKEEPER-603] - zkpython should do a better job of freeing memory under error conditions +
                        • +
                        • [ZOOKEEPER-662] - Too many CLOSE_WAIT socket state on a server +
                        • +
                        • [ZOOKEEPER-690] - AsyncTestHammer test fails on hudson. +
                        • +
                        • [ZOOKEEPER-719] - Add throttling to BookKeeper client +
                        • +
                        • [ZOOKEEPER-720] - Use zookeeper-{version}-sources.jar instead of zookeeper-{version}-src.jar to publish sources in the Maven repository +
                        • +
                        • [ZOOKEEPER-722] - zkServer.sh uses sh's builtin echo on BSD, behaves incorrectly. +
                        • +
                        • [ZOOKEEPER-731] - Zookeeper#delete , #create - async versions miss a verb in the javadoc +
                        • +
                        • [ZOOKEEPER-734] - QuorumPeerTestBase.java and ZooKeeperServerMainTest.java do not handle windows path correctly +
                        • +
                        • [ZOOKEEPER-735] - cppunit test testipv6 assumes that the machine is ipv6 enabled. +
                        • +
                        • [ZOOKEEPER-737] - some 4 letter words may fail with netcat (nc) +
                        • +
                        • [ZOOKEEPER-738] - zookeeper.jute.h fails to compile with -pedantic +
                        • +
                        • [ZOOKEEPER-741] - root level create on REST proxy fails +
                        • +
                        • [ZOOKEEPER-742] - Deallocatng None on writes +
                        • +
                        • [ZOOKEEPER-746] - learner outputs session id to log in dec (should be hex) +
                        • +
                        • [ZOOKEEPER-749] - OSGi metadata not included in binary only jar +
                        • +
                        • [ZOOKEEPER-750] - move maven artifacts into "dist-maven" subdir of the release (package target) +
                        • +
                        • [ZOOKEEPER-758] - zkpython segfaults on invalid acl with missing key +
                        • +
                        • [ZOOKEEPER-763] - Deadlock on close w/ zkpython / c client +
                        • +
                        • [ZOOKEEPER-764] - Observer elected leader due to inconsistent voting view +
                        • +
                        • [ZOOKEEPER-766] - forrest recipes docs don't mention the lock/queue recipe implementations available in the release +
                        • +
                        • [ZOOKEEPER-769] - Leader can treat observers as quorum members +
                        • +
                        • [ZOOKEEPER-772] - zkpython segfaults when watcher from async get children is invoked. +
                        • +
                        • [ZOOKEEPER-774] - Recipes tests are slightly outdated: they do not compile against JUnit 4.8 +
                        • +
                        • [ZOOKEEPER-782] - Incorrect C API documentation for Watches +
                        • +
                        • [ZOOKEEPER-783] - committedLog in ZKDatabase is not properly synchronized +
                        • +
                        • [ZOOKEEPER-785] - Zookeeper 3.3.1 shouldn't infinite loop if someone creates a server.0 line +
                        • +
                        • [ZOOKEEPER-787] - groupId in deployed pom is wrong +
                        • +
                        • [ZOOKEEPER-790] - Last processed zxid set prematurely while establishing leadership +
                        • +
                        • [ZOOKEEPER-792] - zkpython memory leak +
                        • +
                        • [ZOOKEEPER-794] - Callbacks are not invoked when the client is closed +
                        • +
                        • [ZOOKEEPER-795] - eventThread isn't shutdown after a connection "session expired" event coming +
                        • +
                        • [ZOOKEEPER-796] - zkServer.sh should support an external PIDFILE variable +
                        • +
                        • [ZOOKEEPER-800] - zoo_add_auth returns ZOK if zookeeper handle is in ZOO_CLOSED_STATE +
                        • +
                        • [ZOOKEEPER-804] - c unit tests failing due to "assertion cptr failed" +
                        • +
                        • [ZOOKEEPER-813] - maven install is broken due to incorrect organisation +
                        • +
                        • [ZOOKEEPER-814] - monitoring scripts are missing apache license headers +
                        • +
                        • [ZOOKEEPER-820] - update c unit tests to ensure "zombie" java server processes don't cause failure +
                        • +
                        • [ZOOKEEPER-822] - Leader election taking a long time to complete +
                        • +
                        • [ZOOKEEPER-831] - BookKeeper: Throttling improved for reads +
                        • +
                        • [ZOOKEEPER-844] - handle auth failure in java client +
                        • +
                        • [ZOOKEEPER-846] - zookeeper client doesn't shut down cleanly on the close call +
                        • +
                        • [ZOOKEEPER-854] - BookKeeper does not compile due to changes in the ZooKeeper code +
                        • +
                        • [ZOOKEEPER-855] - clientPortBindAddress should be clientPortAddress +
                        • +
                        • [ZOOKEEPER-861] - Missing the test SSL certificate used for running junit tests. +
                        • +
                        • [ZOOKEEPER-867] - ClientTest is failing on hudson - fd cleanup +
                        • +
                        • [ZOOKEEPER-870] - Zookeeper trunk build broken. +
                        • +
                        • [ZOOKEEPER-874] - FileTxnSnapLog.restore does not call listener +
                        • +
                        • [ZOOKEEPER-880] - QuorumCnxManager$SendWorker grows without bounds +
                        • +
                        • [ZOOKEEPER-881] - ZooKeeperServer.loadData loads database twice +
                        • +
                        • [ZOOKEEPER-882] - Startup loads last transaction from snapshot +
                        • +
                        • [ZOOKEEPER-884] - Remove LedgerSequence references from BookKeeper documentation and comments in tests +
                        • +
                        • [ZOOKEEPER-888] - c-client / zkpython: Double free corruption on node watcher +
                        • +
                        • [ZOOKEEPER-893] - ZooKeeper high cpu usage when invalid requests +
                        • +
                        • [ZOOKEEPER-897] - C Client seg faults during close +
                        • +
                        • [ZOOKEEPER-898] - C Client might not cleanup correctly during close +
                        • +
                        • [ZOOKEEPER-902] - Fix findbug issue in trunk "Malicious code vulnerability" +
                        • +
                        • [ZOOKEEPER-904] - super digest is not actually acting as a full superuser +
                        • +
                        • [ZOOKEEPER-907] - Spurious "KeeperErrorCode = Session moved" messages +
                        • +
                        • [ZOOKEEPER-913] - Version parser fails to parse "3.3.2-dev" from build.xml. +
                        • +
                        • [ZOOKEEPER-919] - Ephemeral nodes remains in one of ensemble after deliberate SIGKILL +
                        • +
                        • [ZOOKEEPER-921] - zkPython incorrectly checks for existence of required ACL elements +
                        • +
                        • [ZOOKEEPER-937] - test -e not available on solaris /bin/sh +
                        • +
                        • [ZOOKEEPER-957] - zkCleanup.sh doesn't do anything +
                        • +
                        • [ZOOKEEPER-958] - Flag to turn off autoconsume in hedwig c++ client +
                        • +
                        • [ZOOKEEPER-961] - Watch recovery after disconnection when connection string contains a prefix +
                        • +
                        • [ZOOKEEPER-962] - leader/follower coherence issue when follower is receiving a DIFF +
                        • +
                        • [ZOOKEEPER-963] - Make Forrest work with JDK6 +
                        • +
                        • [ZOOKEEPER-975] - new peer goes in LEADING state even if ensemble is online +
                        • +
                        • [ZOOKEEPER-976] - ZooKeeper startup script doesn't use JAVA_HOME +
                        • +
                        • [ZOOKEEPER-981] - Hang in zookeeper_close() in the multi-threaded C client +
                        • +
                        • [ZOOKEEPER-983] - running zkServer.sh start remotely using ssh hangs +
                        • +
                        • [ZOOKEEPER-985] - Test BookieRecoveryTest fails on trunk. +
                        • +
                        • [ZOOKEEPER-994] - "eclipse" target in the build script doesnot include libraray required for test classes in the classpath +
                        • +
                        • [ZOOKEEPER-1006] - QuorumPeer "Address already in use" -- regression in 3.3.3 +
                        • +
                        • [ZOOKEEPER-1007] - iarchive leak in C client +
                        • +
                        • [ZOOKEEPER-1013] - zkServer.sh usage message should mention all startup options +
                        • +
                        • [ZOOKEEPER-1027] - chroot not transparent in zoo_create() +
                        • +
                        • [ZOOKEEPER-1028] - In python bindings, zookeeper.set2() should return a stat dict but instead returns None +
                        • +
                        • [ZOOKEEPER-1033] - c client should install includes into INCDIR/zookeeper, not INCDIR/c-client-src +
                        • +
                        • [ZOOKEEPER-1034] - perl bindings should automatically find the zookeeper c-client headers +
                        • +
                        • [ZOOKEEPER-1046] - Creating a new sequential node results in a ZNODEEXISTS error +
                        • +
                        • [ZOOKEEPER-1049] - Session expire/close flooding renders heartbeats to delay significantly +
                        • +
                        • [ZOOKEEPER-1051] - SIGPIPE in Zookeeper 0.3.* when send'ing after cluster disconnection +
                        • +
                        • [ZOOKEEPER-1052] - Findbugs warning in QuorumPeer.ResponderThread.run() +
                        • +
                        • [ZOOKEEPER-1055] - check for duplicate ACLs in addACL() and create() +
                        • +
                        • [ZOOKEEPER-1058] - fix typo in opToString for getData +
                        • +
                        • [ZOOKEEPER-1059] - stat command isses on non-existing node causes NPE +
                        • +
                        • [ZOOKEEPER-1060] - QuorumPeer takes a long time to shutdown +
                        • +
                        • [ZOOKEEPER-1061] - Zookeeper stop fails if start called twice +
                        • +
                        • [ZOOKEEPER-1063] - Dubious synchronization in Zookeeper and ClientCnxnSocketNIO classes +
                        • +
                        • [ZOOKEEPER-1068] - Documentation and default config suggest incorrect location for Zookeeper state +
                        • +
                        • [ZOOKEEPER-1069] - Calling shutdown() on a QuorumPeer too quickly can lead to a corrupt log +
                        • +
                        • [ZOOKEEPER-1073] - address a documentation issue in ZOOKEEPER-1030 +
                        • +
                        • [ZOOKEEPER-1074] - zkServer.sh is missing nohup/sleep, which are necessary for remote invocation +
                        • +
                        • [ZOOKEEPER-1076] - some quorum tests are unnecessarily extending QuorumBase +
                        • +
                        • [ZOOKEEPER-1083] - Javadoc for WatchedEvent not being generated +
                        • +
                        • [ZOOKEEPER-1086] - zookeeper test jar has non mavenised dependency. +
                        • +
                        • [ZOOKEEPER-1087] - ForceSync VM arguement not working when set to "no" +
                        • +
                        • [ZOOKEEPER-1090] - Race condition while taking snapshot can lead to not restoring data tree correctly +
                        • +
                        • [ZOOKEEPER-1091] - when the chrootPath of ClientCnxn is not null and the Watches of zooKeeper is not null and the method primeConnection(SelectionKey k) of ClientCnxn Occurred again for some reason ,then the wrong watcher clientPath is sended to server +
                        • +
                        • [ZOOKEEPER-1097] - Quota is not correctly rehydrated on snapshot reload +
                        • +
                        • [ZOOKEEPER-1101] - Upload zookeeper-test maven artifacts to maven repository. +
                        • +
                        • [ZOOKEEPER-1108] - Various bugs in zoo_add_auth in C +
                        • +
                        • [ZOOKEEPER-1109] - Zookeeper service is down when SyncRequestProcessor meets any exception. +
                        • +
                        • [ZOOKEEPER-1111] - JMXEnv uses System.err instead of logging +
                        • +
                        • [ZOOKEEPER-1117] - zookeeper 3.3.3 fails to build with gcc >= 4.6.1 on Debian/Ubuntu +
                        • +
                        • [ZOOKEEPER-1119] - zkServer stop command incorrectly reading comment lines in zoo.cfg +
                        • +
                        • [ZOOKEEPER-1124] - Multiop submitted to non-leader always fails due to timeout +
                        • +
                        • [ZOOKEEPER-1134] - ClientCnxnSocket string comparison using == rather than equals +
                        • +
                        • [ZOOKEEPER-1136] - NEW_LEADER should be queued not sent to match the Zab 1.0 protocol on the twiki +
                        • +
                        • [ZOOKEEPER-1138] - release audit failing for a number of new files +
                        • +
                        • [ZOOKEEPER-1139] - jenkins is reporting two warnings, fix these +
                        • +
                        • [ZOOKEEPER-1140] - server shutdown is not stopping threads +
                        • +
                        • [ZOOKEEPER-1141] - zkpython fails tests under python 2.4 +
                        • +
                        • [ZOOKEEPER-1142] - incorrect stat output +
                        • +
                        • [ZOOKEEPER-1144] - ZooKeeperServer not starting on leader due to a race condition +
                        • +
                        • [ZOOKEEPER-1145] - ObserverTest.testObserver fails at particular point after several runs of ant junt.run -Dtestcase=ObserverTest +
                        • +
                        • [ZOOKEEPER-1146] - significant regression in client (c/python) performance +
                        • +
                        • [ZOOKEEPER-1152] - Exceptions thrown from handleAuthentication can cause buffer corruption issues in NIOServer +
                        • +
                        • [ZOOKEEPER-1154] - Data inconsistency when the node(s) with the highest zxid is not present at the time of leader election +
                        • +
                        • [ZOOKEEPER-1156] - Log truncation truncating log too much - can cause data loss +
                        • +
                        • [ZOOKEEPER-1165] - better eclipse support in tests +
                        • +
                        • [ZOOKEEPER-1168] - ZooKeeper fails to run with IKVM +
                        • +
                        • [ZOOKEEPER-1171] - fix build for java 7 +
                        • +
                        • [ZOOKEEPER-1174] - FD leak when network unreachable +
                        • +
                        • [ZOOKEEPER-1181] - Fix problems with Kerberos TGT renewal +
                        • +
                        • [ZOOKEEPER-1185] - Send AuthFailed event to client if SASL authentication fails +
                        • +
                        • [ZOOKEEPER-1189] - For an invalid snapshot file(less than 10bytes size) RandomAccessFile stream is leaking. +
                        • +
                        • [ZOOKEEPER-1190] - ant package is not including many of the bin scripts in the package (zkServer.sh for example) +
                        • +
                        • [ZOOKEEPER-1192] - Leader.waitForEpochAck() checks waitingForNewEpoch instead of checking electionFinished +
                        • +
                        • [ZOOKEEPER-1194] - Two possible race conditions during leader establishment +
                        • +
                        • [ZOOKEEPER-1195] - SASL authorizedID being incorrectly set: should use getHostName() rather than getServiceName() +
                        • +
                        • [ZOOKEEPER-1203] - Zookeeper systest is missing Junit Classes +
                        • +
                        • [ZOOKEEPER-1206] - Sequential node creation does not use always use digits in node name given certain Locales. +
                        • +
                        • [ZOOKEEPER-1208] - Ephemeral node not removed after the client session is long gone +
                        • +
                        • [ZOOKEEPER-1212] - zkServer.sh stop action is not conformat with LSB para 20.2 Init Script Actions +
                        • +
                        • [ZOOKEEPER-1264] - FollowerResyncConcurrencyTest failing intermittently +
                        • +
                        • [ZOOKEEPER-1268] - problems with read only mode, intermittent test failures and ERRORs in the log +
                        • +
                        • [ZOOKEEPER-1270] - testEarlyLeaderAbandonment failing intermittently, quorum formed, no serving. +
                        • +
                        • [ZOOKEEPER-1271] - testEarlyLeaderAbandonment failing on solaris - clients not retrying connection +
                        • +
                        • [ZOOKEEPER-1299] - Add winconfig.h file to ignore in release audit. +
                        • +
                        + +

                        Improvement +

                        +
                          +
                        • [ZOOKEEPER-494] - zookeeper should install include headers in /usr/local/include/zookeeper +
                        • +
                        • [ZOOKEEPER-500] - Async methods shouldnt throw exceptions +
                        • +
                        • [ZOOKEEPER-631] - zkpython's C code could do with a style clean-up +
                        • +
                        • [ZOOKEEPER-636] - configure.ac has instructions which override the contents of CFLAGS and CXXFLAGS. +
                        • +
                        • [ZOOKEEPER-724] - Improve junit test integration - log harness information +
                        • +
                        • [ZOOKEEPER-733] - use netty to handle client connections +
                        • +
                        • [ZOOKEEPER-765] - Add python example script +
                        • +
                        • [ZOOKEEPER-773] - Log visualisation +
                        • +
                        • [ZOOKEEPER-788] - Add server id to message logs +
                        • +
                        • [ZOOKEEPER-789] - Improve FLE log messages +
                        • +
                        • [ZOOKEEPER-797] - c client source with AI_ADDRCONFIG cannot be compiled with early glibc +
                        • +
                        • [ZOOKEEPER-809] - Improved REST Interface +
                        • +
                        • [ZOOKEEPER-821] - Add ZooKeeper version information to zkpython +
                        • +
                        • [ZOOKEEPER-850] - Switch from log4j to slf4j +
                        • +
                        • [ZOOKEEPER-853] - Make zookeeper.is_unrecoverable return True or False and not an integer +
                        • +
                        • [ZOOKEEPER-862] - Hedwig created ledgers with hardcoded Bookkeeper ensemble and quorum size. Make these a server config parameter instead. +
                        • +
                        • [ZOOKEEPER-864] - Hedwig C++ client improvements +
                        • +
                        • [ZOOKEEPER-891] - Allow non-numeric version strings +
                        • +
                        • [ZOOKEEPER-905] - enhance zkServer.sh for easier zookeeper automation-izing +
                        • +
                        • [ZOOKEEPER-926] - Fork Hadoop common's test-patch.sh and modify for Zookeeper +
                        • +
                        • [ZOOKEEPER-977] - passing null for path_buffer in zoo_create +
                        • +
                        • [ZOOKEEPER-980] - allow configuration parameters for log4j.properties +
                        • +
                        • [ZOOKEEPER-993] - Code improvements +
                        • +
                        • [ZOOKEEPER-997] - ZkClient ignores command if there are any space in front of it +
                        • +
                        • [ZOOKEEPER-1018] - The connection permutation in get_addrs uses a weak and inefficient shuffle +
                        • +
                        • [ZOOKEEPER-1025] - zkCli is overly sensitive to to spaces. +
                        • +
                        • [ZOOKEEPER-1030] - Increase default for maxClientCnxns +
                        • +
                        • [ZOOKEEPER-1094] - Small improvements to LeaderElection and Vote classes +
                        • +
                        • [ZOOKEEPER-1095] - Simple leader election recipe +
                        • +
                        • [ZOOKEEPER-1103] - In QuorumTest, use the same "for ( .. try { break } catch { } )" pattern in testFollowersStartAfterLeaders as in testSessionMove. +
                        • +
                        • [ZOOKEEPER-1104] - CLONE - In QuorumTest, use the same "for ( .. try { break } catch { } )" pattern in testFollowersStartAfterLeaders as in testSessionMove. +
                        • +
                        • [ZOOKEEPER-1143] - quorum send & recv workers are missing thread names +
                        • +
                        • [ZOOKEEPER-1153] - Deprecate AuthFLE and LE +
                        • +
                        • [ZOOKEEPER-1166] - Please add a few svn:ignore properties +
                        • +
                        • [ZOOKEEPER-1169] - Fix compiler (eclipse) warnings in (generated) jute code +
                        • +
                        • [ZOOKEEPER-1239] - add logging/stats to identify fsync stalls +
                        • +
                        + +

                        New Feature +

                        + + +

                        Task +

                        +
                          +
                        • [ZOOKEEPER-754] - numerous misspellings "succesfully" +
                        • +
                        • [ZOOKEEPER-1149] - users cannot migrate from 3.4->3.3->3.4 server code against a single datadir +
                        • +
                        + +

                        Test +

                        +

                        @@ -1902,7 +1559,7 @@

                        Changes Since ZooKeeper 2.2.1

                        @@ -61,10 +61,10 @@ +-->
                        • -Project +Project
                        • -Wiki +Wiki
                        • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                          @@ -291,6 +291,9 @@

                          A Guide to Deployment and Administration

                          Authentication & Authorization Options
                        • +Experimental Options/Features +
                        • +
                        • Unsafe Options
                        • @@ -376,45 +379,121 @@

                          Deployment

                          System Requirements

                          Supported Platforms

                          +

                          ZooKeeper consists of multiple components. Some components are + supported broadly, and other components are supported only on a smaller + set of platforms.

                          • -

                            GNU/Linux is supported as a development and production - platform for both server and client.

                            +

                            +Client is the Java client + library, used by applications to connect to a ZooKeeper ensemble. +

                          • -

                            Sun Solaris is supported as a development and production - platform for both server and client.

                            +

                            +Server is the Java server + that runs on the ZooKeeper ensemble nodes.

                          • -

                            FreeBSD is supported as a development and production - platform for clients only. Java NIO selector support in - the FreeBSD JVM is broken.

                            +

                            +Native Client is a client + implemented in C, similar to the Java client, used by applications + to connect to a ZooKeeper ensemble.

                          • -

                            Win32 is supported as a development - platform only for both server and client.

                            +

                            +Contrib refers to multiple + optional add-on components.

                          • + +
                          +

                          The following matrix describes the level of support committed for + running each component on different operating system platforms.

                          + + -
                        • - -

                          MacOSX is supported as a development - platform only for both server and client.

                          +Support Matrix -
                        • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - +
                          Support Matrix
                          Operating SystemClientServerNative ClientContrib
                          GNU/LinuxDevelopment and ProductionDevelopment and ProductionDevelopment and ProductionDevelopment and Production
                          SolarisDevelopment and ProductionDevelopment and ProductionNot SupportedNot Supported
                          FreeBSDDevelopment and ProductionDevelopment and ProductionNot SupportedNot Supported
                          WindowsDevelopment and ProductionDevelopment and ProductionNot SupportedNot Supported
                          Mac OS XDevelopment OnlyDevelopment OnlyNot SupportedNot Supported
                          +

                          For any operating system not explicitly mentioned as supported in + the matrix, components may or may not work. The ZooKeeper community + will fix obvious bugs that are reported for other platforms, but there + is no full support.

                          Required Software

                          ZooKeeper runs in Java, release 1.6 or greater (JDK 6 or @@ -434,6 +513,35 @@

                          Clustered (Multi-Server) Setup

                          only handle the failure of a single machine; if two machines fail, the remaining two machines do not constitute a majority. However, with five machines ZooKeeper can handle the failure of two machines.

                          +
                          +
                          Note
                          +
                          + +

                          + As mentioned in the + ZooKeeper Getting Started Guide + , a minimum of three servers are required for a fault tolerant + clustered setup, and it is strongly recommended that you have an + odd number of servers. +

                          + +

                          Usually three servers is more than enough for a production + install, but for maximum reliability during maintenance, you may + wish to install five servers. With three servers, if you perform + maintenance on one of them, you are vulnerable to a failure on one + of the other two servers during that maintenance. If you have five + of them running, you can take one down for maintenance, and know + that you're still OK if one of the other four suddenly fails. +

                          + +

                          Your redundancy considerations should include all aspects of + your environment. If you have three ZooKeeper servers, but their + network cables are all plugged into the same network switch, then + the failure of that switch will take down your entire ensemble. +

                          + +
                          +

                          Here are the steps to setting a server that will be part of an ensemble. These steps should be performed on every host in the ensemble:

                          @@ -472,8 +580,8 @@

                          Clustered (Multi-Server) Setup

                          - - http://hadoop.apache.org/zookeeper/releases.html + + http://zookeeper.apache.org/releases.html

                          @@ -558,54 +666,12 @@

                          Clustered (Multi-Server) Setup

                          Test your deployment by connecting to the hosts:

                          -
                            - -
                          • -

                            In Java, you can run the following command to execute - simple operations:

                            - - -

                            -$ java -cp zookeeper.jar:lib/slf4j-api-1.6.1.jar:lib/slf4j-log4j12-1.6.1.jar:lib/log4j-1.2.15.jar:conf:src/java/lib/jline-0.9.94.jar \ - org.apache.zookeeper.ZooKeeperMain -server 127.0.0.1:2181 -

                            - -
                          • - - -
                          • - -

                            In C, you can compile either the single threaded client or - the multithreaded client: or n the c subdirectory in the - ZooKeeper sources. This compiles the single threaded - client:

                            - - -

                            -$ make cli_st -

                            - - -

                            And this compiles the mulithreaded client:

                            - - -

                            -$ make cli_mt -

                            - -
                          • - -
                          - - -

                          Running either program gives you a shell in which to execute - simple file-system-like operations. To connect to ZooKeeper with the - multithreaded client, for example, you would run:

                          + simple operations:

                          -$ cli_mt 127.0.0.1:2181 +$ bin/zkCli.sh -server 127.0.0.1:2181

                        • @@ -874,10 +940,11 @@

                          Ongoing Data Directory Cleanup

                          logs.

                          A ZooKeeper server will not remove - old snapshots and log files, this is the - responsibility of the operator. Every serving environment is - different and therefore the requirements of managing these - files may differ from install to install (backup for example). + old snapshots and log files when using the default + configuration (see autopurge below), this is the + responsibility of the operator. Every serving environment is + different and therefore the requirements of managing these + files may differ from install to install (backup for example).

                          The PurgeTxnLog utility implements a simple retention policy that administrators can use. The API docs contains details on @@ -891,6 +958,11 @@

                          Ongoing Data Directory Cleanup

                          can be run as a cron job on the ZooKeeper server machines to clean up the logs daily.

                           java -cp zookeeper.jar:lib/slf4j-api-1.6.1.jar:lib/slf4j-log4j12-1.6.1.jar:lib/log4j-1.2.15.jar:conf org.apache.zookeeper.server.PurgeTxnLog <dataDir> <snapDir> -n <count>
                          +

                          Automatic purging of the snapshots and corresponding + transaction logs was introduced in version 3.4.0 and can be + enabled via the following configuration parameters autopurge.snapRetainCount and autopurge.purgeInterval. For more on + this, see Advanced Configuration + below.

                          Debug Log Cleanup (log4j)

                          See the section on logging in this document. It is @@ -1081,20 +1153,6 @@

                          Advanced Configuration

                          -
                          -traceFile -
                          -
                          -

                          (Java system property: requestTraceFile)

                          -

                          If this option is defined, requests will be will logged to - a trace file named traceFile.year.month.day. Use of this option - provides useful debugging information, but will impact - performance. (Note: The system property has no zookeeper prefix, - and the configuration variable name is different from the system - property. Yes - it's not consistent, and it's annoying.)

                          -
                          - -
                          maxClientCnxns
                          @@ -1148,6 +1206,63 @@

                          Advanced Configuration

                          will allow the client to negotiate. Defaults to 20 times the tickTime.

                          + + +
                          +fsync.warningthresholdms +
                          +
                          +

                          (Java system property: zookeeper.fsync.warningthresholdms)

                          +

                          +New in 3.3.4: A + warning message will be output to the log whenever an + fsync in the Transactional Log (WAL) takes longer than + this value. The values is specified in milliseconds and + defaults to 1000. This value can only be set as a + system property.

                          +
                          + + +
                          +autopurge.snapRetainCount +
                          +
                          +

                          (No Java system property)

                          +

                          +New in 3.4.0: + When enabled, ZooKeeper auto purge feature retains + the autopurge.snapRetainCount most + recent snapshots and the corresponding transaction logs in the + dataDir and dataLogDir respectively and deletes the rest. + Defaults to 3. Minimum value is 3.

                          +
                          + + +
                          +autopurge.purgeInterval +
                          +
                          +

                          (No Java system property)

                          +

                          +New in 3.4.0: The + time interval in hours for which the purge task has to + be triggered. Set to a positive integer (1 and above) + to enable the auto purging. Defaults to 0.

                          +
                          + + +
                          +syncEnabled +
                          +
                          +

                          (Java system property: zookeeper.observer.syncEnabled)

                          +

                          +New in 3.4.6, 3.5.0: + The observers now log transaction and write snapshot to disk + by default like the participants. This reduces the recovery time + of the observers on restart. Set to "false" to disable this + feature. Default is "true"

                          +
                          @@ -1301,7 +1416,52 @@

                          Cluster Options

                          - + + +
                          +4lw.commands.whitelist +
                          +
                          +

                          (Java system property: zookeeper.4lw.commands.whitelist)

                          +

                          +New in 3.4.10: + This property contains a list of comma separated + Four Letter Words commands. It is introduced + to provide fine grained control over the set of commands ZooKeeper can execute, + so users can turn off certain commands if necessary. + By default it contains all supported four letter word commands except "wchp" and "wchc", + if the property is not specified. If the property is specified, then only commands listed + in the whitelist are enabled. +

                          +

                          Here's an example of the configuration that enables stat, ruok, conf, and isro + command while disabling the rest of Four Letter Words command:

                          +
                          +                4lw.commands.whitelist=stat, ruok, conf, isro
                          +              
                          +

                          Users can also use asterisk option so they don't have to include every command one by one in the list. + As an example, this will enable all four letter word commands: +

                          +
                          +                4lw.commands.whitelist=*
                          +              
                          +
                          + +
                          +tcpKeepAlive +
                          +
                          +

                          (Java system property: zookeeper.tcpKeepAlive)

                          +

                          +New in 3.4.11: + Sets the keepAlive flag on the sockets used by quorum members to perform elections. + This will allow for connections between quorum members to remain up when there is + network infrastructure that may otherwise terminate them. Some NATs and firewalls may + terminate or lose state for long running or idle connections.

                          + Enabling this option relies on OS level settings to work properly, check your operating + system's options regarding TCP keepalive for more information. + Defaults to false.

                          +
                          +

                          @@ -1336,6 +1496,166 @@

                          Authentication & Authorization Options

                          localhost (not over the network) or over an encrypted connection.

                          + + +
                          +isro +
                          +
                          +

                          +New in 3.4.0: Tests if + server is running in read-only mode. The server will respond with + "ro" if in read-only mode or "rw" if not in read-only mode.

                          +
                          + + +
                          +gtmk +
                          +
                          +

                          Gets the current trace mask as a 64-bit signed long value in + decimal format. See stmk for an explanation of + the possible values.

                          +
                          + + +
                          +stmk +
                          +
                          +

                          Sets the current trace mask. The trace mask is 64 bits, + where each bit enables or disables a specific category of trace + logging on the server. Log4J must be configured to enable + TRACE level first in order to see trace logging + messages. The bits of the trace mask correspond to the following + trace logging categories.

                          + + + +Trace Mask Bit Values + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                          Trace Mask Bit Values
                          0b0000000000Unused, reserved for future use.
                          0b0000000010Logs client requests, excluding ping + requests.
                          0b0000000100Unused, reserved for future use.
                          0b0000001000Logs client ping requests.
                          0b0000010000Logs packets received from the quorum peer that is + the current leader, excluding ping requests.
                          0b0000100000Logs addition, removal and validation of client + sessions.
                          0b0001000000Logs delivery of watch events to client + sessions.
                          0b0010000000Logs ping packets received from the quorum peer + that is the current leader.
                          0b0100000000Unused, reserved for future use.
                          0b1000000000Unused, reserved for future use.
                          +

                          All remaining bits in the 64-bit value are unused and + reserved for future use. Multiple trace logging categories are + specified by calculating the bitwise OR of the documented values. + The default trace mask is 0b0100110010. Thus, by default, trace + logging includes client requests, packets received from the + leader and sessions.

                          +

                          To set a different trace mask, send a request containing the + stmk four-letter word followed by the trace + mask represented as a 64-bit signed long value. This example uses + the Perl pack function to construct a trace + mask that enables all trace logging categories described above and + convert it to a 64-bit signed long value with big-endian byte + order. The result is appended to stmk and sent + to the server using netcat. The server responds with the new + trace mask in decimal format.

                          +
                          $ perl -e "print 'stmk', pack('q>', 0b0011111010)" | nc localhost 2181
                          +250
                          +              
                          +
                          + + + +

                          Experimental Options/Features

                          +

                          New features that are currently considered experimental.

                          +
                          + +
                          +Read Only Mode Server +
                          +
                          +

                          (Java system property: readonlymode.enabled)

                          +

                          +New in 3.4.0: + Setting this value to true enables Read Only Mode server + support (disabled by default). ROM allows clients + sessions which requested ROM support to connect to the + server even when the server might be partitioned from + the quorum. In this mode ROM clients can still read + values from the ZK service, but will be unable to write + values and see changes from other clients. See + ZOOKEEPER-784 for more details. +

                          +
                          +
                          @@ -1381,6 +1701,20 @@

                          Unsafe Options

                          Skips ACL checks. This results in a boost in throughput, but opens up full access to the data tree to everyone.

                          + + +
                          +quorumListenOnAllIPs +
                          +
                          +

                          When set to true the ZooKeeper server will listen + for connections from its peers on all available IP addresses, + and not only the address configured in the server list of the + configuration file. It affects the connections handling the + ZAB protocol and the Fast Leader Election protocol. Default + value is false.

                          +
                          + @@ -1678,6 +2012,18 @@

                          File Management

                          this document for more details on setting a retention policy and maintenance of ZooKeeper storage.

                          +
                          +
                          Note
                          +
                          + +

                          The data stored in these files is not encrypted. In the case of + storing sensitive data in ZooKeeper, necessary measures need to be + taken to prevent unauthorized access. Such measures are external to + ZooKeeper (e.g., control access to the files) and depend on the + individual settings in which it is being deployed.

                          + +
                          +

                          Things to Avoid

                          Here are some common problems you can avoid by configuring @@ -1730,6 +2076,17 @@

                          Things to Avoid

                          is to run load tests, and then make sure you are well below the usage limit that would cause the system to swap.

                          + + +
                          +Publicly accessible deployment +
                          +
                          +

                          + A ZooKeeper ensemble is expected to operate in a trusted computing environment. + It is thus recommended to deploy ZooKeeper behind a firewall. +

                          +
                          @@ -1762,7 +2119,7 @@

                          Best Practices

                          @@ -61,10 +61,10 @@ +-->
                          • -Project +Project
                          • -Wiki +Wiki
                          • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                            @@ -266,7 +266,7 @@

                            Introduction to hierarchical quorums

                            @@ -61,10 +61,10 @@ +-->
                            • -Project +Project
                            • -Wiki +Wiki
                            • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                              @@ -795,7 +795,7 @@

                              Use of Standard slf4j Idioms

                              @@ -61,10 +61,10 @@ +-->
                              • -Project +Project
                              • -Wiki +Wiki
                              • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                                @@ -469,7 +469,7 @@

                                ZooKeeper MBean Reference

                                @@ -61,10 +61,10 @@ +-->
                                • -Project +Project
                                • -Wiki +Wiki
                                • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                                  @@ -286,7 +286,7 @@

                                  How to use Observers

                                  to add an Observer to your ZooKeeper cluster. Now you can connect to it as though it were an ordinary Follower. Try it out, by running:

                                  -      bin/zkCli.sh -server localhost:2181
                                  +      $ bin/zkCli.sh -server localhost:2181
                                       

                                  where localhost:2181 is the hostname and port number of the Observer as @@ -356,7 +356,7 @@

                                  Example use cases

                                  @@ -61,10 +61,10 @@ +-->
                                  • -Project +Project
                                  • -Wiki +Wiki
                                  • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                                    @@ -577,7 +577,7 @@

                                    Performance

                                    Note

                                    In version 3.2 r/w performance improved by ~2x - compared to the previous + compared to the previous 3.1 release.

                                    @@ -653,7 +653,7 @@

                                    Reliability

                                    The ZooKeeper Project

                                    ZooKeeper has been - + successfully used in many industrial applications. It is used at Yahoo! as the @@ -667,7 +667,7 @@

                                    The ZooKeeper Project

                                    All users and developers are encouraged to join the community and contribute their expertise. See the - + Zookeeper Project on Apache for more information. @@ -694,7 +694,7 @@

                                    The ZooKeeper Project

                                    @@ -61,10 +61,10 @@ +-->
                                    • -Project +Project
                                    • -Wiki +Wiki
                                    • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                                      @@ -245,6 +245,9 @@

                                      Developing Distributed Applications that use ZooKeeper

                                      ZooKeeper Watches
                                    • @@ -467,8 +470,7 @@

                                      The ZooKeeper Data Model

                                    • The following characters are not allowed: \ud800 -uF8FFF, - \uFFF0-uFFFF, \uXFFFE - \uXFFFF (where X is a digit 1 - E), \uF0000 - - \uFFFFF.

                                      + \uFFF0 - uFFFF.

                                    • @@ -496,7 +498,7 @@

                                      ZNodes

                                      znode. Znodes maintain a stat structure that includes version numbers for data changes, acl changes. The stat structure also has timestamps. The version number, together with the - timestamp allow ZooKeeper to validate the cache and to coordinate + timestamp, allows ZooKeeper to validate the cache and to coordinate updates. Each time a znode's data changes, the version number increases. For instance, whenever a client retrieves data, it also receives the version of the data. And when a client performs an update or a delete, @@ -520,7 +522,7 @@

                                      ZNodes

                                      -

                                      Znodes are the main enitity that a programmer access. They have +

                                      A znode is the main abstraction a programmer needs to be aware of. Znodes have several characteristics that are worth mentioning here.

                                      Watches

                                      @@ -597,7 +599,7 @@

                                      Time in ZooKeeper

                                      -

                                      Every change to a a node will cause an increase to one of the +

                                      Every change to a node will cause an increase to one of the version numbers of that node. The three version numbers are version (number of changes to the data of a znode), cversion (number of changes to the children of a znode), and aversion (number of changes @@ -669,6 +671,18 @@

                                      ZooKeeper Stat Structure

                                      +
                                    • + +

                                      +pzxid +

                                      + + +

                                      The zxid of the change that last modified children of this znode.

                                      + +
                                    • + +
                                    • @@ -942,7 +956,7 @@

                                      ZooKeeper Sessions

                                      Added in 3.2.0 -- SessionMovedException. There is an internal exception that is generally not seen by clients called the SessionMovedException. This exception occurs because a request was received on a connection for a session - which has be reestablished on a different server. The normal cause of this error is + which has been reestablished on a different server. The normal cause of this error is a client that sends a request to a server, but the network packet gets delayed, so the client times out and connects to a new server. When the delayed packet arrives at the first server, the old server detects that the session has moved, and closes the @@ -951,7 +965,7 @@

                                      ZooKeeper Sessions

                                      condition can be seen is when two clients try to reestablish the same connection using a saved session id and password. One of the clients will reestablish the connection and the second client will be disconnected (causing the pair to attempt to re-establish - it's connection/session indefinitely).

                                      + its connection/session indefinitely).

                                      @@ -1030,14 +1044,66 @@

                                      ZooKeeper Watches

                                    Watches are maintained locally at the ZooKeeper server to which the - client is connected. This allows watches to be light weight to set, + client is connected. This allows watches to be lightweight to set, maintain, and dispatch. When a client connects to a new server, the watch will be triggered for any session events. Watches will not be received while disconnected from a server. When a client reconnects, any previously registered watches will be reregistered and triggered if needed. In general this all occurs transparently. There is one case where a watch - may be missed: a watch for the existance of a znode not yet created will + may be missed: a watch for the existence of a znode not yet created will be missed if the znode is created and deleted while disconnected.

                                    + +

                                    Semantics of Watches

                                    +

                                    We can set watches with the three calls that read the state of + ZooKeeper: exists, getData, and getChildren. The following list details + the events that a watch can trigger and the calls that enable them: +

                                    +
                                      + +
                                    • + +

                                      +Created event: +

                                      + +

                                      Enabled with a call to exists.

                                      + +
                                    • + + +
                                    • + +

                                      +Deleted event: +

                                      + +

                                      Enabled with a call to exists, getData, and getChildren.

                                      + +
                                    • + + +
                                    • + +

                                      +Changed event: +

                                      + +

                                      Enabled with a call to exists and getData.

                                      + +
                                    • + + +
                                    • + +

                                      +Child event: +

                                      + +

                                      Enabled with a call to getChildren.

                                      + +
                                    • + +

                                    What ZooKeeper Guarantees about Watches

                                    With regard to watches, ZooKeeper maintains these @@ -1424,7 +1490,7 @@

                                    ZooKeeper C client API

                                    This is a very simple example which is intended to show how to interact with ZooKeeper ACLs specifically. See .../trunk/src/c/src/cli.c - for an example of a proper C client implementation

                                    + for an example of a C client implementation

                                    @@ -1632,7 +1698,7 @@

                                    Consistency Guarantees

                                    have been applied. On some failures (communication errors, timeouts, etc) the client will not know if the update has applied or not. We take steps to minimize the failures, but the - only guarantee is only present with successful return codes. + guarantee is only present with successful return codes. (This is called the monotonicity condition in Paxos.)

                                  • @@ -1655,7 +1721,7 @@

                                    Consistency Guarantees

                                    The clients view of the system is guaranteed to be up-to-date - within a certain time bound. (On the order of tens of seconds.) + within a certain time bound (on the order of tens of seconds). Either system changes will be seen by a client within this bound, or the client will detect a service outage.

                                    @@ -1926,31 +1992,8 @@

                                    Installation

                                    - -

                                    Using the C Client

                                    -

                                    You can test your client by running a ZooKeeper server (see - instructions on the project wiki page on how to run it) and connecting - to it using one of the cli applications that were built as part of the - installation procedure. cli_mt (multithreaded, built against - zookeeper_mt library) is shown in this example, but you could also use - cli_st (singlethreaded, built against zookeeper_st library):

                                    -

                                    -$ cli_mt zookeeper_host:9876 -

                                    -

                                    This is a client application that gives you a shell for - executing simple ZooKeeper commands. Once successfully started - and connected to the server it displays a shell prompt. You - can now enter ZooKeeper commands. For example, to create a - node:

                                    -

                                    -> create /my_new_node -

                                    -

                                    To verify that the node's been created:

                                    -

                                    -> ls / -

                                    -

                                    You should see a list of node who are children of the root node - "/".

                                    + +

                                    Building Your Own C Client

                                    In order to be able to use the ZooKeeper API in your application you have to remember to

                                      @@ -1958,7 +2001,7 @@

                                      Using the C Client

                                    1. Include ZooKeeper header: #include - <zookeeper/zookeeper.h

                                      + <zookeeper/zookeeper.h>

                                    2. @@ -1976,11 +2019,15 @@

                                      Using the C Client

                                    -

                                    Refer to Program Structure, with Simple Example - for examples of usage in Java and C. - [tbd] +

                                    +
                                    Note
                                    +
                                    +

                                    + See .../trunk/src/c/src/cli.c + for an example of a C client implementation

                                    -

                                    +
                                    +
                                    @@ -2045,7 +2092,7 @@

                                    Gotchas: Common Problems and Troubleshooting

                                    If you are using watches, you must look for the connected watch event. When a ZooKeeper client disconnects from a server, you will not receive notification of changes until reconnected. If you are - watching for a znode to come into existance, you will miss the event + watching for a znode to come into existence, you will miss the event if the znode is created and deleted while you are disconnected.

                                    @@ -2162,7 +2209,7 @@

                                    Gotchas: Common Problems and Troubleshooting

                                    -Barrier and +Barrier and Queue Tutorial
                                    @@ -2174,7 +2221,7 @@

                                    Gotchas: Common Problems and Troubleshooting

                                    -ZooKeeper +ZooKeeper - A Reliable, Scalable Distributed Coordination System
                                    @@ -2228,7 +2275,7 @@

                                    Gotchas: Common Problems and Troubleshooting

                                    @@ -61,10 +61,10 @@ +-->
                                    • -Project +Project
                                    • -Wiki +Wiki
                                    • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                                      @@ -236,8 +236,7 @@

                                      Quotas

                                      are printed in the log of the ZooKeeper.

                                      -$java -cp zookeeper.jar:src/java/lib/log4j-1.2.15.jar/conf:src/java/lib/jline-0.9.94.jar \ - org.apache.zookeeper.ZooKeeperMain -server host:port +$ bin/zkCli.sh -server host:port

                                      The above command gives you a command line option of using quotas.

                                      @@ -281,7 +280,7 @@

                                      Deleting Quotas

                                      @@ -61,10 +61,10 @@ +-->
                                      • -Project +Project
                                      • -Wiki +Wiki
                                      • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                                        @@ -264,7 +264,7 @@

                                        Pre-requisites

                                        Download

                                        To get a ZooKeeper distribution, download a recent - + stable release from one of the Apache Download Mirrors.

                                        @@ -347,54 +347,8 @@

                                        Managing ZooKeeper Storage

                                        more details.

                                        Connecting to ZooKeeper

                                        -

                                        Once ZooKeeper is running, you have several options for connection - to it:

                                        -
                                          - -
                                        • - -

                                          -Java: Use

                                          - - -
                                          bin/zkCli.sh -server 127.0.0.1:2181
                                          - - +
                                          $ bin/zkCli.sh -server 127.0.0.1:2181

                                          This lets you perform simple, file-like operations.

                                          - -
                                        • - - -
                                        • - -

                                          -C: compile cli_mt - (multi-threaded) or cli_st (single-threaded) by running - make cli_mt or make - cli_st in - the src/c subdirectory in - the ZooKeeper sources. See the README contained within - src/c for full details.

                                          - - -

                                          You can run the program - from src/c using:

                                          - - -
                                          LD_LIBRARY_PATH=. cli_mt 127.0.0.1:2181
                                          - - -

                                          or

                                          - - -
                                          LD_LIBRARY_PATH=. cli_st 127.0.0.1:2181
                                          - -

                                          This will give you a simple shell to execute file - system like operations on ZooKeeper.

                                          - -
                                        • - -

                                        Once you have connected, you should see something like:

                                        @@ -533,8 +487,30 @@ 

                                        Running Replicated ZooKeeper

                                        ZooKeeper in replicated mode. A replicated group of servers in the same application is called a quorum, and in replicated mode, all servers in the quorum have copies of the same configuration - file. The file is similar to the one used in standalone mode, but with a - few differences. Here is an example:

                                        + file.

                                        +
                                        +
                                        Note
                                        +
                                        + +

                                        + For replicated mode, a minimum of three servers are required, + and it is strongly recommended that you have an odd number of + servers. If you only have two servers, then you are in a + situation where if one of them fails, there are not enough + machines to form a majority quorum. Two servers is inherently + less + stable than a single server, because there are two single + points of failure. +

                                        + +
                                        +
                                        +

                                        + The required + conf/zoo.cfg + file for replicated mode is similar to the one used in standalone + mode, but with a few differences. Here is an example: +

                                         tickTime=2000
                                         dataDir=/var/lib/zookeeper
                                        @@ -583,6 +559,14 @@ 

                                        Running Replicated ZooKeeper

                                        (in the above replicated example, running on a single localhost, you would still have three config files).

                                        + +

                                        Please be aware that setting up multiple servers on a single + machine will not create any redundancy. If something were to + happen which caused the machine to die, all of the zookeeper + servers would be offline. Full redundancy requires that each + server have its own machine. It must be a completely separate + physical server. Multiple virtual machines on the same physical + host are still vulnerable to the complete failure of that host.

                                        @@ -635,7 +619,7 @@

                                        Other Optimizations

                                        @@ -61,10 +61,10 @@ +-->
                                        • -Project +Project
                                        • -Wiki +Wiki
                                        • ZooKeeper 3.4 Documentation @@ -174,13 +174,13 @@
                                          @@ -884,7 +884,7 @@

                                          Complete Source Listing

                                          - + - + + + - + + @@ -66,7 +75,43 @@ + rev="3.2.2" conf="releaseaudit->default"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ivysettings.xml b/ivysettings.xml index 52cfa52dfb8..1d06c403cee 100644 --- a/ivysettings.xml +++ b/ivysettings.xml @@ -18,11 +18,9 @@ --> + value="https://repo1.maven.org/maven2/" override="false"/> - + value="https://repository.jboss.org/nexus/content/groups/public/" override="false"/> @@ -33,13 +31,10 @@ pattern="${maven2.pattern.ext}" m2compatible="true"/> - - diff --git a/src/LICENSE.txt b/src/LICENSE.txt new file mode 100644 index 00000000000..867ac6d84cf --- /dev/null +++ b/src/LICENSE.txt @@ -0,0 +1,216 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +This distribution bundles jline 0.9.94, which is available under the +2-clause BSD License. For details, see a copy of the license in +lib/jline-0.9.94.LICENSE.txt + +This distribution bundles SLF4J 1.6.1, which is available under the MIT +License. For details, see a copy of the license in +lib/slf4j-1.6.1.LICENSE.txt + +This distribution bundles a modified version of 'JZLib' as part of +Netty-3.7.0, which is available under the 3-clause BSD licence. For +details, see a copy of the licence in META-INF/license/LICENSE-jzlib.txt +as part of the Netty jar in lib/netty-3.7.0.Final.jar. diff --git a/src/NOTICE.txt b/src/NOTICE.txt new file mode 100644 index 00000000000..80d018867ba --- /dev/null +++ b/src/NOTICE.txt @@ -0,0 +1,100 @@ +Apache ZooKeeper +Copyright 2009-2017 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +This product includes software developed by +The Netty Project (http://netty.io/) +Copyright 2011 The Netty Project + +The Netty NOTICE file contains the following items: +This product contains the extensions to Java Collections Framework which has +been derived from the works by JSR-166 EG, Doug Lea, and Jason T. Greene: + + * LICENSE: + * license/LICENSE.jsr166y.txt (Public Domain) + * HOMEPAGE: + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/ + * http://viewvc.jboss.org/cgi-bin/viewvc.cgi/jbosscache/experimental/jsr166/ + +This product contains a modified version of Robert Harder's Public Domain +Base64 Encoder and Decoder, which can be obtained at: + + * LICENSE: + * license/LICENSE.base64.txt (Public Domain) + * HOMEPAGE: + * http://iharder.sourceforge.net/current/java/base64/ + +This product contains a modified version of 'JZlib', a re-implementation of +zlib in pure Java, which can be obtained at: + + * LICENSE: + * license/LICENSE.jzlib.txt (BSD Style License) + * HOMEPAGE: + * http://www.jcraft.com/jzlib/ + +This product contains a modified version of 'Webbit', a Java event based +WebSocket and HTTP server: + + * LICENSE: + * license/LICENSE.webbit.txt (BSD License) + * HOMEPAGE: + * https://github.com/joewalnes/webbit + +This product optionally depends on 'Protocol Buffers', Google's data +interchange format, which can be obtained at: + + * LICENSE: + * license/LICENSE.protobuf.txt (New BSD License) + * HOMEPAGE: + * http://code.google.com/p/protobuf/ + +This product optionally depends on 'Bouncy Castle Crypto APIs' to generate +a temporary self-signed X.509 certificate when the JVM does not provide the +equivalent functionality. It can be obtained at: + + * LICENSE: + * license/LICENSE.bouncycastle.txt (MIT License) + * HOMEPAGE: + * http://www.bouncycastle.org/ + +This product optionally depends on 'SLF4J', a simple logging facade for Java, +which can be obtained at: + + * LICENSE: + * license/LICENSE.slf4j.txt (MIT License) + * HOMEPAGE: + * http://www.slf4j.org/ + +This product optionally depends on 'Apache Commons Logging', a logging +framework, which can be obtained at: + + * LICENSE: + * license/LICENSE.commons-logging.txt (Apache License 2.0) + * HOMEPAGE: + * http://commons.apache.org/logging/ + +This product optionally depends on 'Apache Log4J', a logging framework, +which can be obtained at: + + * LICENSE: + * license/LICENSE.log4j.txt (Apache License 2.0) + * HOMEPAGE: + * http://logging.apache.org/log4j/ + +This product optionally depends on 'JBoss Logging', a logging framework, +which can be obtained at: + + * LICENSE: + * license/LICENSE.jboss-logging.txt (GNU LGPL 2.1) + * HOMEPAGE: + * http://anonsvn.jboss.org/repos/common/common-logging-spi/ + +This product optionally depends on 'Apache Felix', an open source OSGi +framework implementation, which can be obtained at: + + * LICENSE: + * license/LICENSE.felix.txt (Apache License 2.0) + * HOMEPAGE: + * http://felix.apache.org/ diff --git a/src/c/CMakeLists.txt b/src/c/CMakeLists.txt new file mode 100644 index 00000000000..6219fc4cc75 --- /dev/null +++ b/src/c/CMakeLists.txt @@ -0,0 +1,237 @@ +# 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. + +cmake_minimum_required(VERSION 3.6) + +project(zookeeper VERSION 3.4.10) +set(email user@zookeeper.apache.org) +set(description "zookeeper C client") + +# general options +include_directories(include tests generated ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) +if(UNIX) + add_compile_options(-Wall -fPIC) +elseif(WIN32) + add_compile_options(/W3) +endif() +add_definitions(-DUSE_STATIC_LIB) + +# TODO: Enable /WX and /W4 on Windows. Currently there are ~1000 warnings. +# TODO: Add Solaris support. +# TODO: Add a shared library option. +# TODO: Specify symbols to export. +# TODO: Generate doxygen documentation. + +# Sync API option +option(WANT_SYNCAPI "Enables Sync API support" ON) +if(WANT_SYNCAPI) + add_definitions(-DTHREADED) + if(WIN32) + # Note that the generator expression ensures that `/MTd` is used when Debug + # configurations are built. + add_compile_options(/MT$<$:d>) + endif() +endif() + +# CppUnit option +if(WIN32 OR APPLE) + # The tests do not yet compile on Windows or macOS, + # so we set this to off by default. + # + # Note that CMake does not have expressions except in conditionals, + # so we're left with this if/else/endif pattern. + set(DEFAULT_WANT_CPPUNIT OFF) +else() + set(DEFAULT_WANT_CPPUNIT ON) +endif() +option(WANT_CPPUNIT "Enables CppUnit and tests" ${DEFAULT_WANT_CPPUNIT}) + +# The function `to_have(in out)` converts a header name like `arpa/inet.h` +# into an Autotools style preprocessor definition `HAVE_ARPA_INET_H`. +# This is then set or unset in `configure_file()` step. +# +# Note that CMake functions do not have return values; instead an "out" +# variable must be passed, and explicitly set with parent scope. +function(to_have in out) + string(TOUPPER ${in} str) + string(REGEX REPLACE "/|\\." "_" str ${str}) + set(${out} "HAVE_${str}" PARENT_SCOPE) +endfunction() + +# include file checks +foreach(f generated/zookeeper.jute.h generated/zookeeper.jute.c) + if(EXISTS "${CMAKE_SOURCE_DIR}/${f}") + to_have(${f} name) + set(${name} 1) + else() + message(FATAL_ERROR + "jute files are missing!\n" + "Please run 'ant compile_jute' while in the ZooKeeper top level directory.") + endif() +endforeach() + +# header checks +include(CheckIncludeFile) +set(check_headers + arpa/inet.h + dlfcn.h + fcntl.h + inttypes.h + memory.h + netdb.h + netinet/in.h + stdint.h + stdlib.h + string.h + strings.h + sys/socket.h + sys/stat.h + sys/time.h + sys/types.h + unistd.h + sys/utsname.h) + +foreach(f ${check_headers}) + to_have(${f} name) + check_include_file(${f} ${name}) +endforeach() + +# function checks +include(CheckFunctionExists) +set(check_functions + getcwd + gethostbyname + gethostname + getlogin + getpwuid_r + gettimeofday + getuid + memmove + memset + poll + socket + strchr + strdup + strerror + strtol) + +foreach(fn ${check_functions}) + to_have(${fn} name) + check_function_exists(${fn} ${name}) +endforeach() + +# library checks +set(check_libraries rt m pthread) +foreach(lib ${check_libraries}) + to_have("lib${lib}" name) + find_library(${name} ${lib}) +endforeach() + +# IPv6 check +include(CheckStructHasMember) +check_struct_has_member("struct sockaddr_in6" sin6_addr "netinet/in.h" ZOO_IPV6_ENABLED) + +# configure +configure_file(cmake_config.h.in ${CMAKE_SOURCE_DIR}/include/config.h) + +# hashtable library +set(hashtable_sources src/hashtable/hashtable_itr.c src/hashtable/hashtable.c) +add_library(hashtable STATIC ${hashtable_sources}) +target_link_libraries(hashtable PUBLIC $<$:m>) + +# zookeeper library +set(zookeeper_sources + src/zookeeper.c + src/recordio.c + generated/zookeeper.jute.c + src/zk_log.c + src/zk_hashtable.c) + +if(WANT_SYNCAPI) + list(APPEND zookeeper_sources src/mt_adaptor.c) +else() + list(APPEND zookeeper_sources src/st_adaptor.c) +endif() + +if(WIN32) + list(APPEND zookeeper_sources src/winport.c) +endif() + +add_library(zookeeper STATIC ${zookeeper_sources}) +target_link_libraries(zookeeper PUBLIC + hashtable + $<$:rt> # clock_gettime + $<$:ws2_32>) # Winsock 2.0 + +if(WANT_SYNCAPI AND NOT WIN32) + find_package(Threads REQUIRED) + target_link_libraries(zookeeper PUBLIC Threads::Threads) +endif() + +# cli executable +add_executable(cli src/cli.c) +target_link_libraries(cli zookeeper) + +# load_gen executable +if(WANT_SYNCAPI AND NOT WIN32) + add_executable(load_gen src/load_gen.c) + target_link_libraries(load_gen zookeeper) +endif() + +# tests +set(test_sources + tests/TestDriver.cc + tests/LibCMocks.cc + tests/LibCSymTable.cc + tests/MocksBase.cc + tests/ZKMocks.cc + tests/Util.cc + tests/ThreadingUtil.cc + tests/TestZookeeperInit.cc + tests/TestZookeeperClose.cc + tests/TestClientRetry.cc + tests/TestOperations.cc + tests/TestMulti.cc + tests/TestWatchers.cc + tests/TestClient.cc) + +if(WANT_SYNCAPI) + list(APPEND test_sources tests/PthreadMocks.cc) +endif() + +if(WANT_CPPUNIT) + add_executable(zktest ${test_sources}) + target_compile_definitions(zktest + PRIVATE -DZKSERVER_CMD="${CMAKE_SOURCE_DIR}/tests/zkServer.sh") + # TODO: Use `find_library()` for `cppunit`. + target_link_libraries(zktest zookeeper cppunit dl) + + # This reads the link flags from the file `tests/wrappers.opt` into + # the variable `symbol_wrappers` for use in `target_link_libraries`. + # It is a holdover from the original build system. + file(STRINGS tests/wrappers.opt symbol_wrappers) + if(WANT_SYNCAPI) + file(STRINGS tests/wrappers-mt.opt symbol_wrappers_mt) + endif() + + target_link_libraries(zktest ${symbol_wrappers} ${symbol_wrappers_mt}) + + enable_testing() + add_test(NAME zktest_runner COMMAND zktest) + set_property(TEST zktest_runner PROPERTY ENVIRONMENT + "ZKROOT=${CMAKE_SOURCE_DIR}/../.." + "CLASSPATH=$CLASSPATH:$CLOVER_HOME/lib/clover.jar") +endif() diff --git a/src/c/Cli.vcproj b/src/c/Cli.vcproj deleted file mode 100644 index ba93ffc6432..00000000000 --- a/src/c/Cli.vcproj +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/c/Makefile.am b/src/c/Makefile.am index dbf4080f54e..5c2315244b8 100644 --- a/src/c/Makefile.am +++ b/src/c/Makefile.am @@ -1,11 +1,15 @@ # need this for Doxygen integration include $(top_srcdir)/aminclude.am -AM_CPPFLAGS = -I${srcdir}/include -I${srcdir}/tests -I${srcdir}/generated +if SOLARIS + SOLARIS_CPPFLAGS = -D_POSIX_PTHREAD_SEMANTICS + SOLARIS_LIB_LDFLAGS = -lnsl -lsocket +endif +AM_CPPFLAGS = -I${srcdir}/include -I${srcdir}/tests -I${srcdir}/generated $(SOLARIS_CPPFLAGS) AM_CFLAGS = -Wall -Werror AM_CXXFLAGS = -Wall $(USEIPV6) -LIB_LDFLAGS = -no-undefined -version-info 2 +LIB_LDFLAGS = -no-undefined -version-info 2 $(SOLARIS_LIB_LDFLAGS) pkginclude_HEADERS = include/zookeeper.h include/zookeeper_version.h include/zookeeper_log.h include/proto.h include/recordio.h generated/zookeeper.jute.h EXTRA_DIST=LICENSE @@ -22,7 +26,7 @@ COMMON_SRC = src/zookeeper.c include/zookeeper.h include/zookeeper_version.h inc src/zookeeper_log.h src/zk_log.c src/zk_hashtable.h src/zk_hashtable.c # These are the symbols (classes, mostly) we want to export from our library. -EXPORT_SYMBOLS = '(zoo_|zookeeper_|zhandle|Z|format_log_message|log_message|logLevel|deallocate_|zerror|is_unrecoverable)' +EXPORT_SYMBOLS = '(zoo_|zookeeper_|zhandle|Z|format_log_message|log_message|logLevel|deallocate_|allocate_|zerror|is_unrecoverable)' noinst_LTLIBRARIES += libzkst.la libzkst_la_SOURCES =$(COMMON_SRC) src/st_adaptor.c libzkst_la_LIBADD = -lm @@ -78,21 +82,31 @@ TEST_SOURCES = tests/TestDriver.cc tests/LibCMocks.cc tests/LibCSymTable.cc \ tests/TestMulti.cc tests/TestWatchers.cc -SYMBOL_WRAPPERS=$(shell cat ${srcdir}/tests/wrappers.opt) +if SOLARIS + SHELL_SYMBOL_WRAPPERS = cat ${srcdir}/tests/wrappers.opt + SYMBOL_WRAPPERS=$(SHELL_SYMBOL_WRAPPERS:sh) +else + SYMBOL_WRAPPERS=$(shell cat ${srcdir}/tests/wrappers.opt) +endif check_PROGRAMS = zktest-st nodist_zktest_st_SOURCES = $(TEST_SOURCES) zktest_st_LDADD = libzkst.la libhashtable.la $(CPPUNIT_LIBS) -zktest_st_CXXFLAGS = -DUSE_STATIC_LIB $(CPPUNIT_CFLAGS) $(USEIPV6) -zktest_st_LDFLAGS = -static-libtool-libs $(SYMBOL_WRAPPERS) +zktest_st_CXXFLAGS = -DUSE_STATIC_LIB $(CPPUNIT_CFLAGS) $(USEIPV6) $(SOLARIS_CPPFLAGS) +zktest_st_LDFLAGS = -static-libtool-libs $(SYMBOL_WRAPPERS) $(SOLARIS_LIB_LDFLAGS) if WANT_SYNCAPI check_PROGRAMS += zktest-mt nodist_zktest_mt_SOURCES = $(TEST_SOURCES) tests/PthreadMocks.cc zktest_mt_LDADD = libzkmt.la libhashtable.la -lpthread $(CPPUNIT_LIBS) zktest_mt_CXXFLAGS = -DUSE_STATIC_LIB -DTHREADED $(CPPUNIT_CFLAGS) $(USEIPV6) +if SOLARIS + SHELL_SYMBOL_WRAPPERS_MT = cat ${srcdir}/tests/wrappers-mt.opt + SYMBOL_WRAPPERS_MT=$(SYMBOL_WRAPPERS) $(SHELL_SYMBOL_WRAPPERS_MT:sh) +else SYMBOL_WRAPPERS_MT=$(SYMBOL_WRAPPERS) $(shell cat ${srcdir}/tests/wrappers-mt.opt) - zktest_mt_LDFLAGS = -static-libtool-libs $(SYMBOL_WRAPPERS_MT) +endif + zktest_mt_LDFLAGS = -static-libtool-libs $(SYMBOL_WRAPPERS_MT) $(SOLARIS_LIB_LDFLAGS) endif run-check: check diff --git a/src/c/NOTICE.txt b/src/c/NOTICE.txt index 07ef1ad516c..440b74d5aa2 100644 --- a/src/c/NOTICE.txt +++ b/src/c/NOTICE.txt @@ -1,5 +1,5 @@ Apache ZooKeeper -Copyright 2009-2011 The Apache Software Foundation +Copyright 2009-2017 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/src/c/README b/src/c/README index 397d04c0d04..51158455a2a 100644 --- a/src/c/README +++ b/src/c/README @@ -4,9 +4,9 @@ This package provides a C client interface to Zookeeper server. For the latest information about ZooKeeper, please visit our website at: - http://hadoop.apache.org/zookeeper/ + http://zookeeper.apache.org/ and our wiki, at: - http://wiki.apache.org/hadoop/ZooKeeper + https://cwiki.apache.org/confluence/display/ZOOKEEPER Full documentation for this release can also be found in ../../docs/index.html @@ -43,7 +43,12 @@ tar downloaded from Apache please skip to step 2. 2) unzip/untar the source tarball and cd to the zookeeper-x.x.x/src/c directory 3) change directory to src/c and do a "autoreconf -if" to bootstrap autoconf, automake and libtool. Please make sure you have autoconf - version 2.59 or greater installed. + version 2.59 or greater installed. If cppunit is installed in a non-standard + directory, you need to specify where to find cppunit.m4. For example, if + cppunit is installed under /usr/local, run: + + ACLOCAL="aclocal -I /usr/local/share/aclocal" autoreconf -if + 4) do a "./configure [OPTIONS]" to generate the makefile. See INSTALL for general information about running configure. Additionally, the configure supports the following options: @@ -66,8 +71,35 @@ tar downloaded from Apache please skip to step 2. default only HTML documentation is generated. For information on other document formats please use "./configure --help" - -USING THE CLIENT +Alternatively you can use the CMake build system. On Windows, this is required. +Follow steps 1 and 2 above, and then continue here. + +1) do a "cmake [OPTIONS]" to generate the makefile or msbuild files (the correct + build system will be generated based on your platform). Some options from above + are supported: + -DCMAKE_BUILD_TYPE Debug by default, Release enables optimzation etc. + -DWANT_SYNCAPI ON by default, OFF disables the Sync API support + -DWANT_CPPUNIT ON except on Windows, OFF disables the tests + -DBUILD_SHARED_LIBS not yet supported, only static libraries are built + other CMake options see "cmake --help" for generic options, such as generator + +2) do a "cmake --build ." to build the default targets. Alternatively you can + invoke "make" or "msbuild" manually. If the tests were enabled, use "ctest -V" + to run them. + +Current limitations of the CMake build system include lack of Solaris support, +no shared library option, no explicitly exported symbols (all are exported by +default), no versions on the libraries, and no documentation generation. +Features of CMake include a single, easily consumed cross-platform build system +to generate the ZooKeeper C Client libraries for any project, with little to no +configuration. + +EXAMPLE/SAMPLE C CLIENT SHELL + +NOTE: the ZooKeeper C client shell (cli_st and cli_mt) is meant as a +example/sample of ZooKeeper C client API usage. It is not a full +fledged client and not meant for production usage - see the Java +client shell for a fully featured shell. You can test your client by running a zookeeper server (see instructions on the project wiki page on how to run it) and connecting diff --git a/src/c/c-doc.Doxyfile b/src/c/c-doc.Doxyfile index e5f9e317865..e9029cb1745 100644 --- a/src/c/c-doc.Doxyfile +++ b/src/c/c-doc.Doxyfile @@ -1,1252 +1,1252 @@ -# Doxyfile 1.4.7 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project -# -# All text after a hash (#) is considered a comment and will be ignored -# The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. - -PROJECT_NAME = $(PROJECT)-$(VERSION) - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. - -PROJECT_NUMBER = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. - -OUTPUT_DIRECTORY = $(DOCDIR) - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. - -CREATE_SUBDIRS = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, -# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, -# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, -# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, -# Swedish, and Ukrainian. - -OUTPUT_LANGUAGE = English - -# This tag can be used to specify the encoding used in the generated output. -# The encoding is not always determined by the language that is chosen, -# but also whether or not the output is meant for Windows or non-Windows users. -# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES -# forces the Windows encoding (this is the default for the Windows binary), -# whereas setting the tag to NO uses a Unix-style encoding (the default for -# all platforms other than Windows). - -USE_WINDOWS_ENCODING = NO - -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief -# description. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. - -FULL_PATH_NAMES = YES - -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems -# doesn't support long names like on DOS, Mac, or CD-ROM. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like the Qt-style comments (thus requiring an -# explicit @brief command for a brief description. - -JAVADOC_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the DETAILS_AT_TOP tag is set to YES then Doxygen -# will output the detailed description near the top, like JavaDoc. -# If set to NO, the detailed description appears after the member -# documentation. - -DETAILS_AT_TOP = NO - -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. - -TAB_SIZE = 8 - -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for Java. -# For instance, namespaces will be presented as packages, qualified scopes -# will look different, etc. - -OPTIMIZE_OUTPUT_JAVA = NO - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to -# include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. - -BUILTIN_STL_SUPPORT = NO - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. - -DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. - -SUBGROUPING = YES - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES - -EXTRACT_ALL = NO - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. - -EXTRACT_STATIC = YES - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. - -EXTRACT_LOCAL_METHODS = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. - -SHOW_INCLUDE_FILES = NO - -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. - -SORT_BRIEF_DOCS = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. - -SORT_BY_SCOPE_NAME = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. - -GENERATE_DEPRECATEDLIST = YES - -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. - -SHOW_USED_FILES = YES - -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = NO - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from the -# version control system). Doxygen will invoke the program by executing (via -# popen()) the command , where is the value of -# the FILE_VERSION_FILTER tag, and is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. - -FILE_VERSION_FILTER = - -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. - -WARNINGS = YES - -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. - -WARN_IF_UNDOCUMENTED = YES - -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. - -WARN_NO_PARAMDOC = NO - -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. - -INPUT = include/zookeeper.h - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py - -FILE_PATTERNS = - -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. - -RECURSIVE = NO - -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded -# from the input. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command , where -# is the value of the INPUT_FILTER tag, and is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. If FILTER_PATTERNS is specified, this tag will be -# ignored. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER -# is applied to all files. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). - -FILTER_SOURCE_FILES = NO - -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES (the default) -# then for each documented function all documented -# functions referencing it will be listed. - -REFERENCED_BY_RELATION = YES - -# If the REFERENCES_RELATION tag is set to YES (the default) -# then for each documented function all documented entities -# called/used by that function will be listed. - -REFERENCES_RELATION = YES - -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. Otherwise they will link to the documentstion. - -REFERENCES_LINK_SOURCE = YES - -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. - -ALPHABETICAL_INDEX = NO - -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. - -GENERATE_HTML = $(GENERATE_HTML) - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = - -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. - -HTML_ALIGN_MEMBERS = YES - -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) -# of the generated HTML documentation. - -GENERATE_HTMLHELP = $(GENERATE_HTMLHELP) - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be -# written to the html output directory. - -CHM_FILE = ../$(PROJECT).chm - -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. - -HHC_LOCATION = $(HHC_PATH) - -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). - -GENERATE_CHI = $(GENERATE_CHI) - -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. - -TOC_EXPAND = NO - -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. - -DISABLE_INDEX = NO - -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. - -ENUM_VALUES_PER_LINE = 4 - -# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be -# generated containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, -# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are -# probably better off using the HTML help feature. - -GENERATE_TREEVIEW = NO - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. - -TREEVIEW_WIDTH = 250 - -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. - -GENERATE_LATEX = $(GENERATE_LATEX) - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and -# executive. If left blank a4wide will be used. - -PAPER_TYPE = $(PAPER_SIZE) - -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! - -LATEX_HEADER = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. - -PDF_HYPERLINKS = NO - -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a -# higher quality PDF documentation. - -USE_PDFLATEX = $(GENERATE_PDF) - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. - -LATEX_BATCHMODE = NO - -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. - -LATEX_HIDE_INDICES = NO - -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. - -GENERATE_RTF = $(GENERATE_RTF) - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. - -RTF_EXTENSIONS_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages - -GENERATE_MAN = $(GENERATE_MAN) - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) - -MAN_EXTENSION = .3 - -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. - -GENERATE_XML = $(GENERATE_XML) - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. - -XML_OUTPUT = xml - -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. - -XML_PROGRAMLISTING = YES - -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. This is useful -# if you want to understand what is going on. On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# in the INCLUDE_PATH (see below) will be search if a #include is found. - -SEARCH_INCLUDES = YES - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. - -PREDEFINED = - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse -# the parser if not removed. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. - -GENERATE_TAGFILE = $(DOCDIR)/$(PROJECT).tag - -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. - -EXTERNAL_GROUPS = YES - -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option is superseded by the HAVE_DOT option below. This is only a -# fallback. It is recommended to install and use dot, since it yields more -# powerful graphs. - -CLASS_DIAGRAMS = YES - -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) - -HAVE_DOT = $(HAVE_DOT) - -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. - -UML_LOOK = NO - -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. - -TEMPLATE_RELATIONS = NO - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. - -INCLUDE_GRAPH = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will -# generate a call dependency graph for every global function or class method. -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will -# generate a caller dependency graph for every global function or class method. -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are png, jpg, or gif -# If left blank png will be used. - -DOT_IMAGE_FORMAT = png - -# The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. - -DOT_PATH = $(DOT_PATH) - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). - -DOTFILE_DIRS = - -# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width -# (in pixels) of the graphs generated by dot. If a graph becomes larger than -# this value, doxygen will try to truncate the graph, so that it fits within -# the specified constraint. Beware that most browsers cannot cope with very -# large images. - -MAX_DOT_GRAPH_WIDTH = 1024 - -# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height -# (in pixels) of the graphs generated by dot. If a graph becomes larger than -# this value, doxygen will try to truncate the graph, so that it fits within -# the specified constraint. Beware that most browsers cannot cope with very -# large images. - -MAX_DOT_GRAPH_HEIGHT = 1024 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that a graph may be further truncated if the graph's -# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH -# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), -# the graph is not depth-constrained. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, which results in a white background. -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. - -DOT_MULTI_TARGETS = NO - -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. - -DOT_CLEANUP = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- - -# The SEARCHENGINE tag specifies whether or not a search engine should be -# used. If set to NO the values of all tags below this one will be ignored. - -SEARCHENGINE = NO +# Doxyfile 1.4.7 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = $(PROJECT)-$(VERSION) + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = $(DOCDIR) + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to +# include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST = YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = include/zookeeper.h + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = $(GENERATE_HTML) + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = $(GENERATE_HTMLHELP) + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = ../$(PROJECT).chm + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = $(HHC_PATH) + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = $(GENERATE_CHI) + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = $(GENERATE_LATEX) + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = $(PAPER_SIZE) + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = $(GENERATE_PDF) + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = $(GENERATE_RTF) + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = $(GENERATE_MAN) + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = $(GENERATE_XML) + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = $(DOCDIR)/$(PROJECT).tag + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = $(HAVE_DOT) + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a caller dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = $(DOT_PATH) + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that a graph may be further truncated if the graph's +# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH +# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), +# the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/src/c/cmake_config.h.in b/src/c/cmake_config.h.in new file mode 100644 index 00000000000..55efd8a86f9 --- /dev/null +++ b/src/c/cmake_config.h.in @@ -0,0 +1,154 @@ +/** + * 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. + */ + +#ifndef CONFIG_H_ +#define CONFIG_H_ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_FCNTL_H 1 + +/* Define to 1 if you have the file `generated/zookeeper.jute.c'. */ +#cmakedefine HAVE_GENERATED_ZOOKEEPER_JUTE_C 1 + +/* Define to 1 if you have the file `generated/zookeeper.jute.h'. */ +#cmakedefine HAVE_GENERATED_ZOOKEEPER_JUTE_H 1 + +/* Define to 1 if you have the `getcwd' function. */ +#cmakedefine HAVE_GETCWD 1 + +/* Define to 1 if you have the `gethostbyname' function. */ +#cmakedefine HAVE_GETHOSTBYNAME 1 + +/* Define to 1 if you have the `gethostname' function. */ +#cmakedefine HAVE_GETHOSTNAME 1 + +/* Define to 1 if you have the `getlogin' function. */ +#cmakedefine HAVE_GETLOGIN 1 + +/* Define to 1 if you have the `getpwuid_r' function. */ +#cmakedefine HAVE_GETPWUID_R 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#cmakedefine HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `getuid' function. */ +#cmakedefine HAVE_GETUID 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `rt' library (-lrt). */ +#cmakedefine HAVE_LIBRT 1 + +/* Define to 1 if you have the `memmove' function. */ +#cmakedefine HAVE_MEMMOVE 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `memset' function. */ +#cmakedefine HAVE_MEMSET 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the `poll' function. */ +#cmakedefine HAVE_POLL 1 + +/* Define to 1 if you have the `socket' function. */ +#cmakedefine HAVE_SOCKET 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strchr' function. */ +#cmakedefine HAVE_STRCHR 1 + +/* Define to 1 if you have the `strdup' function. */ +#cmakedefine HAVE_STRDUP 1 + +/* Define to 1 if you have the `strerror' function. */ +#cmakedefine HAVE_STRERROR 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRING_H 1 + +/* Define to 1 if you have the `strtol' function. */ +#cmakedefine HAVE_STRTOL 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UTSNAME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if IPv6 support is available. */ +#cmakedefine ZOO_IPV6_ENABLED 1 + +/* poll() second argument type */ +#define POLL_NFDS_TYPE nfds_t + +/* Name of package */ +#define PACKAGE "${PROJECT_NAME}" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "${email}" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "${description}" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "${description} ${PROJECT_VERSION}" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "${PROJECT_NAME}" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "${PROJECT_VERSION}" + +/* Version number of package */ +#define VERSION "${PROJECT_VERSION}" + +#endif diff --git a/src/c/configure.ac b/src/c/configure.ac index 4e1a8d5c22b..21d752c9b36 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.4.0,[zookeeper-user@hadoop.apache.org],[zookeeper]) +AC_INIT([zookeeper C client],3.4.10,[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 @@ -146,4 +146,14 @@ fi AC_CHECK_FUNCS([getcwd gethostbyname gethostname getlogin getpwuid_r gettimeofday getuid memmove memset poll socket strchr strdup strerror strtol]) AC_CONFIG_FILES([Makefile]) +AC_CANONICAL_HOST +AM_CONDITIONAL([SOLARIS],[ + case "$host_os" in + *solaris*) + true + ;; + *) + false + ;; + esac ]) AC_OUTPUT diff --git a/src/c/include/recordio.h b/src/c/include/recordio.h index 4e1b78eab24..eed5f99c2ef 100644 --- a/src/c/include/recordio.h +++ b/src/c/include/recordio.h @@ -19,11 +19,12 @@ #define __RECORDIO_H__ #include -#ifndef WIN32 -#define STRUCT_INITIALIZER(l,r) .l = r -#else -#define STRUCT_INITIALIZER(l,r) r +#include /* for int64_t */ +#ifdef WIN32 #include "winconfig.h" +#define STRUCT_INITIALIZER(l,r) r +#else +#define STRUCT_INITIALIZER(l,r) .l = r #endif #ifdef __cplusplus @@ -73,7 +74,7 @@ void close_buffer_iarchive(struct iarchive **ia); char *get_buffer(struct oarchive *); int get_buffer_len(struct oarchive *); -int64_t htonll(int64_t v); +int64_t zoo_htonll(int64_t v); #ifdef __cplusplus } diff --git a/src/c/include/winconfig.h b/src/c/include/winconfig.h index b89843c8829..c273a932a24 100644 --- a/src/c/include/winconfig.h +++ b/src/c/include/winconfig.h @@ -1,191 +1,15 @@ -/* Define to 1 if you have the header file. */ -#undef HAVE_ARPA_INET_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_DLFCN_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_FCNTL_H - -/* Define to 1 if you have the file `generated/zookeeper.jute.c'. */ -#define HAVE_GENERATED_ZOOKEEPER_JUTE_C 1 - -/* Define to 1 if you have the file `generated/zookeeper.jute.h'. */ -#define HAVE_GENERATED_ZOOKEEPER_JUTE_H 1 - -/* Define to 1 if you have the `getcwd' function. */ -#undef HAVE_GETCWD - -/* Define to 1 if you have the `gethostbyname' function. */ -#undef HAVE_GETHOSTBYNAME - -/* Define to 1 if you have the `gethostname' function. */ -#define HAVE_GETHOSTNAME 1 - -/* Define to 1 if you have the `getlogin' function. */ -#undef HAVE_GETLOGIN - -/* Define to 1 if you have the `getpwuid_r' function. */ -#undef HAVE_GETPWUID_R - -/* Define to 1 if you have the `gettimeofday' function. */ -#undef HAVE_GETTIMEOFDAY - -/* Define to 1 if you have the `getuid' function. */ -#undef HAVE_GETUID - -/* Define to 1 if you have the header file. */ -#undef HAVE_INTTYPES_H - -/* Define to 1 if you have the `memmove' function. */ -#undef HAVE_MEMMOVE - -/* Define to 1 if you have the header file. */ -#undef HAVE_MEMORY_H - -/* Define to 1 if you have the `memset' function. */ -#undef HAVE_MEMSET - -/* Define to 1 if you have the header file. */ -#undef HAVE_NETDB_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_NETINET_IN_H - -/* Define to 1 if you have the `poll' function. */ -#undef HAVE_POLL - -/* Define to 1 if you have the `socket' function. */ -#undef HAVE_SOCKET - -/* Define to 1 if you have the header file. */ -#undef HAVE_STDINT_H - -/* Define to 1 if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the `strchr' function. */ -#define HAVE_STRCHR 1 - -/* Define to 1 if you have the `strdup' function. */ -#define HAVE_STRDUP 1 - -/* Define to 1 if you have the `strerror' function. */ -#define HAVE_STRERROR 1 - -/* Define to 1 if you have the header file. */ -#undef HAVE_STRINGS_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_STRING_H - -/* Define to 1 if you have the `strtol' function. */ -#undef HAVE_STRTOL - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_SOCKET_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_STAT_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_TIME_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_TYPES_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_UTSNAME_H - -/* Define to 1 if you have the header file. */ -#undef HAVE_UNISTD_H - -/* Define to the sub-directory in which libtool stores uninstalled libraries. - */ -#define LT_OBJDIR - -/* Define to 1 if your C compiler doesn't accept -c and -o together. */ -/* #undef NO_MINUS_C_MINUS_O */ - -/* Name of package */ -#define PACKAGE "c-client-src" - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "zookeeper-user@hadoop.apache.org" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "zookeeper C client" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "zookeeper C client 3.4.0 win32" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "c-client-src" - -/* Define to the home page for this package. */ -#define PACKAGE_URL "" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "3.4.0" - -/* poll() second argument type */ -#define POLL_NFDS_TYPE - -/* Define to 1 if you have the ANSI C header files. */ -#define STDC_HEADERS - -/* Define to 1 if you can safely include both and . */ -#define TIME_WITH_SYS_TIME - -/* Version number of package */ -#define VERSION "3.4.0" - -/* Define to empty if `const' does not conform to ANSI C. */ -/* #undef const */ +#ifndef WINCONFIG_H_ +#define WINCONFIG_H_ /* Define to `__inline__' or `__inline' if that's what the C compiler calls it, or to nothing if 'inline' is not supported under any name. */ #ifndef __cplusplus #define inline __inline #endif -#ifdef WIN32 + #define __attribute__(x) #define __func__ __FUNCTION__ -#ifndef _WIN32_WINNT_NT4 -#define _WIN32_WINNT_NT4 0x0400 -#endif - -#define NTDDI_VERSION _WIN32_WINNT_NT4 -#define _WIN32_WINNT _WIN32_WINNT_NT4 - -#define _CRT_SECURE_NO_WARNINGS -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include -#include -#undef AF_INET6 -#undef min -#undef max - -#include - -#define strtok_r strtok_s -#define localtime_r(a,b) localtime_s(b,a) -#define get_errno() errno=GetLastError() -#define random rand -#define snprintf _snprintf - -#define ACL ZKACL // Conflict with windows API - -#define EAI_ADDRFAMILY WSAEINVAL -#define EHOSTDOWN EPIPE -#define ESTALE ENODEV - -#define EWOULDBLOCK WSAEWOULDBLOCK -#define EINPROGRESS WSAEINPROGRESS +#define ACL ZKACL /* Conflict with windows API */ -typedef int pid_t; #endif diff --git a/src/c/include/winstdint.h b/src/c/include/winstdint.h deleted file mode 100644 index d02608a5972..00000000000 --- a/src/c/include/winstdint.h +++ /dev/null @@ -1,247 +0,0 @@ -// ISO C9x compliant stdint.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006-2008 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _MSC_VER // [ -#error "Use this header only with Microsoft Visual C++ compilers!" -#endif // _MSC_VER ] - -#ifndef _MSC_STDINT_H_ // [ -#define _MSC_STDINT_H_ - -#if _MSC_VER > 1000 -#pragma once -#endif - -#include - -// For Visual Studio 6 in C++ mode and for many Visual Studio versions when -// compiling for ARM we should wrap include with 'extern "C++" {}' -// or compiler give many errors like this: -// error C2733: second C linkage of overloaded function 'wmemchr' not allowed -#ifdef __cplusplus -extern "C" { -#endif -# include -#ifdef __cplusplus -} -#endif - -// Define _W64 macros to mark types changing their size, like intptr_t. -#ifndef _W64 -# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 -# define _W64 __w64 -# else -# define _W64 -# endif -#endif - - -// 7.18.1 Integer types - -// 7.18.1.1 Exact-width integer types - -// Visual Studio 6 and Embedded Visual C++ 4 doesn't -// realize that, e.g. char has the same size as __int8 -// so we give up on __intX for them. -#if (_MSC_VER < 1300) - typedef signed char int8_t; - typedef signed short int16_t; - typedef signed int int32_t; - typedef unsigned char uint8_t; - typedef unsigned short uint16_t; - typedef unsigned int uint32_t; -#else - typedef signed __int8 int8_t; - typedef signed __int16 int16_t; - typedef signed __int32 int32_t; - typedef unsigned __int8 uint8_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int32 uint32_t; -#endif -typedef signed __int64 int64_t; -typedef unsigned __int64 uint64_t; - - -// 7.18.1.2 Minimum-width integer types -typedef int8_t int_least8_t; -typedef int16_t int_least16_t; -typedef int32_t int_least32_t; -typedef int64_t int_least64_t; -typedef uint8_t uint_least8_t; -typedef uint16_t uint_least16_t; -typedef uint32_t uint_least32_t; -typedef uint64_t uint_least64_t; - -// 7.18.1.3 Fastest minimum-width integer types -typedef int8_t int_fast8_t; -typedef int16_t int_fast16_t; -typedef int32_t int_fast32_t; -typedef int64_t int_fast64_t; -typedef uint8_t uint_fast8_t; -typedef uint16_t uint_fast16_t; -typedef uint32_t uint_fast32_t; -typedef uint64_t uint_fast64_t; - -// 7.18.1.4 Integer types capable of holding object pointers -#ifdef _WIN64 // [ - typedef signed __int64 intptr_t; - typedef unsigned __int64 uintptr_t; -#else // _WIN64 ][ - typedef _W64 signed int intptr_t; - typedef _W64 unsigned int uintptr_t; -#endif // _WIN64 ] - -// 7.18.1.5 Greatest-width integer types -typedef int64_t intmax_t; -typedef uint64_t uintmax_t; - - -// 7.18.2 Limits of specified-width integer types - -#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 - -// 7.18.2.1 Limits of exact-width integer types -#define INT8_MIN ((int8_t)_I8_MIN) -#define INT8_MAX _I8_MAX -#define INT16_MIN ((int16_t)_I16_MIN) -#define INT16_MAX _I16_MAX -#define INT32_MIN ((int32_t)_I32_MIN) -#define INT32_MAX _I32_MAX -#define INT64_MIN ((int64_t)_I64_MIN) -#define INT64_MAX _I64_MAX -#define UINT8_MAX _UI8_MAX -#define UINT16_MAX _UI16_MAX -#define UINT32_MAX _UI32_MAX -#define UINT64_MAX _UI64_MAX - -// 7.18.2.2 Limits of minimum-width integer types -#define INT_LEAST8_MIN INT8_MIN -#define INT_LEAST8_MAX INT8_MAX -#define INT_LEAST16_MIN INT16_MIN -#define INT_LEAST16_MAX INT16_MAX -#define INT_LEAST32_MIN INT32_MIN -#define INT_LEAST32_MAX INT32_MAX -#define INT_LEAST64_MIN INT64_MIN -#define INT_LEAST64_MAX INT64_MAX -#define UINT_LEAST8_MAX UINT8_MAX -#define UINT_LEAST16_MAX UINT16_MAX -#define UINT_LEAST32_MAX UINT32_MAX -#define UINT_LEAST64_MAX UINT64_MAX - -// 7.18.2.3 Limits of fastest minimum-width integer types -#define INT_FAST8_MIN INT8_MIN -#define INT_FAST8_MAX INT8_MAX -#define INT_FAST16_MIN INT16_MIN -#define INT_FAST16_MAX INT16_MAX -#define INT_FAST32_MIN INT32_MIN -#define INT_FAST32_MAX INT32_MAX -#define INT_FAST64_MIN INT64_MIN -#define INT_FAST64_MAX INT64_MAX -#define UINT_FAST8_MAX UINT8_MAX -#define UINT_FAST16_MAX UINT16_MAX -#define UINT_FAST32_MAX UINT32_MAX -#define UINT_FAST64_MAX UINT64_MAX - -// 7.18.2.4 Limits of integer types capable of holding object pointers -#ifdef _WIN64 // [ -# define INTPTR_MIN INT64_MIN -# define INTPTR_MAX INT64_MAX -# define UINTPTR_MAX UINT64_MAX -#else // _WIN64 ][ -# define INTPTR_MIN INT32_MIN -# define INTPTR_MAX INT32_MAX -# define UINTPTR_MAX UINT32_MAX -#endif // _WIN64 ] - -// 7.18.2.5 Limits of greatest-width integer types -#define INTMAX_MIN INT64_MIN -#define INTMAX_MAX INT64_MAX -#define UINTMAX_MAX UINT64_MAX - -// 7.18.3 Limits of other integer types - -#ifdef _WIN64 // [ -# define PTRDIFF_MIN _I64_MIN -# define PTRDIFF_MAX _I64_MAX -#else // _WIN64 ][ -# define PTRDIFF_MIN _I32_MIN -# define PTRDIFF_MAX _I32_MAX -#endif // _WIN64 ] - -#define SIG_ATOMIC_MIN INT_MIN -#define SIG_ATOMIC_MAX INT_MAX - -#ifndef SIZE_MAX // [ -# ifdef _WIN64 // [ -# define SIZE_MAX _UI64_MAX -# else // _WIN64 ][ -# define SIZE_MAX _UI32_MAX -# endif // _WIN64 ] -#endif // SIZE_MAX ] - -// WCHAR_MIN and WCHAR_MAX are also defined in -#ifndef WCHAR_MIN // [ -# define WCHAR_MIN 0 -#endif // WCHAR_MIN ] -#ifndef WCHAR_MAX // [ -# define WCHAR_MAX _UI16_MAX -#endif // WCHAR_MAX ] - -#define WINT_MIN 0 -#define WINT_MAX _UI16_MAX - -#endif // __STDC_LIMIT_MACROS ] - - -// 7.18.4 Limits of other integer types - -#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 - -// 7.18.4.1 Macros for minimum-width integer constants - -#define INT8_C(val) val##i8 -#define INT16_C(val) val##i16 -#define INT32_C(val) val##i32 -#define INT64_C(val) val##i64 - -#define UINT8_C(val) val##ui8 -#define UINT16_C(val) val##ui16 -#define UINT32_C(val) val##ui32 -#define UINT64_C(val) val##ui64 - -// 7.18.4.2 Macros for greatest-width integer constants -#define INTMAX_C INT64_C -#define UINTMAX_C UINT64_C - -#endif // __STDC_CONSTANT_MACROS ] - - -#endif // _MSC_STDINT_H_ ] diff --git a/src/c/include/zookeeper.h b/src/c/include/zookeeper.h index 7d1066a9313..2503d69eb39 100644 --- a/src/c/include/zookeeper.h +++ b/src/c/include/zookeeper.h @@ -20,12 +20,22 @@ #define ZOOKEEPER_H_ #include -#ifndef WIN32 + +#include "config.h" + +#ifdef HAVE_SYS_SOCKET_H #include +#endif + +#ifdef HAVE_SYS_TIME_H #include -#else -#include "winconfig.h" #endif + +#ifdef WIN32 +#include /* must always be included before ws2tcpip.h */ +#include /* for struct sock_addr and socklen_t */ +#endif + #include #include @@ -468,8 +478,8 @@ ZOOAPI zhandle_t *zookeeper_init(const char *host, watcher_fn fn, * ZBADARGUMENTS - invalid input parameters * ZMARSHALLINGERROR - failed to marshall a request; possibly, out of memory * ZOPERATIONTIMEOUT - failed to flush the buffers within the specified timeout. - * ZCONNECTIONLOSS - a network error occured while attempting to send request to server - * ZSYSTEMERROR -- a system (OS) error occured; it's worth checking errno to get details + * ZCONNECTIONLOSS - a network error occurred while attempting to send request to server + * ZSYSTEMERROR -- a system (OS) error occurred; it's worth checking errno to get details */ ZOOAPI int zookeeper_close(zhandle_t *zh); @@ -523,12 +533,12 @@ ZOOAPI struct sockaddr* zookeeper_get_connected_host(zhandle_t *zh, * ZOK - success * ZBADARGUMENTS - invalid input parameters * ZINVALIDSTATE - zhandle state is either ZOO_SESSION_EXPIRED_STATE or ZOO_AUTH_FAILED_STATE - * ZCONNECTIONLOSS - a network error occured while attempting to establish + * ZCONNECTIONLOSS - a network error occurred while attempting to establish * a connection to the server * ZMARSHALLINGERROR - failed to marshall a request; possibly, out of memory * ZOPERATIONTIMEOUT - hasn't received anything from the server for 2/3 of the * timeout value specified in zookeeper_init() - * ZSYSTEMERROR -- a system (OS) error occured; it's worth checking errno to get details + * ZSYSTEMERROR -- a system (OS) error occurred; it's worth checking errno to get details */ #ifdef WIN32 ZOOAPI int zookeeper_interest(zhandle_t *zh, SOCKET *fd, int *interest, @@ -547,11 +557,11 @@ ZOOAPI int zookeeper_interest(zhandle_t *zh, int *fd, int *interest, * ZOK - success * ZBADARGUMENTS - invalid input parameters * ZINVALIDSTATE - zhandle state is either ZOO_SESSION_EXPIRED_STATE or ZOO_AUTH_FAILED_STATE - * ZCONNECTIONLOSS - a network error occured while attempting to send request to server + * ZCONNECTIONLOSS - a network error occurred while attempting to send request to server * ZSESSIONEXPIRED - connection attempt failed -- the session's expired * ZAUTHFAILED - authentication request failed, e.i. invalid credentials * ZRUNTIMEINCONSISTENCY - a server response came out of order - * ZSYSTEMERROR -- a system (OS) error occured; it's worth checking errno to get details + * ZSYSTEMERROR -- a system (OS) error occurred; it's worth checking errno to get details * ZNOTHING -- not an error; simply indicates that there no more data from the server * to be processed (when called with ZOOKEEPER_READ flag). */ @@ -1166,7 +1176,7 @@ ZOOAPI const char* zerror(int c); * ZBADARGUMENTS - invalid input parameters * ZINVALIDSTATE - zhandle state is either ZOO_SESSION_EXPIRED_STATE or ZOO_AUTH_FAILED_STATE * ZMARSHALLINGERROR - failed to marshall a request; possibly, out of memory - * ZSYSTEMERROR - a system error occured + * ZSYSTEMERROR - a system error occurred */ ZOOAPI int zoo_add_auth(zhandle_t *zh,const char* scheme,const char* cert, int certLen, void_completion_t completion, const void *data); diff --git a/src/c/include/zookeeper_version.h b/src/c/include/zookeeper_version.h index 789653f0c16..186dc74d6d1 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 4 -#define ZOO_PATCH_VERSION 0 +#define ZOO_PATCH_VERSION 10 #ifdef __cplusplus } diff --git a/src/c/src/cli.c b/src/c/src/cli.c index c9fe4ddd36a..ef32a1045bb 100644 --- a/src/c/src/cli.c +++ b/src/c/src/cli.c @@ -16,6 +16,14 @@ * limitations under the License. */ +/** + * cli.c is a example/sample C client shell for ZooKeeper. It contains + * basic shell functionality which exercises some of the features of + * the ZooKeeper C client API. It is not a full fledged client and is + * not meant for production usage - see the Java client shell for a + * fully featured shell. + */ + #include #include #include @@ -84,13 +92,30 @@ static const char* state2String(int state){ return "INVALID_STATE"; } +static const char* type2String(int state){ + if (state == ZOO_CREATED_EVENT) + return "CREATED_EVENT"; + if (state == ZOO_DELETED_EVENT) + return "DELETED_EVENT"; + if (state == ZOO_CHANGED_EVENT) + return "CHANGED_EVENT"; + if (state == ZOO_CHILD_EVENT) + return "CHILD_EVENT"; + if (state == ZOO_SESSION_EVENT) + return "SESSION_EVENT"; + if (state == ZOO_NOTWATCHING_EVENT) + return "NOTWATCHING_EVENT"; + + return "UNKNOWN_EVENT_TYPE"; +} + void watcher(zhandle_t *zzh, int type, int state, const char *path, void* context) { /* Be careful using zh here rather than zzh - as this may be mt code * the client lib may call the watcher before zookeeper_init returns */ - fprintf(stderr, "Watcher %d state = %s", type, state2String(state)); + fprintf(stderr, "Watcher %s state = %s", type2String(type), state2String(state)); if (path && strlen(path) > 0) { fprintf(stderr, " for path %s", path); } @@ -165,6 +190,11 @@ void my_string_completion(int rc, const char *name, const void *data) { shutdownThisThing=1; } +void my_string_completion_free_data(int rc, const char *name, const void *data) { + my_string_completion(rc, name, data); + free((void*)data); +} + void my_data_completion(int rc, const char *value, int value_len, const struct Stat *stat, const void *data) { struct timeval tv; @@ -298,6 +328,7 @@ void processline(char *line) { fprintf(stderr, " ls2 \n"); fprintf(stderr, " sync \n"); fprintf(stderr, " exists \n"); + fprintf(stderr, " wexists \n"); fprintf(stderr, " myid\n"); fprintf(stderr, " verbose\n"); fprintf(stderr, " addauth \n"); @@ -400,7 +431,7 @@ void processline(char *line) { // my_string_completion, strdup(line)); // } rc = zoo_acreate(zh, line, "new", 3, &ZOO_OPEN_ACL_UNSAFE, flags, - my_string_completion, strdup(line)); + my_string_completion_free_data, strdup(line)); if (rc) { fprintf(stderr, "Error %d for %s\n", rc, line); } @@ -424,7 +455,24 @@ void processline(char *line) { fprintf(stderr, "Path must start with /, found: %s\n", line); return; } - rc = zoo_async(zh, line, my_string_completion, strdup(line)); + rc = zoo_async(zh, line, my_string_completion_free_data, strdup(line)); + if (rc) { + fprintf(stderr, "Error %d for %s\n", rc, line); + } + } else if (startsWith(line, "wexists ")) { +#ifdef THREADED + struct Stat stat; +#endif + line += 8; + if (line[0] != '/') { + fprintf(stderr, "Path must start with /, found: %s\n", line); + return; + } +#ifndef THREADED + rc = zoo_awexists(zh, line, watcher, (void*) 0, my_stat_completion, strdup(line)); +#else + rc = zoo_wexists(zh, line, watcher, (void*) 0, &stat); +#endif if (rc) { fprintf(stderr, "Error %d for %s\n", rc, line); } @@ -469,7 +517,7 @@ void processline(char *line) { *ptr = '\0'; ptr++; } - zoo_add_auth(zh, line, ptr, ptr ? strlen(ptr)-1 : 0, NULL, NULL); + zoo_add_auth(zh, line, ptr, ptr ? strlen(ptr) : 0, NULL, NULL); } } @@ -500,7 +548,15 @@ int main(int argc, char **argv) { } if (argc > 2) { if(strncmp("cmd:",argv[2],4)==0){ - strcpy(cmd,argv[2]+4); + size_t cmdlen = strlen(argv[2]); + if (cmdlen > sizeof(cmd)) { + fprintf(stderr, + "Command length %zu exceeds max length of %zu\n", + cmdlen, + sizeof(cmd)); + return 2; + } + strncpy(cmd, argv[2]+4, sizeof(cmd)); batchMode=1; fprintf(stderr,"Batch mode: %s\n",cmd); }else{ diff --git a/src/c/src/hashtable/hashtable_itr.h b/src/c/src/hashtable/hashtable_itr.h index 234d3e3b9d0..30379c7ee1e 100644 --- a/src/c/src/hashtable/hashtable_itr.h +++ b/src/c/src/hashtable/hashtable_itr.h @@ -32,7 +32,7 @@ hashtable_iterator(struct hashtable *h); /* hashtable_iterator_key * - return the value of the (key,value) pair at the current position */ -extern inline void * +static inline void * hashtable_iterator_key(struct hashtable_itr *i) { return i->e->k; @@ -41,7 +41,7 @@ hashtable_iterator_key(struct hashtable_itr *i) /*****************************************************************************/ /* value - return the value of the (key,value) pair at the current position */ -extern inline void * +static inline void * hashtable_iterator_value(struct hashtable_itr *i) { return i->e->v; diff --git a/src/c/src/load_gen.c b/src/c/src/load_gen.c index 3527948c2d4..546d5d16d74 100644 --- a/src/c/src/load_gen.c +++ b/src/c/src/load_gen.c @@ -19,13 +19,9 @@ #include #include "zookeeper_log.h" #include -#ifndef WIN32 #ifdef THREADED #include #endif -#else -#include "win32port.h" -#endif #include #include @@ -106,6 +102,7 @@ int doCreateNodes(const char* root, int count){ } if(rc!=ZOK) return rc; } + return ZOK; } int createRoot(const char* root){ @@ -131,6 +128,7 @@ int doWrites(const char* root, int count){ rc=zoo_aset(zh, nodeName, "second", 6,-1,write_completion, 0); if(rc!=ZOK) return rc; } + return ZOK; } void read_completion(int rc, const char *value, int value_len, @@ -159,6 +157,7 @@ int doReads(const char* root, int count){ rc=zoo_aget(zh, nodeName,0,read_completion, 0); if(rc!=ZOK) return rc; } + return ZOK; } void delete_completion(int rc, const void *data) { @@ -176,6 +175,7 @@ int doDeletes(const char* root, int count){ rc=zoo_adelete(zh, nodeName,-1,delete_completion, 0); if(rc!=ZOK) return rc; } + return ZOK; } static int free_String_vector(struct String_vector *v) { @@ -252,7 +252,7 @@ int main(int argc, char **argv) { deletedCounter=0; rc=recursiveDelete(argv[2]); if(rc==ZOK){ - LOG_INFO(("Succesfully deleted a subtree starting at %s (%d nodes)", + LOG_INFO(("Successfully deleted a subtree starting at %s (%d nodes)", argv[2],deletedCounter)); exit(0); } diff --git a/src/c/src/mt_adaptor.c b/src/c/src/mt_adaptor.c index 974063fda66..52d86d5d994 100644 --- a/src/c/src/mt_adaptor.c +++ b/src/c/src/mt_adaptor.c @@ -19,7 +19,7 @@ #define THREADED #endif -#ifndef DLL_EXPORT +#if !defined(DLL_EXPORT) && !defined(USE_STATIC_LIB) # define USE_STATIC_LIB #endif @@ -44,30 +44,30 @@ #include #endif -void zoo_lock_auth(zhandle_t *zh) +int zoo_lock_auth(zhandle_t *zh) { - pthread_mutex_lock(&zh->auth_h.lock); + return pthread_mutex_lock(&zh->auth_h.lock); } -void zoo_unlock_auth(zhandle_t *zh) +int zoo_unlock_auth(zhandle_t *zh) { - pthread_mutex_unlock(&zh->auth_h.lock); + return pthread_mutex_unlock(&zh->auth_h.lock); } -void lock_buffer_list(buffer_head_t *l) +int lock_buffer_list(buffer_head_t *l) { - pthread_mutex_lock(&l->lock); + return pthread_mutex_lock(&l->lock); } -void unlock_buffer_list(buffer_head_t *l) +int unlock_buffer_list(buffer_head_t *l) { - pthread_mutex_unlock(&l->lock); + return pthread_mutex_unlock(&l->lock); } -void lock_completion_list(completion_head_t *l) +int lock_completion_list(completion_head_t *l) { - pthread_mutex_lock(&l->lock); + return pthread_mutex_lock(&l->lock); } -void unlock_completion_list(completion_head_t *l) +int unlock_completion_list(completion_head_t *l) { pthread_cond_broadcast(&l->cond); - pthread_mutex_unlock(&l->lock); + return pthread_mutex_unlock(&l->lock); } struct sync_completion *alloc_sync_completion(void) { @@ -373,8 +373,7 @@ void *do_io(void *v) int interest; int timeout; int maxfd=1; - int rc; - + zookeeper_interest(zh, &fd, &interest, &tv); if (fd != -1) { fds[1].fd=fd; @@ -436,7 +435,7 @@ void *do_io(void *v) } #endif // dispatch zookeeper events - rc = zookeeper_process(zh, interest); + zookeeper_process(zh, interest); // check the current state of the zhandle and terminate // if it is_unrecoverable() if(is_unrecoverable(zh)) @@ -483,25 +482,9 @@ int32_t inc_ref_counter(zhandle_t* zh,int i) int32_t fetch_and_add(volatile int32_t* operand, int incr) { #ifndef WIN32 - int32_t result; - asm __volatile__( - "lock xaddl %0,%1\n" - : "=r"(result), "=m"(*(int *)operand) - : "0"(incr) - : "memory"); - return result; + return __sync_fetch_and_add(operand, incr); #else - volatile int32_t result; - _asm - { - mov eax, operand; //eax = v; - mov ebx, incr; // ebx = i; - mov ecx, 0x0; // ecx = 0; - lock xadd dword ptr [eax], ecx; - lock xadd dword ptr [eax], ebx; - mov result, ecx; // result = ebx; - } - return result; + return InterlockedExchangeAdd(operand, incr); #endif } @@ -515,16 +498,22 @@ __attribute__((constructor)) int32_t get_xid() return fetch_and_add(&xid,1); } -void enter_critical(zhandle_t* zh) +int enter_critical(zhandle_t* zh) { struct adaptor_threads *adaptor = zh->adaptor_priv; - if(adaptor) - pthread_mutex_lock(&adaptor->zh_lock); + if (adaptor) { + return pthread_mutex_lock(&adaptor->zh_lock); + } else { + return 0; + } } -void leave_critical(zhandle_t* zh) +int leave_critical(zhandle_t* zh) { struct adaptor_threads *adaptor = zh->adaptor_priv; - if(adaptor) - pthread_mutex_unlock(&adaptor->zh_lock); + if (adaptor) { + return pthread_mutex_unlock(&adaptor->zh_lock); + } else { + return 0; + } } diff --git a/src/c/src/recordio.c b/src/c/src/recordio.c index cf8a1ac7c81..0fcf48e5ff7 100644 --- a/src/c/src/recordio.c +++ b/src/c/src/recordio.c @@ -23,6 +23,8 @@ #include #ifndef WIN32 #include +#else +#include /* for _htonl and _ntohl */ #endif void deallocate_String(char **s) @@ -80,7 +82,7 @@ int oa_serialize_int(struct oarchive *oa, const char *tag, const int32_t *d) priv->off+=sizeof(i); return 0; } -int64_t htonll(int64_t v) +int64_t zoo_htonll(int64_t v) { int i = 0; char *s = (char *)&v; @@ -98,7 +100,7 @@ int64_t htonll(int64_t v) int oa_serialize_long(struct oarchive *oa, const char *tag, const int64_t *d) { - const int64_t i = htonll(*d); + const int64_t i = zoo_htonll(*d); struct buff_struct *priv = oa->priv; if ((priv->len - priv->off) < sizeof(i)) { int rc = resize_buffer(priv, priv->len + sizeof(i)); @@ -207,7 +209,7 @@ int ia_deserialize_long(struct iarchive *ia, const char *tag, int64_t *count) } memcpy(count, priv->buffer+priv->off, sizeof(*count)); priv->off+=sizeof(*count); - v = htonll(*count); // htonll and ntohll do the same + v = zoo_htonll(*count); // htonll and ntohll do the same *count = v; return 0; } @@ -296,9 +298,11 @@ static struct oarchive oa_default = { STRUCT_INITIALIZER (start_record , oa_star struct iarchive *create_buffer_iarchive(char *buffer, int len) { - struct iarchive *ia = malloc(sizeof(*ia)); - struct buff_struct *buff = malloc(sizeof(struct buff_struct)); + struct iarchive *ia; + struct buff_struct *buff; + ia = malloc(sizeof(*ia)); if (!ia) return 0; + buff = malloc(sizeof(struct buff_struct)); if (!buff) { free(ia); return 0; @@ -313,9 +317,11 @@ struct iarchive *create_buffer_iarchive(char *buffer, int len) struct oarchive *create_buffer_oarchive() { - struct oarchive *oa = malloc(sizeof(*oa)); - struct buff_struct *buff = malloc(sizeof(struct buff_struct)); + struct oarchive *oa; + struct buff_struct *buff; + oa = malloc(sizeof(*oa)); if (!oa) return 0; + buff = malloc(sizeof(struct buff_struct)); if (!buff) { free(oa); return 0; diff --git a/src/c/src/st_adaptor.c b/src/c/src/st_adaptor.c index 7609edf8c0b..23573b3aa90 100644 --- a/src/c/src/st_adaptor.c +++ b/src/c/src/st_adaptor.c @@ -24,23 +24,29 @@ #include #include -void zoo_lock_auth(zhandle_t *zh) +int zoo_lock_auth(zhandle_t *zh) { + return 0; } -void zoo_unlock_auth(zhandle_t *zh) +int zoo_unlock_auth(zhandle_t *zh) { + return 0; } -void lock_buffer_list(buffer_head_t *l) +int lock_buffer_list(buffer_head_t *l) { + return 0; } -void unlock_buffer_list(buffer_head_t *l) +int unlock_buffer_list(buffer_head_t *l) { + return 0; } -void lock_completion_list(completion_head_t *l) +int lock_completion_list(completion_head_t *l) { + return 0; } -void unlock_completion_list(completion_head_t *l) +int unlock_completion_list(completion_head_t *l) { + return 0; } struct sync_completion *alloc_sync_completion(void) { @@ -95,5 +101,13 @@ int32_t get_xid() } return xid++; } -void enter_critical(zhandle_t* zh){} -void leave_critical(zhandle_t* zh){} + +int enter_critical(zhandle_t* zh) +{ + return 0; +} + +int leave_critical(zhandle_t* zh) +{ + return 0; +} diff --git a/src/c/src/winport.c b/src/c/src/winport.c index 35eec7ed8f6..3592ea149b5 100644 --- a/src/c/src/winport.c +++ b/src/c/src/winport.c @@ -18,8 +18,10 @@ #ifdef WIN32 #include "winport.h" -#include -#include +#include +#include /* for int64_t */ +#include /* must always be included before ws2tcpip.h */ +#include /* for SOCKET */ int pthread_mutex_lock(pthread_mutex_t* _mutex ){ int rc = WaitForSingleObject( *_mutex, // handle to mutex @@ -109,6 +111,7 @@ pthread_cond_init (pthread_cond_t *cv, int pthread_cond_destroy(pthread_cond_t *cond) { CloseHandle( cond->sema_); + DeleteCriticalSection(&cond->waiters_count_lock_); return (CloseHandle( cond->waiters_done_ ) == 0)? GetLastError(): 0 ; } @@ -254,6 +257,14 @@ int pthread_setspecific(pthread_key_t key, const void *value) return ((rc != 0 ) ? 0 : GetLastError()); } +int gettimeofday(struct timeval *tp, void *tzp) { + int64_t now = 0; + if (tzp != 0) { errno = EINVAL; return -1; } + GetSystemTimeAsFileTime( (LPFILETIME)&now ); + tp->tv_sec = (long)(now / 10000000 - 11644473600LL); + tp->tv_usec = (now / 10) % 1000000; + return 0; +} int close(SOCKET fd) { return closesocket(fd); diff --git a/src/c/src/winport.h b/src/c/src/winport.h index 32272c03d87..da6028cd3db 100644 --- a/src/c/src/winport.h +++ b/src/c/src/winport.h @@ -25,9 +25,31 @@ #define WINPORT_H_ #ifdef WIN32 -#include +#include "winconfig.h" + +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include /* must always be included before ws2tcpip.h */ +#include /* for struct sock_addr used in zookeeper.h */ + +/* POSIX names are deprecated, use ISO conformant names instead. */ +#define strdup _strdup +#define getcwd _getcwd +#define getpid _getpid + +/* Windows "secure" versions of POSIX reentrant functions */ +#define strtok_r strtok_s +#define localtime_r(a,b) localtime_s(b,a) + +/* After this version of MSVC, snprintf became a defined function, + and so cannot be redefined, nor can #ifndef be used to guard it. */ +#if ((defined(_MSC_VER) && _MSC_VER < 1900) || !defined(_MSC_VER)) +#define snprintf _snprintf +#endif + + #include #include +#include /* for int64_t */ #include #include @@ -105,14 +127,7 @@ int pthread_key_delete(pthread_key_t key); void *pthread_getspecific(pthread_key_t key); int pthread_setspecific(pthread_key_t key, const void *value); -inline int gettimeofday(struct timeval *tp, void *tzp) { - int64_t now = 0; - if (tzp != 0) { errno = EINVAL; return -1; } - GetSystemTimeAsFileTime( (LPFILETIME)&now ); - tp->tv_sec = (long)(now / 10000000 - 11644473600LL); - tp->tv_usec = (now / 10) % 1000000; - return 0; -} +int gettimeofday(struct timeval *tp, void *tzp); int close(SOCKET fd); int Win32WSAStartup(); void Win32WSACleanup(); diff --git a/src/c/src/zk_adaptor.h b/src/c/src/zk_adaptor.h index 6aed38fab22..b99077963fc 100644 --- a/src/c/src/zk_adaptor.h +++ b/src/c/src/zk_adaptor.h @@ -75,10 +75,10 @@ typedef struct _completion_head { #endif } completion_head_t; -void lock_buffer_list(buffer_head_t *l); -void unlock_buffer_list(buffer_head_t *l); -void lock_completion_list(completion_head_t *l); -void unlock_completion_list(completion_head_t *l); +int lock_buffer_list(buffer_head_t *l); +int unlock_buffer_list(buffer_head_t *l); +int lock_completion_list(completion_head_t *l); +int unlock_completion_list(completion_head_t *l); struct sync_completion { int rc; @@ -243,12 +243,12 @@ void process_completions(zhandle_t *zh); int flush_send_queue(zhandle_t*zh, int timeout); char* sub_string(zhandle_t *zh, const char* server_path); void free_duplicate_path(const char* free_path, const char* path); -void zoo_lock_auth(zhandle_t *zh); -void zoo_unlock_auth(zhandle_t *zh); +int zoo_lock_auth(zhandle_t *zh); +int zoo_unlock_auth(zhandle_t *zh); // critical section guards -void enter_critical(zhandle_t* zh); -void leave_critical(zhandle_t* zh); +int enter_critical(zhandle_t* zh); +int leave_critical(zhandle_t* zh); // zhandle object reference counting void api_prolog(zhandle_t* zh); int api_epilog(zhandle_t *zh, int rc); diff --git a/src/c/src/zk_hashtable.c b/src/c/src/zk_hashtable.c index 054a603c33c..0efc5aaf440 100644 --- a/src/c/src/zk_hashtable.c +++ b/src/c/src/zk_hashtable.c @@ -189,8 +189,12 @@ static int do_insert_watcher_object(zk_hashtable *ht, const char *path, watcher_ res=hashtable_insert(ht->ht,strdup(path),create_watcher_object_list(wo)); assert(res); }else{ - /* path already exists; check if the watcher already exists */ - res = add_to_list(&wl, wo, 1); + /* + * Path already exists; check if the watcher already exists. + * Don't clone the watcher since it's allocated on the heap --- avoids + * a memory leak and saves a clone operation (calloc + copy). + */ + res = add_to_list(&wl, wo, 0); } return res; } diff --git a/src/c/src/zk_log.c b/src/c/src/zk_log.c index 4dc0f216788..6b4fdfa2bcd 100644 --- a/src/c/src/zk_log.c +++ b/src/c/src/zk_log.c @@ -16,13 +16,16 @@ * limitations under the License. */ -#ifndef DLL_EXPORT +#if !defined(DLL_EXPORT) && !defined(USE_STATIC_LIB) # define USE_STATIC_LIB #endif #include "zookeeper_log.h" #ifndef WIN32 #include +#else +typedef DWORD pid_t; +#include /* for getpid */ #endif #include @@ -133,7 +136,8 @@ void log_message(ZooLogLevel curLevel,int line,const char* funcName, #endif if(pid==0)pid=getpid(); #ifndef THREADED - fprintf(LOGSTREAM, "%s:%d:%s@%s@%d: %s\n", time_now(get_time_buffer()),pid, + // pid_t is long on Solaris + fprintf(LOGSTREAM, "%s:%ld:%s@%s@%d: %s\n", time_now(get_time_buffer()),(long)pid, dbgLevelStr[curLevel],funcName,line,message); #else #ifdef WIN32 @@ -141,7 +145,7 @@ void log_message(ZooLogLevel curLevel,int line,const char* funcName, (unsigned long int)(pthread_self().thread_id), dbgLevelStr[curLevel],funcName,line,message); #else - fprintf(LOGSTREAM, "%s:%d(0x%lx):%s@%s@%d: %s\n", time_now(get_time_buffer()),pid, + fprintf(LOGSTREAM, "%s:%ld(0x%lx):%s@%s@%d: %s\n", time_now(get_time_buffer()),(long)pid, (unsigned long int)pthread_self(), dbgLevelStr[curLevel],funcName,line,message); #endif diff --git a/src/c/src/zookeeper.c b/src/c/src/zookeeper.c index 64d7a4d0ec4..f15e9e7c7ac 100644 --- a/src/c/src/zookeeper.c +++ b/src/c/src/zookeeper.c @@ -16,7 +16,7 @@ * limitations under the License. */ -#ifndef DLL_EXPORT +#if !defined(DLL_EXPORT) && !defined(USE_STATIC_LIB) # define USE_STATIC_LIB #endif @@ -41,16 +41,33 @@ #include #include -#ifndef WIN32 +#ifdef HAVE_SYS_TIME_H #include +#endif + +#ifdef HAVE_SYS_SOCKET_H #include +#endif + +#ifdef HAVE_POLL #include +#endif + +#ifdef HAVE_NETINET_IN_H #include #include +#endif + +#ifdef HAVE_ARPA_INET_H #include +#endif + +#ifdef HAVE_NETDB_H #include -#include -#include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +#include // needed for _POSIX_MONOTONIC_CLOCK #endif #ifdef HAVE_SYS_UTSNAME_H @@ -61,6 +78,15 @@ #include #endif +#ifdef WIN32 +#define random rand /* replace POSIX random with Windows rand */ +#include /* for getpid */ +#include /* for getcwd */ +#define EAI_ADDRFAMILY WSAEINVAL /* is this still needed? */ +#define EHOSTDOWN EPIPE +#define ESTALE ENODEV +#endif + #define IF_DEBUG(x) if(logLevel==ZOO_LOG_LEVEL_DEBUG) {x;} const int ZOOKEEPER_WRITE = 1 << 0; @@ -424,8 +450,22 @@ static void setup_random() if (fd == -1) { seed = getpid(); } else { - int rc = read(fd, &seed, sizeof(seed)); - assert(rc == sizeof(seed)); + int seed_len = 0; + + /* Enter a loop to fill in seed with random data from /dev/urandom. + * This is done in a loop so that we can safely handle short reads + * which can happen due to signal interruptions. + */ + while (seed_len < sizeof(seed)) { + /* Assert we either read something or we were interrupted due to a + * signal (errno == EINTR) in which case we need to retry. + */ + int rc = read(fd, &seed + seed_len, sizeof(seed) - seed_len); + assert(rc > 0 || errno == EINTR); + if (rc > 0) { + seed_len += rc; + } + } close(fd); } srandom(seed); @@ -442,7 +482,8 @@ static void setup_random() static int getaddrinfo_errno(int rc) { switch(rc) { case EAI_NONAME: -#if EAI_NODATA != EAI_NONAME +// ZOOKEEPER-1323 EAI_NODATA and EAI_ADDRFAMILY are deprecated in FreeBSD. +#if defined EAI_NODATA && EAI_NODATA != EAI_NONAME case EAI_NODATA: #endif return ENOENT; @@ -578,7 +619,12 @@ int getaddrs(zhandle_t *zh) // ai_flags as AI_ADDRCONFIG #ifdef AI_ADDRCONFIG if ((hints.ai_flags == AI_ADDRCONFIG) && +// ZOOKEEPER-1323 EAI_NODATA and EAI_ADDRFAMILY are deprecated in FreeBSD. +#ifdef EAI_ADDRFAMILY ((rc ==EAI_BADFLAGS) || (rc == EAI_ADDRFAMILY))) { +#else + (rc == EAI_BADFLAGS)) { +#endif //reset ai_flags to null hints.ai_flags = 0; //retry getaddrinfo @@ -774,7 +820,7 @@ zhandle_t *zookeeper_init(const char *host, watcher_fn watcher, recv_timeout, watcher, (clientid == 0 ? 0 : clientid->client_id), - ((clientid == 0) || (clientid->passwd == 0) ? + ((clientid == 0) || (clientid->passwd[0] == 0) ? "" : ""), context, flags)); @@ -802,8 +848,12 @@ zhandle_t *zookeeper_init(const char *host, watcher_fn watcher, index_chroot = strchr(host, '/'); if (index_chroot) { zh->chroot = strdup(index_chroot); + if (zh->chroot == NULL) { + goto abort; + } // if chroot is just / set it to null if (strlen(zh->chroot) == 1) { + free(zh->chroot); zh->chroot = NULL; } // cannot use strndup so allocate and strcpy @@ -871,7 +921,7 @@ void free_duplicate_path(const char *free_path, const char* path) { */ static char* prepend_string(zhandle_t *zh, const char* client_path) { char *ret_str; - if (zh->chroot == NULL) + if (zh == NULL || zh->chroot == NULL) return (char *) client_path; // handle the chroot itself, client_path = "/" if (strlen(client_path) == 1) { @@ -1140,27 +1190,24 @@ void free_completions(zhandle_t *zh,int callCompletion,int reason) void_completion_t auth_completion = NULL; auth_completion_list_t a_list, *a_tmp; - lock_completion_list(&zh->sent_requests); - tmp_list = zh->sent_requests; - zh->sent_requests.head = 0; - zh->sent_requests.last = 0; - unlock_completion_list(&zh->sent_requests); - while (tmp_list.head) { - completion_list_t *cptr = tmp_list.head; + if (lock_completion_list(&zh->sent_requests) == 0) { + tmp_list = zh->sent_requests; + zh->sent_requests.head = 0; + zh->sent_requests.last = 0; + unlock_completion_list(&zh->sent_requests); + + while (tmp_list.head) { + completion_list_t *cptr = tmp_list.head; - tmp_list.head = cptr->next; - if (cptr->c.data_result == SYNCHRONOUS_MARKER) { - struct sync_completion - *sc = (struct sync_completion*)cptr->data; - sc->rc = reason; - notify_sync_completion(sc); - zh->outstanding_sync--; - destroy_completion_entry(cptr); - } else if (callCompletion) { - if(cptr->xid == PING_XID){ - // Nothing to do with a ping response + tmp_list.head = cptr->next; + if (cptr->c.data_result == SYNCHRONOUS_MARKER) { + struct sync_completion + *sc = (struct sync_completion*)cptr->data; + sc->rc = reason; + notify_sync_completion(sc); + zh->outstanding_sync--; destroy_completion_entry(cptr); - } else { + } else if (callCompletion) { // Fake the response buffer_list_t *bptr; h.xid = cptr->xid; @@ -1178,19 +1225,22 @@ void free_completions(zhandle_t *zh,int callCompletion,int reason) } } } - a_list.completion = NULL; - a_list.next = NULL; - zoo_lock_auth(zh); - get_auth_completions(&zh->auth_h, &a_list); - zoo_unlock_auth(zh); - a_tmp = &a_list; - // chain call user's completion function - while (a_tmp->completion != NULL) { - auth_completion = a_tmp->completion; - auth_completion(reason, a_tmp->auth_data); - a_tmp = a_tmp->next; - if (a_tmp == NULL) - break; + if (zoo_lock_auth(zh) == 0) { + a_list.completion = NULL; + a_list.next = NULL; + + get_auth_completions(&zh->auth_h, &a_list); + zoo_unlock_auth(zh); + + a_tmp = &a_list; + // chain call user's completion function + while (a_tmp->completion != NULL) { + auth_completion = a_tmp->completion; + auth_completion(reason, a_tmp->auth_data); + a_tmp = a_tmp->next; + if (a_tmp == NULL) + break; + } } free_auth_completion(&a_list); } @@ -1398,7 +1448,7 @@ static int serialize_prime_connect(struct connect_req *req, char* buffer){ memcpy(buffer + offset, &req->protocolVersion, sizeof(req->protocolVersion)); offset = offset + sizeof(req->protocolVersion); - req->lastZxidSeen = htonll(req->lastZxidSeen); + req->lastZxidSeen = zoo_htonll(req->lastZxidSeen); memcpy(buffer + offset, &req->lastZxidSeen, sizeof(req->lastZxidSeen)); offset = offset + sizeof(req->lastZxidSeen); @@ -1406,7 +1456,7 @@ static int serialize_prime_connect(struct connect_req *req, char* buffer){ memcpy(buffer + offset, &req->timeOut, sizeof(req->timeOut)); offset = offset + sizeof(req->timeOut); - req->sessionId = htonll(req->sessionId); + req->sessionId = zoo_htonll(req->sessionId); memcpy(buffer + offset, &req->sessionId, sizeof(req->sessionId)); offset = offset + sizeof(req->sessionId); @@ -1437,7 +1487,7 @@ static int serialize_prime_connect(struct connect_req *req, char* buffer){ memcpy(&req->sessionId, buffer + offset, sizeof(req->sessionId)); offset = offset + sizeof(req->sessionId); - req->sessionId = htonll(req->sessionId); + req->sessionId = zoo_htonll(req->sessionId); memcpy(&req->passwd_len, buffer + offset, sizeof(req->passwd_len)); offset = offset + sizeof(req->passwd_len); @@ -1516,7 +1566,6 @@ static struct timeval get_timeval(int interval) rc = serialize_RequestHeader(oa, "header", &h); enter_critical(zh); gettimeofday(&zh->last_ping, 0); - rc = rc < 0 ? rc : add_void_completion(zh, h.xid, 0, 0); rc = rc < 0 ? rc : queue_buffer_bytes(&zh->to_send, get_buffer(oa), get_buffer_len(oa)); leave_critical(zh); @@ -1588,7 +1637,26 @@ int zookeeper_interest(zhandle_t *zh, int *fd, int *interest, #endif rc = connect(zh->fd, (struct sockaddr*) &zh->addrs[zh->connect_index], sizeof(struct sockaddr_in)); #ifdef WIN32 - get_errno(); + errno = GetLastError(); + +#ifndef EWOULDBLOCK +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif + +#ifndef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#endif + +#if _MSC_VER >= 1600 + switch (errno) { + case WSAEWOULDBLOCK: + errno = EWOULDBLOCK; + break; + case WSAEINPROGRESS: + errno = EINPROGRESS; + break; + } +#endif #endif } if (rc == -1) { @@ -1627,24 +1695,28 @@ int zookeeper_interest(zhandle_t *zh, int *fd, int *interest, #else errno = ETIMEDOUT; #endif - *fd=-1; *interest=0; *tv = get_timeval(0); return api_epilog(zh,handle_socket_error_msg(zh, __LINE__,ZOPERATIONTIMEOUT, - "connection timed out (exceeded timeout by %dms)",-recv_to)); + "connection to %s timed out (exceeded timeout by %dms)", + format_endpoint_info(&zh->addrs[zh->connect_index]), + -recv_to)); + } // We only allow 1/3 of our timeout time to expire before sending // a PING if (zh->state==ZOO_CONNECTED_STATE) { send_to = zh->recv_timeout/3 - idle_send; - if (send_to <= 0 && zh->sent_requests.head==0) { -// LOG_DEBUG(("Sending PING to %s (exceeded idle by %dms)", -// format_current_endpoint_info(zh),-send_to)); - int rc=send_ping(zh); - if (rc < 0){ - LOG_ERROR(("failed to send PING request (zk retcode=%d)",rc)); - return api_epilog(zh,rc); + if (send_to <= 0) { + if (zh->sent_requests.head==0) { +// LOG_DEBUG(("Sending PING to %s (exceeded idle by %dms)", +// format_current_endpoint_info(zh),-send_to)); + int rc=send_ping(zh); + if (rc < 0){ + LOG_ERROR(("failed to send PING request (zk retcode=%d)",rc)); + return api_epilog(zh,rc); + } } send_to = zh->recv_timeout/3; } @@ -1967,6 +2039,10 @@ static int deserialize_multi(int xid, completion_list_t *cptr, struct iarchive * deserialize_response(entry->c.type, xid, mhdr.type == -1, mhdr.err, entry, ia); deserialize_MultiHeader(ia, "multiheader", &mhdr); + //While deserializing the response we must destroy completion entry for each operation in + //the zoo_multi transaction. Otherwise this results in memory leak when client invokes zoo_multi + //operation. + destroy_completion_entry(entry); } return rc; @@ -2031,6 +2107,7 @@ static void deserialize_response(int type, int xid, int failed, int rc, completi cptr->c.string_result(rc, 0, cptr->data); } else { struct CreateResponse res; + memset(&res, 0, sizeof(res)); deserialize_CreateResponse(ia, "reply", &res); cptr->c.string_result(rc, res.path, cptr->data); deallocate_CreateResponse(&res); @@ -2051,12 +2128,8 @@ static void deserialize_response(int type, int xid, int failed, int rc, completi case COMPLETION_VOID: LOG_DEBUG(("Calling COMPLETION_VOID for xid=%#x failed=%d rc=%d", cptr->xid, failed, rc)); - if (xid == PING_XID) { - // We want to skip the ping - } else { - assert(cptr->c.void_result); - cptr->c.void_result(rc, cptr->data); - } + assert(cptr->c.void_result); + cptr->c.void_result(rc, cptr->data); break; case COMPLETION_MULTI: LOG_DEBUG(("Calling COMPLETION_MULTI for xid=%#x failed=%d rc=%d", @@ -2172,7 +2245,15 @@ int zookeeper_process(zhandle_t *zh, int events) // fprintf(stderr, "Got %#x for %#x\n", hdr.zxid, hdr.xid); } - if (hdr.xid == WATCHER_EVENT_XID) { + if (hdr.xid == PING_XID) { + // Ping replies can arrive out-of-order + int elapsed = 0; + struct timeval now; + gettimeofday(&now, 0); + elapsed = calculate_interval(&zh->last_ping, &now); + LOG_DEBUG(("Got ping response in %d ms", elapsed)); + free_buffer(bptr); + } else if (hdr.xid == WATCHER_EVENT_XID) { struct WatcherEvent evt; int type = 0; char *path = NULL; @@ -2214,12 +2295,10 @@ int zookeeper_process(zhandle_t *zh, int events) completion_list_t *cptr = dequeue_completion(&zh->sent_requests); /* [ZOOKEEPER-804] Don't assert if zookeeper_close has been called. */ - if (zh->close_requested == 1) { - if (cptr) { - destroy_completion_entry(cptr); - cptr = NULL; - } + if (zh->close_requested == 1 && cptr == NULL) { + LOG_DEBUG(("Completion queue has been cleared by zookeeper_close()")); close_buffer_iarchive(&ia); + free_buffer(bptr); return api_epilog(zh,ZINVALIDSTATE); } assert(cptr); @@ -2233,30 +2312,18 @@ int zookeeper_process(zhandle_t *zh, int events) // put the completion back on the queue (so it gets properly // signaled and deallocated) and disconnect from the server queue_completion(&zh->sent_requests,cptr,1); - return handle_socket_error_msg(zh, __LINE__,ZRUNTIMEINCONSISTENCY, - "unexpected server response: expected %#x, but received %#x", - hdr.xid,cptr->xid); + return api_epilog(zh, + handle_socket_error_msg(zh, __LINE__,ZRUNTIMEINCONSISTENCY, + "unexpected server response: expected %#x, but received %#x", + hdr.xid,cptr->xid)); } activateWatcher(zh, cptr->watcher, rc); if (cptr->c.void_result != SYNCHRONOUS_MARKER) { - if(hdr.xid == PING_XID){ - int elapsed = 0; - struct timeval now; - gettimeofday(&now, 0); - elapsed = calculate_interval(&zh->last_ping, &now); - LOG_DEBUG(("Got ping response in %d ms", elapsed)); - - // Nothing to do with a ping response - free_buffer(bptr); - destroy_completion_entry(cptr); - } else { - LOG_DEBUG(("Queueing asynchronous response")); - - cptr->buffer = bptr; - queue_completion(&zh->completions_to_process, cptr, 0); - } + LOG_DEBUG(("Queueing asynchronous response")); + cptr->buffer = bptr; + queue_completion(&zh->completions_to_process, cptr, 0); } else { struct sync_completion *sc = (struct sync_completion*)cptr->data; diff --git a/src/c/tests/LibCMocks.cc b/src/c/tests/LibCMocks.cc index 57de1e36dc2..5db45532e7f 100644 --- a/src/c/tests/LibCMocks.cc +++ b/src/c/tests/LibCMocks.cc @@ -147,7 +147,7 @@ Mock_calloc* Mock_calloc::mock_=0; // realloc #ifndef USING_DUMA -void* realloc(void* p, size_t s){ +DECLARE_WRAPPER(void*,realloc,(void* p, size_t s)){ if(!Mock_realloc::mock_) return LIBC_SYMBOLS.realloc(p,s); return Mock_realloc::mock_->call(p,s); @@ -310,6 +310,17 @@ int poll(struct pollfd *fds, POLL_NFDS_TYPE nfds, int timeout){ } +/* + * Recent gcc with -O2 and glibc FORTIFY feature may cause our poll + * mock to be ignored. + */ +#if __USE_FORTIFY_LEVEL > 0 +int __poll_chk (struct pollfd *__fds, nfds_t __nfds, int __timeout, + __SIZE_TYPE__ __fdslen) { + return poll(__fds, __nfds, __timeout); +} +#endif + // ***************************************************************************** // gettimeofday int gettimeofday(struct timeval *tp, GETTIMEOFDAY_ARG2_TYPE tzp){ diff --git a/src/c/tests/TestClient.cc b/src/c/tests/TestClient.cc index 2d45cecc371..154fbed9eab 100644 --- a/src/c/tests/TestClient.cc +++ b/src/c/tests/TestClient.cc @@ -186,6 +186,7 @@ class Zookeeper_simpleSystem : public CPPUNIT_NS::TestFixture CPPUNIT_TEST_SUITE(Zookeeper_simpleSystem); CPPUNIT_TEST(testAsyncWatcherAutoReset); CPPUNIT_TEST(testDeserializeString); + CPPUNIT_TEST(testFirstServerDown); #ifdef THREADED CPPUNIT_TEST(testNullData); #ifdef ZOO_IPV6_ENABLED @@ -287,6 +288,17 @@ class Zookeeper_simpleSystem : public CPPUNIT_NS::TestFixture struct String_vector str_vec = {0, NULL}; zrc = zoo_wget_children(zzh, "/mytest", default_zoo_watcher, NULL, &str_vec); } + + /** ZOOKEEPER-1057 This checks that the client connects to the second server when the first is not reachable **/ + void testFirstServerDown() { + watchctx_t ctx; + + zoo_deterministic_conn_order(true); + + zhandle_t* zk = createClient("127.0.0.1:22182,127.0.0.1:22181", &ctx); + CPPUNIT_ASSERT(zk != 0); + CPPUNIT_ASSERT(ctx.waitForConnected(zk)); + } /** this checks for a deadlock in calling zookeeper_close and calls from a default watcher that might get triggered just when zookeeper_close() is in progress **/ void testHangingClient() { @@ -792,6 +804,10 @@ class Zookeeper_simpleSystem : public CPPUNIT_NS::TestFixture zk_ch = createchClient(&ctx_ch, "127.0.0.1:22181/testch1/mahadev"); CPPUNIT_ASSERT(zk_ch != NULL); zk = createClient(&ctx); + // first test with a NULL zk handle, make sure client library does not + // dereference a null pointer, but instead returns ZBADARGUMENTS + rc = zoo_create(NULL, "/testch1", "", 0, &ZOO_OPEN_ACL_UNSAFE, 0, 0, 0); + CPPUNIT_ASSERT_EQUAL((int) ZBADARGUMENTS, rc); rc = zoo_create(zk, "/testch1", "", 0, &ZOO_OPEN_ACL_UNSAFE, 0, 0, 0); CPPUNIT_ASSERT_EQUAL((int) ZOK, rc); rc = zoo_create(zk, "/testch1/mahadev", data, 7, &ZOO_OPEN_ACL_UNSAFE, 0, 0, 0); diff --git a/src/c/tests/TestDriver.cc b/src/c/tests/TestDriver.cc index 3f32ba483c4..d60db69dc99 100644 --- a/src/c/tests/TestDriver.cc +++ b/src/c/tests/TestDriver.cc @@ -144,7 +144,7 @@ int main( int argc, char* argv[] ) { runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() ); try { - CPPUNIT_NS::stdCOut() << "Running " << endl; + CPPUNIT_NS::stdCOut() << endl << "Running " << endl; zoo_set_debug_level(ZOO_LOG_LEVEL_INFO); //zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG); diff --git a/src/c/tests/TestMulti.cc b/src/c/tests/TestMulti.cc index 6bf61b2013a..a54e047b3ba 100644 --- a/src/c/tests/TestMulti.cc +++ b/src/c/tests/TestMulti.cc @@ -177,6 +177,7 @@ class Zookeeper_multi : public CPPUNIT_NS::TestFixture CPPUNIT_TEST(testMultiFail); CPPUNIT_TEST(testCheck); CPPUNIT_TEST(testWatch); + CPPUNIT_TEST(testSequentialNodeCreateInAsyncMulti); #endif CPPUNIT_TEST_SUITE_END(); @@ -244,6 +245,10 @@ class Zookeeper_multi : public CPPUNIT_NS::TestFixture count++; } + static void multi_completion_fn_no_assert(int rc, const void *data) { + count++; + } + static void waitForMultiCompletion(int seconds) { time_t expires = time(0) + seconds; while(count == 0 && time(0) < expires) { @@ -252,6 +257,10 @@ class Zookeeper_multi : public CPPUNIT_NS::TestFixture count--; } + static void resetCounter() { + count = 0; + } + /** * Test basic multi-op create functionality */ @@ -646,6 +655,47 @@ class Zookeeper_multi : public CPPUNIT_NS::TestFixture // wait for multi completion in doMultiInWatch waitForMultiCompletion(5); } + + /** + * ZOOKEEPER-1624: PendingChanges of create sequential node request didn't + * get rollbacked correctly when multi-op failed. This caused + * create sequential node request in subsequent multi-op to failed because + * sequential node name generation is incorrect. + * + * The check is to make sure that each request in multi-op failed with + * the correct reason. + */ + void testSequentialNodeCreateInAsyncMulti() { + int rc; + watchctx_t ctx; + zhandle_t *zk = createClient(&ctx); + + int iteration = 4; + int nops = 2; + + zoo_op_result_t results[iteration][nops]; + zoo_op_t ops[nops]; + zoo_create_op_init(&ops[0], "/node-", "", 0, &ZOO_OPEN_ACL_UNSAFE, ZOO_SEQUENCE, NULL, 0); + zoo_create_op_init(&ops[1], "/dup", "", 0, &ZOO_OPEN_ACL_UNSAFE, 0, NULL, 0); + for (int i = 0; i < iteration ; ++i) { + rc = zoo_amulti(zk, nops, ops, results[i], multi_completion_fn_no_assert, 0); + CPPUNIT_ASSERT_EQUAL((int)ZOK, rc); + } + + waitForMultiCompletion(10); + + CPPUNIT_ASSERT_EQUAL((int)ZOK, results[0][0].err); + CPPUNIT_ASSERT_EQUAL((int)ZOK, results[1][0].err); + CPPUNIT_ASSERT_EQUAL((int)ZOK, results[2][0].err); + CPPUNIT_ASSERT_EQUAL((int)ZOK, results[3][0].err); + + CPPUNIT_ASSERT_EQUAL((int)ZOK, results[0][1].err); + CPPUNIT_ASSERT_EQUAL((int)ZNODEEXISTS, results[1][1].err); + CPPUNIT_ASSERT_EQUAL((int)ZNODEEXISTS, results[2][1].err); + CPPUNIT_ASSERT_EQUAL((int)ZNODEEXISTS, results[3][1].err); + + resetCounter(); + } }; volatile int Zookeeper_multi::count; diff --git a/src/c/tests/TestOperations.cc b/src/c/tests/TestOperations.cc index b0370e9af05..27d92709bd8 100644 --- a/src/c/tests/TestOperations.cc +++ b/src/c/tests/TestOperations.cc @@ -29,6 +29,7 @@ class Zookeeper_operations : public CPPUNIT_NS::TestFixture CPPUNIT_TEST_SUITE(Zookeeper_operations); #ifndef THREADED CPPUNIT_TEST(testPing); + CPPUNIT_TEST(testUnsolicitedPing); CPPUNIT_TEST(testTimeoutCausedByWatches1); CPPUNIT_TEST(testTimeoutCausedByWatches2); #else @@ -305,6 +306,40 @@ class Zookeeper_operations : public CPPUNIT_NS::TestFixture CPPUNIT_ASSERT_EQUAL(1,zkServer.pingCount_); } + // ZOOKEEPER-2253: Permit unsolicited pings + void testUnsolicitedPing() + { + const int TIMEOUT=9; // timeout in secs + Mock_gettimeofday timeMock; + PingCountingServer zkServer; + // must call zookeeper_close() while all the mocks are in scope + CloseFinally guard(&zh); + + // receive timeout is in milliseconds + zh=zookeeper_init("localhost:1234",watcher,TIMEOUT*1000,TEST_CLIENT_ID,0,0); + CPPUNIT_ASSERT(zh!=0); + // simulate connected state + forceConnected(zh); + + int fd=0; + int interest=0; + timeval tv; + + int rc=zookeeper_interest(zh,&fd,&interest,&tv); + CPPUNIT_ASSERT_EQUAL((int)ZOK,rc); + + // verify no ping sent + CPPUNIT_ASSERT(zkServer.pingCount_==0); + + // we're going to receive a unsolicited PING response; ensure + // that the client has updated its last_recv timestamp + timeMock.tick(tv); + zkServer.addRecvResponse(new PingResponse); + rc=zookeeper_process(zh,interest); + CPPUNIT_ASSERT_EQUAL((int)ZOK,rc); + CPPUNIT_ASSERT(timeMock==zh->last_recv); + } + // simulate a watch arriving right before a ping is due // assert the ping is sent nevertheless void testTimeoutCausedByWatches1() diff --git a/src/c/tests/TestZookeeperInit.cc b/src/c/tests/TestZookeeperInit.cc index ca55f3c7b26..eadf41d2d3c 100644 --- a/src/c/tests/TestZookeeperInit.cc +++ b/src/c/tests/TestZookeeperInit.cc @@ -230,7 +230,7 @@ class Zookeeper_init : public CPPUNIT_NS::TestFixture const string INVALID_HOST("host1:1111+host:123"); zh=zookeeper_init(INVALID_HOST.c_str(),0,0,0,0,0); CPPUNIT_ASSERT(zh==0); - CPPUNIT_ASSERT_EQUAL(ENOENT,errno); + CPPUNIT_ASSERT((ENOENT|EINVAL) & errno); } void testNonexistentHost() { diff --git a/src/c/tests/ThreadingUtil.cc b/src/c/tests/ThreadingUtil.cc index 6b0bfe5f3d8..3b5170abbe7 100644 --- a/src/c/tests/ThreadingUtil.cc +++ b/src/c/tests/ThreadingUtil.cc @@ -47,6 +47,9 @@ void Mutex::release() { // Atomics int32_t atomic_post_incr(volatile int32_t* operand, int32_t incr) { +#if defined(__GNUC__) + return __sync_fetch_and_add(operand,incr); +#else int32_t result; __asm__ __volatile__( "lock xaddl %0,%1\n" @@ -54,15 +57,20 @@ int32_t atomic_post_incr(volatile int32_t* operand, int32_t incr) : "0"(incr) : "memory"); return result; +#endif } int32_t atomic_fetch_store(volatile int32_t *ptr, int32_t value) { +#if defined(__GNUC__) + return __sync_lock_test_and_set(ptr,value); +#else int32_t result; __asm__ __volatile__("lock xchgl %0,%1\n" : "=r"(result), "=m"(*ptr) : "0"(value) : "memory"); return result; +#endif } #else int32_t atomic_post_incr(volatile int32_t* operand, int32_t incr){ diff --git a/src/c/tests/ZKMocks.cc b/src/c/tests/ZKMocks.cc index 89166747fc2..69bea166f32 100644 --- a/src/c/tests/ZKMocks.cc +++ b/src/c/tests/ZKMocks.cc @@ -41,7 +41,7 @@ HandshakeRequest* HandshakeRequest::parse(const std::string& buf){ int offset=sizeof(req->protocolVersion); memcpy(&req->lastZxidSeen,buf.data()+offset,sizeof(req->lastZxidSeen)); - req->lastZxidSeen = htonll(req->lastZxidSeen); + req->lastZxidSeen = zoo_htonll(req->lastZxidSeen); offset+=sizeof(req->lastZxidSeen); memcpy(&req->timeOut,buf.data()+offset,sizeof(req->timeOut)); @@ -49,7 +49,7 @@ HandshakeRequest* HandshakeRequest::parse(const std::string& buf){ offset+=sizeof(req->timeOut); memcpy(&req->sessionId,buf.data()+offset,sizeof(req->sessionId)); - req->sessionId = htonll(req->sessionId); + req->sessionId = zoo_htonll(req->sessionId); offset+=sizeof(req->sessionId); memcpy(&req->passwd_len,buf.data()+offset,sizeof(req->passwd_len)); @@ -322,7 +322,7 @@ string HandshakeResponse::toString() const { buf.append((char*)&tmp,sizeof(tmp)); tmp=htonl(timeOut); buf.append((char*)&tmp,sizeof(tmp)); - int64_t tmp64=htonll(sessionId); + int64_t tmp64=zoo_htonll(sessionId); buf.append((char*)&tmp64,sizeof(sessionId)); tmp=htonl(passwd_len); buf.append((char*)&tmp,sizeof(tmp)); diff --git a/src/c/tests/wrappers.opt b/src/c/tests/wrappers.opt index 963e7ea5806..bce192fcfa9 100644 --- a/src/c/tests/wrappers.opt +++ b/src/c/tests/wrappers.opt @@ -4,3 +4,4 @@ -Wl,--wrap -Wl,get_xid -Wl,--wrap -Wl,deliverWatchers -Wl,--wrap -Wl,activateWatcher +-Wl,--wrap -Wl,realloc diff --git a/src/c/zookeeper.sln b/src/c/zookeeper.sln deleted file mode 100644 index 4b82253d761..00000000000 --- a/src/c/zookeeper.sln +++ /dev/null @@ -1,57 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 9.00 -# Visual Studio 2005 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zookeeper", "zookeeper.vcproj", "{5754FB2B-5EA5-4988-851D-908CA533A626}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cli", "Cli.vcproj", "{050228F9-070F-4806-A2B5-E6B95D8EC4AF}" - ProjectSection(ProjectDependencies) = postProject - {5754FB2B-5EA5-4988-851D-908CA533A626} = {5754FB2B-5EA5-4988-851D-908CA533A626} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5754FB2B-5EA5-4988-851D-908CA533A626}.Debug|Win32.ActiveCfg = Debug|Win32 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Debug|Win32.Build.0 = Debug|Win32 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Release|Win32.ActiveCfg = Release|Win32 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Release|Win32.Build.0 = Release|Win32 - {050228F9-070F-4806-A2B5-E6B95D8EC4AF}.Debug|Win32.ActiveCfg = Debug|Win32 - {050228F9-070F-4806-A2B5-E6B95D8EC4AF}.Debug|Win32.Build.0 = Debug|Win32 - {050228F9-070F-4806-A2B5-E6B95D8EC4AF}.Release|Win32.ActiveCfg = Release|Win32 - {050228F9-070F-4806-A2B5-E6B95D8EC4AF}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal - -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zookeeper", "zookeeper.vcproj", "{5754FB2B-5EA5-4988-851D-908CA533A626}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cli", "Cli.vcproj", "{050228F9-070F-4806-A2B5-E6B95D8EC4AF}" - ProjectSection(ProjectDependencies) = postProject - {5754FB2B-5EA5-4988-851D-908CA533A626} = {5754FB2B-5EA5-4988-851D-908CA533A626} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5754FB2B-5EA5-4988-851D-908CA533A626}.Debug|Win32.ActiveCfg = Debug|Win32 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Debug|Win32.Build.0 = Debug|Win32 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Release|Win32.ActiveCfg = Release|Win32 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Release|Win32.Build.0 = Release|Win32 - {050228F9-070F-4806-A2B5-E6B95D8EC4AF}.Debug|Win32.ActiveCfg = Debug|Win32 - {050228F9-070F-4806-A2B5-E6B95D8EC4AF}.Debug|Win32.Build.0 = Debug|Win32 - {050228F9-070F-4806-A2B5-E6B95D8EC4AF}.Release|Win32.ActiveCfg = Release|Win32 - {050228F9-070F-4806-A2B5-E6B95D8EC4AF}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/c/zookeeper.vcproj b/src/c/zookeeper.vcproj deleted file mode 100644 index fcd58c2457e..00000000000 --- a/src/c/zookeeper.vcproj +++ /dev/null @@ -1,606 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/contrib/build-contrib.xml b/src/contrib/build-contrib.xml index 96c4dc8e17b..276516e2a4b 100644 --- a/src/contrib/build-contrib.xml +++ b/src/contrib/build-contrib.xml @@ -41,9 +41,9 @@ - + + value="https://repo1.maven.org/maven2/org/apache/ivy/ivy" /> @@ -144,8 +144,8 @@ - - + diff --git a/src/contrib/fatjar/build.xml b/src/contrib/fatjar/build.xml index cd56844a799..8935f3940fe 100644 --- a/src/contrib/fatjar/build.xml +++ b/src/contrib/fatjar/build.xml @@ -46,6 +46,7 @@ + diff --git a/src/contrib/huebrowser/README b/src/contrib/huebrowser/README index ef921f5aed0..c03ea90fdd7 100644 --- a/src/contrib/huebrowser/README +++ b/src/contrib/huebrowser/README @@ -56,7 +56,7 @@ Hue is both a web UI for Hadoop and a framework to create interactive web applic What is ZooKeeper? ------------------ -http://hadoop.apache.org/zookeeper/ +http://zookeeper.apache.org/ ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. All of these kinds of services are used in some form or another by distributed applications. Each time they are implemented there is a lot of work that goes into fixing the bugs and race conditions that are inevitable. Because of the difficulty of implementing these kinds of services, applications initially usually skimp on them ,which make them brittle in the presence of change and difficult to manage. Even when done correctly, different implementations of these services lead to management complexity when the applications are deployed. diff --git a/src/contrib/huebrowser/zkui/setup.py b/src/contrib/huebrowser/zkui/setup.py index d2ae009123d..68d1352ca2c 100644 --- a/src/contrib/huebrowser/zkui/setup.py +++ b/src/contrib/huebrowser/zkui/setup.py @@ -28,7 +28,7 @@ def expand_package_data(src_dirs, strip=""): setup( name = "zkui", version = "0.1", - url = 'http://hadoop.apache.org/zookeeper/', + url = 'http://zookeeper.apache.org/', description = 'ZooKeeper Browser', packages = find_packages('src'), package_dir = {'': 'src'}, diff --git a/src/contrib/huebrowser/zkui/src/zkui/templates/tree.mako b/src/contrib/huebrowser/zkui/src/zkui/templates/tree.mako index aa721695aca..c74c2020901 100644 --- a/src/contrib/huebrowser/zkui/src/zkui/templates/tree.mako +++ b/src/contrib/huebrowser/zkui/src/zkui/templates/tree.mako @@ -69,7 +69,7 @@ ${shared.header("ZooKeeper Browser > Tree > %s > %s" % (cluster['nice_name'], pa
                                          -Details on stat information. +Details on stat information. ${shared.footer()} diff --git a/src/contrib/loggraph/ivy.xml b/src/contrib/loggraph/ivy.xml index ee69011bfb7..d6fa9d6d759 100644 --- a/src/contrib/loggraph/ivy.xml +++ b/src/contrib/loggraph/ivy.xml @@ -21,7 +21,7 @@ - + ZooKeeper Graphing diff --git a/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/Log4JSource.java b/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/Log4JSource.java index 78f0898b125..84a9d983e78 100644 --- a/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/Log4JSource.java +++ b/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/Log4JSource.java @@ -351,11 +351,21 @@ public static void main(String[] args) throws IOException { LogIterator iter = s.iterator(starttime, endtime); System.out.println(iter); + try { + iter.close(); + } catch (IOException ioe) { + System.out.println(ioe.getMessage()); + } }; }; Thread t2 = new Thread() { public void run () { LogIterator iter = s.iterator(starttime, endtime); System.out.println(iter); + try { + iter.close(); + } catch (IOException ioe) { + System.out.println(ioe.getMessage()); + } }; }; Thread t3 = new Thread() { public void run () { diff --git a/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/RandomAccessFileReader.java b/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/RandomAccessFileReader.java index 827a8a7a0a8..13a41a5ae3a 100644 --- a/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/RandomAccessFileReader.java +++ b/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/RandomAccessFileReader.java @@ -324,5 +324,6 @@ public static void main(String[] args) throws IOException { System.out.println(f.readLine()); f.seek(pos2); System.out.println(f.readLine()); + f.close(); } }; diff --git a/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/servlets/NumEvents.java b/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/servlets/NumEvents.java index ed46945816e..5961a125832 100644 --- a/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/servlets/NumEvents.java +++ b/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/servlets/NumEvents.java @@ -81,6 +81,7 @@ String handleRequest(JsonRequest request) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("handle(start= " + starttime + ", end=" + endtime + ", numEntries=" + size +")"); } + iter.close(); return JSONValue.toJSONString(data); } } diff --git a/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/servlets/StaticContent.java b/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/servlets/StaticContent.java index 4af78959a84..d91acb60096 100644 --- a/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/servlets/StaticContent.java +++ b/src/contrib/loggraph/src/java/org/apache/zookeeper/graph/servlets/StaticContent.java @@ -39,9 +39,12 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } - - while (resource.available() > 0) { + try { + while (resource.available() > 0) { response.getWriter().write(resource.read()); + } + } finally { + resource.close(); } // response.setContentType("text/plain;charset=utf-8"); response.setStatus(HttpServletResponse.SC_OK); diff --git a/src/contrib/loggraph/web/org/apache/zookeeper/graph/resources/date.format.js b/src/contrib/loggraph/web/org/apache/zookeeper/graph/resources/date.format.js index 3992c50f73a..55150099217 100644 --- a/src/contrib/loggraph/web/org/apache/zookeeper/graph/resources/date.format.js +++ b/src/contrib/loggraph/web/org/apache/zookeeper/graph/resources/date.format.js @@ -1,126 +1,126 @@ -/* - * Date Format 1.2.3 - * (c) 2007-2009 Steven Levithan - * MIT license - * - * Includes enhancements by Scott Trenda - * and Kris Kowal - * - * Accepts a date, a mask, or a date and a mask. - * Returns a formatted version of the given date. - * The date defaults to the current date/time. - * The mask defaults to dateFormat.masks.default. - */ - -var dateFormat = function () { - var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, - timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, - timezoneClip = /[^-+\dA-Z]/g, - pad = function (val, len) { - val = String(val); - len = len || 2; - while (val.length < len) val = "0" + val; - return val; - }; - - // Regexes and supporting functions are cached through closure - return function (date, mask, utc) { - var dF = dateFormat; - - // You can't provide utc if you skip other args (use the "UTC:" mask prefix) - if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { - mask = date; - date = undefined; - } - - // Passing date through Date applies Date.parse, if necessary - date = date ? new Date(date) : new Date; - if (isNaN(date)) throw SyntaxError("invalid date"); - - mask = String(dF.masks[mask] || mask || dF.masks["default"]); - - // Allow setting the utc argument via the mask - if (mask.slice(0, 4) == "UTC:") { - mask = mask.slice(4); - utc = true; - } - - var _ = utc ? "getUTC" : "get", - d = date[_ + "Date"](), - D = date[_ + "Day"](), - m = date[_ + "Month"](), - y = date[_ + "FullYear"](), - H = date[_ + "Hours"](), - M = date[_ + "Minutes"](), - s = date[_ + "Seconds"](), - L = date[_ + "Milliseconds"](), - o = utc ? 0 : date.getTimezoneOffset(), - flags = { - d: d, - dd: pad(d), - ddd: dF.i18n.dayNames[D], - dddd: dF.i18n.dayNames[D + 7], - m: m + 1, - mm: pad(m + 1), - mmm: dF.i18n.monthNames[m], - mmmm: dF.i18n.monthNames[m + 12], - yy: String(y).slice(2), - yyyy: y, - h: H % 12 || 12, - hh: pad(H % 12 || 12), - H: H, - HH: pad(H), - M: M, - MM: pad(M), - s: s, - ss: pad(s), - l: pad(L, 3), - L: pad(L > 99 ? Math.round(L / 10) : L), - t: H < 12 ? "a" : "p", - tt: H < 12 ? "am" : "pm", - T: H < 12 ? "A" : "P", - TT: H < 12 ? "AM" : "PM", - Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), - o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), - S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] - }; - - return mask.replace(token, function ($0) { - return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); - }); - }; -}(); - -// Some common format strings -dateFormat.masks = { - "default": "ddd mmm dd yyyy HH:MM:ss", - shortDate: "m/d/yy", - mediumDate: "mmm d, yyyy", - longDate: "mmmm d, yyyy", - fullDate: "dddd, mmmm d, yyyy", - shortTime: "h:MM TT", - mediumTime: "h:MM:ss TT", - longTime: "h:MM:ss TT Z", - isoDate: "yyyy-mm-dd", - isoTime: "HH:MM:ss", - isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", - isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" -}; - -// Internationalization strings -dateFormat.i18n = { - dayNames: [ - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", - "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" - ], - monthNames: [ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" - ] -}; - -// For convenience... -Date.prototype.format = function (mask, utc) { - return dateFormat(this, mask, utc); -}; - +/* + * Date Format 1.2.3 + * (c) 2007-2009 Steven Levithan + * MIT license + * + * Includes enhancements by Scott Trenda + * and Kris Kowal + * + * Accepts a date, a mask, or a date and a mask. + * Returns a formatted version of the given date. + * The date defaults to the current date/time. + * The mask defaults to dateFormat.masks.default. + */ + +var dateFormat = function () { + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, + timezoneClip = /[^-+\dA-Z]/g, + pad = function (val, len) { + val = String(val); + len = len || 2; + while (val.length < len) val = "0" + val; + return val; + }; + + // Regexes and supporting functions are cached through closure + return function (date, mask, utc) { + var dF = dateFormat; + + // You can't provide utc if you skip other args (use the "UTC:" mask prefix) + if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { + mask = date; + date = undefined; + } + + // Passing date through Date applies Date.parse, if necessary + date = date ? new Date(date) : new Date; + if (isNaN(date)) throw SyntaxError("invalid date"); + + mask = String(dF.masks[mask] || mask || dF.masks["default"]); + + // Allow setting the utc argument via the mask + if (mask.slice(0, 4) == "UTC:") { + mask = mask.slice(4); + utc = true; + } + + var _ = utc ? "getUTC" : "get", + d = date[_ + "Date"](), + D = date[_ + "Day"](), + m = date[_ + "Month"](), + y = date[_ + "FullYear"](), + H = date[_ + "Hours"](), + M = date[_ + "Minutes"](), + s = date[_ + "Seconds"](), + L = date[_ + "Milliseconds"](), + o = utc ? 0 : date.getTimezoneOffset(), + flags = { + d: d, + dd: pad(d), + ddd: dF.i18n.dayNames[D], + dddd: dF.i18n.dayNames[D + 7], + m: m + 1, + mm: pad(m + 1), + mmm: dF.i18n.monthNames[m], + mmmm: dF.i18n.monthNames[m + 12], + yy: String(y).slice(2), + yyyy: y, + h: H % 12 || 12, + hh: pad(H % 12 || 12), + H: H, + HH: pad(H), + M: M, + MM: pad(M), + s: s, + ss: pad(s), + l: pad(L, 3), + L: pad(L > 99 ? Math.round(L / 10) : L), + t: H < 12 ? "a" : "p", + tt: H < 12 ? "am" : "pm", + T: H < 12 ? "A" : "P", + TT: H < 12 ? "AM" : "PM", + Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), + o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), + S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] + }; + + return mask.replace(token, function ($0) { + return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); + }); + }; +}(); + +// Some common format strings +dateFormat.masks = { + "default": "ddd mmm dd yyyy HH:MM:ss", + shortDate: "m/d/yy", + mediumDate: "mmm d, yyyy", + longDate: "mmmm d, yyyy", + fullDate: "dddd, mmmm d, yyyy", + shortTime: "h:MM TT", + mediumTime: "h:MM:ss TT", + longTime: "h:MM:ss TT Z", + isoDate: "yyyy-mm-dd", + isoTime: "HH:MM:ss", + isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", + isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" +}; + +// Internationalization strings +dateFormat.i18n = { + dayNames: [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + ], + monthNames: [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" + ] +}; + +// For convenience... +Date.prototype.format = function (mask, utc) { + return dateFormat(this, mask, utc); +}; + diff --git a/src/contrib/monitoring/README b/src/contrib/monitoring/README index c88a9d86dbc..d48e2ce26cf 100644 --- a/src/contrib/monitoring/README +++ b/src/contrib/monitoring/README @@ -80,5 +80,5 @@ Apache License 2.0 or later. ZooKeeper 4letterwords Commands ------------------------------- -http://hadoop.apache.org/zookeeper/docs/current/zookeeperAdmin.html#sc_zkCommands +http://zookeeper.apache.org/docs/current/zookeeperAdmin.html#sc_zkCommands diff --git a/src/contrib/monitoring/check_zookeeper.py b/src/contrib/monitoring/check_zookeeper.py index 348ac393b93..c00db8bc0b9 100755 --- a/src/contrib/monitoring/check_zookeeper.py +++ b/src/contrib/monitoring/check_zookeeper.py @@ -74,6 +74,11 @@ def analyze(self, opts, cluster_stats): elif (warning < critical and critical <= value) or (warning > critical and critical >= value): critical_state.append(host) + if not values: + # Zookeeper may be down, not serving requests or we may have a bad configuration + print 'Critical, %s not found' % opts.key + return 2 + values = ' '.join(values) if critical_state: print 'Critical "%s" %s!|%s' % (opts.key, ', '.join(critical_state), values) diff --git a/src/contrib/rest/build.xml b/src/contrib/rest/build.xml index 649dff75295..fb628bcd416 100644 --- a/src/contrib/rest/build.xml +++ b/src/contrib/rest/build.xml @@ -48,6 +48,22 @@ + + + + + + + + + + + + + + + + diff --git a/src/contrib/rest/ivy.xml b/src/contrib/rest/ivy.xml index f4ee8ba0a8f..903d3903e87 100644 --- a/src/contrib/rest/ivy.xml +++ b/src/contrib/rest/ivy.xml @@ -21,7 +21,7 @@ - + ZooKeeper REST diff --git a/src/contrib/rest/src/java/org/apache/zookeeper/server/jersey/cfg/RestCfg.java b/src/contrib/rest/src/java/org/apache/zookeeper/server/jersey/cfg/RestCfg.java index c7730201d4c..93dd63246fb 100644 --- a/src/contrib/rest/src/java/org/apache/zookeeper/server/jersey/cfg/RestCfg.java +++ b/src/contrib/rest/src/java/org/apache/zookeeper/server/jersey/cfg/RestCfg.java @@ -36,9 +36,13 @@ public RestCfg(String resource) throws IOException { } public RestCfg(InputStream io) throws IOException { + try { cfg.load(io); extractEndpoints(); extractCredentials(); + } finally { + io.close(); + } } private void extractCredentials() { diff --git a/src/contrib/zkfuse/README.txt b/src/contrib/zkfuse/README.txt index ee8ed9ee656..901d3634fed 100644 --- a/src/contrib/zkfuse/README.txt +++ b/src/contrib/zkfuse/README.txt @@ -11,6 +11,7 @@ Pre-requisites a. fuse b. log4cxx c. pthread + d. boost Build instructions ------------------ diff --git a/src/contrib/zkfuse/configure.ac b/src/contrib/zkfuse/configure.ac index ae2996bc506..c86e7cdc84c 100644 --- a/src/contrib/zkfuse/configure.ac +++ b/src/contrib/zkfuse/configure.ac @@ -23,7 +23,7 @@ AC_PROG_CXX # Checks for libraries. AC_CHECK_LIB([fuse], [main]) -AC_CHECK_LIB([log4cxx], [main]) +AC_CHECK_LIB([log4cxx], [main], [], [AC_MSG_ERROR("We need log4cxx to build zkfuse")]) AC_CHECK_LIB([thread], [thr_create]) AC_CHECK_LIB([pthread], [pthread_create]) AC_CHECK_LIB([rt], [clock_gettime]) @@ -32,7 +32,8 @@ AC_CHECK_LIB([nsl], [gethostbyname]) AC_CHECK_LIB([ulockmgr], [ulockmgr_op]) ZOOKEEPER_PATH=${BUILD_PATH}/../../c -AC_CHECK_LIB(zookeeper_mt, main, [ZOOKEEPER_LD="-L${ZOOKEEPER_PATH}/.libs -lzookeeper_mt"],,["-L${ZOOKEEPER_PATH}/.libs"]) +ZOOKEEPER_BUILD_PATH=${BUILD_PATH}/../../../build/c +AC_CHECK_LIB(zookeeper_mt, main, [ZOOKEEPER_LD="-L${ZOOKEEPER_BUILD_PATH}/.libs -lzookeeper_mt"],,["-L${ZOOKEEPER_BUILD_PATH}/.libs"]) AC_SUBST(ZOOKEEPER_PATH) AC_SUBST(ZOOKEEPER_LD) @@ -41,6 +42,7 @@ AC_SUBST(ZOOKEEPER_LD) AC_HEADER_DIRENT AC_HEADER_STDC AC_CHECK_HEADERS([fcntl.h stdlib.h string.h sys/time.h unistd.h]) +AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/utility.hpp boost/weak_ptr.hpp],, AC_MSG_ERROR([boost library headers not found. Please install boost library.])) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL diff --git a/src/contrib/zkfuse/src/event.h b/src/contrib/zkfuse/src/event.h index 0506932f7a3..936ecc60453 100644 --- a/src/contrib/zkfuse/src/event.h +++ b/src/contrib/zkfuse/src/event.h @@ -213,7 +213,7 @@ class GenericEvent { /** * The event represented as abstract wrapper. */ - shared_ptr m_eventWrapper; + boost::shared_ptr m_eventWrapper; }; diff --git a/src/contrib/zkfuse/src/zkadapter.cc b/src/contrib/zkfuse/src/zkadapter.cc index 886051d97ba..7f02fa33b72 100644 --- a/src/contrib/zkfuse/src/zkadapter.cc +++ b/src/contrib/zkfuse/src/zkadapter.cc @@ -673,7 +673,7 @@ ZooKeeperAdapter::deleteNode(const string &path, LOG_WARN( LOG, "Error %d for %s", rc, path.c_str() ); //get all children and delete them recursively... vector nodeList; - getNodeChildren( nodeList, path, false ); + getNodeChildren( nodeList, path, NULL ); for (vector::const_iterator i = nodeList.begin(); i != nodeList.end(); ++i) { @@ -845,7 +845,10 @@ ZooKeeperAdapter::getNodeData(const string &path, string("Unable to get data of node ") + path, rc ); } else { - return string( buffer, buffer + len ); + if (len == -1) { + len = 0; + } + return string( buffer, len ); } } diff --git a/src/contrib/zkfuse/src/zkfuse.cc b/src/contrib/zkfuse/src/zkfuse.cc index bcf662bd582..6a8216885fa 100644 --- a/src/contrib/zkfuse/src/zkfuse.cc +++ b/src/contrib/zkfuse/src/zkfuse.cc @@ -81,7 +81,7 @@ uint64_t secsToMillisecs(uint64_t secs) inline uint64_t nanosecsToMillisecs(uint64_t nanosecs) { - return nanosecs * 1000000; + return nanosecs / 1000000; } inline uint64_t timespecToMillisecs(const struct timespec & ts) diff --git a/src/contrib/zkperl/README b/src/contrib/zkperl/README index 520e62468fc..bbe2a0d8f3b 100644 --- a/src/contrib/zkperl/README +++ b/src/contrib/zkperl/README @@ -3,10 +3,10 @@ Net::ZooKeeper - Perl extension for Apache ZooKeeper Net::ZooKeeper provides a Perl interface to the synchronous C API of Apache ZooKeeper. ZooKeeper is coordination service for -distributed applications and is a sub-project of the Apache Hadoop -project. For details see the ZooKeeper home page at: +distributed applications. +For details see the ZooKeeper home page at: -http://hadoop.apache.org/zookeeper/ +http://zookeeper.apache.org/ INSTALLATION diff --git a/src/contrib/zkperl/ZooKeeper.pm b/src/contrib/zkperl/ZooKeeper.pm index 2440603b558..507f0298d16 100644 --- a/src/contrib/zkperl/ZooKeeper.pm +++ b/src/contrib/zkperl/ZooKeeper.pm @@ -169,8 +169,7 @@ Net::ZooKeeper - Perl extension for Apache ZooKeeper Net::ZooKeeper provides a Perl interface to the synchronous C API of Apache ZooKeeper. ZooKeeper is coordination service for -distributed applications and is a sub-project of the Apache Hadoop -project. +distributed applications. Each connection to ZooKeeper is represented as a handle object of the class Net::ZooKeeper, similar to the manner in which database @@ -1229,7 +1228,7 @@ Everything from all of the above tagsets. =head1 SEE ALSO The Apache ZooKeeper project's home page at -L provides a wealth of detail +L provides a wealth of detail on how to develop applications using ZooKeeper. =head1 AUTHOR diff --git a/src/contrib/zkperl/ZooKeeper.xs b/src/contrib/zkperl/ZooKeeper.xs index 271789b9579..4b6067b1024 100644 --- a/src/contrib/zkperl/ZooKeeper.xs +++ b/src/contrib/zkperl/ZooKeeper.xs @@ -251,12 +251,12 @@ static void _zk_release_watch(pTHX_ zk_watch_t *watch, int list) if (list) { if (watch->prev) { watch->prev->next = watch->next; - watch->prev = NULL; } if (watch->next) { watch->next->prev = watch->prev; - watch->next = NULL; } + watch->prev = NULL; + watch->next = NULL; } if (--watch->ref_count == 0) { @@ -695,13 +695,13 @@ zk_acl_constant(alias=Nullch) alias = GvNAME(CvGV(cv)); } - if (ix == 1 || strEQ(alias, "ZOO_OPEN_ACL_UNSAFE")) { + if (ix == 1 || (alias != NULL && strEQ(alias, "ZOO_OPEN_ACL_UNSAFE"))) { acl = ZOO_OPEN_ACL_UNSAFE; } - else if (ix == 2 || strEQ(alias, "ZOO_READ_ACL_UNSAFE")) { + else if (ix == 2 || (alias != NULL && strEQ(alias, "ZOO_READ_ACL_UNSAFE"))) { acl = ZOO_READ_ACL_UNSAFE; } - else if (ix == 3 || strEQ(alias, "ZOO_CREATOR_ALL_ACL")) { + else if (ix == 3 || (alias != NULL && strEQ(alias, "ZOO_CREATOR_ALL_ACL"))) { acl = ZOO_CREATOR_ALL_ACL; } else { @@ -1713,7 +1713,7 @@ zk_get(zkh, path, ...) old_watch, new_watch); } - if (ret == ZOK) { + if (ret == ZOK && buf_len != -1) { ST(0) = sv_newmortal(); #ifdef SV_HAS_TRAILING_NUL buf[buf_len] = '\0'; @@ -2604,6 +2604,7 @@ zkw_wait(zkwh, ...) unsigned int timeout; struct timeval end_timeval; int i, done; + struct timespec wait_timespec; PPCODE: watch = _zkw_get_handle_outer(aTHX_ zkwh, NULL); @@ -2630,25 +2631,19 @@ zkw_wait(zkwh, ...) end_timeval.tv_sec += timeout / 1000; end_timeval.tv_usec += (timeout % 1000) * 1000; + wait_timespec.tv_sec = end_timeval.tv_sec; + wait_timespec.tv_nsec = end_timeval.tv_usec * 1000; + pthread_mutex_lock(&watch->mutex); while (!watch->done) { struct timeval curr_timeval; - struct timespec wait_timespec; gettimeofday(&curr_timeval, NULL); - wait_timespec.tv_sec = end_timeval.tv_sec - curr_timeval.tv_sec; - wait_timespec.tv_nsec = - (end_timeval.tv_usec - curr_timeval.tv_usec) * 1000; - - if (wait_timespec.tv_nsec < 0) { - --wait_timespec.tv_sec; - wait_timespec.tv_nsec += 1000000000; - } - - if (wait_timespec.tv_sec < 0 || - (wait_timespec.tv_sec == 0 && wait_timespec.tv_nsec <= 0)) { + if (end_timeval.tv_sec < curr_timeval.tv_sec || + (end_timeval.tv_sec == curr_timeval.tv_sec && + end_timeval.tv_usec <= curr_timeval.tv_usec)) { break; } diff --git a/src/contrib/zkperl/t/50_access.t b/src/contrib/zkperl/t/50_access.t index 1610319f898..ef61ed6688f 100644 --- a/src/contrib/zkperl/t/50_access.t +++ b/src/contrib/zkperl/t/50_access.t @@ -17,7 +17,7 @@ # limitations under the License. use File::Spec; -use Test::More tests => 38; +use Test::More tests => 40; BEGIN { use_ok('Net::ZooKeeper', qw(:all)) }; @@ -51,6 +51,22 @@ SKIP: { $no_read_acl->[0]->{'perms'} == ZOO_PERM_ALL), '_zk_acl_constant(): returned default ACL'); + my $zoo_read_acl_unsafe = ZOO_READ_ACL_UNSAFE; + ok((ref($zoo_read_acl_unsafe) eq 'ARRAY' and + @{$zoo_read_acl_unsafe} == 1 and + ref($zoo_read_acl_unsafe->[0]) eq 'HASH' and + keys(%{$zoo_read_acl_unsafe->[0]}) == 3 and + $zoo_read_acl_unsafe->[0]->{'perms'} == ZOO_PERM_READ), + '_zk_acl_constant(): returned good ACL'); + + my $zoo_creator_all_acl = ZOO_CREATOR_ALL_ACL; + ok((ref($zoo_creator_all_acl) eq 'ARRAY' and + @{$zoo_creator_all_acl} == 1 and + ref($zoo_creator_all_acl->[0]) eq 'HASH' and + keys(%{$zoo_creator_all_acl->[0]}) == 3 and + $zoo_creator_all_acl->[0]->{'perms'} == ZOO_PERM_ALL), + '_zk_acl_constant(): returned good ACL'); + $no_read_acl->[0]->{'perms'} &= ~ZOO_PERM_READ; is($no_read_acl->[0]->{'perms'}, ((ZOO_PERM_ALL) & ~ZOO_PERM_READ), 'assign: altered default ACL'); diff --git a/src/contrib/zkpython/build.xml b/src/contrib/zkpython/build.xml index 7ceeda9e8a9..029d4f29cff 100644 --- a/src/contrib/zkpython/build.xml +++ b/src/contrib/zkpython/build.xml @@ -132,7 +132,7 @@ - + @@ -141,7 +141,7 @@ - + diff --git a/src/contrib/zkpython/src/c/pyzk_docstrings.h b/src/contrib/zkpython/src/c/pyzk_docstrings.h index d2c4d60f6a4..1f38d53fc74 100644 --- a/src/contrib/zkpython/src/c/pyzk_docstrings.h +++ b/src/contrib/zkpython/src/c/pyzk_docstrings.h @@ -276,7 +276,7 @@ const char pyzk_add_auth_doc[] = "BADARGUMENTS - invalid input parameters\n" "INVALIDSTATE - zhandle state is either SESSION_EXPIRED_STATE or AUTH_FAILED_STATE\n" "MARSHALLINGERROR - failed to marshall a request; possibly, out of memory\n" - "SYSTEMERROR - a system error occured\n"; + "SYSTEMERROR - a system error occurred\n"; const char pyzk_is_unrecoverable_doc[] = " checks if the current zookeeper connection state can't be recovered.\n" @@ -513,8 +513,8 @@ static const char pyzk_close_doc[] = "BADARGUMENTS - invalid input parameters\n" "MARSHALLINGERROR - failed to marshall a request; possibly, out of memory\n" "OPERATIONTIMEOUT - failed to flush the buffers within the specified timeout.\n" -"CONNECTIONLOSS - a network error occured while attempting to send request to server\n" - "SYSTEMERROR -- a system (OS) error occured; it's worth checking errno to get details\n"; +"CONNECTIONLOSS - a network error occurred while attempting to send request to server\n" + "SYSTEMERROR -- a system (OS) error occurred; it's worth checking errno to get details\n"; static const char pyzk_set2_doc[] = "\n" diff --git a/src/contrib/zkpython/src/c/zookeeper.c b/src/contrib/zkpython/src/c/zookeeper.c index 07e32d68954..4474661860a 100644 --- a/src/contrib/zkpython/src/c/zookeeper.c +++ b/src/contrib/zkpython/src/c/zookeeper.c @@ -52,6 +52,8 @@ PyObject *err_to_exception(int errcode) { switch (errcode) { case ZSYSTEMERROR: return SystemErrorException; + case ZINVALIDSTATE: + return InvalidStateException; case ZRUNTIMEINCONSISTENCY: return RuntimeInconsistencyException; case ZDATAINCONSISTENCY: @@ -90,7 +92,10 @@ PyObject *err_to_exception(int errcode) { return InvalidCallbackException; case ZSESSIONMOVED: return SessionMovedException; - + case ZCLOSING: + return ClosingException; + case ZNOTHING: + return NothingException; case ZOK: default: return NULL; @@ -270,7 +275,11 @@ PyObject *build_string_vector(const struct String_vector *sv) if (ret) { int i; for (i=0;icount;++i) { +#if PY_MAJOR_VERSION >= 3 + PyObject *s = PyUnicode_FromString(sv->data[i]); +#else PyObject *s = PyString_FromString(sv->data[i]); +#endif if (!s) { if (ret != Py_None) { Py_DECREF(ret); @@ -376,9 +385,15 @@ int parse_acls(struct ACL_vector *acls, PyObject *pyacls) a = PyList_GetItem(pyacls, i); // a is now a dictionary PyObject *perms = PyDict_GetItemString( a, "perms" ); +#if PY_MAJOR_VERSION >= 3 + acls->data[i].perms = (int32_t)(PyLong_AsLong(perms)); + acls->data[i].id.id = strdup( PyUnicode_AsUnicode( PyDict_GetItemString( a, "id" ) ) ); + acls->data[i].id.scheme = strdup( PyUnicode_AsUnicode( PyDict_GetItemString( a, "scheme" ) ) ); +#else acls->data[i].perms = (int32_t)(PyInt_AsLong(perms)); acls->data[i].id.id = strdup( PyString_AsString( PyDict_GetItemString( a, "id" ) ) ); acls->data[i].id.scheme = strdup( PyString_AsString( PyDict_GetItemString( a, "scheme" ) ) ); +#endif } return 1; } @@ -436,7 +451,8 @@ void watcher_dispatch(zhandle_t *zzh, int type, int state, if (PyObject_CallObject((PyObject*)callback, arglist) == NULL) { PyErr_Print(); } - if (pyw->permanent == 0 && (type != ZOO_SESSION_EVENT || is_unrecoverable(zzh))) { + Py_DECREF(arglist); + if (pyw->permanent == 0 && (type != ZOO_SESSION_EVENT || state < 0)) { free_pywatcher(pyw); } PyGILState_Release(gstate); @@ -457,6 +473,7 @@ void void_completion_dispatch(int rc, const void *data) PyObject *arglist = Py_BuildValue("(i,i)", pyw->zhandle, rc); if (PyObject_CallObject((PyObject*)callback, arglist) == NULL) PyErr_Print(); + Py_DECREF(arglist); free_pywatcher(pyw); PyGILState_Release(gstate); } @@ -474,9 +491,9 @@ void stat_completion_dispatch(int rc, const struct Stat *stat, const void *data) PyObject *pystat = build_stat(stat); PyObject *arglist = Py_BuildValue("(i,i,O)", pyw->zhandle,rc, pystat); Py_DECREF(pystat); - if (PyObject_CallObject((PyObject*)callback, arglist) == NULL) PyErr_Print(); + Py_DECREF(arglist); free_pywatcher(pyw); PyGILState_Release(gstate); } @@ -498,6 +515,7 @@ void data_completion_dispatch(int rc, const char *value, int value_len, const st if (PyObject_CallObject((PyObject*)callback, arglist) == NULL) PyErr_Print(); + Py_DECREF(arglist); free_pywatcher(pyw); PyGILState_Release(gstate); } @@ -518,6 +536,7 @@ void strings_completion_dispatch(int rc, const struct String_vector *strings, co PyObject *arglist = Py_BuildValue("(i,i,O)", pyw->zhandle, rc, pystrings); if (arglist == NULL || PyObject_CallObject((PyObject*)callback, arglist) == NULL) PyErr_Print(); + Py_DECREF(arglist); } else PyErr_Print(); @@ -540,6 +559,7 @@ void string_completion_dispatch(int rc, const char *value, const void *data) PyObject *arglist = Py_BuildValue("(i,i,s)", pyw->zhandle,rc, value); if (PyObject_CallObject((PyObject*)callback, arglist) == NULL) PyErr_Print(); + Py_DECREF(arglist); free_pywatcher(pyw); PyGILState_Release(gstate); } @@ -565,6 +585,7 @@ void acl_completion_dispatch(int rc, struct ACL_vector *acl, struct Stat *stat, if (PyObject_CallObject((PyObject*)callback, arglist) == NULL) { PyErr_Print(); } + Py_DECREF(arglist); free_pywatcher(pyw); PyGILState_Release(gstate); } @@ -700,7 +721,7 @@ PyObject *pyzoo_adelete(PyObject *self, PyObject *args) return Py_BuildValue("i", err); } -/* Asynchronous node existance check, returns integer error code */ +/* Asynchronous node existence check, returns integer error code */ PyObject *pyzoo_aexists(PyObject *self, PyObject *args) { int zkhid; char *path; @@ -1038,7 +1059,7 @@ static PyObject *pyzoo_delete(PyObject *self, PyObject *args) return Py_BuildValue("i", err); } -/* Synchronous node existance check, returns stat if exists, None if +/* Synchronous node existence check, returns stat if exists, None if absent */ static PyObject *pyzoo_exists(PyObject *self, PyObject *args) { @@ -1202,7 +1223,7 @@ static PyObject *pyzoo_get(PyObject *self, PyObject *args) } PyObject *stat_dict = build_stat( &stat ); - PyObject *ret = Py_BuildValue( "(s#,N)", buffer,buffer_len, stat_dict ); + PyObject *ret = Py_BuildValue( "(s#,N)", buffer,buffer_len < 0 ? 0 : buffer_len, stat_dict ); free(buffer); return ret; @@ -1421,7 +1442,14 @@ PyObject *pyzoo_set_log_stream(PyObject *self, PyObject *args) PyErr_SetString(PyExc_ValueError, "Must supply a Python object to set_log_stream"); return NULL; } - if (!PyFile_Check(pystream)) { + +#if PY_MAJOR_VERSION >= 3 + extern PyTypeObject PyIOBase_Type; + if (!PyObject_IsInstance(pystream, (PyObject *)&PyIOBase_Type)) { +#else + if(!PyFile_Check(pystream)) { +#endif + PyErr_SetString(PyExc_ValueError, "Must supply a file object to set_log_stream"); return NULL; } @@ -1432,7 +1460,14 @@ PyObject *pyzoo_set_log_stream(PyObject *self, PyObject *args) log_stream = pystream; Py_INCREF(log_stream); - zoo_set_log_stream(PyFile_AsFile(log_stream)); + +#if PY_MAJOR_VERSION >= 3 + int fd = PyObject_AsFileDescriptor(log_stream); + FILE *fp = fdopen(fd, "w"); +#else + FILE *fp = PyFile_AsFile(log_stream); +#endif + zoo_set_log_stream(fp); Py_INCREF(Py_None); return Py_None; @@ -1494,6 +1529,20 @@ static PyMethodDef ZooKeeperMethods[] = { {NULL, NULL} }; +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef zookeeper_moddef = { + PyModuleDef_HEAD_INIT, + "zookeeper", + NULL, + 0, + ZooKeeperMethods, + 0, + 0, + 0, + 0 +}; +#endif + #define ADD_INTCONSTANT(x) PyModule_AddIntConstant(module, #x, ZOO_##x) #define ADD_INTCONSTANTZ(x) PyModule_AddIntConstant(module, #x, Z##x) @@ -1501,10 +1550,18 @@ static PyMethodDef ZooKeeperMethods[] = { Py_INCREF(x); \ PyModule_AddObject(module, #x, x); - +#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC PyInit_zookeeper(void) { +#else PyMODINIT_FUNC initzookeeper(void) { +#endif PyEval_InitThreads(); - PyObject *module = Py_InitModule("zookeeper", ZooKeeperMethods ); + +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&zookeeper_moddef); +#else + PyObject *module = Py_InitModule("zookeeper", ZooKeeperMethods); +#endif if (init_zhandles(32) == 0) { return; // TODO: Is there any way to raise an exception here? } @@ -1600,4 +1657,8 @@ PyMODINIT_FUNC initzookeeper(void) { ADD_EXCEPTION(ClosingException); ADD_EXCEPTION(NothingException); ADD_EXCEPTION(SessionMovedException); + +#if PY_MAJOR_VERSION >= 3 + return module; +#endif } diff --git a/src/contrib/zkpython/src/packages/rpm/spec/zkpython.spec b/src/contrib/zkpython/src/packages/rpm/spec/zkpython.spec index 2c640b04eb2..d1f981431b6 100644 --- a/src/contrib/zkpython/src/packages/rpm/spec/zkpython.spec +++ b/src/contrib/zkpython/src/packages/rpm/spec/zkpython.spec @@ -52,7 +52,6 @@ Version: %{version} Release: %{release} Source0: %{_python_lib} Prefix: %{_prefix} -Buildroot: %{_build_dir} Requires: zookeeper-lib == %{version} AutoReqProv: no Provides: zkpython diff --git a/src/contrib/zkpython/src/python/zk.py b/src/contrib/zkpython/src/python/zk.py index abafaa07081..9c0f37482ed 100755 --- a/src/contrib/zkpython/src/python/zk.py +++ b/src/contrib/zkpython/src/python/zk.py @@ -24,22 +24,22 @@ def my_connection_watcher(handle,type,state,path): global connected, conn_cv - print "Connected, handle is ", handle + print("Connected, handle is ", handle) conn_cv.acquire() connected = True conn_cv.notifyAll() conn_cv.release() conn_cv.acquire() -print "Connecting to localhost:2181 -- " +print("Connecting to localhost:2181 -- ") handle = zookeeper.init("localhost:2181", my_connection_watcher, 10000, 0) while not connected: conn_cv.wait() conn_cv.release() def my_getc_watch( handle, type, state, path ): - print "Watch fired -- " - print type, state, path + print("Watch fired -- ") + print(type, state, path) ZOO_OPEN_ACL_UNSAFE = {"perms":0x1f, "scheme":"world", "id" :"anyone"}; @@ -47,12 +47,12 @@ def my_getc_watch( handle, type, state, path ): zookeeper.create(handle, "/zk-python", "data", [ZOO_OPEN_ACL_UNSAFE], 0) zookeeper.get_children(handle, "/zk-python", my_getc_watch) for i in xrange(5): - print "Creating sequence node ", i, " ", zookeeper.create(handle, "/zk-python/sequencenode", "data", [ZOO_OPEN_ACL_UNSAFE], zookeeper.SEQUENCE ) + print("Creating sequence node ", i, " ", zookeeper.create(handle, "/zk-python/sequencenode", "data", [ZOO_OPEN_ACL_UNSAFE], zookeeper.SEQUENCE )) except: pass def pp_zk(handle,root, indent = 0): - """Pretty print a zookeeper tree, starting at root""" + """Pretty print(a zookeeper tree, starting at root""") def make_path(child): if root == "/": return "/" + child @@ -62,15 +62,15 @@ def make_path(child): for i in xrange(indent): out += "\t" out += "|---"+root + " :: " + zookeeper.get(handle, root, None)[0] - print out + print(out) for child in children: pp_zk(handle,make_path(child),indent+1) -print "ZNode tree -- " +print("ZNode tree -- ") pp_zk(handle,"/") -print "Getting ACL / Stat for /zk-python --" +print("Getting ACL / Stat for /zk-python --") (stat, acl) = zookeeper.get_acl(handle, "/zk-python") -print "Stat:: ", stat -print "Acl:: ", acl +print("Stat:: ", stat) +print("Acl:: ", acl) diff --git a/src/contrib/zkpython/src/test/connection_test.py b/src/contrib/zkpython/src/test/connection_test.py index b852d1a6fce..3913fe3b7d9 100755 --- a/src/contrib/zkpython/src/test/connection_test.py +++ b/src/contrib/zkpython/src/test/connection_test.py @@ -16,7 +16,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest, threading, re +import unittest, threading, re, sys +if sys.version_info < (3,): + range = xrange import zookeeper, zktestbase ZOO_OPEN_ACL_UNSAFE = {"perms":0x1f, "scheme":"world", "id" :"anyone"} @@ -71,7 +73,7 @@ def connection_watcher(handle, type, state, path): cv.release() cv.acquire() - handles = [ zookeeper.init(self.host) for i in xrange(10) ] + handles = [ zookeeper.init(self.host) for i in range(10) ] ret = zookeeper.init(self.host, connection_watcher) cv.wait(15.0) cv.release() @@ -93,7 +95,7 @@ def testmanyhandles(self): """ # We'd like to do more, but currently the C client doesn't # work with > 83 handles (fails to create a pipe) on MacOS 10.5.8 - handles = [ zookeeper.init(self.host) for i in xrange(63) ] + handles = [ zookeeper.init(self.host) for i in range(9) ] cv = threading.Condition() self.connected = False diff --git a/src/contrib/zkpython/src/test/get_set_test.py b/src/contrib/zkpython/src/test/get_set_test.py index 64d76f4aad4..b77b3b22165 100755 --- a/src/contrib/zkpython/src/test/get_set_test.py +++ b/src/contrib/zkpython/src/test/get_set_test.py @@ -16,7 +16,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import zookeeper, zktestbase, unittest, threading +import zookeeper, zktestbase, unittest, threading, sys +if sys.version_info < (3,): + range = xrange + ZOO_OPEN_ACL_UNSAFE = {"perms":0x1f, "scheme":"world", "id" :"anyone"} class GetSetTest(zktestbase.TestBase): @@ -29,6 +32,19 @@ def setUp( self ): except: pass + def test_empty_node(self): + """ + Test for a bug when instead of empty string we can get + random data from buffer malloc'ed to hold node contents. + See ZOOKEEPER-1906 for details + """ + NODE_PATH = "/zk-python-test-empty-node" + self.ensureDeleted(NODE_PATH) + zookeeper.create(self.handle, NODE_PATH, "", + [{"perms":0x1f, "scheme":"world", "id" :"anyone"}]) + (data,stat) = zookeeper.get(self.handle, NODE_PATH, None) + self.assertEqual(data, "", "Data is not empty as expected: " + data) + def test_sync_getset(self): self.assertEqual(self.connected, True, "Not connected!") (data,stat) = zookeeper.get(self.handle, "/zk-python-getsettest", None) @@ -84,7 +100,7 @@ def test_sync_get_large_datanode(self): 1Mb with default parameters (depends on ZooKeeper server). """ - data = ''.join(["A" for x in xrange(1024*1023)]) + data = ''.join(["A" for x in range(1024*1023)]) self.ensureDeleted("/zk-python-test-large-datanode") zookeeper.create(self.handle, "/zk-python-test-large-datanode", data, [{"perms":0x1f, "scheme":"world", "id" :"anyone"}]) diff --git a/src/contrib/zkpython/src/test/zktestbase.py b/src/contrib/zkpython/src/test/zktestbase.py index cf0f0557914..8229418e3e7 100755 --- a/src/contrib/zkpython/src/test/zktestbase.py +++ b/src/contrib/zkpython/src/test/zktestbase.py @@ -34,7 +34,7 @@ def __init__(self,methodName='runTest'): f = open(logfile,"w") zookeeper.set_log_stream(f) except IOError: - print "Couldn't open " + logfile + " for writing" + print("Couldn't open " + logfile + " for writing") def setUp(self): diff --git a/src/contrib/zktreeutil/src/ZkAdaptor.cc b/src/contrib/zktreeutil/src/ZkAdaptor.cc index baec8f9b0fa..1df175a9bac 100644 --- a/src/contrib/zktreeutil/src/ZkAdaptor.cc +++ b/src/contrib/zktreeutil/src/ZkAdaptor.cc @@ -445,7 +445,7 @@ namespace zktreeutil if (rc == ZNONODE) return false; // Some error - std::cerr << "[zktreeutil] Error in checking existance of " << path << std::endl; + std::cerr << "[zktreeutil] Error in checking existence of " << path << std::endl; throw ZooKeeperException( string("Unable to check existence of node ") + path, rc ); } else { return true; diff --git a/src/contrib/zktreeutil/src/ZkAdaptor.h b/src/contrib/zktreeutil/src/ZkAdaptor.h index d94b033c51b..4b68e28db1f 100644 --- a/src/contrib/zktreeutil/src/ZkAdaptor.h +++ b/src/contrib/zktreeutil/src/ZkAdaptor.h @@ -255,7 +255,7 @@ namespace zktreeutil vector getNodeChildren( const string &path) throw(ZooKeeperException); /** - * \brief Check the existance of path to a znode. + * \brief Check the existence of path to a znode. * * @param path the absolute path name of the znode * @return TRUE if the znode exists; FALSE otherwise diff --git a/src/contrib/zktreeutil/src/ZkTreeUtil.cc b/src/contrib/zktreeutil/src/ZkTreeUtil.cc index 83f0cbf3f94..270bf3105c7 100644 --- a/src/contrib/zktreeutil/src/ZkTreeUtil.cc +++ b/src/contrib/zktreeutil/src/ZkTreeUtil.cc @@ -347,7 +347,7 @@ namespace zktreeutil std::cerr << "[zktreeutil] connected to ZK serverfor reading" << std::endl; - // Check the existance of the path to znode + // Check the existence of the path to znode if (!zkHandle->nodeExists (path)) { string errMsg = string("[zktreeutil] path does not exists : ") + path; diff --git a/src/contrib/zooinspector/NOTICE.txt b/src/contrib/zooinspector/NOTICE.txt index 059bdc117f9..e0bf2f0d798 100644 --- a/src/contrib/zooinspector/NOTICE.txt +++ b/src/contrib/zooinspector/NOTICE.txt @@ -1,3 +1,3 @@ This contrib module includes icons available under the Eclipse Public Licence Version 1.0 -. from the Eclipse Java Devlopment Platform. +. from the Eclipse Java Devlopment Platform. The lib sub-directory includes a binary only jar library developed at http://sourceforge.net/projects/jtoaster/ \ No newline at end of file diff --git a/src/contrib/zooinspector/README.txt b/src/contrib/zooinspector/README.txt index 4868e3a8e47..3c2a58f42e4 100644 --- a/src/contrib/zooinspector/README.txt +++ b/src/contrib/zooinspector/README.txt @@ -1,94 +1,94 @@ -========================================== -ZooInspector - Browser and Editor for ZooKeeper Instances -Author: Colin Goodheart-Smithe -Date: February 2010 -========================================== - -ZooInspector is a Java Swing based application for browsing and editing ZooKeeper instances. - -Contents --------- - - Features - - Pre-requisites - - Build Instructions - - Using ZooInspector - - Creating and Using Plugins - -Features --------- - Below is a list of features in the current release of ZooInspector. - - Load connection settings from a zookeeper properties file - - Plugable DataEncryptionManagers to specify how data should be encrypted and decrypted in the Zookeeper instance - - Browseable tree view of the ZooKeeper instance - - View the data in a node - - View the ACL's currently applied to a node - - View the metadata for a node (Version, Number of Children, Last modified Tiem, etc.) - - Plugable NodeViewers interface - - Ability to save/load and set default Node Viewers - -Pre-requisites --------------- - - The main zookeeper build script must have been run before building this module - -Build Instructions ------------------- - 1. Open a command line. - 2. cd into this directory - 3. Run command: ant - 4. ZooInspector will be built to ../../../build/contrib/ZooInspector - 5. Copy zookeeper-3.x.x.jar into the lib sub-directory (if you are using zookeeper-3.3.0.jar it will have been - copied to this directory during the build - 6. By default the zookeeper.cmd and zookeeper.sh files expect zookeeper-3.3.0.jar. If you are using another version - you will need to change these files to point to the zookeeper-3.x.x.jar you copied to the lib directory - 7. To run ZooInspector run zooInspector.cmd (on Windows) or zooInspector.sh (on Linux). If you are using - zookeeper-3.3.0.jar and do not require any classpath changes you can run the zookeeper-dev-ZooInspector.jar - directly - -Using ZooInspector ------------------- - To start ZooInspector run zooInspector.cmd (on Windows) or zooInspector.sh (on Linux). If you are using - zookeeper-3.3.0.jar and do not require any classpath changes you can run the zookeeper-dev-ZooInspector.jar - directly. - - Click the play button on the toolbar to bring up the connection dialog. From here you can enter connection - information for your zookeeper instance. You can also load the connection properties from a file. This file can - have the format as a normal zookeeper properties file (i.e. hosts and timeout key-value pairs) and van optional have - an encryptionManager key-value pair to specify the DataEncryptionManager to use for this connection - (DataEncryptionManagers are explained in further detail in the 'Creating and Using Plugins' section below). You can - also set the entered information as the defaults so that when you first start ZooInspector these settings are - automatically loaded into this dialog. Pressing the OK button with connect to your ZooKeeper instance and show the - current node tree on the left of the main panel. - - Clicking a node in the node tree will load the data for that node into the node viewers. Three node viewers are - currently distributed with ZooInspector: - 1. Node Data - This enables you to see the data current stored on that node. This data can be modified and - saved. The data is decrypted and encrypted using the DataEncryptionManager specified on the connection - dialog. - 2. Node Metadata - This enables you to see the metadata associiated with this node. This is Essentially the data - obtained from the Stat object for this node. - 3. Node ACLs - This allows you to see the ACLs currently applied to this node. Currently there is no ability - to change the ACLs on a node, but it is a feature I would like to add. - Other custom Node Viewers can be added, this is explained in the 'Creating and Using Plugins' section below. - - -Creating and Using Plugins --------------------------- - There are two types of plugin which can be used with ZooInspector: - 1. DataEncryptionManager - This specifies how data should be encrypted and decrypted when working with a - zookeeper instance. - 2. ZooInspectorNodeViewer - This is a GUI panel which provides a view of visualisation on a node. - More information on these interfaces can be found in the javadocs for this module. - - To use a plugin in ZooInspector, build the plugin to a jar and copy the jar to the lib sub-directory. Edit the - zooInspector.cmd and/or zooInspector.sh files to include your new jar on the classpath and run ZooInspector. - - For DataEncryptionManagers, click the play button to open the connection dialog and enter the full class name of - your DataEncryptionManager in the 'Data Encryption Manager' field. You can make this Data Encryption Manager the - default by clicking 'Set As Default'. Click the 'OK' button to instantiate and use your plugin. - - For ZooInspectorNodeViewers, Click the 'Change Node Viewers' button on the toolbar (looks like a tree with a pencil) - and enter the full classname for your Node Viewer in the field left of the 'Add' button, then click the 'Add' - button. The Node Viewer will be instantiated and should appear in the list. You can change the order of the Node - viewers by clicking the up and dpwn buttons and delete a Node Viewer by clicking the delete button. You can save - to configuration to a file or set it as the default if necessary. Then click the 'OK' button and your Node Viewer +========================================== +ZooInspector - Browser and Editor for ZooKeeper Instances +Author: Colin Goodheart-Smithe +Date: February 2010 +========================================== + +ZooInspector is a Java Swing based application for browsing and editing ZooKeeper instances. + +Contents +-------- + - Features + - Pre-requisites + - Build Instructions + - Using ZooInspector + - Creating and Using Plugins + +Features +-------- + Below is a list of features in the current release of ZooInspector. + - Load connection settings from a zookeeper properties file + - Plugable DataEncryptionManagers to specify how data should be encrypted and decrypted in the Zookeeper instance + - Browseable tree view of the ZooKeeper instance + - View the data in a node + - View the ACL's currently applied to a node + - View the metadata for a node (Version, Number of Children, Last modified Tiem, etc.) + - Plugable NodeViewers interface + - Ability to save/load and set default Node Viewers + +Pre-requisites +-------------- + - The main zookeeper build script must have been run before building this module + +Build Instructions +------------------ + 1. Open a command line. + 2. cd into this directory + 3. Run command: ant + 4. ZooInspector will be built to ../../../build/contrib/ZooInspector + 5. Copy zookeeper-3.x.x.jar into the lib sub-directory (if you are using zookeeper-3.3.0.jar it will have been + copied to this directory during the build + 6. By default the zookeeper.cmd and zookeeper.sh files expect zookeeper-3.3.0.jar. If you are using another version + you will need to change these files to point to the zookeeper-3.x.x.jar you copied to the lib directory + 7. To run ZooInspector run zooInspector.cmd (on Windows) or zooInspector.sh (on Linux). If you are using + zookeeper-3.3.0.jar and do not require any classpath changes you can run the zookeeper-dev-ZooInspector.jar + directly + +Using ZooInspector +------------------ + To start ZooInspector run zooInspector.cmd (on Windows) or zooInspector.sh (on Linux). If you are using + zookeeper-3.3.0.jar and do not require any classpath changes you can run the zookeeper-dev-ZooInspector.jar + directly. + + Click the play button on the toolbar to bring up the connection dialog. From here you can enter connection + information for your zookeeper instance. You can also load the connection properties from a file. This file can + have the format as a normal zookeeper properties file (i.e. hosts and timeout key-value pairs) and van optional have + an encryptionManager key-value pair to specify the DataEncryptionManager to use for this connection + (DataEncryptionManagers are explained in further detail in the 'Creating and Using Plugins' section below). You can + also set the entered information as the defaults so that when you first start ZooInspector these settings are + automatically loaded into this dialog. Pressing the OK button with connect to your ZooKeeper instance and show the + current node tree on the left of the main panel. + + Clicking a node in the node tree will load the data for that node into the node viewers. Three node viewers are + currently distributed with ZooInspector: + 1. Node Data - This enables you to see the data current stored on that node. This data can be modified and + saved. The data is decrypted and encrypted using the DataEncryptionManager specified on the connection + dialog. + 2. Node Metadata - This enables you to see the metadata associiated with this node. This is Essentially the data + obtained from the Stat object for this node. + 3. Node ACLs - This allows you to see the ACLs currently applied to this node. Currently there is no ability + to change the ACLs on a node, but it is a feature I would like to add. + Other custom Node Viewers can be added, this is explained in the 'Creating and Using Plugins' section below. + + +Creating and Using Plugins +-------------------------- + There are two types of plugin which can be used with ZooInspector: + 1. DataEncryptionManager - This specifies how data should be encrypted and decrypted when working with a + zookeeper instance. + 2. ZooInspectorNodeViewer - This is a GUI panel which provides a view of visualisation on a node. + More information on these interfaces can be found in the javadocs for this module. + + To use a plugin in ZooInspector, build the plugin to a jar and copy the jar to the lib sub-directory. Edit the + zooInspector.cmd and/or zooInspector.sh files to include your new jar on the classpath and run ZooInspector. + + For DataEncryptionManagers, click the play button to open the connection dialog and enter the full class name of + your DataEncryptionManager in the 'Data Encryption Manager' field. You can make this Data Encryption Manager the + default by clicking 'Set As Default'. Click the 'OK' button to instantiate and use your plugin. + + For ZooInspectorNodeViewers, Click the 'Change Node Viewers' button on the toolbar (looks like a tree with a pencil) + and enter the full classname for your Node Viewer in the field left of the 'Add' button, then click the 'Add' + button. The Node Viewer will be instantiated and should appear in the list. You can change the order of the Node + viewers by clicking the up and dpwn buttons and delete a Node Viewer by clicking the delete button. You can save + to configuration to a file or set it as the default if necessary. Then click the 'OK' button and your Node Viewer should appear in the tabs on the right of the main panel. \ No newline at end of file diff --git a/src/contrib/zooinspector/build.xml b/src/contrib/zooinspector/build.xml index bf757d7638a..a2427909ee4 100644 --- a/src/contrib/zooinspector/build.xml +++ b/src/contrib/zooinspector/build.xml @@ -1,152 +1,152 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Tests failed! - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests failed! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/contrib/zooinspector/config/defaultConnectionSettings.cfg b/src/contrib/zooinspector/config/defaultConnectionSettings.cfg index 806789e10b3..36a34ff3088 100644 --- a/src/contrib/zooinspector/config/defaultConnectionSettings.cfg +++ b/src/contrib/zooinspector/config/defaultConnectionSettings.cfg @@ -14,7 +14,8 @@ # limitations under the License. # #Default connection for ZooInspector -#Sun Feb 28 14:46:55 GMT 2010 hosts=localhost\:2181 encryptionManager=org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager timeout=5000 +authScheme= +authData= diff --git a/src/contrib/zooinspector/ivy.xml b/src/contrib/zooinspector/ivy.xml index 4e5107e36aa..bc02946ee6c 100644 --- a/src/contrib/zooinspector/ivy.xml +++ b/src/contrib/zooinspector/ivy.xml @@ -1,48 +1,48 @@ - - - - - - - - ZooInspector - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + ZooInspector + + + + + + + + + + + + + + + + + + + + diff --git a/src/contrib/zooinspector/lib/log4j.properties b/src/contrib/zooinspector/lib/log4j.properties index 970670c7177..6f96d844ac2 100644 --- a/src/contrib/zooinspector/lib/log4j.properties +++ b/src/contrib/zooinspector/lib/log4j.properties @@ -1,9 +1,9 @@ -# ***** Set root logger level to INFO and it appender to stdout. -log4j.rootLogger=info, stdout - -# ***** stdout is set to be a ConsoleAppender. -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -# ***** stdout uses PatternLayout. -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -# ***** Pattern to output the caller's file name and line number. +# ***** Set root logger level to INFO and it appender to stdout. +log4j.rootLogger=info, stdout + +# ***** stdout is set to be a ConsoleAppender. +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +# ***** stdout uses PatternLayout. +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +# ***** Pattern to output the caller's file name and line number. log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n \ No newline at end of file diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/ZooInspector.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/ZooInspector.java index 28a4b2100bf..b637bfd4cf0 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/ZooInspector.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/ZooInspector.java @@ -1,66 +1,66 @@ -/** - * 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.inspector; - -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javax.swing.JFrame; -import javax.swing.JOptionPane; -import javax.swing.UIManager; - -import org.apache.zookeeper.inspector.gui.ZooInspectorPanel; -import org.apache.zookeeper.inspector.logger.LoggerFactory; -import org.apache.zookeeper.inspector.manager.ZooInspectorManagerImpl; - -/** - * - */ -public class ZooInspector { - /** - * @param args - * - not used. The value of these parameters will have no effect - * on the application - */ - public static void main(String[] args) { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - JFrame frame = new JFrame("ZooInspector"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - final ZooInspectorPanel zooInspectorPanel = new ZooInspectorPanel( - new ZooInspectorManagerImpl()); - frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - super.windowClosed(e); - zooInspectorPanel.disconnect(true); - } - }); - - frame.setContentPane(zooInspectorPanel); - frame.setSize(1024, 768); - frame.setVisible(true); - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred loading ZooInspector", e); - JOptionPane.showMessageDialog(null, - "ZooInspector failed to start: " + e.getMessage(), "Error", - JOptionPane.ERROR_MESSAGE); - } - } -} +/** + * 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.inspector; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.UIManager; + +import org.apache.zookeeper.inspector.gui.ZooInspectorPanel; +import org.apache.zookeeper.inspector.logger.LoggerFactory; +import org.apache.zookeeper.inspector.manager.ZooInspectorManagerImpl; + +/** + * + */ +public class ZooInspector { + /** + * @param args + * - not used. The value of these parameters will have no effect + * on the application + */ + public static void main(String[] args) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + JFrame frame = new JFrame("ZooInspector"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + final ZooInspectorPanel zooInspectorPanel = new ZooInspectorPanel( + new ZooInspectorManagerImpl()); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + super.windowClosed(e); + zooInspectorPanel.disconnect(true); + } + }); + + frame.setContentPane(zooInspectorPanel); + frame.setSize(1024, 768); + frame.setVisible(true); + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred loading ZooInspector", e); + JOptionPane.showMessageDialog(null, + "ZooInspector failed to start: " + e.getMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/encryption/BasicDataEncryptionManager.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/encryption/BasicDataEncryptionManager.java index bab799c13a5..a9e5ac477cf 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/encryption/BasicDataEncryptionManager.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/encryption/BasicDataEncryptionManager.java @@ -1,50 +1,50 @@ -/** - * 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.inspector.encryption; - -/** - * - */ -public class BasicDataEncryptionManager implements DataEncryptionManager { - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.encryption.DataEncryptionManager#decryptData - * (byte[]) - */ - public String decryptData(byte[] encrypted) throws Exception { - return new String(encrypted); - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.encryption.DataEncryptionManager#encryptData - * (java.lang.String) - */ - public byte[] encryptData(String data) throws Exception { - if (data == null) { - return new byte[0]; - } - return data.getBytes(); - } - -} +/** + * 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.inspector.encryption; + +/** + * + */ +public class BasicDataEncryptionManager implements DataEncryptionManager { + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.encryption.DataEncryptionManager#decryptData + * (byte[]) + */ + public String decryptData(byte[] encrypted) throws Exception { + return new String(encrypted); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.encryption.DataEncryptionManager#encryptData + * (java.lang.String) + */ + public byte[] encryptData(String data) throws Exception { + if (data == null) { + return new byte[0]; + } + return data.getBytes(); + } + +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/encryption/DataEncryptionManager.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/encryption/DataEncryptionManager.java index 8cca3ca69ec..15a9ee4350f 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/encryption/DataEncryptionManager.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/encryption/DataEncryptionManager.java @@ -1,39 +1,39 @@ -/** - * 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.inspector.encryption; - -/** - * A class which describes how data should be encrypted and decrypted - */ -public interface DataEncryptionManager { - /** - * @param data - * - the data to be encrypted - * @return the encrypted data - * @throws Exception - */ - public byte[] encryptData(String data) throws Exception; - - /** - * @param encrypted - * - the data to be decrypted - * @return the decrypted data - * @throws Exception - */ - public String decryptData(byte[] encrypted) throws Exception; -} +/** + * 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.inspector.encryption; + +/** + * A class which describes how data should be encrypted and decrypted + */ +public interface DataEncryptionManager { + /** + * @param data + * - the data to be encrypted + * @return the encrypted data + * @throws Exception + */ + public byte[] encryptData(String data) throws Exception; + + /** + * @param encrypted + * - the data to be decrypted + * @return the decrypted data + * @throws Exception + */ + public String decryptData(byte[] encrypted) throws Exception; +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/NodeViewersChangeListener.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/NodeViewersChangeListener.java index ce8d187d3fa..b0b1e9801c7 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/NodeViewersChangeListener.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/NodeViewersChangeListener.java @@ -1,37 +1,37 @@ -/** - * 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.inspector.gui; - -import java.util.List; - -import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer; - -/** - * A Listener for changes to the configuration of which node viewers are shown - */ -public interface NodeViewersChangeListener { - /** - * Called when the node viewers configuration is changed (i.e node viewers - * are added, removed or the order of the node viewers is changed) - * - * @param newViewers - * - a {@link List} of {@link ZooInspectorNodeViewer}s which are - * to be shown - */ - public void nodeViewersChanged(List newViewers); -} +/** + * 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.inspector.gui; + +import java.util.List; + +import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer; + +/** + * A Listener for changes to the configuration of which node viewers are shown + */ +public interface NodeViewersChangeListener { + /** + * Called when the node viewers configuration is changed (i.e node viewers + * are added, removed or the order of the node viewers is changed) + * + * @param newViewers + * - a {@link List} of {@link ZooInspectorNodeViewer}s which are + * to be shown + */ + public void nodeViewersChanged(List newViewers); +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorAboutDialog.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorAboutDialog.java index 1acb16aa8f7..fbae8f10fe2 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorAboutDialog.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorAboutDialog.java @@ -1,80 +1,80 @@ -/** - * 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.inspector.gui; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Frame; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.IOException; - -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JEditorPane; -import javax.swing.JPanel; - -import org.apache.zookeeper.inspector.logger.LoggerFactory; - -/** - * The About Dialog for the application - */ -public class ZooInspectorAboutDialog extends JDialog { - /** - * @param frame - * - the Frame from which the dialog is displayed - */ - public ZooInspectorAboutDialog(Frame frame) { - super(frame); - this.setLayout(new BorderLayout()); - this.setIconImage(ZooInspectorIconResources.getInformationIcon() - .getImage()); - this.setTitle("About ZooInspector"); - this.setModal(true); - this.setAlwaysOnTop(true); - this.setResizable(false); - JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - JEditorPane aboutPane = new JEditorPane(); - aboutPane.setEditable(false); - aboutPane.setOpaque(false); - java.net.URL aboutURL = ZooInspectorAboutDialog.class - .getResource("about.html"); - try { - aboutPane.setPage(aboutURL); - } catch (IOException e) { - LoggerFactory.getLogger().error( - "Error loading about.html, file may be corrupt", e); - } - panel.add(aboutPane, BorderLayout.CENTER); - panel.setPreferredSize(new Dimension(600, 200)); - JPanel buttonsPanel = new JPanel(); - buttonsPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10)); - JButton okButton = new JButton("OK"); - okButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - ZooInspectorAboutDialog.this.dispose(); - } - }); - buttonsPanel.add(okButton); - this.add(panel, BorderLayout.CENTER); - this.add(buttonsPanel, BorderLayout.SOUTH); - this.pack(); - } -} +/** + * 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.inspector.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JEditorPane; +import javax.swing.JPanel; + +import org.apache.zookeeper.inspector.logger.LoggerFactory; + +/** + * The About Dialog for the application + */ +public class ZooInspectorAboutDialog extends JDialog { + /** + * @param frame + * - the Frame from which the dialog is displayed + */ + public ZooInspectorAboutDialog(Frame frame) { + super(frame); + this.setLayout(new BorderLayout()); + this.setIconImage(ZooInspectorIconResources.getInformationIcon() + .getImage()); + this.setTitle("About ZooInspector"); + this.setModal(true); + this.setAlwaysOnTop(true); + this.setResizable(false); + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + JEditorPane aboutPane = new JEditorPane(); + aboutPane.setEditable(false); + aboutPane.setOpaque(false); + java.net.URL aboutURL = ZooInspectorAboutDialog.class + .getResource("about.html"); + try { + aboutPane.setPage(aboutURL); + } catch (IOException e) { + LoggerFactory.getLogger().error( + "Error loading about.html, file may be corrupt", e); + } + panel.add(aboutPane, BorderLayout.CENTER); + panel.setPreferredSize(new Dimension(600, 200)); + JPanel buttonsPanel = new JPanel(); + buttonsPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10)); + JButton okButton = new JButton("OK"); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ZooInspectorAboutDialog.this.dispose(); + } + }); + buttonsPanel.add(okButton); + this.add(panel, BorderLayout.CENTER); + this.add(buttonsPanel, BorderLayout.SOUTH); + this.pack(); + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorConnectionPropertiesDialog.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorConnectionPropertiesDialog.java index 6c7e88d8fc2..c7db5243b2c 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorConnectionPropertiesDialog.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorConnectionPropertiesDialog.java @@ -1,321 +1,321 @@ -/** - * 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.inspector.gui; - -import java.awt.BorderLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Map.Entry; - -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFileChooser; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; - -import org.apache.zookeeper.inspector.logger.LoggerFactory; -import org.apache.zookeeper.inspector.manager.Pair; - -/** - * The connection properties dialog. This is used to determine the settings for - * connecting to a zookeeper instance - */ -public class ZooInspectorConnectionPropertiesDialog extends JDialog { - - private final HashMap components; - - /** - * @param lastConnectionProps - * - the last connection properties used. if this is the first - * conneciton since starting the applications this will be the - * default settings - * @param connectionPropertiesTemplateAndLabels - * - the connection properties and labels to show in this dialog - * @param zooInspectorPanel - * - the {@link ZooInspectorPanel} linked to this dialog - */ - public ZooInspectorConnectionPropertiesDialog( - Properties lastConnectionProps, - Pair>, Map> connectionPropertiesTemplateAndLabels, - final ZooInspectorPanel zooInspectorPanel) { - final Map> connectionPropertiesTemplate = connectionPropertiesTemplateAndLabels - .getKey(); - final Map connectionPropertiesLabels = connectionPropertiesTemplateAndLabels - .getValue(); - this.setLayout(new BorderLayout()); - this.setTitle("Connection Settings"); - this.setModal(true); - this.setAlwaysOnTop(true); - this.setResizable(false); - final JPanel options = new JPanel(); - final JFileChooser fileChooser = new JFileChooser(); - options.setLayout(new GridBagLayout()); - int i = 0; - components = new HashMap(); - for (Entry> entry : connectionPropertiesTemplate - .entrySet()) { - int rowPos = 2 * i + 1; - JLabel label = new JLabel(connectionPropertiesLabels.get(entry - .getKey())); - GridBagConstraints c1 = new GridBagConstraints(); - c1.gridx = 0; - c1.gridy = rowPos; - c1.gridwidth = 1; - c1.gridheight = 1; - c1.weightx = 0; - c1.weighty = 0; - c1.anchor = GridBagConstraints.WEST; - c1.fill = GridBagConstraints.HORIZONTAL; - c1.insets = new Insets(5, 5, 5, 5); - c1.ipadx = 0; - c1.ipady = 0; - options.add(label, c1); - if (entry.getValue().size() == 0) { - JTextField text = new JTextField(); - GridBagConstraints c2 = new GridBagConstraints(); - c2.gridx = 2; - c2.gridy = rowPos; - c2.gridwidth = 1; - c2.gridheight = 1; - c2.weightx = 0; - c2.weighty = 0; - c2.anchor = GridBagConstraints.WEST; - c2.fill = GridBagConstraints.HORIZONTAL; - c2.insets = new Insets(5, 5, 5, 5); - c2.ipadx = 0; - c2.ipady = 0; - options.add(text, c2); - components.put(entry.getKey(), text); - } else if (entry.getValue().size() == 1) { - JTextField text = new JTextField(entry.getValue().get(0)); - GridBagConstraints c2 = new GridBagConstraints(); - c2.gridx = 2; - c2.gridy = rowPos; - c2.gridwidth = 1; - c2.gridheight = 1; - c2.weightx = 0; - c2.weighty = 0; - c2.anchor = GridBagConstraints.WEST; - c2.fill = GridBagConstraints.HORIZONTAL; - c2.insets = new Insets(5, 5, 5, 5); - c2.ipadx = 0; - c2.ipady = 0; - options.add(text, c2); - components.put(entry.getKey(), text); - } else { - List list = entry.getValue(); - JComboBox combo = new JComboBox(list.toArray(new String[list - .size()])); - combo.setSelectedItem(list.get(0)); - GridBagConstraints c2 = new GridBagConstraints(); - c2.gridx = 2; - c2.gridy = rowPos; - c2.gridwidth = 1; - c2.gridheight = 1; - c2.weightx = 0; - c2.weighty = 0; - c2.anchor = GridBagConstraints.WEST; - c2.fill = GridBagConstraints.HORIZONTAL; - c2.insets = new Insets(5, 5, 5, 5); - c2.ipadx = 0; - c2.ipady = 0; - options.add(combo, c2); - components.put(entry.getKey(), combo); - } - i++; - } - loadConnectionProps(lastConnectionProps); - JPanel buttonsPanel = new JPanel(); - buttonsPanel.setLayout(new GridBagLayout()); - JButton loadPropsFileButton = new JButton("Load from file"); - loadPropsFileButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - int result = fileChooser - .showOpenDialog(ZooInspectorConnectionPropertiesDialog.this); - if (result == JFileChooser.APPROVE_OPTION) { - File propsFilePath = fileChooser.getSelectedFile(); - Properties props = new Properties(); - try { - FileReader reader = new FileReader(propsFilePath); - try { - props.load(reader); - loadConnectionProps(props); - } finally { - reader.close(); - } - } catch (IOException ex) { - LoggerFactory - .getLogger() - .error( - "An Error occurred loading connection properties from file", - ex); - JOptionPane - .showMessageDialog( - ZooInspectorConnectionPropertiesDialog.this, - "An Error occurred loading connection properties from file", - "Error", JOptionPane.ERROR_MESSAGE); - } - options.revalidate(); - options.repaint(); - } - - } - }); - GridBagConstraints c3 = new GridBagConstraints(); - c3.gridx = 0; - c3.gridy = 0; - c3.gridwidth = 1; - c3.gridheight = 1; - c3.weightx = 0; - c3.weighty = 1; - c3.anchor = GridBagConstraints.SOUTHWEST; - c3.fill = GridBagConstraints.NONE; - c3.insets = new Insets(5, 5, 5, 5); - c3.ipadx = 0; - c3.ipady = 0; - buttonsPanel.add(loadPropsFileButton, c3); - JButton saveDefaultPropsFileButton = new JButton("Set As Default"); - saveDefaultPropsFileButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - - Properties connectionProps = getConnectionProps(); - try { - zooInspectorPanel - .setdefaultConnectionProps(connectionProps); - } catch (IOException ex) { - LoggerFactory - .getLogger() - .error( - "An Error occurred saving the default connection properties file", - ex); - JOptionPane - .showMessageDialog( - ZooInspectorConnectionPropertiesDialog.this, - "An Error occurred saving the default connection properties file", - "Error", JOptionPane.ERROR_MESSAGE); - } - } - }); - GridBagConstraints c6 = new GridBagConstraints(); - c6.gridx = 1; - c6.gridy = 0; - c6.gridwidth = 1; - c6.gridheight = 1; - c6.weightx = 1; - c6.weighty = 1; - c6.anchor = GridBagConstraints.SOUTHWEST; - c6.fill = GridBagConstraints.NONE; - c6.insets = new Insets(5, 5, 5, 5); - c6.ipadx = 0; - c6.ipady = 0; - buttonsPanel.add(saveDefaultPropsFileButton, c6); - JButton okButton = new JButton("OK"); - okButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - ZooInspectorConnectionPropertiesDialog.this.dispose(); - Properties connectionProps = getConnectionProps(); - zooInspectorPanel.connect(connectionProps); - } - }); - GridBagConstraints c4 = new GridBagConstraints(); - c4.gridx = 2; - c4.gridy = 0; - c4.gridwidth = 1; - c4.gridheight = 1; - c4.weightx = 0; - c4.weighty = 1; - c4.anchor = GridBagConstraints.SOUTH; - c4.fill = GridBagConstraints.HORIZONTAL; - c4.insets = new Insets(5, 5, 5, 5); - c4.ipadx = 0; - c4.ipady = 0; - buttonsPanel.add(okButton, c4); - JButton cancelButton = new JButton("Cancel"); - cancelButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - ZooInspectorConnectionPropertiesDialog.this.dispose(); - } - }); - GridBagConstraints c5 = new GridBagConstraints(); - c5.gridx = 3; - c5.gridy = 0; - c5.gridwidth = 1; - c5.gridheight = 1; - c5.weightx = 0; - c5.weighty = 1; - c5.anchor = GridBagConstraints.SOUTH; - c5.fill = GridBagConstraints.HORIZONTAL; - c5.insets = new Insets(5, 5, 5, 5); - c5.ipadx = 0; - c5.ipady = 0; - buttonsPanel.add(cancelButton, c5); - this.add(options, BorderLayout.CENTER); - this.add(buttonsPanel, BorderLayout.SOUTH); - this.pack(); - } - - private void loadConnectionProps(Properties props) { - if (props != null) { - for (Object key : props.keySet()) { - String propsKey = (String) key; - if (components.containsKey(propsKey)) { - JComponent component = components.get(propsKey); - String value = props.getProperty(propsKey); - if (component instanceof JTextField) { - ((JTextField) component).setText(value); - } else if (component instanceof JComboBox) { - ((JComboBox) component).setSelectedItem(value); - } - } - } - } - } - - private Properties getConnectionProps() { - Properties connectionProps = new Properties(); - for (Entry entry : components.entrySet()) { - String value = null; - JComponent component = entry.getValue(); - if (component instanceof JTextField) { - value = ((JTextField) component).getText(); - } else if (component instanceof JComboBox) { - value = ((JComboBox) component).getSelectedItem().toString(); - } - connectionProps.put(entry.getKey(), value); - } - return connectionProps; - } -} +/** + * 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.inspector.gui; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Map.Entry; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.apache.zookeeper.inspector.logger.LoggerFactory; +import org.apache.zookeeper.inspector.manager.Pair; + +/** + * The connection properties dialog. This is used to determine the settings for + * connecting to a zookeeper instance + */ +public class ZooInspectorConnectionPropertiesDialog extends JDialog { + + private final HashMap components; + + /** + * @param lastConnectionProps + * - the last connection properties used. if this is the first + * conneciton since starting the applications this will be the + * default settings + * @param connectionPropertiesTemplateAndLabels + * - the connection properties and labels to show in this dialog + * @param zooInspectorPanel + * - the {@link ZooInspectorPanel} linked to this dialog + */ + public ZooInspectorConnectionPropertiesDialog( + Properties lastConnectionProps, + Pair>, Map> connectionPropertiesTemplateAndLabels, + final ZooInspectorPanel zooInspectorPanel) { + final Map> connectionPropertiesTemplate = connectionPropertiesTemplateAndLabels + .getKey(); + final Map connectionPropertiesLabels = connectionPropertiesTemplateAndLabels + .getValue(); + this.setLayout(new BorderLayout()); + this.setTitle("Connection Settings"); + this.setModal(true); + this.setAlwaysOnTop(true); + this.setResizable(false); + final JPanel options = new JPanel(); + final JFileChooser fileChooser = new JFileChooser(); + options.setLayout(new GridBagLayout()); + int i = 0; + components = new HashMap(); + for (Entry> entry : connectionPropertiesTemplate + .entrySet()) { + int rowPos = 2 * i + 1; + JLabel label = new JLabel(connectionPropertiesLabels.get(entry + .getKey())); + GridBagConstraints c1 = new GridBagConstraints(); + c1.gridx = 0; + c1.gridy = rowPos; + c1.gridwidth = 1; + c1.gridheight = 1; + c1.weightx = 0; + c1.weighty = 0; + c1.anchor = GridBagConstraints.WEST; + c1.fill = GridBagConstraints.HORIZONTAL; + c1.insets = new Insets(5, 5, 5, 5); + c1.ipadx = 0; + c1.ipady = 0; + options.add(label, c1); + if (entry.getValue().size() == 0) { + JTextField text = new JTextField(); + GridBagConstraints c2 = new GridBagConstraints(); + c2.gridx = 2; + c2.gridy = rowPos; + c2.gridwidth = 1; + c2.gridheight = 1; + c2.weightx = 0; + c2.weighty = 0; + c2.anchor = GridBagConstraints.WEST; + c2.fill = GridBagConstraints.HORIZONTAL; + c2.insets = new Insets(5, 5, 5, 5); + c2.ipadx = 0; + c2.ipady = 0; + options.add(text, c2); + components.put(entry.getKey(), text); + } else if (entry.getValue().size() == 1) { + JTextField text = new JTextField(entry.getValue().get(0)); + GridBagConstraints c2 = new GridBagConstraints(); + c2.gridx = 2; + c2.gridy = rowPos; + c2.gridwidth = 1; + c2.gridheight = 1; + c2.weightx = 0; + c2.weighty = 0; + c2.anchor = GridBagConstraints.WEST; + c2.fill = GridBagConstraints.HORIZONTAL; + c2.insets = new Insets(5, 5, 5, 5); + c2.ipadx = 0; + c2.ipady = 0; + options.add(text, c2); + components.put(entry.getKey(), text); + } else { + List list = entry.getValue(); + JComboBox combo = new JComboBox(list.toArray(new String[list + .size()])); + combo.setSelectedItem(list.get(0)); + GridBagConstraints c2 = new GridBagConstraints(); + c2.gridx = 2; + c2.gridy = rowPos; + c2.gridwidth = 1; + c2.gridheight = 1; + c2.weightx = 0; + c2.weighty = 0; + c2.anchor = GridBagConstraints.WEST; + c2.fill = GridBagConstraints.HORIZONTAL; + c2.insets = new Insets(5, 5, 5, 5); + c2.ipadx = 0; + c2.ipady = 0; + options.add(combo, c2); + components.put(entry.getKey(), combo); + } + i++; + } + loadConnectionProps(lastConnectionProps); + JPanel buttonsPanel = new JPanel(); + buttonsPanel.setLayout(new GridBagLayout()); + JButton loadPropsFileButton = new JButton("Load from file"); + loadPropsFileButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + int result = fileChooser + .showOpenDialog(ZooInspectorConnectionPropertiesDialog.this); + if (result == JFileChooser.APPROVE_OPTION) { + File propsFilePath = fileChooser.getSelectedFile(); + Properties props = new Properties(); + try { + FileReader reader = new FileReader(propsFilePath); + try { + props.load(reader); + loadConnectionProps(props); + } finally { + reader.close(); + } + } catch (IOException ex) { + LoggerFactory + .getLogger() + .error( + "An Error occurred loading connection properties from file", + ex); + JOptionPane + .showMessageDialog( + ZooInspectorConnectionPropertiesDialog.this, + "An Error occurred loading connection properties from file", + "Error", JOptionPane.ERROR_MESSAGE); + } + options.revalidate(); + options.repaint(); + } + + } + }); + GridBagConstraints c3 = new GridBagConstraints(); + c3.gridx = 0; + c3.gridy = 0; + c3.gridwidth = 1; + c3.gridheight = 1; + c3.weightx = 0; + c3.weighty = 1; + c3.anchor = GridBagConstraints.SOUTHWEST; + c3.fill = GridBagConstraints.NONE; + c3.insets = new Insets(5, 5, 5, 5); + c3.ipadx = 0; + c3.ipady = 0; + buttonsPanel.add(loadPropsFileButton, c3); + JButton saveDefaultPropsFileButton = new JButton("Set As Default"); + saveDefaultPropsFileButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + + Properties connectionProps = getConnectionProps(); + try { + zooInspectorPanel + .setdefaultConnectionProps(connectionProps); + } catch (IOException ex) { + LoggerFactory + .getLogger() + .error( + "An Error occurred saving the default connection properties file", + ex); + JOptionPane + .showMessageDialog( + ZooInspectorConnectionPropertiesDialog.this, + "An Error occurred saving the default connection properties file", + "Error", JOptionPane.ERROR_MESSAGE); + } + } + }); + GridBagConstraints c6 = new GridBagConstraints(); + c6.gridx = 1; + c6.gridy = 0; + c6.gridwidth = 1; + c6.gridheight = 1; + c6.weightx = 1; + c6.weighty = 1; + c6.anchor = GridBagConstraints.SOUTHWEST; + c6.fill = GridBagConstraints.NONE; + c6.insets = new Insets(5, 5, 5, 5); + c6.ipadx = 0; + c6.ipady = 0; + buttonsPanel.add(saveDefaultPropsFileButton, c6); + JButton okButton = new JButton("OK"); + okButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + ZooInspectorConnectionPropertiesDialog.this.dispose(); + Properties connectionProps = getConnectionProps(); + zooInspectorPanel.connect(connectionProps); + } + }); + GridBagConstraints c4 = new GridBagConstraints(); + c4.gridx = 2; + c4.gridy = 0; + c4.gridwidth = 1; + c4.gridheight = 1; + c4.weightx = 0; + c4.weighty = 1; + c4.anchor = GridBagConstraints.SOUTH; + c4.fill = GridBagConstraints.HORIZONTAL; + c4.insets = new Insets(5, 5, 5, 5); + c4.ipadx = 0; + c4.ipady = 0; + buttonsPanel.add(okButton, c4); + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + ZooInspectorConnectionPropertiesDialog.this.dispose(); + } + }); + GridBagConstraints c5 = new GridBagConstraints(); + c5.gridx = 3; + c5.gridy = 0; + c5.gridwidth = 1; + c5.gridheight = 1; + c5.weightx = 0; + c5.weighty = 1; + c5.anchor = GridBagConstraints.SOUTH; + c5.fill = GridBagConstraints.HORIZONTAL; + c5.insets = new Insets(5, 5, 5, 5); + c5.ipadx = 0; + c5.ipady = 0; + buttonsPanel.add(cancelButton, c5); + this.add(options, BorderLayout.CENTER); + this.add(buttonsPanel, BorderLayout.SOUTH); + this.pack(); + } + + private void loadConnectionProps(Properties props) { + if (props != null) { + for (Object key : props.keySet()) { + String propsKey = (String) key; + if (components.containsKey(propsKey)) { + JComponent component = components.get(propsKey); + String value = props.getProperty(propsKey); + if (component instanceof JTextField) { + ((JTextField) component).setText(value); + } else if (component instanceof JComboBox) { + ((JComboBox) component).setSelectedItem(value); + } + } + } + } + } + + private Properties getConnectionProps() { + Properties connectionProps = new Properties(); + for (Entry entry : components.entrySet()) { + String value = null; + JComponent component = entry.getValue(); + if (component instanceof JTextField) { + value = ((JTextField) component).getText(); + } else if (component instanceof JComboBox) { + value = ((JComboBox) component).getSelectedItem().toString(); + } + connectionProps.put(entry.getKey(), value); + } + return connectionProps; + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorIconResources.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorIconResources.java index cc925a98fdb..fa45ab31ede 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorIconResources.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorIconResources.java @@ -1,118 +1,118 @@ -/** - * 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.inspector.gui; - -import javax.swing.ImageIcon; - -/** - * A class containing static methods for retrieving {@link ImageIcon}s used in - * the application - */ -public class ZooInspectorIconResources { - - /** - * @return file icon - */ - public static ImageIcon getTreeLeafIcon() { - return new ImageIcon("icons/file_obj.gif"); //$NON-NLS-1$ - } - - /** - * @return folder open icon - */ - public static ImageIcon getTreeOpenIcon() { - return new ImageIcon("icons/fldr_obj.gif"); //$NON-NLS-1$ - } - - /** - * @return folder closed icon - */ - public static ImageIcon getTreeClosedIcon() { - return new ImageIcon("icons/fldr_obj.gif"); //$NON-NLS-1$ - } - - /** - * @return connect icon - */ - public static ImageIcon getConnectIcon() { - return new ImageIcon("icons/launch_run.gif"); //$NON-NLS-1$ - } - - /** - * @return disconnect icon - */ - public static ImageIcon getDisconnectIcon() { - return new ImageIcon("icons/launch_stop.gif"); //$NON-NLS-1$ - } - - /** - * @return save icon - */ - public static ImageIcon getSaveIcon() { - return new ImageIcon("icons/save_edit.gif"); //$NON-NLS-1$ - } - - /** - * @return add icon - */ - public static ImageIcon getAddNodeIcon() { - return new ImageIcon("icons/new_con.gif"); //$NON-NLS-1$ - } - - /** - * @return delete icon - */ - public static ImageIcon getDeleteNodeIcon() { - return new ImageIcon("icons/trash.gif"); //$NON-NLS-1$ - } - - /** - * @return refresh icon - */ - public static ImageIcon getRefreshIcon() { - return new ImageIcon("icons/refresh.gif"); //$NON-NLS-1$ - } - - /** - * @return information icon - */ - public static ImageIcon getInformationIcon() { - return new ImageIcon("icons/info_obj.gif"); //$NON-NLS-1$ - } - - /** - * @return node viewers icon - */ - public static ImageIcon getChangeNodeViewersIcon() { - return new ImageIcon("icons/edtsrclkup_co.gif"); //$NON-NLS-1$ - } - - /** - * @return up icon - */ - public static ImageIcon getUpIcon() { - return new ImageIcon("icons/search_prev.gif"); //$NON-NLS-1$ - } - - /** - * @return down icon - */ - public static ImageIcon getDownIcon() { - return new ImageIcon("icons/search_next.gif"); //$NON-NLS-1$ - } -} +/** + * 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.inspector.gui; + +import javax.swing.ImageIcon; + +/** + * A class containing static methods for retrieving {@link ImageIcon}s used in + * the application + */ +public class ZooInspectorIconResources { + + /** + * @return file icon + */ + public static ImageIcon getTreeLeafIcon() { + return new ImageIcon("icons/file_obj.gif"); //$NON-NLS-1$ + } + + /** + * @return folder open icon + */ + public static ImageIcon getTreeOpenIcon() { + return new ImageIcon("icons/fldr_obj.gif"); //$NON-NLS-1$ + } + + /** + * @return folder closed icon + */ + public static ImageIcon getTreeClosedIcon() { + return new ImageIcon("icons/fldr_obj.gif"); //$NON-NLS-1$ + } + + /** + * @return connect icon + */ + public static ImageIcon getConnectIcon() { + return new ImageIcon("icons/launch_run.gif"); //$NON-NLS-1$ + } + + /** + * @return disconnect icon + */ + public static ImageIcon getDisconnectIcon() { + return new ImageIcon("icons/launch_stop.gif"); //$NON-NLS-1$ + } + + /** + * @return save icon + */ + public static ImageIcon getSaveIcon() { + return new ImageIcon("icons/save_edit.gif"); //$NON-NLS-1$ + } + + /** + * @return add icon + */ + public static ImageIcon getAddNodeIcon() { + return new ImageIcon("icons/new_con.gif"); //$NON-NLS-1$ + } + + /** + * @return delete icon + */ + public static ImageIcon getDeleteNodeIcon() { + return new ImageIcon("icons/trash.gif"); //$NON-NLS-1$ + } + + /** + * @return refresh icon + */ + public static ImageIcon getRefreshIcon() { + return new ImageIcon("icons/refresh.gif"); //$NON-NLS-1$ + } + + /** + * @return information icon + */ + public static ImageIcon getInformationIcon() { + return new ImageIcon("icons/info_obj.gif"); //$NON-NLS-1$ + } + + /** + * @return node viewers icon + */ + public static ImageIcon getChangeNodeViewersIcon() { + return new ImageIcon("icons/edtsrclkup_co.gif"); //$NON-NLS-1$ + } + + /** + * @return up icon + */ + public static ImageIcon getUpIcon() { + return new ImageIcon("icons/search_prev.gif"); //$NON-NLS-1$ + } + + /** + * @return down icon + */ + public static ImageIcon getDownIcon() { + return new ImageIcon("icons/search_next.gif"); //$NON-NLS-1$ + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersDialog.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersDialog.java index 66125fd9c20..fd0b00ac0b1 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersDialog.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersDialog.java @@ -1,605 +1,605 @@ -/** - * 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.inspector.gui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.FlowLayout; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.datatransfer.Transferable; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.swing.DefaultListCellRenderer; -import javax.swing.DefaultListModel; -import javax.swing.DropMode; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFileChooser; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.ListSelectionModel; -import javax.swing.TransferHandler; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer; -import org.apache.zookeeper.inspector.logger.LoggerFactory; -import org.apache.zookeeper.inspector.manager.ZooInspectorManager; - -/** - * A {@link JDialog} for configuring which {@link ZooInspectorNodeViewer}s to - * show in the application - */ -public class ZooInspectorNodeViewersDialog extends JDialog implements - ListSelectionListener { - - private final JButton upButton; - private final JButton downButton; - private final JButton removeButton; - private final JButton addButton; - private final JList viewersList; - private final JButton saveFileButton; - private final JButton loadFileButton; - private final JButton setDefaultsButton; - private final JFileChooser fileChooser = new JFileChooser(new File(".")); - - /** - * @param frame - * - the Frame from which the dialog is displayed - * @param currentViewers - * - the {@link ZooInspectorNodeViewer}s to show - * @param listeners - * - the {@link NodeViewersChangeListener}s which need to be - * notified of changes to the node viewers configuration - * @param manager - * - the {@link ZooInspectorManager} for the application - * - */ - public ZooInspectorNodeViewersDialog(Frame frame, - final List currentViewers, - final Collection listeners, - final ZooInspectorManager manager) { - super(frame); - final List newViewers = new ArrayList( - currentViewers); - this.setLayout(new BorderLayout()); - this.setIconImage(ZooInspectorIconResources.getChangeNodeViewersIcon() - .getImage()); - this.setTitle("About ZooInspector"); - this.setModal(true); - this.setAlwaysOnTop(true); - this.setResizable(true); - final JPanel panel = new JPanel(); - panel.setLayout(new GridBagLayout()); - viewersList = new JList(); - DefaultListModel model = new DefaultListModel(); - for (ZooInspectorNodeViewer viewer : newViewers) { - model.addElement(viewer); - } - viewersList.setModel(model); - viewersList.setCellRenderer(new DefaultListCellRenderer() { - @Override - public Component getListCellRendererComponent(JList list, - Object value, int index, boolean isSelected, - boolean cellHasFocus) { - ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) value; - JLabel label = (JLabel) super.getListCellRendererComponent( - list, value, index, isSelected, cellHasFocus); - label.setText(viewer.getTitle()); - return label; - } - }); - viewersList.setDropMode(DropMode.INSERT); - viewersList.enableInputMethods(true); - viewersList.setDragEnabled(true); - viewersList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - viewersList.getSelectionModel().addListSelectionListener(this); - viewersList.setTransferHandler(new TransferHandler() { - - @Override - public boolean canImport(TransferHandler.TransferSupport info) { - // we only import NodeViewers - if (!info - .isDataFlavorSupported(ZooInspectorNodeViewer.nodeViewerDataFlavor)) { - return false; - } - - JList.DropLocation dl = (JList.DropLocation) info - .getDropLocation(); - if (dl.getIndex() == -1) { - return false; - } - return true; - } - - @Override - public boolean importData(TransferHandler.TransferSupport info) { - JList.DropLocation dl = (JList.DropLocation) info - .getDropLocation(); - DefaultListModel listModel = (DefaultListModel) viewersList - .getModel(); - int index = dl.getIndex(); - boolean insert = dl.isInsert(); - // Get the string that is being dropped. - Transferable t = info.getTransferable(); - String data; - try { - data = (String) t - .getTransferData(ZooInspectorNodeViewer.nodeViewerDataFlavor); - } catch (Exception e) { - return false; - } - try { - ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) Class - .forName(data).newInstance(); - if (listModel.contains(viewer)) { - listModel.removeElement(viewer); - } - if (insert) { - listModel.add(index, viewer); - } else { - listModel.set(index, viewer); - } - return true; - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error instantiating class: " + data, e); - return false; - } - - } - - @Override - public int getSourceActions(JComponent c) { - return MOVE; - } - - @Override - protected Transferable createTransferable(JComponent c) { - JList list = (JList) c; - ZooInspectorNodeViewer value = (ZooInspectorNodeViewer) list - .getSelectedValue(); - return value; - } - }); - JScrollPane scroller = new JScrollPane(viewersList); - GridBagConstraints c1 = new GridBagConstraints(); - c1.gridx = 0; - c1.gridy = 0; - c1.gridwidth = 3; - c1.gridheight = 3; - c1.weightx = 0; - c1.weighty = 1; - c1.anchor = GridBagConstraints.CENTER; - c1.fill = GridBagConstraints.BOTH; - c1.insets = new Insets(5, 5, 5, 5); - c1.ipadx = 0; - c1.ipady = 0; - panel.add(scroller, c1); - upButton = new JButton(ZooInspectorIconResources.getUpIcon()); - downButton = new JButton(ZooInspectorIconResources.getDownIcon()); - removeButton = new JButton(ZooInspectorIconResources - .getDeleteNodeIcon()); - addButton = new JButton(ZooInspectorIconResources.getAddNodeIcon()); - upButton.setEnabled(false); - downButton.setEnabled(false); - removeButton.setEnabled(false); - addButton.setEnabled(true); - upButton.setToolTipText("Move currently selected node viewer up"); - downButton.setToolTipText("Move currently selected node viewer down"); - removeButton.setToolTipText("Remove currently selected node viewer"); - addButton.setToolTipText("Add node viewer"); - final JTextField newViewerTextField = new JTextField(); - GridBagConstraints c2 = new GridBagConstraints(); - c2.gridx = 3; - c2.gridy = 0; - c2.gridwidth = 1; - c2.gridheight = 1; - c2.weightx = 0; - c2.weighty = 0; - c2.anchor = GridBagConstraints.NORTH; - c2.fill = GridBagConstraints.HORIZONTAL; - c2.insets = new Insets(5, 5, 5, 5); - c2.ipadx = 0; - c2.ipady = 0; - panel.add(upButton, c2); - GridBagConstraints c3 = new GridBagConstraints(); - c3.gridx = 3; - c3.gridy = 2; - c3.gridwidth = 1; - c3.gridheight = 1; - c3.weightx = 0; - c3.weighty = 0; - c3.anchor = GridBagConstraints.NORTH; - c3.fill = GridBagConstraints.HORIZONTAL; - c3.insets = new Insets(5, 5, 5, 5); - c3.ipadx = 0; - c3.ipady = 0; - panel.add(downButton, c3); - GridBagConstraints c4 = new GridBagConstraints(); - c4.gridx = 3; - c4.gridy = 1; - c4.gridwidth = 1; - c4.gridheight = 1; - c4.weightx = 0; - c4.weighty = 0; - c4.anchor = GridBagConstraints.NORTH; - c4.fill = GridBagConstraints.HORIZONTAL; - c4.insets = new Insets(5, 5, 5, 5); - c4.ipadx = 0; - c4.ipady = 0; - panel.add(removeButton, c4); - GridBagConstraints c5 = new GridBagConstraints(); - c5.gridx = 0; - c5.gridy = 3; - c5.gridwidth = 3; - c5.gridheight = 1; - c5.weightx = 0; - c5.weighty = 0; - c5.anchor = GridBagConstraints.CENTER; - c5.fill = GridBagConstraints.BOTH; - c5.insets = new Insets(5, 5, 5, 5); - c5.ipadx = 0; - c5.ipady = 0; - panel.add(newViewerTextField, c5); - GridBagConstraints c6 = new GridBagConstraints(); - c6.gridx = 3; - c6.gridy = 3; - c6.gridwidth = 1; - c6.gridheight = 1; - c6.weightx = 0; - c6.weighty = 0; - c6.anchor = GridBagConstraints.CENTER; - c6.fill = GridBagConstraints.BOTH; - c6.insets = new Insets(5, 5, 5, 5); - c6.ipadx = 0; - c6.ipady = 0; - panel.add(addButton, c6); - upButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - DefaultListModel listModel = (DefaultListModel) viewersList - .getModel(); - ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) viewersList - .getSelectedValue(); - int index = viewersList.getSelectedIndex(); - if (listModel.contains(viewer)) { - listModel.removeElementAt(index); - listModel.insertElementAt(viewer, index - 1); - viewersList.setSelectedValue(viewer, true); - } - } - }); - downButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - DefaultListModel listModel = (DefaultListModel) viewersList - .getModel(); - ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) viewersList - .getSelectedValue(); - int index = viewersList.getSelectedIndex(); - if (listModel.contains(viewer)) { - listModel.removeElementAt(index); - listModel.insertElementAt(viewer, index + 1); - viewersList.setSelectedValue(viewer, true); - } - } - }); - removeButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - DefaultListModel listModel = (DefaultListModel) viewersList - .getModel(); - ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) viewersList - .getSelectedValue(); - int index = viewersList.getSelectedIndex(); - if (listModel.contains(viewer)) { - listModel.removeElement(viewer); - viewersList - .setSelectedIndex(index == listModel.size() ? index - 1 - : index); - } - } - }); - addButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - String className = newViewerTextField.getText(); - if (className == null || className.length() == 0) { - JOptionPane - .showMessageDialog( - ZooInspectorNodeViewersDialog.this, - "Please enter the full class name for a Node Viewer and click the add button", - "Input Error", JOptionPane.ERROR_MESSAGE); - } else { - try { - DefaultListModel listModel = (DefaultListModel) viewersList - .getModel(); - ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) Class - .forName(className).newInstance(); - if (listModel.contains(viewer)) { - JOptionPane - .showMessageDialog( - ZooInspectorNodeViewersDialog.this, - "Node viewer already exists. Each node viewer can only be added once.", - "Input Error", - JOptionPane.ERROR_MESSAGE); - } else { - listModel.addElement(viewer); - } - } catch (Exception ex) { - LoggerFactory - .getLogger() - .error( - "An error occurred while instaniating the node viewer. ", - ex); - JOptionPane.showMessageDialog( - ZooInspectorNodeViewersDialog.this, - "An error occurred while instaniating the node viewer: " - + ex.getMessage(), "Error", - JOptionPane.ERROR_MESSAGE); - } - } - } - }); - saveFileButton = new JButton("Save"); - loadFileButton = new JButton("Load"); - setDefaultsButton = new JButton("Set As Defaults"); - saveFileButton - .setToolTipText("Save current node viewer configuration to file"); - loadFileButton - .setToolTipText("Load node viewer configuration frm file"); - setDefaultsButton - .setToolTipText("Set current configuration asd defaults"); - GridBagConstraints c7 = new GridBagConstraints(); - c7.gridx = 0; - c7.gridy = 4; - c7.gridwidth = 1; - c7.gridheight = 1; - c7.weightx = 1; - c7.weighty = 0; - c7.anchor = GridBagConstraints.WEST; - c7.fill = GridBagConstraints.VERTICAL; - c7.insets = new Insets(5, 5, 5, 5); - c7.ipadx = 0; - c7.ipady = 0; - panel.add(saveFileButton, c7); - GridBagConstraints c8 = new GridBagConstraints(); - c8.gridx = 1; - c8.gridy = 4; - c8.gridwidth = 1; - c8.gridheight = 1; - c8.weightx = 0; - c8.weighty = 0; - c8.anchor = GridBagConstraints.WEST; - c8.fill = GridBagConstraints.VERTICAL; - c8.insets = new Insets(5, 5, 5, 5); - c8.ipadx = 0; - c8.ipady = 0; - panel.add(loadFileButton, c8); - GridBagConstraints c9 = new GridBagConstraints(); - c9.gridx = 2; - c9.gridy = 4; - c9.gridwidth = 1; - c9.gridheight = 1; - c9.weightx = 0; - c9.weighty = 0; - c9.anchor = GridBagConstraints.WEST; - c9.fill = GridBagConstraints.VERTICAL; - c9.insets = new Insets(5, 5, 5, 5); - c9.ipadx = 0; - c9.ipady = 0; - panel.add(setDefaultsButton, c9); - saveFileButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - int result = fileChooser - .showSaveDialog(ZooInspectorNodeViewersDialog.this); - if (result == JFileChooser.APPROVE_OPTION) { - File selectedFile = fileChooser.getSelectedFile(); - int answer = JOptionPane.YES_OPTION; - if (selectedFile.exists()) { - answer = JOptionPane - .showConfirmDialog( - ZooInspectorNodeViewersDialog.this, - "The specified file already exists. do you want to overwrite it?", - "Confirm Overwrite", - JOptionPane.YES_NO_OPTION, - JOptionPane.WARNING_MESSAGE); - } - if (answer == JOptionPane.YES_OPTION) { - DefaultListModel listModel = (DefaultListModel) viewersList - .getModel(); - List nodeViewersClassNames = new ArrayList(); - Object[] modelContents = listModel.toArray(); - for (Object o : modelContents) { - nodeViewersClassNames - .add(((ZooInspectorNodeViewer) o) - .getClass().getCanonicalName()); - } - try { - manager.saveNodeViewersFile(selectedFile, - nodeViewersClassNames); - } catch (IOException ex) { - LoggerFactory - .getLogger() - .error( - "Error saving node veiwer configuration from file.", - ex); - JOptionPane.showMessageDialog( - ZooInspectorNodeViewersDialog.this, - "Error saving node veiwer configuration from file: " - + ex.getMessage(), "Error", - JOptionPane.ERROR_MESSAGE); - } - } - } - } - }); - loadFileButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - int result = fileChooser - .showOpenDialog(ZooInspectorNodeViewersDialog.this); - if (result == JFileChooser.APPROVE_OPTION) { - try { - List nodeViewersClassNames = manager - .loadNodeViewersFile(fileChooser - .getSelectedFile()); - List nodeViewers = new ArrayList(); - for (String nodeViewersClassName : nodeViewersClassNames) { - ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) Class - .forName(nodeViewersClassName) - .newInstance(); - nodeViewers.add(viewer); - } - DefaultListModel model = new DefaultListModel(); - for (ZooInspectorNodeViewer viewer : nodeViewers) { - model.addElement(viewer); - } - viewersList.setModel(model); - panel.revalidate(); - panel.repaint(); - } catch (Exception ex) { - LoggerFactory - .getLogger() - .error( - "Error loading node veiwer configuration from file.", - ex); - JOptionPane.showMessageDialog( - ZooInspectorNodeViewersDialog.this, - "Error loading node veiwer configuration from file: " - + ex.getMessage(), "Error", - JOptionPane.ERROR_MESSAGE); - } - } - } - }); - setDefaultsButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - int answer = JOptionPane - .showConfirmDialog( - ZooInspectorNodeViewersDialog.this, - "Are you sure you want to save this configuration as the default?", - "Confirm Set Defaults", - JOptionPane.YES_NO_OPTION, - JOptionPane.WARNING_MESSAGE); - if (answer == JOptionPane.YES_OPTION) { - DefaultListModel listModel = (DefaultListModel) viewersList - .getModel(); - List nodeViewersClassNames = new ArrayList(); - Object[] modelContents = listModel.toArray(); - for (Object o : modelContents) { - nodeViewersClassNames.add(((ZooInspectorNodeViewer) o) - .getClass().getCanonicalName()); - } - try { - manager - .setDefaultNodeViewerConfiguration(nodeViewersClassNames); - } catch (IOException ex) { - LoggerFactory - .getLogger() - .error( - "Error setting default node veiwer configuration.", - ex); - JOptionPane.showMessageDialog( - ZooInspectorNodeViewersDialog.this, - "Error setting default node veiwer configuration: " - + ex.getMessage(), "Error", - JOptionPane.ERROR_MESSAGE); - } - } - } - }); - - JPanel buttonsPanel = new JPanel(); - buttonsPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10)); - JButton okButton = new JButton("OK"); - okButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - ZooInspectorNodeViewersDialog.this.dispose(); - DefaultListModel listModel = (DefaultListModel) viewersList - .getModel(); - newViewers.clear(); - Object[] modelContents = listModel.toArray(); - for (Object o : modelContents) { - newViewers.add((ZooInspectorNodeViewer) o); - } - currentViewers.clear(); - currentViewers.addAll(newViewers); - for (NodeViewersChangeListener listener : listeners) { - listener.nodeViewersChanged(currentViewers); - } - } - }); - buttonsPanel.add(okButton); - JButton cancelButton = new JButton("Cancel"); - cancelButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - ZooInspectorNodeViewersDialog.this.dispose(); - } - }); - buttonsPanel.add(cancelButton); - this.add(panel, BorderLayout.CENTER); - this.add(buttonsPanel, BorderLayout.SOUTH); - this.pack(); - } - - /* - * (non-Javadoc) - * - * @see - * javax.swing.event.ListSelectionListener#valueChanged(javax.swing.event - * .ListSelectionEvent) - */ - public void valueChanged(ListSelectionEvent e) { - int index = viewersList.getSelectedIndex(); - if (index == -1) { - removeButton.setEnabled(false); - upButton.setEnabled(false); - downButton.setEnabled(false); - } else { - removeButton.setEnabled(true); - if (index == 0) { - upButton.setEnabled(false); - } else { - upButton.setEnabled(true); - } - if (index == ((DefaultListModel) viewersList.getModel()).getSize()) { - downButton.setEnabled(false); - } else { - downButton.setEnabled(true); - } - } - } -} +/** + * 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.inspector.gui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.datatransfer.Transferable; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListModel; +import javax.swing.DropMode; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.TransferHandler; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer; +import org.apache.zookeeper.inspector.logger.LoggerFactory; +import org.apache.zookeeper.inspector.manager.ZooInspectorManager; + +/** + * A {@link JDialog} for configuring which {@link ZooInspectorNodeViewer}s to + * show in the application + */ +public class ZooInspectorNodeViewersDialog extends JDialog implements + ListSelectionListener { + + private final JButton upButton; + private final JButton downButton; + private final JButton removeButton; + private final JButton addButton; + private final JList viewersList; + private final JButton saveFileButton; + private final JButton loadFileButton; + private final JButton setDefaultsButton; + private final JFileChooser fileChooser = new JFileChooser(new File(".")); + + /** + * @param frame + * - the Frame from which the dialog is displayed + * @param currentViewers + * - the {@link ZooInspectorNodeViewer}s to show + * @param listeners + * - the {@link NodeViewersChangeListener}s which need to be + * notified of changes to the node viewers configuration + * @param manager + * - the {@link ZooInspectorManager} for the application + * + */ + public ZooInspectorNodeViewersDialog(Frame frame, + final List currentViewers, + final Collection listeners, + final ZooInspectorManager manager) { + super(frame); + final List newViewers = new ArrayList( + currentViewers); + this.setLayout(new BorderLayout()); + this.setIconImage(ZooInspectorIconResources.getChangeNodeViewersIcon() + .getImage()); + this.setTitle("About ZooInspector"); + this.setModal(true); + this.setAlwaysOnTop(true); + this.setResizable(true); + final JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + viewersList = new JList(); + DefaultListModel model = new DefaultListModel(); + for (ZooInspectorNodeViewer viewer : newViewers) { + model.addElement(viewer); + } + viewersList.setModel(model); + viewersList.setCellRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, + Object value, int index, boolean isSelected, + boolean cellHasFocus) { + ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) value; + JLabel label = (JLabel) super.getListCellRendererComponent( + list, value, index, isSelected, cellHasFocus); + label.setText(viewer.getTitle()); + return label; + } + }); + viewersList.setDropMode(DropMode.INSERT); + viewersList.enableInputMethods(true); + viewersList.setDragEnabled(true); + viewersList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + viewersList.getSelectionModel().addListSelectionListener(this); + viewersList.setTransferHandler(new TransferHandler() { + + @Override + public boolean canImport(TransferHandler.TransferSupport info) { + // we only import NodeViewers + if (!info + .isDataFlavorSupported(ZooInspectorNodeViewer.nodeViewerDataFlavor)) { + return false; + } + + JList.DropLocation dl = (JList.DropLocation) info + .getDropLocation(); + if (dl.getIndex() == -1) { + return false; + } + return true; + } + + @Override + public boolean importData(TransferHandler.TransferSupport info) { + JList.DropLocation dl = (JList.DropLocation) info + .getDropLocation(); + DefaultListModel listModel = (DefaultListModel) viewersList + .getModel(); + int index = dl.getIndex(); + boolean insert = dl.isInsert(); + // Get the string that is being dropped. + Transferable t = info.getTransferable(); + String data; + try { + data = (String) t + .getTransferData(ZooInspectorNodeViewer.nodeViewerDataFlavor); + } catch (Exception e) { + return false; + } + try { + ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) Class + .forName(data).newInstance(); + if (listModel.contains(viewer)) { + listModel.removeElement(viewer); + } + if (insert) { + listModel.add(index, viewer); + } else { + listModel.set(index, viewer); + } + return true; + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error instantiating class: " + data, e); + return false; + } + + } + + @Override + public int getSourceActions(JComponent c) { + return MOVE; + } + + @Override + protected Transferable createTransferable(JComponent c) { + JList list = (JList) c; + ZooInspectorNodeViewer value = (ZooInspectorNodeViewer) list + .getSelectedValue(); + return value; + } + }); + JScrollPane scroller = new JScrollPane(viewersList); + GridBagConstraints c1 = new GridBagConstraints(); + c1.gridx = 0; + c1.gridy = 0; + c1.gridwidth = 3; + c1.gridheight = 3; + c1.weightx = 0; + c1.weighty = 1; + c1.anchor = GridBagConstraints.CENTER; + c1.fill = GridBagConstraints.BOTH; + c1.insets = new Insets(5, 5, 5, 5); + c1.ipadx = 0; + c1.ipady = 0; + panel.add(scroller, c1); + upButton = new JButton(ZooInspectorIconResources.getUpIcon()); + downButton = new JButton(ZooInspectorIconResources.getDownIcon()); + removeButton = new JButton(ZooInspectorIconResources + .getDeleteNodeIcon()); + addButton = new JButton(ZooInspectorIconResources.getAddNodeIcon()); + upButton.setEnabled(false); + downButton.setEnabled(false); + removeButton.setEnabled(false); + addButton.setEnabled(true); + upButton.setToolTipText("Move currently selected node viewer up"); + downButton.setToolTipText("Move currently selected node viewer down"); + removeButton.setToolTipText("Remove currently selected node viewer"); + addButton.setToolTipText("Add node viewer"); + final JTextField newViewerTextField = new JTextField(); + GridBagConstraints c2 = new GridBagConstraints(); + c2.gridx = 3; + c2.gridy = 0; + c2.gridwidth = 1; + c2.gridheight = 1; + c2.weightx = 0; + c2.weighty = 0; + c2.anchor = GridBagConstraints.NORTH; + c2.fill = GridBagConstraints.HORIZONTAL; + c2.insets = new Insets(5, 5, 5, 5); + c2.ipadx = 0; + c2.ipady = 0; + panel.add(upButton, c2); + GridBagConstraints c3 = new GridBagConstraints(); + c3.gridx = 3; + c3.gridy = 2; + c3.gridwidth = 1; + c3.gridheight = 1; + c3.weightx = 0; + c3.weighty = 0; + c3.anchor = GridBagConstraints.NORTH; + c3.fill = GridBagConstraints.HORIZONTAL; + c3.insets = new Insets(5, 5, 5, 5); + c3.ipadx = 0; + c3.ipady = 0; + panel.add(downButton, c3); + GridBagConstraints c4 = new GridBagConstraints(); + c4.gridx = 3; + c4.gridy = 1; + c4.gridwidth = 1; + c4.gridheight = 1; + c4.weightx = 0; + c4.weighty = 0; + c4.anchor = GridBagConstraints.NORTH; + c4.fill = GridBagConstraints.HORIZONTAL; + c4.insets = new Insets(5, 5, 5, 5); + c4.ipadx = 0; + c4.ipady = 0; + panel.add(removeButton, c4); + GridBagConstraints c5 = new GridBagConstraints(); + c5.gridx = 0; + c5.gridy = 3; + c5.gridwidth = 3; + c5.gridheight = 1; + c5.weightx = 0; + c5.weighty = 0; + c5.anchor = GridBagConstraints.CENTER; + c5.fill = GridBagConstraints.BOTH; + c5.insets = new Insets(5, 5, 5, 5); + c5.ipadx = 0; + c5.ipady = 0; + panel.add(newViewerTextField, c5); + GridBagConstraints c6 = new GridBagConstraints(); + c6.gridx = 3; + c6.gridy = 3; + c6.gridwidth = 1; + c6.gridheight = 1; + c6.weightx = 0; + c6.weighty = 0; + c6.anchor = GridBagConstraints.CENTER; + c6.fill = GridBagConstraints.BOTH; + c6.insets = new Insets(5, 5, 5, 5); + c6.ipadx = 0; + c6.ipady = 0; + panel.add(addButton, c6); + upButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + DefaultListModel listModel = (DefaultListModel) viewersList + .getModel(); + ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) viewersList + .getSelectedValue(); + int index = viewersList.getSelectedIndex(); + if (listModel.contains(viewer)) { + listModel.removeElementAt(index); + listModel.insertElementAt(viewer, index - 1); + viewersList.setSelectedValue(viewer, true); + } + } + }); + downButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + DefaultListModel listModel = (DefaultListModel) viewersList + .getModel(); + ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) viewersList + .getSelectedValue(); + int index = viewersList.getSelectedIndex(); + if (listModel.contains(viewer)) { + listModel.removeElementAt(index); + listModel.insertElementAt(viewer, index + 1); + viewersList.setSelectedValue(viewer, true); + } + } + }); + removeButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + DefaultListModel listModel = (DefaultListModel) viewersList + .getModel(); + ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) viewersList + .getSelectedValue(); + int index = viewersList.getSelectedIndex(); + if (listModel.contains(viewer)) { + listModel.removeElement(viewer); + viewersList + .setSelectedIndex(index == listModel.size() ? index - 1 + : index); + } + } + }); + addButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + String className = newViewerTextField.getText(); + if (className == null || className.length() == 0) { + JOptionPane + .showMessageDialog( + ZooInspectorNodeViewersDialog.this, + "Please enter the full class name for a Node Viewer and click the add button", + "Input Error", JOptionPane.ERROR_MESSAGE); + } else { + try { + DefaultListModel listModel = (DefaultListModel) viewersList + .getModel(); + ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) Class + .forName(className).newInstance(); + if (listModel.contains(viewer)) { + JOptionPane + .showMessageDialog( + ZooInspectorNodeViewersDialog.this, + "Node viewer already exists. Each node viewer can only be added once.", + "Input Error", + JOptionPane.ERROR_MESSAGE); + } else { + listModel.addElement(viewer); + } + } catch (Exception ex) { + LoggerFactory + .getLogger() + .error( + "An error occurred while instaniating the node viewer. ", + ex); + JOptionPane.showMessageDialog( + ZooInspectorNodeViewersDialog.this, + "An error occurred while instaniating the node viewer: " + + ex.getMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + }); + saveFileButton = new JButton("Save"); + loadFileButton = new JButton("Load"); + setDefaultsButton = new JButton("Set As Defaults"); + saveFileButton + .setToolTipText("Save current node viewer configuration to file"); + loadFileButton + .setToolTipText("Load node viewer configuration frm file"); + setDefaultsButton + .setToolTipText("Set current configuration asd defaults"); + GridBagConstraints c7 = new GridBagConstraints(); + c7.gridx = 0; + c7.gridy = 4; + c7.gridwidth = 1; + c7.gridheight = 1; + c7.weightx = 1; + c7.weighty = 0; + c7.anchor = GridBagConstraints.WEST; + c7.fill = GridBagConstraints.VERTICAL; + c7.insets = new Insets(5, 5, 5, 5); + c7.ipadx = 0; + c7.ipady = 0; + panel.add(saveFileButton, c7); + GridBagConstraints c8 = new GridBagConstraints(); + c8.gridx = 1; + c8.gridy = 4; + c8.gridwidth = 1; + c8.gridheight = 1; + c8.weightx = 0; + c8.weighty = 0; + c8.anchor = GridBagConstraints.WEST; + c8.fill = GridBagConstraints.VERTICAL; + c8.insets = new Insets(5, 5, 5, 5); + c8.ipadx = 0; + c8.ipady = 0; + panel.add(loadFileButton, c8); + GridBagConstraints c9 = new GridBagConstraints(); + c9.gridx = 2; + c9.gridy = 4; + c9.gridwidth = 1; + c9.gridheight = 1; + c9.weightx = 0; + c9.weighty = 0; + c9.anchor = GridBagConstraints.WEST; + c9.fill = GridBagConstraints.VERTICAL; + c9.insets = new Insets(5, 5, 5, 5); + c9.ipadx = 0; + c9.ipady = 0; + panel.add(setDefaultsButton, c9); + saveFileButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + int result = fileChooser + .showSaveDialog(ZooInspectorNodeViewersDialog.this); + if (result == JFileChooser.APPROVE_OPTION) { + File selectedFile = fileChooser.getSelectedFile(); + int answer = JOptionPane.YES_OPTION; + if (selectedFile.exists()) { + answer = JOptionPane + .showConfirmDialog( + ZooInspectorNodeViewersDialog.this, + "The specified file already exists. do you want to overwrite it?", + "Confirm Overwrite", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE); + } + if (answer == JOptionPane.YES_OPTION) { + DefaultListModel listModel = (DefaultListModel) viewersList + .getModel(); + List nodeViewersClassNames = new ArrayList(); + Object[] modelContents = listModel.toArray(); + for (Object o : modelContents) { + nodeViewersClassNames + .add(((ZooInspectorNodeViewer) o) + .getClass().getCanonicalName()); + } + try { + manager.saveNodeViewersFile(selectedFile, + nodeViewersClassNames); + } catch (IOException ex) { + LoggerFactory + .getLogger() + .error( + "Error saving node veiwer configuration from file.", + ex); + JOptionPane.showMessageDialog( + ZooInspectorNodeViewersDialog.this, + "Error saving node veiwer configuration from file: " + + ex.getMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + } + }); + loadFileButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + int result = fileChooser + .showOpenDialog(ZooInspectorNodeViewersDialog.this); + if (result == JFileChooser.APPROVE_OPTION) { + try { + List nodeViewersClassNames = manager + .loadNodeViewersFile(fileChooser + .getSelectedFile()); + List nodeViewers = new ArrayList(); + for (String nodeViewersClassName : nodeViewersClassNames) { + ZooInspectorNodeViewer viewer = (ZooInspectorNodeViewer) Class + .forName(nodeViewersClassName) + .newInstance(); + nodeViewers.add(viewer); + } + DefaultListModel model = new DefaultListModel(); + for (ZooInspectorNodeViewer viewer : nodeViewers) { + model.addElement(viewer); + } + viewersList.setModel(model); + panel.revalidate(); + panel.repaint(); + } catch (Exception ex) { + LoggerFactory + .getLogger() + .error( + "Error loading node veiwer configuration from file.", + ex); + JOptionPane.showMessageDialog( + ZooInspectorNodeViewersDialog.this, + "Error loading node veiwer configuration from file: " + + ex.getMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + }); + setDefaultsButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + int answer = JOptionPane + .showConfirmDialog( + ZooInspectorNodeViewersDialog.this, + "Are you sure you want to save this configuration as the default?", + "Confirm Set Defaults", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE); + if (answer == JOptionPane.YES_OPTION) { + DefaultListModel listModel = (DefaultListModel) viewersList + .getModel(); + List nodeViewersClassNames = new ArrayList(); + Object[] modelContents = listModel.toArray(); + for (Object o : modelContents) { + nodeViewersClassNames.add(((ZooInspectorNodeViewer) o) + .getClass().getCanonicalName()); + } + try { + manager + .setDefaultNodeViewerConfiguration(nodeViewersClassNames); + } catch (IOException ex) { + LoggerFactory + .getLogger() + .error( + "Error setting default node veiwer configuration.", + ex); + JOptionPane.showMessageDialog( + ZooInspectorNodeViewersDialog.this, + "Error setting default node veiwer configuration: " + + ex.getMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + }); + + JPanel buttonsPanel = new JPanel(); + buttonsPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10)); + JButton okButton = new JButton("OK"); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ZooInspectorNodeViewersDialog.this.dispose(); + DefaultListModel listModel = (DefaultListModel) viewersList + .getModel(); + newViewers.clear(); + Object[] modelContents = listModel.toArray(); + for (Object o : modelContents) { + newViewers.add((ZooInspectorNodeViewer) o); + } + currentViewers.clear(); + currentViewers.addAll(newViewers); + for (NodeViewersChangeListener listener : listeners) { + listener.nodeViewersChanged(currentViewers); + } + } + }); + buttonsPanel.add(okButton); + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ZooInspectorNodeViewersDialog.this.dispose(); + } + }); + buttonsPanel.add(cancelButton); + this.add(panel, BorderLayout.CENTER); + this.add(buttonsPanel, BorderLayout.SOUTH); + this.pack(); + } + + /* + * (non-Javadoc) + * + * @see + * javax.swing.event.ListSelectionListener#valueChanged(javax.swing.event + * .ListSelectionEvent) + */ + public void valueChanged(ListSelectionEvent e) { + int index = viewersList.getSelectedIndex(); + if (index == -1) { + removeButton.setEnabled(false); + upButton.setEnabled(false); + downButton.setEnabled(false); + } else { + removeButton.setEnabled(true); + if (index == 0) { + upButton.setEnabled(false); + } else { + upButton.setEnabled(true); + } + if (index == ((DefaultListModel) viewersList.getModel()).getSize()) { + downButton.setEnabled(false); + } else { + downButton.setEnabled(true); + } + } + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersPanel.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersPanel.java index 2dd763ac971..05c256b5a85 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersPanel.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorNodeViewersPanel.java @@ -1,140 +1,140 @@ -/** - * 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.inspector.gui; - -import java.awt.BorderLayout; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JPanel; -import javax.swing.JTabbedPane; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.TreePath; - -import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer; -import org.apache.zookeeper.inspector.manager.ZooInspectorManager; -import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; - -/** - * This is the {@link JPanel} which contains the {@link ZooInspectorNodeViewer}s - */ -public class ZooInspectorNodeViewersPanel extends JPanel implements - TreeSelectionListener, ChangeListener { - - private final List nodeVeiwers = new ArrayList(); - private final List needsReload = new ArrayList(); - private final JTabbedPane tabbedPane; - private final List selectedNodes = new ArrayList(); - private final ZooInspectorNodeManager zooInspectorManager; - - /** - * @param zooInspectorManager - * - the {@link ZooInspectorManager} for the application - * @param nodeVeiwers - * - the {@link ZooInspectorNodeViewer}s to show - */ - public ZooInspectorNodeViewersPanel( - ZooInspectorNodeManager zooInspectorManager, - List nodeVeiwers) { - this.zooInspectorManager = zooInspectorManager; - this.setLayout(new BorderLayout()); - tabbedPane = new JTabbedPane(JTabbedPane.TOP, - JTabbedPane.WRAP_TAB_LAYOUT); - setNodeViewers(nodeVeiwers); - tabbedPane.addChangeListener(this); - this.add(tabbedPane, BorderLayout.CENTER); - reloadSelectedViewer(); - } - - /** - * @param nodeViewers - * - the {@link ZooInspectorNodeViewer}s to show - */ - public void setNodeViewers(List nodeViewers) { - this.nodeVeiwers.clear(); - this.nodeVeiwers.addAll(nodeViewers); - needsReload.clear(); - tabbedPane.removeAll(); - for (ZooInspectorNodeViewer nodeViewer : nodeVeiwers) { - nodeViewer.setZooInspectorManager(zooInspectorManager); - needsReload.add(true); - tabbedPane.add(nodeViewer.getTitle(), nodeViewer); - } - this.revalidate(); - this.repaint(); - } - - private void reloadSelectedViewer() { - int index = this.tabbedPane.getSelectedIndex(); - if (index != -1 && this.needsReload.get(index)) { - ZooInspectorNodeViewer viewer = this.nodeVeiwers.get(index); - viewer.nodeSelectionChanged(selectedNodes); - this.needsReload.set(index, false); - } - } - - /* - * (non-Javadoc) - * - * @see - * javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event - * .TreeSelectionEvent) - */ - public void valueChanged(TreeSelectionEvent e) { - TreePath[] paths = e.getPaths(); - selectedNodes.clear(); - for (TreePath path : paths) { - boolean appended = false; - StringBuilder sb = new StringBuilder(); - Object[] pathArray = path.getPath(); - for (Object o : pathArray) { - if (o != null) { - String nodeName = o.toString(); - if (nodeName != null) { - if (nodeName.length() > 0) { - appended = true; - sb.append("/"); //$NON-NLS-1$ - sb.append(o.toString()); - } - } - } - } - if (appended) { - selectedNodes.add(sb.toString()); - } - } - for (int i = 0; i < needsReload.size(); i++) { - this.needsReload.set(i, true); - } - reloadSelectedViewer(); - } - - /* - * (non-Javadoc) - * - * @see - * javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent - * ) - */ - public void stateChanged(ChangeEvent e) { - reloadSelectedViewer(); - } -} +/** + * 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.inspector.gui; + +import java.awt.BorderLayout; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreePath; + +import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer; +import org.apache.zookeeper.inspector.manager.ZooInspectorManager; +import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; + +/** + * This is the {@link JPanel} which contains the {@link ZooInspectorNodeViewer}s + */ +public class ZooInspectorNodeViewersPanel extends JPanel implements + TreeSelectionListener, ChangeListener { + + private final List nodeVeiwers = new ArrayList(); + private final List needsReload = new ArrayList(); + private final JTabbedPane tabbedPane; + private final List selectedNodes = new ArrayList(); + private final ZooInspectorNodeManager zooInspectorManager; + + /** + * @param zooInspectorManager + * - the {@link ZooInspectorManager} for the application + * @param nodeVeiwers + * - the {@link ZooInspectorNodeViewer}s to show + */ + public ZooInspectorNodeViewersPanel( + ZooInspectorNodeManager zooInspectorManager, + List nodeVeiwers) { + this.zooInspectorManager = zooInspectorManager; + this.setLayout(new BorderLayout()); + tabbedPane = new JTabbedPane(JTabbedPane.TOP, + JTabbedPane.WRAP_TAB_LAYOUT); + setNodeViewers(nodeVeiwers); + tabbedPane.addChangeListener(this); + this.add(tabbedPane, BorderLayout.CENTER); + reloadSelectedViewer(); + } + + /** + * @param nodeViewers + * - the {@link ZooInspectorNodeViewer}s to show + */ + public void setNodeViewers(List nodeViewers) { + this.nodeVeiwers.clear(); + this.nodeVeiwers.addAll(nodeViewers); + needsReload.clear(); + tabbedPane.removeAll(); + for (ZooInspectorNodeViewer nodeViewer : nodeVeiwers) { + nodeViewer.setZooInspectorManager(zooInspectorManager); + needsReload.add(true); + tabbedPane.add(nodeViewer.getTitle(), nodeViewer); + } + this.revalidate(); + this.repaint(); + } + + private void reloadSelectedViewer() { + int index = this.tabbedPane.getSelectedIndex(); + if (index != -1 && this.needsReload.get(index)) { + ZooInspectorNodeViewer viewer = this.nodeVeiwers.get(index); + viewer.nodeSelectionChanged(selectedNodes); + this.needsReload.set(index, false); + } + } + + /* + * (non-Javadoc) + * + * @see + * javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event + * .TreeSelectionEvent) + */ + public void valueChanged(TreeSelectionEvent e) { + TreePath[] paths = e.getPaths(); + selectedNodes.clear(); + for (TreePath path : paths) { + boolean appended = false; + StringBuilder sb = new StringBuilder(); + Object[] pathArray = path.getPath(); + for (Object o : pathArray) { + if (o != null) { + String nodeName = o.toString(); + if (nodeName != null) { + if (nodeName.length() > 0) { + appended = true; + sb.append("/"); //$NON-NLS-1$ + sb.append(o.toString()); + } + } + } + } + if (appended) { + selectedNodes.add(sb.toString()); + } + } + for (int i = 0; i < needsReload.size(); i++) { + this.needsReload.set(i, true); + } + reloadSelectedViewer(); + } + + /* + * (non-Javadoc) + * + * @see + * javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent + * ) + */ + public void stateChanged(ChangeEvent e) { + reloadSelectedViewer(); + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorPanel.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorPanel.java index c2d0fac6247..7a4efafa465 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorPanel.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorPanel.java @@ -1,361 +1,361 @@ -/** - * 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.inspector.gui; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.ExecutionException; - -import javax.swing.JButton; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JToolBar; -import javax.swing.SwingWorker; - -import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer; -import org.apache.zookeeper.inspector.logger.LoggerFactory; -import org.apache.zookeeper.inspector.manager.ZooInspectorManager; - -/** - * The parent {@link JPanel} for the whole application - */ -public class ZooInspectorPanel extends JPanel implements - NodeViewersChangeListener { - private final JButton refreshButton; - private final JButton disconnectButton; - private final JButton connectButton; - private final ZooInspectorNodeViewersPanel nodeViewersPanel; - private final ZooInspectorTreeViewer treeViewer; - private final ZooInspectorManager zooInspectorManager; - private final JButton addNodeButton; - private final JButton deleteNodeButton; - private final JButton nodeViewersButton; - private final JButton aboutButton; - private final List listeners = new ArrayList(); - { - listeners.add(this); - } - - /** - * @param zooInspectorManager - * - the {@link ZooInspectorManager} for the application - */ - public ZooInspectorPanel(final ZooInspectorManager zooInspectorManager) { - this.zooInspectorManager = zooInspectorManager; - final ArrayList nodeViewers = new ArrayList(); - try { - List defaultNodeViewersClassNames = this.zooInspectorManager - .getDefaultNodeViewerConfiguration(); - for (String className : defaultNodeViewersClassNames) { - nodeViewers.add((ZooInspectorNodeViewer) Class.forName( - className).newInstance()); - } - } catch (Exception ex) { - LoggerFactory.getLogger().error( - "Error loading default node viewers.", ex); - JOptionPane.showMessageDialog(ZooInspectorPanel.this, - "Error loading default node viewers: " + ex.getMessage(), - "Error", JOptionPane.ERROR_MESSAGE); - } - nodeViewersPanel = new ZooInspectorNodeViewersPanel( - zooInspectorManager, nodeViewers); - treeViewer = new ZooInspectorTreeViewer(zooInspectorManager, - nodeViewersPanel); - this.setLayout(new BorderLayout()); - JToolBar toolbar = new JToolBar(); - toolbar.setFloatable(false); - connectButton = new JButton(ZooInspectorIconResources.getConnectIcon()); - disconnectButton = new JButton(ZooInspectorIconResources - .getDisconnectIcon()); - refreshButton = new JButton(ZooInspectorIconResources.getRefreshIcon()); - addNodeButton = new JButton(ZooInspectorIconResources.getAddNodeIcon()); - deleteNodeButton = new JButton(ZooInspectorIconResources - .getDeleteNodeIcon()); - nodeViewersButton = new JButton(ZooInspectorIconResources - .getChangeNodeViewersIcon()); - aboutButton = new JButton(ZooInspectorIconResources - .getInformationIcon()); - toolbar.add(connectButton); - toolbar.add(disconnectButton); - toolbar.add(refreshButton); - toolbar.add(addNodeButton); - toolbar.add(deleteNodeButton); - toolbar.add(nodeViewersButton); - toolbar.add(aboutButton); - aboutButton.setEnabled(true); - connectButton.setEnabled(true); - disconnectButton.setEnabled(false); - refreshButton.setEnabled(false); - addNodeButton.setEnabled(false); - deleteNodeButton.setEnabled(false); - nodeViewersButton.setEnabled(true); - nodeViewersButton.setToolTipText("Change Node Viewers"); - aboutButton.setToolTipText("About ZooInspector"); - connectButton.setToolTipText("Connect"); - disconnectButton.setToolTipText("Disconnect"); - refreshButton.setToolTipText("Refresh"); - addNodeButton.setToolTipText("Add Node"); - deleteNodeButton.setToolTipText("Delete Node"); - connectButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - ZooInspectorConnectionPropertiesDialog zicpd = new ZooInspectorConnectionPropertiesDialog( - zooInspectorManager.getLastConnectionProps(), - zooInspectorManager.getConnectionPropertiesTemplate(), - ZooInspectorPanel.this); - zicpd.setVisible(true); - } - }); - disconnectButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - disconnect(); - } - }); - refreshButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - treeViewer.refreshView(); - } - }); - addNodeButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final List selectedNodes = treeViewer - .getSelectedNodes(); - if (selectedNodes.size() == 1) { - final String nodeName = JOptionPane.showInputDialog( - ZooInspectorPanel.this, - "Please Enter a name for the new node", - "Create Node", JOptionPane.INFORMATION_MESSAGE); - if (nodeName != null && nodeName.length() > 0) { - SwingWorker worker = new SwingWorker() { - - @Override - protected Boolean doInBackground() throws Exception { - return ZooInspectorPanel.this.zooInspectorManager - .createNode(selectedNodes.get(0), - nodeName); - } - - @Override - protected void done() { - treeViewer.refreshView(); - } - }; - worker.execute(); - } - } else { - JOptionPane.showMessageDialog(ZooInspectorPanel.this, - "Please select 1 parent node for the new node."); - } - } - }); - deleteNodeButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final List selectedNodes = treeViewer - .getSelectedNodes(); - if (selectedNodes.size() == 0) { - JOptionPane.showMessageDialog(ZooInspectorPanel.this, - "Please select at least 1 node to be deleted"); - } else { - int answer = JOptionPane.showConfirmDialog( - ZooInspectorPanel.this, - "Are you sure you want to delete the selected nodes?" - + "(This action cannot be reverted)", - "Confirm Delete", JOptionPane.YES_NO_OPTION, - JOptionPane.WARNING_MESSAGE); - if (answer == JOptionPane.YES_OPTION) { - SwingWorker worker = new SwingWorker() { - - @Override - protected Boolean doInBackground() throws Exception { - for (String nodePath : selectedNodes) { - ZooInspectorPanel.this.zooInspectorManager - .deleteNode(nodePath); - } - return true; - } - - @Override - protected void done() { - treeViewer.refreshView(); - } - }; - worker.execute(); - } - } - } - }); - nodeViewersButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - ZooInspectorNodeViewersDialog nvd = new ZooInspectorNodeViewersDialog( - JOptionPane.getRootFrame(), nodeViewers, listeners, - zooInspectorManager); - nvd.setVisible(true); - } - }); - aboutButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - ZooInspectorAboutDialog zicpd = new ZooInspectorAboutDialog( - JOptionPane.getRootFrame()); - zicpd.setVisible(true); - } - }); - JScrollPane treeScroller = new JScrollPane(treeViewer); - JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, - treeScroller, nodeViewersPanel); - splitPane.setResizeWeight(0.25); - this.add(splitPane, BorderLayout.CENTER); - this.add(toolbar, BorderLayout.NORTH); - } - - /** - * @param connectionProps - * the {@link Properties} for connecting to the zookeeper - * instance - */ - public void connect(final Properties connectionProps) { - SwingWorker worker = new SwingWorker() { - - @Override - protected Boolean doInBackground() throws Exception { - zooInspectorManager.setLastConnectionProps(connectionProps); - return zooInspectorManager.connect(connectionProps); - } - - @Override - protected void done() { - try { - if (get()) { - treeViewer.refreshView(); - connectButton.setEnabled(false); - disconnectButton.setEnabled(true); - refreshButton.setEnabled(true); - addNodeButton.setEnabled(true); - deleteNodeButton.setEnabled(true); - } else { - JOptionPane.showMessageDialog(ZooInspectorPanel.this, - "Unable to connect to zookeeper", "Error", - JOptionPane.ERROR_MESSAGE); - } - } catch (InterruptedException e) { - LoggerFactory - .getLogger() - .error( - "Error occurred while connecting to ZooKeeper server", - e); - } catch (ExecutionException e) { - LoggerFactory - .getLogger() - .error( - "Error occurred while connecting to ZooKeeper server", - e); - } - } - - }; - worker.execute(); - } - - /** - * - */ - public void disconnect() { - disconnect(false); - } - - /** - * @param wait - * - set this to true if the method should only return once the - * application has successfully disconnected - */ - public void disconnect(boolean wait) { - SwingWorker worker = new SwingWorker() { - - @Override - protected Boolean doInBackground() throws Exception { - return ZooInspectorPanel.this.zooInspectorManager.disconnect(); - } - - @Override - protected void done() { - try { - if (get()) { - treeViewer.clearView(); - connectButton.setEnabled(true); - disconnectButton.setEnabled(false); - refreshButton.setEnabled(false); - addNodeButton.setEnabled(false); - deleteNodeButton.setEnabled(false); - } - } catch (InterruptedException e) { - LoggerFactory - .getLogger() - .error( - "Error occurred while disconnecting from ZooKeeper server", - e); - } catch (ExecutionException e) { - LoggerFactory - .getLogger() - .error( - "Error occurred while disconnecting from ZooKeeper server", - e); - } - } - - }; - worker.execute(); - if (wait) { - while (!worker.isDone()) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - LoggerFactory - .getLogger() - .error( - "Error occurred while disconnecting from ZooKeeper server", - e); - } - } - } - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.gui.NodeViewersChangeListener# - * nodeViewersChanged(java.util.List) - */ - public void nodeViewersChanged(List newViewers) { - this.nodeViewersPanel.setNodeViewers(newViewers); - } - - /** - * @param connectionProps - * @throws IOException - */ - public void setdefaultConnectionProps(Properties connectionProps) - throws IOException { - this.zooInspectorManager.saveDefaultConnectionFile(connectionProps); - } -} +/** + * 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.inspector.gui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.ExecutionException; + +import javax.swing.JButton; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JToolBar; +import javax.swing.SwingWorker; + +import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer; +import org.apache.zookeeper.inspector.logger.LoggerFactory; +import org.apache.zookeeper.inspector.manager.ZooInspectorManager; + +/** + * The parent {@link JPanel} for the whole application + */ +public class ZooInspectorPanel extends JPanel implements + NodeViewersChangeListener { + private final JButton refreshButton; + private final JButton disconnectButton; + private final JButton connectButton; + private final ZooInspectorNodeViewersPanel nodeViewersPanel; + private final ZooInspectorTreeViewer treeViewer; + private final ZooInspectorManager zooInspectorManager; + private final JButton addNodeButton; + private final JButton deleteNodeButton; + private final JButton nodeViewersButton; + private final JButton aboutButton; + private final List listeners = new ArrayList(); + { + listeners.add(this); + } + + /** + * @param zooInspectorManager + * - the {@link ZooInspectorManager} for the application + */ + public ZooInspectorPanel(final ZooInspectorManager zooInspectorManager) { + this.zooInspectorManager = zooInspectorManager; + final ArrayList nodeViewers = new ArrayList(); + try { + List defaultNodeViewersClassNames = this.zooInspectorManager + .getDefaultNodeViewerConfiguration(); + for (String className : defaultNodeViewersClassNames) { + nodeViewers.add((ZooInspectorNodeViewer) Class.forName( + className).newInstance()); + } + } catch (Exception ex) { + LoggerFactory.getLogger().error( + "Error loading default node viewers.", ex); + JOptionPane.showMessageDialog(ZooInspectorPanel.this, + "Error loading default node viewers: " + ex.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + nodeViewersPanel = new ZooInspectorNodeViewersPanel( + zooInspectorManager, nodeViewers); + treeViewer = new ZooInspectorTreeViewer(zooInspectorManager, + nodeViewersPanel); + this.setLayout(new BorderLayout()); + JToolBar toolbar = new JToolBar(); + toolbar.setFloatable(false); + connectButton = new JButton(ZooInspectorIconResources.getConnectIcon()); + disconnectButton = new JButton(ZooInspectorIconResources + .getDisconnectIcon()); + refreshButton = new JButton(ZooInspectorIconResources.getRefreshIcon()); + addNodeButton = new JButton(ZooInspectorIconResources.getAddNodeIcon()); + deleteNodeButton = new JButton(ZooInspectorIconResources + .getDeleteNodeIcon()); + nodeViewersButton = new JButton(ZooInspectorIconResources + .getChangeNodeViewersIcon()); + aboutButton = new JButton(ZooInspectorIconResources + .getInformationIcon()); + toolbar.add(connectButton); + toolbar.add(disconnectButton); + toolbar.add(refreshButton); + toolbar.add(addNodeButton); + toolbar.add(deleteNodeButton); + toolbar.add(nodeViewersButton); + toolbar.add(aboutButton); + aboutButton.setEnabled(true); + connectButton.setEnabled(true); + disconnectButton.setEnabled(false); + refreshButton.setEnabled(false); + addNodeButton.setEnabled(false); + deleteNodeButton.setEnabled(false); + nodeViewersButton.setEnabled(true); + nodeViewersButton.setToolTipText("Change Node Viewers"); + aboutButton.setToolTipText("About ZooInspector"); + connectButton.setToolTipText("Connect"); + disconnectButton.setToolTipText("Disconnect"); + refreshButton.setToolTipText("Refresh"); + addNodeButton.setToolTipText("Add Node"); + deleteNodeButton.setToolTipText("Delete Node"); + connectButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ZooInspectorConnectionPropertiesDialog zicpd = new ZooInspectorConnectionPropertiesDialog( + zooInspectorManager.getLastConnectionProps(), + zooInspectorManager.getConnectionPropertiesTemplate(), + ZooInspectorPanel.this); + zicpd.setVisible(true); + } + }); + disconnectButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + disconnect(); + } + }); + refreshButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + treeViewer.refreshView(); + } + }); + addNodeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final List selectedNodes = treeViewer + .getSelectedNodes(); + if (selectedNodes.size() == 1) { + final String nodeName = JOptionPane.showInputDialog( + ZooInspectorPanel.this, + "Please Enter a name for the new node", + "Create Node", JOptionPane.INFORMATION_MESSAGE); + if (nodeName != null && nodeName.length() > 0) { + SwingWorker worker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws Exception { + return ZooInspectorPanel.this.zooInspectorManager + .createNode(selectedNodes.get(0), + nodeName); + } + + @Override + protected void done() { + treeViewer.refreshView(); + } + }; + worker.execute(); + } + } else { + JOptionPane.showMessageDialog(ZooInspectorPanel.this, + "Please select 1 parent node for the new node."); + } + } + }); + deleteNodeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final List selectedNodes = treeViewer + .getSelectedNodes(); + if (selectedNodes.size() == 0) { + JOptionPane.showMessageDialog(ZooInspectorPanel.this, + "Please select at least 1 node to be deleted"); + } else { + int answer = JOptionPane.showConfirmDialog( + ZooInspectorPanel.this, + "Are you sure you want to delete the selected nodes?" + + "(This action cannot be reverted)", + "Confirm Delete", JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE); + if (answer == JOptionPane.YES_OPTION) { + SwingWorker worker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws Exception { + for (String nodePath : selectedNodes) { + ZooInspectorPanel.this.zooInspectorManager + .deleteNode(nodePath); + } + return true; + } + + @Override + protected void done() { + treeViewer.refreshView(); + } + }; + worker.execute(); + } + } + } + }); + nodeViewersButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + ZooInspectorNodeViewersDialog nvd = new ZooInspectorNodeViewersDialog( + JOptionPane.getRootFrame(), nodeViewers, listeners, + zooInspectorManager); + nvd.setVisible(true); + } + }); + aboutButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ZooInspectorAboutDialog zicpd = new ZooInspectorAboutDialog( + JOptionPane.getRootFrame()); + zicpd.setVisible(true); + } + }); + JScrollPane treeScroller = new JScrollPane(treeViewer); + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, + treeScroller, nodeViewersPanel); + splitPane.setResizeWeight(0.25); + this.add(splitPane, BorderLayout.CENTER); + this.add(toolbar, BorderLayout.NORTH); + } + + /** + * @param connectionProps + * the {@link Properties} for connecting to the zookeeper + * instance + */ + public void connect(final Properties connectionProps) { + SwingWorker worker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws Exception { + zooInspectorManager.setLastConnectionProps(connectionProps); + return zooInspectorManager.connect(connectionProps); + } + + @Override + protected void done() { + try { + if (get()) { + treeViewer.refreshView(); + connectButton.setEnabled(false); + disconnectButton.setEnabled(true); + refreshButton.setEnabled(true); + addNodeButton.setEnabled(true); + deleteNodeButton.setEnabled(true); + } else { + JOptionPane.showMessageDialog(ZooInspectorPanel.this, + "Unable to connect to zookeeper", "Error", + JOptionPane.ERROR_MESSAGE); + } + } catch (InterruptedException e) { + LoggerFactory + .getLogger() + .error( + "Error occurred while connecting to ZooKeeper server", + e); + } catch (ExecutionException e) { + LoggerFactory + .getLogger() + .error( + "Error occurred while connecting to ZooKeeper server", + e); + } + } + + }; + worker.execute(); + } + + /** + * + */ + public void disconnect() { + disconnect(false); + } + + /** + * @param wait + * - set this to true if the method should only return once the + * application has successfully disconnected + */ + public void disconnect(boolean wait) { + SwingWorker worker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws Exception { + return ZooInspectorPanel.this.zooInspectorManager.disconnect(); + } + + @Override + protected void done() { + try { + if (get()) { + treeViewer.clearView(); + connectButton.setEnabled(true); + disconnectButton.setEnabled(false); + refreshButton.setEnabled(false); + addNodeButton.setEnabled(false); + deleteNodeButton.setEnabled(false); + } + } catch (InterruptedException e) { + LoggerFactory + .getLogger() + .error( + "Error occurred while disconnecting from ZooKeeper server", + e); + } catch (ExecutionException e) { + LoggerFactory + .getLogger() + .error( + "Error occurred while disconnecting from ZooKeeper server", + e); + } + } + + }; + worker.execute(); + if (wait) { + while (!worker.isDone()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + LoggerFactory + .getLogger() + .error( + "Error occurred while disconnecting from ZooKeeper server", + e); + } + } + } + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.gui.NodeViewersChangeListener# + * nodeViewersChanged(java.util.List) + */ + public void nodeViewersChanged(List newViewers) { + this.nodeViewersPanel.setNodeViewers(newViewers); + } + + /** + * @param connectionProps + * @throws IOException + */ + public void setdefaultConnectionProps(Properties connectionProps) + throws IOException { + this.zooInspectorManager.saveDefaultConnectionFile(connectionProps); + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeViewer.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeViewer.java index b49be2d43f7..128b358ebbb 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeViewer.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/ZooInspectorTreeViewer.java @@ -1,362 +1,362 @@ -/** - * 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.inspector.gui; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.swing.JMenuItem; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JTree; -import javax.swing.SwingWorker; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeCellRenderer; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; - -import org.apache.zookeeper.inspector.manager.NodeListener; -import org.apache.zookeeper.inspector.manager.ZooInspectorManager; - -import com.nitido.utils.toaster.Toaster; - -/** - * A {@link JPanel} for showing the tree view of all the nodes in the zookeeper - * instance - */ -public class ZooInspectorTreeViewer extends JPanel implements NodeListener { - private final ZooInspectorManager zooInspectorManager; - private final JTree tree; - private final Toaster toasterManager; - - /** - * @param zooInspectorManager - * - the {@link ZooInspectorManager} for the application - * @param listener - * - the {@link TreeSelectionListener} to listen for changes in - * the selected node on the node tree - */ - public ZooInspectorTreeViewer( - final ZooInspectorManager zooInspectorManager, - TreeSelectionListener listener) { - this.zooInspectorManager = zooInspectorManager; - this.setLayout(new BorderLayout()); - final JPopupMenu popupMenu = new JPopupMenu(); - final JMenuItem addNotify = new JMenuItem("Add Change Notification"); - this.toasterManager = new Toaster(); - this.toasterManager.setBorderColor(Color.BLACK); - this.toasterManager.setMessageColor(Color.BLACK); - this.toasterManager.setToasterColor(Color.WHITE); - addNotify.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - List selectedNodes = getSelectedNodes(); - zooInspectorManager.addWatchers(selectedNodes, - ZooInspectorTreeViewer.this); - } - }); - final JMenuItem removeNotify = new JMenuItem( - "Remove Change Notification"); - removeNotify.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - List selectedNodes = getSelectedNodes(); - zooInspectorManager.removeWatchers(selectedNodes); - } - }); - tree = new JTree(new DefaultMutableTreeNode()); - tree.setCellRenderer(new ZooInspectorTreeCellRenderer()); - tree.setEditable(false); - tree.getSelectionModel().addTreeSelectionListener(listener); - tree.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) { - // TODO only show add if a selected node isn't being - // watched, and only show remove if a selected node is being - // watched - popupMenu.removeAll(); - popupMenu.add(addNotify); - popupMenu.add(removeNotify); - popupMenu.show(ZooInspectorTreeViewer.this, e.getX(), e - .getY()); - } - } - }); - this.add(tree, BorderLayout.CENTER); - } - - /** - * Refresh the tree view - */ - public void refreshView() { - final Set expandedNodes = new LinkedHashSet(); - int rowCount = tree.getRowCount(); - for (int i = 0; i < rowCount; i++) { - TreePath path = tree.getPathForRow(i); - if (tree.isExpanded(path)) { - expandedNodes.add(path); - } - } - final TreePath[] selectedNodes = tree.getSelectionPaths(); - SwingWorker worker = new SwingWorker() { - - @Override - protected Boolean doInBackground() throws Exception { - tree.setModel(new DefaultTreeModel(new ZooInspectorTreeNode( - "/", null))); - return true; - } - - @Override - protected void done() { - for (TreePath path : expandedNodes) { - tree.expandPath(path); - } - tree.getSelectionModel().setSelectionPaths(selectedNodes); - } - }; - worker.execute(); - } - - /** - * clear the tree view of all nodes - */ - public void clearView() { - tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode())); - } - - /** - * @author Colin - * - */ - private static class ZooInspectorTreeCellRenderer extends - DefaultTreeCellRenderer { - public ZooInspectorTreeCellRenderer() { - setLeafIcon(ZooInspectorIconResources.getTreeLeafIcon()); - setOpenIcon(ZooInspectorIconResources.getTreeOpenIcon()); - setClosedIcon(ZooInspectorIconResources.getTreeClosedIcon()); - } - } - - /** - * @author Colin - * - */ - private class ZooInspectorTreeNode implements TreeNode { - private final String nodePath; - private final String nodeName; - private final ZooInspectorTreeNode parent; - - public ZooInspectorTreeNode(String nodePath, ZooInspectorTreeNode parent) { - this.parent = parent; - this.nodePath = nodePath; - int index = nodePath.lastIndexOf("/"); - if (index == -1) { - throw new IllegalArgumentException("Invalid node path" - + nodePath); - } - this.nodeName = nodePath.substring(index + 1); - } - - /* - * (non-Javadoc) - * - * @see javax.swing.tree.TreeNode#children() - */ - public Enumeration children() { - List children = zooInspectorManager - .getChildren(this.nodePath); - Collections.sort(children); - List returnChildren = new ArrayList(); - for (String child : children) { - returnChildren.add(new ZooInspectorTreeNode((this.nodePath - .equals("/") ? "" : this.nodePath) - + "/" + child, this)); - } - return Collections.enumeration(returnChildren); - } - - /* - * (non-Javadoc) - * - * @see javax.swing.tree.TreeNode#getAllowsChildren() - */ - public boolean getAllowsChildren() { - return zooInspectorManager.isAllowsChildren(this.nodePath); - } - - /* - * (non-Javadoc) - * - * @see javax.swing.tree.TreeNode#getChildAt(int) - */ - public TreeNode getChildAt(int childIndex) { - String child = zooInspectorManager.getNodeChild(this.nodePath, - childIndex); - if (child != null) { - return new ZooInspectorTreeNode((this.nodePath.equals("/") ? "" - : this.nodePath) - + "/" + child, this); - } - return null; - } - - /* - * (non-Javadoc) - * - * @see javax.swing.tree.TreeNode#getChildCount() - */ - public int getChildCount() { - return zooInspectorManager.getNumChildren(this.nodePath); - } - - /* - * (non-Javadoc) - * - * @see javax.swing.tree.TreeNode#getIndex(javax.swing.tree.TreeNode) - */ - public int getIndex(TreeNode node) { - return zooInspectorManager.getNodeIndex(this.nodePath); - } - - /* - * (non-Javadoc) - * - * @see javax.swing.tree.TreeNode#getParent() - */ - public TreeNode getParent() { - return this.parent; - } - - /* - * (non-Javadoc) - * - * @see javax.swing.tree.TreeNode#isLeaf() - */ - public boolean isLeaf() { - return !zooInspectorManager.hasChildren(this.nodePath); - } - - @Override - public String toString() { - return this.nodeName; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + getOuterType().hashCode(); - result = prime * result - + ((nodePath == null) ? 0 : nodePath.hashCode()); - result = prime * result - + ((parent == null) ? 0 : parent.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ZooInspectorTreeNode other = (ZooInspectorTreeNode) obj; - if (!getOuterType().equals(other.getOuterType())) - return false; - if (nodePath == null) { - if (other.nodePath != null) - return false; - } else if (!nodePath.equals(other.nodePath)) - return false; - if (parent == null) { - if (other.parent != null) - return false; - } else if (!parent.equals(other.parent)) - return false; - return true; - } - - private ZooInspectorTreeViewer getOuterType() { - return ZooInspectorTreeViewer.this; - } - - } - - /** - * @return {@link List} of the currently selected nodes - */ - public List getSelectedNodes() { - TreePath[] paths = tree.getSelectionPaths(); - List selectedNodes = new ArrayList(); - if (paths != null) { - for (TreePath path : paths) { - StringBuilder sb = new StringBuilder(); - Object[] pathArray = path.getPath(); - for (Object o : pathArray) { - String nodeName = o.toString(); - if (nodeName.length() > 0) { - sb.append("/"); - sb.append(o.toString()); - } - } - selectedNodes.add(sb.toString()); - } - } - return selectedNodes; - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.NodeListener#processEvent(java - * .lang.String, java.lang.String, java.util.Map) - */ - public void processEvent(String nodePath, String eventType, - Map eventInfo) { - StringBuilder sb = new StringBuilder(); - sb.append("Node: "); - sb.append(nodePath); - sb.append("\nEvent: "); - sb.append(eventType); - if (eventInfo != null) { - for (Map.Entry entry : eventInfo.entrySet()) { - sb.append("\n"); - sb.append(entry.getKey()); - sb.append(": "); - sb.append(entry.getValue()); - } - } - this.toasterManager.showToaster(ZooInspectorIconResources - .getInformationIcon(), sb.toString()); - } -} +/** + * 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.inspector.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JTree; +import javax.swing.SwingWorker; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import org.apache.zookeeper.inspector.manager.NodeListener; +import org.apache.zookeeper.inspector.manager.ZooInspectorManager; + +import com.nitido.utils.toaster.Toaster; + +/** + * A {@link JPanel} for showing the tree view of all the nodes in the zookeeper + * instance + */ +public class ZooInspectorTreeViewer extends JPanel implements NodeListener { + private final ZooInspectorManager zooInspectorManager; + private final JTree tree; + private final Toaster toasterManager; + + /** + * @param zooInspectorManager + * - the {@link ZooInspectorManager} for the application + * @param listener + * - the {@link TreeSelectionListener} to listen for changes in + * the selected node on the node tree + */ + public ZooInspectorTreeViewer( + final ZooInspectorManager zooInspectorManager, + TreeSelectionListener listener) { + this.zooInspectorManager = zooInspectorManager; + this.setLayout(new BorderLayout()); + final JPopupMenu popupMenu = new JPopupMenu(); + final JMenuItem addNotify = new JMenuItem("Add Change Notification"); + this.toasterManager = new Toaster(); + this.toasterManager.setBorderColor(Color.BLACK); + this.toasterManager.setMessageColor(Color.BLACK); + this.toasterManager.setToasterColor(Color.WHITE); + addNotify.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + List selectedNodes = getSelectedNodes(); + zooInspectorManager.addWatchers(selectedNodes, + ZooInspectorTreeViewer.this); + } + }); + final JMenuItem removeNotify = new JMenuItem( + "Remove Change Notification"); + removeNotify.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + List selectedNodes = getSelectedNodes(); + zooInspectorManager.removeWatchers(selectedNodes); + } + }); + tree = new JTree(new DefaultMutableTreeNode()); + tree.setCellRenderer(new ZooInspectorTreeCellRenderer()); + tree.setEditable(false); + tree.getSelectionModel().addTreeSelectionListener(listener); + tree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) { + // TODO only show add if a selected node isn't being + // watched, and only show remove if a selected node is being + // watched + popupMenu.removeAll(); + popupMenu.add(addNotify); + popupMenu.add(removeNotify); + popupMenu.show(ZooInspectorTreeViewer.this, e.getX(), e + .getY()); + } + } + }); + this.add(tree, BorderLayout.CENTER); + } + + /** + * Refresh the tree view + */ + public void refreshView() { + final Set expandedNodes = new LinkedHashSet(); + int rowCount = tree.getRowCount(); + for (int i = 0; i < rowCount; i++) { + TreePath path = tree.getPathForRow(i); + if (tree.isExpanded(path)) { + expandedNodes.add(path); + } + } + final TreePath[] selectedNodes = tree.getSelectionPaths(); + SwingWorker worker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws Exception { + tree.setModel(new DefaultTreeModel(new ZooInspectorTreeNode( + "/", null))); + return true; + } + + @Override + protected void done() { + for (TreePath path : expandedNodes) { + tree.expandPath(path); + } + tree.getSelectionModel().setSelectionPaths(selectedNodes); + } + }; + worker.execute(); + } + + /** + * clear the tree view of all nodes + */ + public void clearView() { + tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode())); + } + + /** + * @author Colin + * + */ + private static class ZooInspectorTreeCellRenderer extends + DefaultTreeCellRenderer { + public ZooInspectorTreeCellRenderer() { + setLeafIcon(ZooInspectorIconResources.getTreeLeafIcon()); + setOpenIcon(ZooInspectorIconResources.getTreeOpenIcon()); + setClosedIcon(ZooInspectorIconResources.getTreeClosedIcon()); + } + } + + /** + * @author Colin + * + */ + private class ZooInspectorTreeNode implements TreeNode { + private final String nodePath; + private final String nodeName; + private final ZooInspectorTreeNode parent; + + public ZooInspectorTreeNode(String nodePath, ZooInspectorTreeNode parent) { + this.parent = parent; + this.nodePath = nodePath; + int index = nodePath.lastIndexOf("/"); + if (index == -1) { + throw new IllegalArgumentException("Invalid node path" + + nodePath); + } + this.nodeName = nodePath.substring(index + 1); + } + + /* + * (non-Javadoc) + * + * @see javax.swing.tree.TreeNode#children() + */ + public Enumeration children() { + List children = zooInspectorManager + .getChildren(this.nodePath); + Collections.sort(children); + List returnChildren = new ArrayList(); + for (String child : children) { + returnChildren.add(new ZooInspectorTreeNode((this.nodePath + .equals("/") ? "" : this.nodePath) + + "/" + child, this)); + } + return Collections.enumeration(returnChildren); + } + + /* + * (non-Javadoc) + * + * @see javax.swing.tree.TreeNode#getAllowsChildren() + */ + public boolean getAllowsChildren() { + return zooInspectorManager.isAllowsChildren(this.nodePath); + } + + /* + * (non-Javadoc) + * + * @see javax.swing.tree.TreeNode#getChildAt(int) + */ + public TreeNode getChildAt(int childIndex) { + String child = zooInspectorManager.getNodeChild(this.nodePath, + childIndex); + if (child != null) { + return new ZooInspectorTreeNode((this.nodePath.equals("/") ? "" + : this.nodePath) + + "/" + child, this); + } + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.swing.tree.TreeNode#getChildCount() + */ + public int getChildCount() { + return zooInspectorManager.getNumChildren(this.nodePath); + } + + /* + * (non-Javadoc) + * + * @see javax.swing.tree.TreeNode#getIndex(javax.swing.tree.TreeNode) + */ + public int getIndex(TreeNode node) { + return zooInspectorManager.getNodeIndex(this.nodePath); + } + + /* + * (non-Javadoc) + * + * @see javax.swing.tree.TreeNode#getParent() + */ + public TreeNode getParent() { + return this.parent; + } + + /* + * (non-Javadoc) + * + * @see javax.swing.tree.TreeNode#isLeaf() + */ + public boolean isLeaf() { + return !zooInspectorManager.hasChildren(this.nodePath); + } + + @Override + public String toString() { + return this.nodeName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getOuterType().hashCode(); + result = prime * result + + ((nodePath == null) ? 0 : nodePath.hashCode()); + result = prime * result + + ((parent == null) ? 0 : parent.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ZooInspectorTreeNode other = (ZooInspectorTreeNode) obj; + if (!getOuterType().equals(other.getOuterType())) + return false; + if (nodePath == null) { + if (other.nodePath != null) + return false; + } else if (!nodePath.equals(other.nodePath)) + return false; + if (parent == null) { + if (other.parent != null) + return false; + } else if (!parent.equals(other.parent)) + return false; + return true; + } + + private ZooInspectorTreeViewer getOuterType() { + return ZooInspectorTreeViewer.this; + } + + } + + /** + * @return {@link List} of the currently selected nodes + */ + public List getSelectedNodes() { + TreePath[] paths = tree.getSelectionPaths(); + List selectedNodes = new ArrayList(); + if (paths != null) { + for (TreePath path : paths) { + StringBuilder sb = new StringBuilder(); + Object[] pathArray = path.getPath(); + for (Object o : pathArray) { + String nodeName = o.toString(); + if (nodeName.length() > 0) { + sb.append("/"); + sb.append(o.toString()); + } + } + selectedNodes.add(sb.toString()); + } + } + return selectedNodes; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.NodeListener#processEvent(java + * .lang.String, java.lang.String, java.util.Map) + */ + public void processEvent(String nodePath, String eventType, + Map eventInfo) { + StringBuilder sb = new StringBuilder(); + sb.append("Node: "); + sb.append(nodePath); + sb.append("\nEvent: "); + sb.append(eventType); + if (eventInfo != null) { + for (Map.Entry entry : eventInfo.entrySet()) { + sb.append("\n"); + sb.append(entry.getKey()); + sb.append(": "); + sb.append(entry.getValue()); + } + } + this.toasterManager.showToaster(ZooInspectorIconResources + .getInformationIcon(), sb.toString()); + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/about.html b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/about.html index eed820a9421..17fb3dca20b 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/about.html +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/about.html @@ -1,21 +1,21 @@ - - - - -ZooInspector v0.1 - - -

                                          ZooInspector was developed by Colin Goodheart-Smithe and is -available under the Apache Software Licence v2.0.

                                          -

                                          The Icons used were sourced from the Eclipse project (http://www.eclipse.org) and licensed -under the Eclipse Public Licence v1.0. [http://www.eclipse.org/org/documents/epl-v10.php] -

                                          -

                                          ZooKeeper is available from http://hadoop.apache.org/zookeeper/ -and is licensed under an Apache Software Licence v2.0

                                          -

                                          The ApacheSoftware Licence v2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0

                                          - - \ No newline at end of file + + + + +ZooInspector v0.1 + + +

                                          ZooInspector was developed by Colin Goodheart-Smithe and is +available under the Apache Software Licence v2.0.

                                          +

                                          The Icons used were sourced from the Eclipse project (http://www.eclipse.org) and licensed +under the Eclipse Public Licence v1.0. [http://www.eclipse.org/org/documents/epl-v10.php] +

                                          +

                                          ZooKeeper is available from http://zookeeper.apache.org/ +and is licensed under an Apache Software Licence v2.0

                                          +

                                          The ApacheSoftware Licence v2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0

                                          + + diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerACL.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerACL.java index cc2a4bd2e93..5ac203c28c8 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerACL.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerACL.java @@ -1,187 +1,187 @@ -/** - * 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.inspector.gui.nodeviewer; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; - -import javax.swing.BorderFactory; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.SwingWorker; - -import org.apache.zookeeper.inspector.logger.LoggerFactory; -import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; - -/** - * A node viewer for displaying the ACLs currently applied to the selected node - */ -public class NodeViewerACL extends ZooInspectorNodeViewer { - private ZooInspectorNodeManager zooInspectorManager; - private final JPanel aclDataPanel; - private String selectedNode; - - /** - * - */ - public NodeViewerACL() { - this.setLayout(new BorderLayout()); - this.aclDataPanel = new JPanel(); - this.aclDataPanel.setBackground(Color.WHITE); - JScrollPane scroller = new JScrollPane(this.aclDataPanel); - this.add(scroller, BorderLayout.CENTER); - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# - * getTitle() - */ - @Override - public String getTitle() { - return "Node ACLs"; - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# - * nodeSelectionChanged(java.util.Set) - */ - @Override - public void nodeSelectionChanged(List selectedNodes) { - this.aclDataPanel.removeAll(); - if (selectedNodes.size() > 0) { - this.selectedNode = selectedNodes.get(0); - SwingWorker>, Void> worker = new SwingWorker>, Void>() { - - @Override - protected List> doInBackground() - throws Exception { - return NodeViewerACL.this.zooInspectorManager - .getACLs(NodeViewerACL.this.selectedNode); - } - - @Override - protected void done() { - List> acls = null; - try { - acls = get(); - } catch (InterruptedException e) { - acls = new ArrayList>(); - LoggerFactory.getLogger().error( - "Error retrieving ACL Information for node: " - + NodeViewerACL.this.selectedNode, e); - } catch (ExecutionException e) { - acls = new ArrayList>(); - LoggerFactory.getLogger().error( - "Error retrieving ACL Information for node: " - + NodeViewerACL.this.selectedNode, e); - } - aclDataPanel.setLayout(new GridBagLayout()); - int j = 0; - for (Map data : acls) { - int rowPos = 2 * j + 1; - JPanel aclPanel = new JPanel(); - aclPanel.setBorder(BorderFactory - .createLineBorder(Color.BLACK)); - aclPanel.setBackground(Color.WHITE); - aclPanel.setLayout(new GridBagLayout()); - int i = 0; - for (Map.Entry entry : data.entrySet()) { - int rowPosACL = 2 * i + 1; - JLabel label = new JLabel(entry.getKey()); - JTextField text = new JTextField(entry.getValue()); - text.setEditable(false); - GridBagConstraints c1 = new GridBagConstraints(); - c1.gridx = 1; - c1.gridy = rowPosACL; - c1.gridwidth = 1; - c1.gridheight = 1; - c1.weightx = 0; - c1.weighty = 0; - c1.anchor = GridBagConstraints.NORTHWEST; - c1.fill = GridBagConstraints.BOTH; - c1.insets = new Insets(5, 5, 5, 5); - c1.ipadx = 0; - c1.ipady = 0; - aclPanel.add(label, c1); - GridBagConstraints c2 = new GridBagConstraints(); - c2.gridx = 3; - c2.gridy = rowPosACL; - c2.gridwidth = 1; - c2.gridheight = 1; - c2.weightx = 0; - c2.weighty = 0; - c2.anchor = GridBagConstraints.NORTHWEST; - c2.fill = GridBagConstraints.BOTH; - c2.insets = new Insets(5, 5, 5, 5); - c2.ipadx = 0; - c2.ipady = 0; - aclPanel.add(text, c2); - i++; - } - GridBagConstraints c = new GridBagConstraints(); - c.gridx = 1; - c.gridy = rowPos; - c.gridwidth = 1; - c.gridheight = 1; - c.weightx = 1; - c.weighty = 1; - c.anchor = GridBagConstraints.NORTHWEST; - c.fill = GridBagConstraints.NONE; - c.insets = new Insets(5, 5, 5, 5); - c.ipadx = 0; - c.ipady = 0; - aclDataPanel.add(aclPanel, c); - } - NodeViewerACL.this.aclDataPanel.revalidate(); - NodeViewerACL.this.aclDataPanel.repaint(); - } - }; - worker.execute(); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# - * setZooInspectorManager - * (org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager) - */ - @Override - public void setZooInspectorManager( - ZooInspectorNodeManager zooInspectorManager) { - this.zooInspectorManager = zooInspectorManager; - } - -} +/** + * 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.inspector.gui.nodeviewer; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.SwingWorker; + +import org.apache.zookeeper.inspector.logger.LoggerFactory; +import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; + +/** + * A node viewer for displaying the ACLs currently applied to the selected node + */ +public class NodeViewerACL extends ZooInspectorNodeViewer { + private ZooInspectorNodeManager zooInspectorManager; + private final JPanel aclDataPanel; + private String selectedNode; + + /** + * + */ + public NodeViewerACL() { + this.setLayout(new BorderLayout()); + this.aclDataPanel = new JPanel(); + this.aclDataPanel.setBackground(Color.WHITE); + JScrollPane scroller = new JScrollPane(this.aclDataPanel); + this.add(scroller, BorderLayout.CENTER); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# + * getTitle() + */ + @Override + public String getTitle() { + return "Node ACLs"; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# + * nodeSelectionChanged(java.util.Set) + */ + @Override + public void nodeSelectionChanged(List selectedNodes) { + this.aclDataPanel.removeAll(); + if (selectedNodes.size() > 0) { + this.selectedNode = selectedNodes.get(0); + SwingWorker>, Void> worker = new SwingWorker>, Void>() { + + @Override + protected List> doInBackground() + throws Exception { + return NodeViewerACL.this.zooInspectorManager + .getACLs(NodeViewerACL.this.selectedNode); + } + + @Override + protected void done() { + List> acls = null; + try { + acls = get(); + } catch (InterruptedException e) { + acls = new ArrayList>(); + LoggerFactory.getLogger().error( + "Error retrieving ACL Information for node: " + + NodeViewerACL.this.selectedNode, e); + } catch (ExecutionException e) { + acls = new ArrayList>(); + LoggerFactory.getLogger().error( + "Error retrieving ACL Information for node: " + + NodeViewerACL.this.selectedNode, e); + } + aclDataPanel.setLayout(new GridBagLayout()); + int j = 0; + for (Map data : acls) { + int rowPos = 2 * j + 1; + JPanel aclPanel = new JPanel(); + aclPanel.setBorder(BorderFactory + .createLineBorder(Color.BLACK)); + aclPanel.setBackground(Color.WHITE); + aclPanel.setLayout(new GridBagLayout()); + int i = 0; + for (Map.Entry entry : data.entrySet()) { + int rowPosACL = 2 * i + 1; + JLabel label = new JLabel(entry.getKey()); + JTextField text = new JTextField(entry.getValue()); + text.setEditable(false); + GridBagConstraints c1 = new GridBagConstraints(); + c1.gridx = 1; + c1.gridy = rowPosACL; + c1.gridwidth = 1; + c1.gridheight = 1; + c1.weightx = 0; + c1.weighty = 0; + c1.anchor = GridBagConstraints.NORTHWEST; + c1.fill = GridBagConstraints.BOTH; + c1.insets = new Insets(5, 5, 5, 5); + c1.ipadx = 0; + c1.ipady = 0; + aclPanel.add(label, c1); + GridBagConstraints c2 = new GridBagConstraints(); + c2.gridx = 3; + c2.gridy = rowPosACL; + c2.gridwidth = 1; + c2.gridheight = 1; + c2.weightx = 0; + c2.weighty = 0; + c2.anchor = GridBagConstraints.NORTHWEST; + c2.fill = GridBagConstraints.BOTH; + c2.insets = new Insets(5, 5, 5, 5); + c2.ipadx = 0; + c2.ipady = 0; + aclPanel.add(text, c2); + i++; + } + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 1; + c.gridy = rowPos; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 1; + c.weighty = 1; + c.anchor = GridBagConstraints.NORTHWEST; + c.fill = GridBagConstraints.NONE; + c.insets = new Insets(5, 5, 5, 5); + c.ipadx = 0; + c.ipady = 0; + aclDataPanel.add(aclPanel, c); + } + NodeViewerACL.this.aclDataPanel.revalidate(); + NodeViewerACL.this.aclDataPanel.repaint(); + } + }; + worker.execute(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# + * setZooInspectorManager + * (org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager) + */ + @Override + public void setZooInspectorManager( + ZooInspectorNodeManager zooInspectorManager) { + this.zooInspectorManager = zooInspectorManager; + } + +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerData.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerData.java index 9dd0f38f3b6..001cb7b21a9 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerData.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerData.java @@ -1,146 +1,146 @@ -/** - * 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.inspector.gui.nodeviewer; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.List; -import java.util.concurrent.ExecutionException; - -import javax.swing.JButton; -import javax.swing.JOptionPane; -import javax.swing.JScrollPane; -import javax.swing.JTextPane; -import javax.swing.JToolBar; -import javax.swing.SwingWorker; - -import org.apache.zookeeper.inspector.gui.ZooInspectorIconResources; -import org.apache.zookeeper.inspector.logger.LoggerFactory; -import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; - -/** - * A node viewer for displaying the data for the currently selected node - */ -public class NodeViewerData extends ZooInspectorNodeViewer { - private ZooInspectorNodeManager zooInspectorManager; - private final JTextPane dataArea; - private final JToolBar toolbar; - private String selectedNode; - - /** - * - */ - public NodeViewerData() { - this.setLayout(new BorderLayout()); - this.dataArea = new JTextPane(); - this.toolbar = new JToolBar(); - this.toolbar.setFloatable(false); - JScrollPane scroller = new JScrollPane(this.dataArea); - scroller - .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - this.add(scroller, BorderLayout.CENTER); - this.add(this.toolbar, BorderLayout.NORTH); - JButton saveButton = new JButton(ZooInspectorIconResources - .getSaveIcon()); - saveButton.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - if (selectedNode != null) { - if (JOptionPane.showConfirmDialog(NodeViewerData.this, - "Are you sure you want to save this node?" - + " (this action cannot be reverted)", - "Confirm Save", JOptionPane.YES_NO_OPTION, - JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) { - zooInspectorManager.setData(selectedNode, dataArea - .getText()); - } - } - } - }); - this.toolbar.add(saveButton); - - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# - * getTitle() - */ - @Override - public String getTitle() { - return "Node Data"; - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# - * nodeSelectionChanged(java.util.Set) - */ - @Override - public void nodeSelectionChanged(List selectedNodes) { - if (selectedNodes.size() > 0) { - this.selectedNode = selectedNodes.get(0); - SwingWorker worker = new SwingWorker() { - - @Override - protected String doInBackground() throws Exception { - return NodeViewerData.this.zooInspectorManager - .getData(NodeViewerData.this.selectedNode); - } - - @Override - protected void done() { - String data = ""; - try { - data = get(); - } catch (InterruptedException e) { - LoggerFactory.getLogger().error( - "Error retrieving data for node: " - + NodeViewerData.this.selectedNode, e); - } catch (ExecutionException e) { - LoggerFactory.getLogger().error( - "Error retrieving data for node: " - + NodeViewerData.this.selectedNode, e); - } - NodeViewerData.this.dataArea.setText(data); - } - }; - worker.execute(); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# - * setZooInspectorManager - * (org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager) - */ - @Override - public void setZooInspectorManager( - ZooInspectorNodeManager zooInspectorManager) { - this.zooInspectorManager = zooInspectorManager; - } - -} +/** + * 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.inspector.gui.nodeviewer; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import javax.swing.JButton; +import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.JToolBar; +import javax.swing.SwingWorker; + +import org.apache.zookeeper.inspector.gui.ZooInspectorIconResources; +import org.apache.zookeeper.inspector.logger.LoggerFactory; +import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; + +/** + * A node viewer for displaying the data for the currently selected node + */ +public class NodeViewerData extends ZooInspectorNodeViewer { + private ZooInspectorNodeManager zooInspectorManager; + private final JTextPane dataArea; + private final JToolBar toolbar; + private String selectedNode; + + /** + * + */ + public NodeViewerData() { + this.setLayout(new BorderLayout()); + this.dataArea = new JTextPane(); + this.toolbar = new JToolBar(); + this.toolbar.setFloatable(false); + JScrollPane scroller = new JScrollPane(this.dataArea); + scroller + .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + this.add(scroller, BorderLayout.CENTER); + this.add(this.toolbar, BorderLayout.NORTH); + JButton saveButton = new JButton(ZooInspectorIconResources + .getSaveIcon()); + saveButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + if (selectedNode != null) { + if (JOptionPane.showConfirmDialog(NodeViewerData.this, + "Are you sure you want to save this node?" + + " (this action cannot be reverted)", + "Confirm Save", JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) { + zooInspectorManager.setData(selectedNode, dataArea + .getText()); + } + } + } + }); + this.toolbar.add(saveButton); + + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# + * getTitle() + */ + @Override + public String getTitle() { + return "Node Data"; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# + * nodeSelectionChanged(java.util.Set) + */ + @Override + public void nodeSelectionChanged(List selectedNodes) { + if (selectedNodes.size() > 0) { + this.selectedNode = selectedNodes.get(0); + SwingWorker worker = new SwingWorker() { + + @Override + protected String doInBackground() throws Exception { + return NodeViewerData.this.zooInspectorManager + .getData(NodeViewerData.this.selectedNode); + } + + @Override + protected void done() { + String data = ""; + try { + data = get(); + } catch (InterruptedException e) { + LoggerFactory.getLogger().error( + "Error retrieving data for node: " + + NodeViewerData.this.selectedNode, e); + } catch (ExecutionException e) { + LoggerFactory.getLogger().error( + "Error retrieving data for node: " + + NodeViewerData.this.selectedNode, e); + } + NodeViewerData.this.dataArea.setText(data); + } + }; + worker.execute(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# + * setZooInspectorManager + * (org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager) + */ + @Override + public void setZooInspectorManager( + ZooInspectorNodeManager zooInspectorManager) { + this.zooInspectorManager = zooInspectorManager; + } + +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerMetaData.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerMetaData.java index d7e2b43258f..5c2df8d88c5 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerMetaData.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/NodeViewerMetaData.java @@ -1,186 +1,186 @@ -/** - * 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.inspector.gui.nodeviewer; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextField; -import javax.swing.SwingWorker; - -import org.apache.zookeeper.data.Stat; -import org.apache.zookeeper.inspector.logger.LoggerFactory; -import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; - -/** - * A node viewer for displaying the meta data for the currently selected node. - * The meta data is essentially the information from the {@link Stat} for the - * node - */ -public class NodeViewerMetaData extends ZooInspectorNodeViewer { - private ZooInspectorNodeManager zooInspectorManager; - private final JPanel metaDataPanel; - private String selectedNode; - - /** - * - */ - public NodeViewerMetaData() { - this.setLayout(new BorderLayout()); - this.metaDataPanel = new JPanel(); - this.metaDataPanel.setBackground(Color.WHITE); - JScrollPane scroller = new JScrollPane(this.metaDataPanel); - this.add(scroller, BorderLayout.CENTER); - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# - * getTitle() - */ - @Override - public String getTitle() { - return "Node Metadata"; - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# - * nodeSelectionChanged(java.util.Set) - */ - @Override - public void nodeSelectionChanged(List selectedNodes) { - this.metaDataPanel.removeAll(); - if (selectedNodes.size() > 0) { - this.selectedNode = selectedNodes.get(0); - SwingWorker, Void> worker = new SwingWorker, Void>() { - - @Override - protected Map doInBackground() throws Exception { - return NodeViewerMetaData.this.zooInspectorManager - .getNodeMeta(NodeViewerMetaData.this.selectedNode); - } - - @Override - protected void done() { - Map data = null; - try { - data = get(); - } catch (InterruptedException e) { - data = new HashMap(); - LoggerFactory.getLogger().error( - "Error retrieving meta data for node: " - + NodeViewerMetaData.this.selectedNode, - e); - } catch (ExecutionException e) { - data = new HashMap(); - LoggerFactory.getLogger().error( - "Error retrieving meta data for node: " - + NodeViewerMetaData.this.selectedNode, - e); - } - NodeViewerMetaData.this.metaDataPanel - .setLayout(new GridBagLayout()); - JPanel infoPanel = new JPanel(); - infoPanel.setBackground(Color.WHITE); - infoPanel.setLayout(new GridBagLayout()); - int i = 0; - int rowPos = 0; - for (Map.Entry entry : data.entrySet()) { - rowPos = 2 * i + 1; - JLabel label = new JLabel(entry.getKey()); - JTextField text = new JTextField(entry.getValue()); - text.setEditable(false); - GridBagConstraints c1 = new GridBagConstraints(); - c1.gridx = 0; - c1.gridy = rowPos; - c1.gridwidth = 1; - c1.gridheight = 1; - c1.weightx = 0; - c1.weighty = 0; - c1.anchor = GridBagConstraints.WEST; - c1.fill = GridBagConstraints.HORIZONTAL; - c1.insets = new Insets(5, 5, 5, 5); - c1.ipadx = 0; - c1.ipady = 0; - infoPanel.add(label, c1); - GridBagConstraints c2 = new GridBagConstraints(); - c2.gridx = 2; - c2.gridy = rowPos; - c2.gridwidth = 1; - c2.gridheight = 1; - c2.weightx = 0; - c2.weighty = 0; - c2.anchor = GridBagConstraints.WEST; - c2.fill = GridBagConstraints.HORIZONTAL; - c2.insets = new Insets(5, 5, 5, 5); - c2.ipadx = 0; - c2.ipady = 0; - infoPanel.add(text, c2); - i++; - } - GridBagConstraints c = new GridBagConstraints(); - c.gridx = 1; - c.gridy = rowPos; - c.gridwidth = 1; - c.gridheight = 1; - c.weightx = 1; - c.weighty = 1; - c.anchor = GridBagConstraints.NORTHWEST; - c.fill = GridBagConstraints.NONE; - c.insets = new Insets(5, 5, 5, 5); - c.ipadx = 0; - c.ipady = 0; - NodeViewerMetaData.this.metaDataPanel.add(infoPanel, c); - NodeViewerMetaData.this.metaDataPanel.revalidate(); - NodeViewerMetaData.this.metaDataPanel.repaint(); - } - }; - worker.execute(); - } - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# - * setZooInspectorManager - * (org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager) - */ - @Override - public void setZooInspectorManager( - ZooInspectorNodeManager zooInspectorManager) { - this.zooInspectorManager = zooInspectorManager; - } - -} +/** + * 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.inspector.gui.nodeviewer; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.SwingWorker; + +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.inspector.logger.LoggerFactory; +import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; + +/** + * A node viewer for displaying the meta data for the currently selected node. + * The meta data is essentially the information from the {@link Stat} for the + * node + */ +public class NodeViewerMetaData extends ZooInspectorNodeViewer { + private ZooInspectorNodeManager zooInspectorManager; + private final JPanel metaDataPanel; + private String selectedNode; + + /** + * + */ + public NodeViewerMetaData() { + this.setLayout(new BorderLayout()); + this.metaDataPanel = new JPanel(); + this.metaDataPanel.setBackground(Color.WHITE); + JScrollPane scroller = new JScrollPane(this.metaDataPanel); + this.add(scroller, BorderLayout.CENTER); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# + * getTitle() + */ + @Override + public String getTitle() { + return "Node Metadata"; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# + * nodeSelectionChanged(java.util.Set) + */ + @Override + public void nodeSelectionChanged(List selectedNodes) { + this.metaDataPanel.removeAll(); + if (selectedNodes.size() > 0) { + this.selectedNode = selectedNodes.get(0); + SwingWorker, Void> worker = new SwingWorker, Void>() { + + @Override + protected Map doInBackground() throws Exception { + return NodeViewerMetaData.this.zooInspectorManager + .getNodeMeta(NodeViewerMetaData.this.selectedNode); + } + + @Override + protected void done() { + Map data = null; + try { + data = get(); + } catch (InterruptedException e) { + data = new HashMap(); + LoggerFactory.getLogger().error( + "Error retrieving meta data for node: " + + NodeViewerMetaData.this.selectedNode, + e); + } catch (ExecutionException e) { + data = new HashMap(); + LoggerFactory.getLogger().error( + "Error retrieving meta data for node: " + + NodeViewerMetaData.this.selectedNode, + e); + } + NodeViewerMetaData.this.metaDataPanel + .setLayout(new GridBagLayout()); + JPanel infoPanel = new JPanel(); + infoPanel.setBackground(Color.WHITE); + infoPanel.setLayout(new GridBagLayout()); + int i = 0; + int rowPos = 0; + for (Map.Entry entry : data.entrySet()) { + rowPos = 2 * i + 1; + JLabel label = new JLabel(entry.getKey()); + JTextField text = new JTextField(entry.getValue()); + text.setEditable(false); + GridBagConstraints c1 = new GridBagConstraints(); + c1.gridx = 0; + c1.gridy = rowPos; + c1.gridwidth = 1; + c1.gridheight = 1; + c1.weightx = 0; + c1.weighty = 0; + c1.anchor = GridBagConstraints.WEST; + c1.fill = GridBagConstraints.HORIZONTAL; + c1.insets = new Insets(5, 5, 5, 5); + c1.ipadx = 0; + c1.ipady = 0; + infoPanel.add(label, c1); + GridBagConstraints c2 = new GridBagConstraints(); + c2.gridx = 2; + c2.gridy = rowPos; + c2.gridwidth = 1; + c2.gridheight = 1; + c2.weightx = 0; + c2.weighty = 0; + c2.anchor = GridBagConstraints.WEST; + c2.fill = GridBagConstraints.HORIZONTAL; + c2.insets = new Insets(5, 5, 5, 5); + c2.ipadx = 0; + c2.ipady = 0; + infoPanel.add(text, c2); + i++; + } + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 1; + c.gridy = rowPos; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 1; + c.weighty = 1; + c.anchor = GridBagConstraints.NORTHWEST; + c.fill = GridBagConstraints.NONE; + c.insets = new Insets(5, 5, 5, 5); + c.ipadx = 0; + c.ipady = 0; + NodeViewerMetaData.this.metaDataPanel.add(infoPanel, c); + NodeViewerMetaData.this.metaDataPanel.revalidate(); + NodeViewerMetaData.this.metaDataPanel.repaint(); + } + }; + worker.execute(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer# + * setZooInspectorManager + * (org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager) + */ + @Override + public void setZooInspectorManager( + ZooInspectorNodeManager zooInspectorManager) { + this.zooInspectorManager = zooInspectorManager; + } + +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/ZooInspectorNodeViewer.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/ZooInspectorNodeViewer.java index 2185440ec4b..32119a85c30 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/ZooInspectorNodeViewer.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/nodeviewer/ZooInspectorNodeViewer.java @@ -1,138 +1,138 @@ -/** - * 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.inspector.gui.nodeviewer; - -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.IOException; -import java.util.List; - -import javax.swing.JPanel; - -import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; - -/** - * A {@link JPanel} for displaying information about the currently selected - * node(s) - */ -public abstract class ZooInspectorNodeViewer extends JPanel implements - Transferable { - /** - * The {@link DataFlavor} used for DnD in the node viewer configuration - * dialog - */ - public static final DataFlavor nodeViewerDataFlavor = new DataFlavor( - ZooInspectorNodeViewer.class, "nodeviewer"); - - /** - * @param zooInspectorManager - */ - public abstract void setZooInspectorManager( - ZooInspectorNodeManager zooInspectorManager); - - /** - * Called whenever the selected nodes in the tree view changes. - * - * @param selectedNodes - * - the nodes currently selected in the tree view - * - */ - public abstract void nodeSelectionChanged(List selectedNodes); - - /** - * @return the title of the node viewer. this will be shown on the tab for - * this node viewer. - */ - public abstract String getTitle(); - - /* - * (non-Javadoc) - * - * @see - * java.awt.datatransfer.Transferable#getTransferData(java.awt.datatransfer - * .DataFlavor) - */ - public Object getTransferData(DataFlavor flavor) - throws UnsupportedFlavorException, IOException { - if (flavor.equals(nodeViewerDataFlavor)) { - return this.getClass().getCanonicalName(); - } else { - return null; - } - } - - /* - * (non-Javadoc) - * - * @see java.awt.datatransfer.Transferable#getTransferDataFlavors() - */ - public DataFlavor[] getTransferDataFlavors() { - return new DataFlavor[] { nodeViewerDataFlavor }; - } - - /* - * (non-Javadoc) - * - * @seejava.awt.datatransfer.Transferable#isDataFlavorSupported(java.awt. - * datatransfer.DataFlavor) - */ - public boolean isDataFlavorSupported(DataFlavor flavor) { - return flavor.equals(nodeViewerDataFlavor); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + ((getTitle() == null) ? 0 : getTitle().hashCode()); - return result; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ZooInspectorNodeViewer other = (ZooInspectorNodeViewer) obj; - if (getClass().getCanonicalName() != other.getClass() - .getCanonicalName()) { - return false; - } - if (getTitle() == null) { - if (other.getTitle() != null) - return false; - } else if (!getTitle().equals(other.getTitle())) - return false; - return true; - } -} +/** + * 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.inspector.gui.nodeviewer; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.List; + +import javax.swing.JPanel; + +import org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager; + +/** + * A {@link JPanel} for displaying information about the currently selected + * node(s) + */ +public abstract class ZooInspectorNodeViewer extends JPanel implements + Transferable { + /** + * The {@link DataFlavor} used for DnD in the node viewer configuration + * dialog + */ + public static final DataFlavor nodeViewerDataFlavor = new DataFlavor( + ZooInspectorNodeViewer.class, "nodeviewer"); + + /** + * @param zooInspectorManager + */ + public abstract void setZooInspectorManager( + ZooInspectorNodeManager zooInspectorManager); + + /** + * Called whenever the selected nodes in the tree view changes. + * + * @param selectedNodes + * - the nodes currently selected in the tree view + * + */ + public abstract void nodeSelectionChanged(List selectedNodes); + + /** + * @return the title of the node viewer. this will be shown on the tab for + * this node viewer. + */ + public abstract String getTitle(); + + /* + * (non-Javadoc) + * + * @see + * java.awt.datatransfer.Transferable#getTransferData(java.awt.datatransfer + * .DataFlavor) + */ + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, IOException { + if (flavor.equals(nodeViewerDataFlavor)) { + return this.getClass().getCanonicalName(); + } else { + return null; + } + } + + /* + * (non-Javadoc) + * + * @see java.awt.datatransfer.Transferable#getTransferDataFlavors() + */ + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { nodeViewerDataFlavor }; + } + + /* + * (non-Javadoc) + * + * @seejava.awt.datatransfer.Transferable#isDataFlavorSupported(java.awt. + * datatransfer.DataFlavor) + */ + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavor.equals(nodeViewerDataFlavor); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((getTitle() == null) ? 0 : getTitle().hashCode()); + return result; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ZooInspectorNodeViewer other = (ZooInspectorNodeViewer) obj; + if (getClass().getCanonicalName() != other.getClass() + .getCanonicalName()) { + return false; + } + if (getTitle() == null) { + if (other.getTitle() != null) + return false; + } else if (!getTitle().equals(other.getTitle())) + return false; + return true; + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/logger/LoggerFactory.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/logger/LoggerFactory.java index a8b5fd9aa60..e4fae41698b 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/logger/LoggerFactory.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/logger/LoggerFactory.java @@ -1,36 +1,36 @@ -/** - * 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.inspector.logger; - -/** - * Provides a {@link Logger} for use across the entire application - * - */ -public class LoggerFactory -{ - private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger("org.apache.zookeeper.inspector"); //$NON-NLS-1$ - - /** - * @return {@link Logger} for ZooInspector - */ - public static org.slf4j.Logger getLogger() - { - return logger; - } - -} +/** + * 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.inspector.logger; + +/** + * Provides a {@link Logger} for use across the entire application + * + */ +public class LoggerFactory +{ + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger("org.apache.zookeeper.inspector"); //$NON-NLS-1$ + + /** + * @return {@link Logger} for ZooInspector + */ + public static org.slf4j.Logger getLogger() + { + return logger; + } + +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/NodeListener.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/NodeListener.java index f174f8a29c2..fe55a45b85c 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/NodeListener.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/NodeListener.java @@ -1,37 +1,37 @@ -/** - * 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.inspector.manager; - -import java.util.Map; - -/** - * A Listener for Events on zookeeper nodes - */ -public interface NodeListener { - /** - * @param nodePath - * - the path of the node - * @param eventType - * - the event type - * @param eventInfo - * - a {@link Map} containing any other information about this - * event - */ - public void processEvent(String nodePath, String eventType, - Map eventInfo); -} +/** + * 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.inspector.manager; + +import java.util.Map; + +/** + * A Listener for Events on zookeeper nodes + */ +public interface NodeListener { + /** + * @param nodePath + * - the path of the node + * @param eventType + * - the event type + * @param eventInfo + * - a {@link Map} containing any other information about this + * event + */ + public void processEvent(String nodePath, String eventType, + Map eventInfo); +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/Pair.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/Pair.java index 9ebbd952041..b72950c9567 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/Pair.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/Pair.java @@ -1,120 +1,120 @@ -/** - * 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.inspector.manager; - -/** - * A utility class for storing a pair of objects - * - * @param - * @param - */ -public class Pair { - private K key; - private V value; - - /** - * @param key - * @param value - */ - public Pair(K key, V value) { - this.key = key; - this.value = value; - } - - /** - * - */ - public Pair() { - // Do Nothing - } - - /** - * @return key - */ - public K getKey() { - return key; - } - - /** - * @param key - */ - public void setKey(K key) { - this.key = key; - } - - /** - * @return value - */ - public V getValue() { - return value; - } - - /** - * @param value - */ - public void setValue(V value) { - this.value = value; - } - - @Override - public String toString() { - return "Pair [" + key + ", " + value + "]"; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((key == null) ? 0 : key.hashCode()); - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Pair other = (Pair) obj; - if (key == null) { - if (other.key != null) - return false; - } else if (!key.equals(other.key)) - return false; - if (value == null) { - if (other.value != null) - return false; - } else if (!value.equals(other.value)) - return false; - return true; - } - -} +/** + * 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.inspector.manager; + +/** + * A utility class for storing a pair of objects + * + * @param + * @param + */ +public class Pair { + private K key; + private V value; + + /** + * @param key + * @param value + */ + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + /** + * + */ + public Pair() { + // Do Nothing + } + + /** + * @return key + */ + public K getKey() { + return key; + } + + /** + * @param key + */ + public void setKey(K key) { + this.key = key; + } + + /** + * @return value + */ + public V getValue() { + return value; + } + + /** + * @param value + */ + public void setValue(V value) { + this.value = value; + } + + @Override + public String toString() { + return "Pair [" + key + ", " + value + "]"; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Pair other = (Pair) obj; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } + +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorManager.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorManager.java index e9c7c1d99db..74c3cb20e88 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorManager.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorManager.java @@ -1,139 +1,139 @@ -/** - * 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.inspector.manager; - -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import javax.swing.JComboBox; -import javax.swing.JTextField; - -/** - * A Manager for all interactions between the application and the Zookeeper - * instance - */ -public interface ZooInspectorManager extends ZooInspectorNodeManager, - ZooInspectorNodeTreeManager { - - /** - * @param connectionProps - * @return true if successfully connected - */ - public boolean connect(Properties connectionProps); - - /** - * @return true if successfully disconnected - */ - public boolean disconnect(); - - /** - * @return a {@link Pair} containing the following: - *
                                            - *
                                          • a {@link Map} of property keys to list of possible values. If - * the list size is 1 the value is taken to be the default value for - * a {@link JTextField}. If the list size is greater than 1, the - * values are taken to be the possible options to show in a - * {@link JComboBox} with the first selected as default.
                                          • - *
                                          • a {@link Map} of property keys to the label to show on the UI - *
                                          • - *
                                              - * - */ - public Pair>, Map> getConnectionPropertiesTemplate(); - - /** - * @param selectedNodes - * - the nodes to add the watcher to - * @param nodeListener - * - the node listener for this watcher - */ - public void addWatchers(Collection selectedNodes, - NodeListener nodeListener); - - /** - * @param selectedNodes - * - the nodes to remove the watchers from - */ - public void removeWatchers(Collection selectedNodes); - - /** - * @param selectedFile - * - the file to load which contains the node viewers - * configuration - * @return nodeViewers - the class names of the node viewers from the - * configuration - * @throws IOException - * - if the configuration file cannot be loaded - */ - public List loadNodeViewersFile(File selectedFile) - throws IOException; - - /** - * @param selectedFile - * - the file to save the configuration to - * @param nodeViewersClassNames - * - the class names of the node viewers - * @throws IOException - * - if the configuration file cannot be saved - */ - public void saveNodeViewersFile(File selectedFile, - List nodeViewersClassNames) throws IOException; - - /** - * @param nodeViewersClassNames - * - the class names of the node viewers - * @throws IOException - * - if the default configuration file cannot be loaded - */ - public void setDefaultNodeViewerConfiguration( - List nodeViewersClassNames) throws IOException; - - /** - * @return nodeViewers - the class names of the node viewers from the - * configuration - * @throws IOException - * - if the default configuration file cannot be loaded - */ - List getDefaultNodeViewerConfiguration() throws IOException; - - /** - * @param connectionProps - * - the connection properties last used to connect to the - * zookeeeper instance - */ - public void setLastConnectionProps(Properties connectionProps); - - /** - * @return last connection Properties - the connection properties last used - * to connect to the zookeeeper instance - */ - public Properties getLastConnectionProps(); - - /** - * @param props - * - the properties to use as the default connection settings - * @throws IOException - * - if the default configuration file cannot be saved - */ - public void saveDefaultConnectionFile(Properties props) throws IOException; - -} +/** + * 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.inspector.manager; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.swing.JComboBox; +import javax.swing.JTextField; + +/** + * A Manager for all interactions between the application and the Zookeeper + * instance + */ +public interface ZooInspectorManager extends ZooInspectorNodeManager, + ZooInspectorNodeTreeManager { + + /** + * @param connectionProps + * @return true if successfully connected + */ + public boolean connect(Properties connectionProps); + + /** + * @return true if successfully disconnected + */ + public boolean disconnect(); + + /** + * @return a {@link Pair} containing the following: + *
                                                + *
                                              • a {@link Map} of property keys to list of possible values. If + * the list size is 1 the value is taken to be the default value for + * a {@link JTextField}. If the list size is greater than 1, the + * values are taken to be the possible options to show in a + * {@link JComboBox} with the first selected as default.
                                              • + *
                                              • a {@link Map} of property keys to the label to show on the UI + *
                                              • + *
                                                  + * + */ + public Pair>, Map> getConnectionPropertiesTemplate(); + + /** + * @param selectedNodes + * - the nodes to add the watcher to + * @param nodeListener + * - the node listener for this watcher + */ + public void addWatchers(Collection selectedNodes, + NodeListener nodeListener); + + /** + * @param selectedNodes + * - the nodes to remove the watchers from + */ + public void removeWatchers(Collection selectedNodes); + + /** + * @param selectedFile + * - the file to load which contains the node viewers + * configuration + * @return nodeViewers - the class names of the node viewers from the + * configuration + * @throws IOException + * - if the configuration file cannot be loaded + */ + public List loadNodeViewersFile(File selectedFile) + throws IOException; + + /** + * @param selectedFile + * - the file to save the configuration to + * @param nodeViewersClassNames + * - the class names of the node viewers + * @throws IOException + * - if the configuration file cannot be saved + */ + public void saveNodeViewersFile(File selectedFile, + List nodeViewersClassNames) throws IOException; + + /** + * @param nodeViewersClassNames + * - the class names of the node viewers + * @throws IOException + * - if the default configuration file cannot be loaded + */ + public void setDefaultNodeViewerConfiguration( + List nodeViewersClassNames) throws IOException; + + /** + * @return nodeViewers - the class names of the node viewers from the + * configuration + * @throws IOException + * - if the default configuration file cannot be loaded + */ + List getDefaultNodeViewerConfiguration() throws IOException; + + /** + * @param connectionProps + * - the connection properties last used to connect to the + * zookeeeper instance + */ + public void setLastConnectionProps(Properties connectionProps); + + /** + * @return last connection Properties - the connection properties last used + * to connect to the zookeeeper instance + */ + public Properties getLastConnectionProps(); + + /** + * @param props + * - the properties to use as the default connection settings + * @throws IOException + * - if the default configuration file cannot be saved + */ + public void saveDefaultConnectionFile(Properties props) throws IOException; + +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorManagerImpl.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorManagerImpl.java index e30e317c06d..d8194d5e4a5 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorManagerImpl.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorManagerImpl.java @@ -1,852 +1,884 @@ -/** - * 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.inspector.manager; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.Watcher.Event.EventType; -import org.apache.zookeeper.Watcher.Event.KeeperState; -import org.apache.zookeeper.ZooDefs.Ids; -import org.apache.zookeeper.ZooDefs.Perms; -import org.apache.zookeeper.data.ACL; -import org.apache.zookeeper.data.Stat; -import org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager; -import org.apache.zookeeper.inspector.encryption.DataEncryptionManager; -import org.apache.zookeeper.inspector.logger.LoggerFactory; -import org.apache.zookeeper.retry.ZooKeeperRetry; - -/** - * A default implementation of {@link ZooInspectorManager} for connecting to - * zookeeper instances - */ -public class ZooInspectorManagerImpl implements ZooInspectorManager { - private static final String A_VERSION = "ACL Version"; - private static final String C_TIME = "Creation Time"; - private static final String C_VERSION = "Children Version"; - private static final String CZXID = "Creation ID"; - private static final String DATA_LENGTH = "Data Length"; - private static final String EPHEMERAL_OWNER = "Ephemeral Owner"; - private static final String M_TIME = "Last Modified Time"; - private static final String MZXID = "Modified ID"; - private static final String NUM_CHILDREN = "Number of Children"; - private static final String PZXID = "Node ID"; - private static final String VERSION = "Data Version"; - private static final String ACL_PERMS = "Permissions"; - private static final String ACL_SCHEME = "Scheme"; - private static final String ACL_ID = "Id"; - private static final String SESSION_STATE = "Session State"; - private static final String SESSION_ID = "Session ID"; - /** - * The key used for the connect string in the connection properties file - */ - public static final String CONNECT_STRING = "hosts"; - /** - * The key used for the session timeout in the connection properties file - */ - public static final String SESSION_TIMEOUT = "timeout"; - /** - * The key used for the data encryption manager in the connection properties - * file - */ - public static final String DATA_ENCRYPTION_MANAGER = "encryptionManager"; - - private static final File defaultNodeViewersFile = new File( - "./config/defaultNodeVeiwers.cfg"); - private static final File defaultConnectionFile = new File( - "./config/defaultConnectionSettings.cfg"); - - private DataEncryptionManager encryptionManager; - private String connectString; - private int sessionTimeout; - private ZooKeeper zooKeeper; - private final Map watchers = new HashMap(); - protected boolean connected = true; - private Properties lastConnectionProps; - private String defaultEncryptionManager; - private String defaultTimeout; - private String defaultHosts; - - /** - * @throws IOException - * - thrown if the default connection settings cannot be loaded - * - */ - public ZooInspectorManagerImpl() throws IOException { - loadDefaultConnectionFile(); - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.ZooInspectorManager#connect(java - * .util.Properties) - */ - public boolean connect(Properties connectionProps) { - try { - if (this.zooKeeper == null) { - String connectString = connectionProps - .getProperty(CONNECT_STRING); - String sessionTimeout = connectionProps - .getProperty(SESSION_TIMEOUT); - String encryptionManager = connectionProps - .getProperty(DATA_ENCRYPTION_MANAGER); - if (connectString == null || sessionTimeout == null) { - throw new IllegalArgumentException( - "Both connect string and session timeout are required."); - } - if (encryptionManager == null) { - this.encryptionManager = new BasicDataEncryptionManager(); - } else { - Class clazz = Class.forName(encryptionManager); - - if (Arrays.asList(clazz.getInterfaces()).contains( - DataEncryptionManager.class)) { - this.encryptionManager = (DataEncryptionManager) Class - .forName(encryptionManager).newInstance(); - } else { - throw new IllegalArgumentException( - "Data encryption manager must implement DataEncryptionManager interface"); - } - } - this.connectString = connectString; - this.sessionTimeout = Integer.valueOf(sessionTimeout); - this.zooKeeper = new ZooKeeperRetry(connectString, Integer - .valueOf(sessionTimeout), new Watcher() { - - public void process(WatchedEvent event) { - if (event.getState() == KeeperState.Expired) { - connected = false; - } - } - }); - ((ZooKeeperRetry) this.zooKeeper).setRetryLimit(10); - connected = ((ZooKeeperRetry) this.zooKeeper).testConnection(); - return connected; - } - } catch (Exception e) { - e.printStackTrace(); - } - connected = false; - return connected; - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.ZooInspectorManager#disconnect() - */ - public boolean disconnect() { - try { - if (this.zooKeeper != null) { - this.zooKeeper.close(); - this.zooKeeper = null; - connected = false; - removeWatchers(this.watchers.keySet()); - return true; - } - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred while disconnecting from ZooKeeper server", - e); - } - return false; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# - * getChildren(java.lang.String) - */ - public List getChildren(String nodePath) { - if (connected) { - try { - - return zooKeeper.getChildren(nodePath, false); - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred retrieving children of node: " - + nodePath, e); - } - } - return null; - - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#getData - * (java.lang.String) - */ - public String getData(String nodePath) { - if (connected) { - try { - if (nodePath.length() == 0) { - nodePath = "/"; - } - Stat s = zooKeeper.exists(nodePath, false); - if (s != null) { - return this.encryptionManager.decryptData(zooKeeper - .getData(nodePath, false, s)); - } - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred getting data for node: " + nodePath, e); - } - } - return null; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# - * getNodeChild(java.lang.String, int) - */ - public String getNodeChild(String nodePath, int childIndex) { - if (connected) { - try { - Stat s = zooKeeper.exists(nodePath, false); - if (s != null) { - return this.zooKeeper.getChildren(nodePath, false).get( - childIndex); - } - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred retrieving child " + childIndex - + " of node: " + nodePath, e); - } - } - return null; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# - * getNodeIndex(java.lang.String) - */ - public int getNodeIndex(String nodePath) { - if (connected) { - int index = nodePath.lastIndexOf("/"); - if (index == -1 - || (!nodePath.equals("/") && nodePath.charAt(nodePath - .length() - 1) == '/')) { - throw new IllegalArgumentException("Invalid node path: " - + nodePath); - } - String parentPath = nodePath.substring(0, index); - String child = nodePath.substring(index + 1); - if (parentPath != null && parentPath.length() > 0) { - List children = this.getChildren(parentPath); - if (children != null) { - return children.indexOf(child); - } - } - } - return -1; - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#getACLs - * (java.lang.String) - */ - public List> getACLs(String nodePath) { - List> returnACLs = new ArrayList>(); - if (connected) { - try { - if (nodePath.length() == 0) { - nodePath = "/"; - } - Stat s = zooKeeper.exists(nodePath, false); - if (s != null) { - List acls = zooKeeper.getACL(nodePath, s); - for (ACL acl : acls) { - Map aclMap = new LinkedHashMap(); - aclMap.put(ACL_SCHEME, acl.getId().getScheme()); - aclMap.put(ACL_ID, acl.getId().getId()); - StringBuilder sb = new StringBuilder(); - int perms = acl.getPerms(); - boolean addedPerm = false; - if ((perms & Perms.READ) == Perms.READ) { - sb.append("Read"); - addedPerm = true; - } - if (addedPerm) { - sb.append(", "); - } - if ((perms & Perms.WRITE) == Perms.WRITE) { - sb.append("Write"); - addedPerm = true; - } - if (addedPerm) { - sb.append(", "); - } - if ((perms & Perms.CREATE) == Perms.CREATE) { - sb.append("Create"); - addedPerm = true; - } - if (addedPerm) { - sb.append(", "); - } - if ((perms & Perms.DELETE) == Perms.DELETE) { - sb.append("Delete"); - addedPerm = true; - } - if (addedPerm) { - sb.append(", "); - } - if ((perms & Perms.ADMIN) == Perms.ADMIN) { - sb.append("Admin"); - addedPerm = true; - } - aclMap.put(ACL_PERMS, sb.toString()); - returnACLs.add(aclMap); - } - } - } catch (InterruptedException e) { - LoggerFactory.getLogger().error( - "Error occurred retrieving ACLs of node: " + nodePath, - e); - } catch (KeeperException e) { - LoggerFactory.getLogger().error( - "Error occurred retrieving ACLs of node: " + nodePath, - e); - } - } - return returnACLs; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# - * getNodeMeta(java.lang.String) - */ - public Map getNodeMeta(String nodePath) { - Map nodeMeta = new LinkedHashMap(); - if (connected) { - try { - if (nodePath.length() == 0) { - nodePath = "/"; - } - Stat s = zooKeeper.exists(nodePath, false); - if (s != null) { - nodeMeta.put(A_VERSION, String.valueOf(s.getAversion())); - nodeMeta.put(C_TIME, String.valueOf(s.getCtime())); - nodeMeta.put(C_VERSION, String.valueOf(s.getCversion())); - nodeMeta.put(CZXID, String.valueOf(s.getCzxid())); - nodeMeta - .put(DATA_LENGTH, String.valueOf(s.getDataLength())); - nodeMeta.put(EPHEMERAL_OWNER, String.valueOf(s - .getEphemeralOwner())); - nodeMeta.put(M_TIME, String.valueOf(s.getMtime())); - nodeMeta.put(MZXID, String.valueOf(s.getMzxid())); - nodeMeta.put(NUM_CHILDREN, String.valueOf(s - .getNumChildren())); - nodeMeta.put(PZXID, String.valueOf(s.getPzxid())); - nodeMeta.put(VERSION, String.valueOf(s.getVersion())); - } - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred retrieving meta data for node: " - + nodePath, e); - } - } - return nodeMeta; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# - * getNumChildren(java.lang.String) - */ - public int getNumChildren(String nodePath) { - if (connected) { - try { - Stat s = zooKeeper.exists(nodePath, false); - if (s != null) { - return s.getNumChildren(); - } - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred getting the number of children of node: " - + nodePath, e); - } - } - return -1; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# - * hasChildren(java.lang.String) - */ - public boolean hasChildren(String nodePath) { - return getNumChildren(nodePath) > 0; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# - * isAllowsChildren(java.lang.String) - */ - public boolean isAllowsChildren(String nodePath) { - if (connected) { - try { - Stat s = zooKeeper.exists(nodePath, false); - if (s != null) { - return s.getEphemeralOwner() == 0; - } - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred determining whether node is allowed children: " - + nodePath, e); - } - } - return false; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# - * getSessionMeta() - */ - public Map getSessionMeta() { - Map sessionMeta = new LinkedHashMap(); - try { - if (zooKeeper != null) { - - sessionMeta.put(SESSION_ID, String.valueOf(zooKeeper - .getSessionId())); - sessionMeta.put(SESSION_STATE, String.valueOf(zooKeeper - .getState().toString())); - sessionMeta.put(CONNECT_STRING, this.connectString); - sessionMeta.put(SESSION_TIMEOUT, String - .valueOf(this.sessionTimeout)); - } - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred retrieving session meta data.", e); - } - return sessionMeta; - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.ZooInspectorNodeTreeManager#createNode - * (java.lang.String, java.lang.String) - */ - public boolean createNode(String parent, String nodeName) { - if (connected) { - try { - String[] nodeElements = nodeName.split("/"); - for (String nodeElement : nodeElements) { - String node = parent + "/" + nodeElement; - Stat s = zooKeeper.exists(node, false); - if (s == null) { - zooKeeper.create(node, this.encryptionManager - .encryptData(null), Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT); - parent = node; - } - } - return true; - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred creating node: " + parent + "/" - + nodeName, e); - } - } - return false; - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.ZooInspectorNodeTreeManager#deleteNode - * (java.lang.String) - */ - public boolean deleteNode(String nodePath) { - if (connected) { - try { - Stat s = zooKeeper.exists(nodePath, false); - if (s != null) { - List children = zooKeeper.getChildren(nodePath, - false); - for (String child : children) { - String node = nodePath + "/" + child; - deleteNode(node); - } - zooKeeper.delete(nodePath, -1); - } - return true; - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred deleting node: " + nodePath, e); - } - } - return false; - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager#setData - * (java.lang.String, java.lang.String) - */ - public boolean setData(String nodePath, String data) { - if (connected) { - try { - zooKeeper.setData(nodePath, this.encryptionManager - .encryptData(data), -1); - return true; - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occurred setting data for node: " + nodePath, e); - } - } - return false; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# - * getConnectionPropertiesTemplate() - */ - public Pair>, Map> getConnectionPropertiesTemplate() { - Map> template = new LinkedHashMap>(); - template.put(CONNECT_STRING, Arrays - .asList(new String[] { defaultHosts })); - template.put(SESSION_TIMEOUT, Arrays - .asList(new String[] { defaultTimeout })); - template.put(DATA_ENCRYPTION_MANAGER, Arrays - .asList(new String[] { defaultEncryptionManager })); - Map labels = new LinkedHashMap(); - labels.put(CONNECT_STRING, "Connect String"); - labels.put(SESSION_TIMEOUT, "Session Timeout"); - labels.put(DATA_ENCRYPTION_MANAGER, "Data Encryption Manager"); - return new Pair>, Map>( - template, labels); - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.ZooInspectorManager#addWatchers - * (java.util.Collection, - * org.apache.zookeeper.inspector.manager.NodeListener) - */ - public void addWatchers(Collection selectedNodes, - NodeListener nodeListener) { - // add watcher for each node and add node to collection of - // watched nodes - if (connected) { - for (String node : selectedNodes) { - if (!watchers.containsKey(node)) { - try { - watchers.put(node, new NodeWatcher(node, nodeListener, - zooKeeper)); - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occured adding node watcher for node: " - + node, e); - } - } - } - } - } - - /* - * (non-Javadoc) - * - * @see - * org.apache.zookeeper.inspector.manager.ZooInspectorManager#removeWatchers - * (java.util.Collection) - */ - public void removeWatchers(Collection selectedNodes) { - // remove watcher for each node and remove node from - // collection of watched nodes - if (connected) { - for (String node : selectedNodes) { - if (watchers.containsKey(node)) { - NodeWatcher watcher = watchers.remove(node); - if (watcher != null) { - watcher.stop(); - } - } - } - } - } - - /** - * A Watcher which will re-add itself every time an event is fired - * - */ - public class NodeWatcher implements Watcher { - - private final String nodePath; - private final NodeListener nodeListener; - private final ZooKeeper zookeeper; - private boolean closed = false; - - /** - * @param nodePath - * - the path to the node to watch - * @param nodeListener - * the {@link NodeListener} for this node - * @param zookeeper - * - a {@link ZooKeeper} to use to access zookeeper - * @throws InterruptedException - * @throws KeeperException - */ - public NodeWatcher(String nodePath, NodeListener nodeListener, - ZooKeeper zookeeper) throws KeeperException, - InterruptedException { - this.nodePath = nodePath; - this.nodeListener = nodeListener; - this.zookeeper = zookeeper; - Stat s = zooKeeper.exists(nodePath, this); - if (s != null) { - zookeeper.getChildren(nodePath, this); - } - } - - public void process(WatchedEvent event) { - if (!closed) { - try { - if (event.getType() != EventType.NodeDeleted) { - - Stat s = zooKeeper.exists(nodePath, this); - if (s != null) { - zookeeper.getChildren(nodePath, this); - } - } - } catch (Exception e) { - LoggerFactory.getLogger().error( - "Error occured re-adding node watcherfor node " - + nodePath, e); - } - nodeListener.processEvent(event.getPath(), event.getType() - .name(), null); - } - } - - /** - * - */ - public void stop() { - this.closed = true; - } - - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# - * loadNodeViewersFile(java.io.File) - */ - public List loadNodeViewersFile(File selectedFile) - throws IOException { - List result = new ArrayList(); - if (defaultNodeViewersFile.exists()) { - FileReader reader = new FileReader(selectedFile); - try { - BufferedReader buff = new BufferedReader(reader); - try { - while (buff.ready()) { - String line = buff.readLine(); - if (line != null && line.length() > 0) { - result.add(line); - } - } - } finally { - buff.close(); - } - } finally { - reader.close(); - } - } - return result; - } - - private void loadDefaultConnectionFile() throws IOException { - if (defaultConnectionFile.exists()) { - Properties props = new Properties(); - - FileReader reader = new FileReader(defaultConnectionFile); - try { - props.load(reader); - } finally { - reader.close(); - } - defaultEncryptionManager = props - .getProperty(DATA_ENCRYPTION_MANAGER) == null ? "org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager" - : props.getProperty(DATA_ENCRYPTION_MANAGER); - defaultTimeout = props.getProperty(SESSION_TIMEOUT) == null ? "5000" - : props.getProperty(SESSION_TIMEOUT); - defaultHosts = props.getProperty(CONNECT_STRING) == null ? "localhost:2181" - : props.getProperty(CONNECT_STRING); - } else { - defaultEncryptionManager = "org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager"; - defaultTimeout = "5000"; - defaultHosts = "localhost:2181"; - } - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# - * saveDefaultConnectionFile(java.util.Properties) - */ - public void saveDefaultConnectionFile(Properties props) throws IOException { - File defaultDir = defaultConnectionFile.getParentFile(); - if (!defaultDir.exists()) { - if (!defaultDir.mkdirs()) { - throw new IOException( - "Failed to create configuration directory: " - + defaultDir.getAbsolutePath()); - } - } - if (!defaultConnectionFile.exists()) { - if (!defaultConnectionFile.createNewFile()) { - throw new IOException( - "Failed to create default connection file: " - + defaultConnectionFile.getAbsolutePath()); - } - } - FileWriter writer = new FileWriter(defaultConnectionFile); - try { - props.store(writer, "Default connection for ZooInspector"); - } finally { - writer.close(); - } - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# - * saveNodeViewersFile(java.io.File, java.util.List) - */ - public void saveNodeViewersFile(File selectedFile, - List nodeViewersClassNames) throws IOException { - if (!selectedFile.exists()) { - if (!selectedFile.createNewFile()) { - throw new IOException( - "Failed to create node viewers configuration file: " - + selectedFile.getAbsolutePath()); - } - } - FileWriter writer = new FileWriter(selectedFile); - try { - BufferedWriter buff = new BufferedWriter(writer); - try { - for (String nodeViewersClassName : nodeViewersClassNames) { - buff.append(nodeViewersClassName); - buff.append("\n"); - } - } finally { - buff.flush(); - buff.close(); - } - } finally { - writer.close(); - } - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# - * setDefaultNodeViewerConfiguration(java.io.File, java.util.List) - */ - public void setDefaultNodeViewerConfiguration( - List nodeViewersClassNames) throws IOException { - File defaultDir = defaultNodeViewersFile.getParentFile(); - if (!defaultDir.exists()) { - if (!defaultDir.mkdirs()) { - throw new IOException( - "Failed to create configuration directory: " - + defaultDir.getAbsolutePath()); - } - } - saveNodeViewersFile(defaultNodeViewersFile, nodeViewersClassNames); - } - - public List getDefaultNodeViewerConfiguration() throws IOException { - return loadNodeViewersFile(defaultNodeViewersFile); - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# - * getLastConnectionProps() - */ - public Properties getLastConnectionProps() { - return this.lastConnectionProps; - } - - /* - * (non-Javadoc) - * - * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# - * setLastConnectionProps(java.util.Properties) - */ - public void setLastConnectionProps(Properties connectionProps) { - this.lastConnectionProps = connectionProps; - } -} +/** + * 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.inspector.manager; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.Watcher.Event.EventType; +import org.apache.zookeeper.Watcher.Event.KeeperState; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooDefs.Perms; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager; +import org.apache.zookeeper.inspector.encryption.DataEncryptionManager; +import org.apache.zookeeper.inspector.logger.LoggerFactory; +import org.apache.zookeeper.retry.ZooKeeperRetry; + +/** + * A default implementation of {@link ZooInspectorManager} for connecting to + * zookeeper instances + */ +public class ZooInspectorManagerImpl implements ZooInspectorManager { + private static final String A_VERSION = "ACL Version"; + private static final String C_TIME = "Creation Time"; + private static final String C_VERSION = "Children Version"; + private static final String CZXID = "Creation ID"; + private static final String DATA_LENGTH = "Data Length"; + private static final String EPHEMERAL_OWNER = "Ephemeral Owner"; + private static final String M_TIME = "Last Modified Time"; + private static final String MZXID = "Modified ID"; + private static final String NUM_CHILDREN = "Number of Children"; + private static final String PZXID = "Node ID"; + private static final String VERSION = "Data Version"; + private static final String ACL_PERMS = "Permissions"; + private static final String ACL_SCHEME = "Scheme"; + private static final String ACL_ID = "Id"; + private static final String SESSION_STATE = "Session State"; + private static final String SESSION_ID = "Session ID"; + /** + * The key used for the connect string in the connection properties file + */ + public static final String CONNECT_STRING = "hosts"; + /** + * The key used for the session timeout in the connection properties file + */ + public static final String SESSION_TIMEOUT = "timeout"; + /** + * The key used for the data encryption manager in the connection properties + * file + */ + public static final String DATA_ENCRYPTION_MANAGER = "encryptionManager"; + /** + * The key used for the authentication scheme in the connection properties file + */ + public static final String AUTH_SCHEME_KEY = "authScheme"; + /** + * The key used for the authentication data in the connection properties file + */ + public static final String AUTH_DATA_KEY = "authData"; + + private static final File defaultNodeViewersFile = new File( + "./config/defaultNodeVeiwers.cfg"); + private static final File defaultConnectionFile = new File( + "./config/defaultConnectionSettings.cfg"); + + private DataEncryptionManager encryptionManager; + private String connectString; + private int sessionTimeout; + private ZooKeeper zooKeeper; + private final Map watchers = new HashMap(); + protected boolean connected = true; + private Properties lastConnectionProps; + private String defaultEncryptionManager; + private String defaultTimeout; + private String defaultHosts; + private String defaultAuthScheme; + private String defaultAuthValue; + + /** + * @throws IOException + * - thrown if the default connection settings cannot be loaded + * + */ + public ZooInspectorManagerImpl() throws IOException { + loadDefaultConnectionFile(); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.ZooInspectorManager#connect(java + * .util.Properties) + */ + public boolean connect(Properties connectionProps) { + try { + if (this.zooKeeper == null) { + String connectString = connectionProps + .getProperty(CONNECT_STRING); + String sessionTimeout = connectionProps + .getProperty(SESSION_TIMEOUT); + String encryptionManager = connectionProps + .getProperty(DATA_ENCRYPTION_MANAGER); + String authScheme = connectionProps + .getProperty(AUTH_SCHEME_KEY); + String authData = connectionProps + .getProperty(AUTH_DATA_KEY); + + if (connectString == null || sessionTimeout == null) { + throw new IllegalArgumentException( + "Both connect string and session timeout are required."); + } + if (encryptionManager == null) { + this.encryptionManager = new BasicDataEncryptionManager(); + } else { + Class clazz = Class.forName(encryptionManager); + + if (Arrays.asList(clazz.getInterfaces()).contains( + DataEncryptionManager.class)) { + this.encryptionManager = (DataEncryptionManager) Class + .forName(encryptionManager).newInstance(); + } else { + throw new IllegalArgumentException( + "Data encryption manager must implement DataEncryptionManager interface"); + } + } + this.connectString = connectString; + this.sessionTimeout = Integer.valueOf(sessionTimeout); + this.zooKeeper = new ZooKeeperRetry(connectString, Integer + .valueOf(sessionTimeout), new Watcher() { + + public void process(WatchedEvent event) { + if (event.getState() == KeeperState.Expired) { + connected = false; + } + } + }); + if (authData != null && authData.length() > 0){ + this.zooKeeper.addAuthInfo(authScheme, authData.getBytes()); + } + ((ZooKeeperRetry) this.zooKeeper).setRetryLimit(10); + connected = ((ZooKeeperRetry) this.zooKeeper).testConnection(); + } + } catch (Exception e) { + connected = false; + e.printStackTrace(); + } + if (!connected){ + disconnect(); + } + return connected; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.ZooInspectorManager#disconnect() + */ + public boolean disconnect() { + try { + if (this.zooKeeper != null) { + this.zooKeeper.close(); + this.zooKeeper = null; + connected = false; + removeWatchers(this.watchers.keySet()); + return true; + } + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred while disconnecting from ZooKeeper server", + e); + } + return false; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# + * getChildren(java.lang.String) + */ + public List getChildren(String nodePath) { + if (connected) { + try { + + return zooKeeper.getChildren(nodePath, false); + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred retrieving children of node: " + + nodePath, e); + } + } + return null; + + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#getData + * (java.lang.String) + */ + public String getData(String nodePath) { + if (connected) { + try { + if (nodePath.length() == 0) { + nodePath = "/"; + } + Stat s = zooKeeper.exists(nodePath, false); + if (s != null) { + return this.encryptionManager.decryptData(zooKeeper + .getData(nodePath, false, s)); + } + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred getting data for node: " + nodePath, e); + } + } + return null; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# + * getNodeChild(java.lang.String, int) + */ + public String getNodeChild(String nodePath, int childIndex) { + if (connected) { + try { + Stat s = zooKeeper.exists(nodePath, false); + if (s != null) { + return this.zooKeeper.getChildren(nodePath, false).get( + childIndex); + } + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred retrieving child " + childIndex + + " of node: " + nodePath, e); + } + } + return null; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# + * getNodeIndex(java.lang.String) + */ + public int getNodeIndex(String nodePath) { + if (connected) { + int index = nodePath.lastIndexOf("/"); + if (index == -1 + || (!nodePath.equals("/") && nodePath.charAt(nodePath + .length() - 1) == '/')) { + throw new IllegalArgumentException("Invalid node path: " + + nodePath); + } + String parentPath = nodePath.substring(0, index); + String child = nodePath.substring(index + 1); + if (parentPath != null && parentPath.length() > 0) { + List children = this.getChildren(parentPath); + if (children != null) { + return children.indexOf(child); + } + } + } + return -1; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager#getACLs + * (java.lang.String) + */ + public List> getACLs(String nodePath) { + List> returnACLs = new ArrayList>(); + if (connected) { + try { + if (nodePath.length() == 0) { + nodePath = "/"; + } + Stat s = zooKeeper.exists(nodePath, false); + if (s != null) { + List acls = zooKeeper.getACL(nodePath, s); + for (ACL acl : acls) { + Map aclMap = new LinkedHashMap(); + aclMap.put(ACL_SCHEME, acl.getId().getScheme()); + aclMap.put(ACL_ID, acl.getId().getId()); + StringBuilder sb = new StringBuilder(); + int perms = acl.getPerms(); + boolean addedPerm = false; + if ((perms & Perms.READ) == Perms.READ) { + sb.append("Read"); + addedPerm = true; + } + if (addedPerm) { + sb.append(", "); + } + if ((perms & Perms.WRITE) == Perms.WRITE) { + sb.append("Write"); + addedPerm = true; + } + if (addedPerm) { + sb.append(", "); + } + if ((perms & Perms.CREATE) == Perms.CREATE) { + sb.append("Create"); + addedPerm = true; + } + if (addedPerm) { + sb.append(", "); + } + if ((perms & Perms.DELETE) == Perms.DELETE) { + sb.append("Delete"); + addedPerm = true; + } + if (addedPerm) { + sb.append(", "); + } + if ((perms & Perms.ADMIN) == Perms.ADMIN) { + sb.append("Admin"); + addedPerm = true; + } + aclMap.put(ACL_PERMS, sb.toString()); + returnACLs.add(aclMap); + } + } + } catch (InterruptedException e) { + LoggerFactory.getLogger().error( + "Error occurred retrieving ACLs of node: " + nodePath, + e); + } catch (KeeperException e) { + LoggerFactory.getLogger().error( + "Error occurred retrieving ACLs of node: " + nodePath, + e); + } + } + return returnACLs; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# + * getNodeMeta(java.lang.String) + */ + public Map getNodeMeta(String nodePath) { + Map nodeMeta = new LinkedHashMap(); + if (connected) { + try { + if (nodePath.length() == 0) { + nodePath = "/"; + } + Stat s = zooKeeper.exists(nodePath, false); + if (s != null) { + nodeMeta.put(A_VERSION, String.valueOf(s.getAversion())); + nodeMeta.put(C_TIME, String.valueOf(s.getCtime())); + nodeMeta.put(C_VERSION, String.valueOf(s.getCversion())); + nodeMeta.put(CZXID, String.valueOf(s.getCzxid())); + nodeMeta + .put(DATA_LENGTH, String.valueOf(s.getDataLength())); + nodeMeta.put(EPHEMERAL_OWNER, String.valueOf(s + .getEphemeralOwner())); + nodeMeta.put(M_TIME, String.valueOf(s.getMtime())); + nodeMeta.put(MZXID, String.valueOf(s.getMzxid())); + nodeMeta.put(NUM_CHILDREN, String.valueOf(s + .getNumChildren())); + nodeMeta.put(PZXID, String.valueOf(s.getPzxid())); + nodeMeta.put(VERSION, String.valueOf(s.getVersion())); + } + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred retrieving meta data for node: " + + nodePath, e); + } + } + return nodeMeta; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# + * getNumChildren(java.lang.String) + */ + public int getNumChildren(String nodePath) { + if (connected) { + try { + Stat s = zooKeeper.exists(nodePath, false); + if (s != null) { + return s.getNumChildren(); + } + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred getting the number of children of node: " + + nodePath, e); + } + } + return -1; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# + * hasChildren(java.lang.String) + */ + public boolean hasChildren(String nodePath) { + return getNumChildren(nodePath) > 0; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# + * isAllowsChildren(java.lang.String) + */ + public boolean isAllowsChildren(String nodePath) { + if (connected) { + try { + Stat s = zooKeeper.exists(nodePath, false); + if (s != null) { + return s.getEphemeralOwner() == 0; + } + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred determining whether node is allowed children: " + + nodePath, e); + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorReadOnlyManager# + * getSessionMeta() + */ + public Map getSessionMeta() { + Map sessionMeta = new LinkedHashMap(); + try { + if (zooKeeper != null) { + + sessionMeta.put(SESSION_ID, String.valueOf(zooKeeper + .getSessionId())); + sessionMeta.put(SESSION_STATE, String.valueOf(zooKeeper + .getState().toString())); + sessionMeta.put(CONNECT_STRING, this.connectString); + sessionMeta.put(SESSION_TIMEOUT, String + .valueOf(this.sessionTimeout)); + } + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred retrieving session meta data.", e); + } + return sessionMeta; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.ZooInspectorNodeTreeManager#createNode + * (java.lang.String, java.lang.String) + */ + public boolean createNode(String parent, String nodeName) { + if (connected) { + try { + String[] nodeElements = nodeName.split("/"); + for (String nodeElement : nodeElements) { + String node = parent + "/" + nodeElement; + Stat s = zooKeeper.exists(node, false); + if (s == null) { + zooKeeper.create(node, this.encryptionManager + .encryptData(null), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + parent = node; + } + } + return true; + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred creating node: " + parent + "/" + + nodeName, e); + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.ZooInspectorNodeTreeManager#deleteNode + * (java.lang.String) + */ + public boolean deleteNode(String nodePath) { + if (connected) { + try { + Stat s = zooKeeper.exists(nodePath, false); + if (s != null) { + List children = zooKeeper.getChildren(nodePath, + false); + for (String child : children) { + String node = nodePath + "/" + child; + deleteNode(node); + } + zooKeeper.delete(nodePath, -1); + } + return true; + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred deleting node: " + nodePath, e); + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.ZooInspectorNodeManager#setData + * (java.lang.String, java.lang.String) + */ + public boolean setData(String nodePath, String data) { + if (connected) { + try { + zooKeeper.setData(nodePath, this.encryptionManager + .encryptData(data), -1); + return true; + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred setting data for node: " + nodePath, e); + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# + * getConnectionPropertiesTemplate() + */ + public Pair>, Map> getConnectionPropertiesTemplate() { + Map> template = new LinkedHashMap>(); + template.put(CONNECT_STRING, Arrays + .asList(new String[] { defaultHosts })); + template.put(SESSION_TIMEOUT, Arrays + .asList(new String[] { defaultTimeout })); + template.put(DATA_ENCRYPTION_MANAGER, Arrays + .asList(new String[] { defaultEncryptionManager })); + template.put(AUTH_SCHEME_KEY, Arrays + .asList(new String[] { defaultAuthScheme })); + template.put(AUTH_DATA_KEY, Arrays + .asList(new String[] { defaultAuthValue })); + Map labels = new LinkedHashMap(); + labels.put(CONNECT_STRING, "Connect String"); + labels.put(SESSION_TIMEOUT, "Session Timeout"); + labels.put(DATA_ENCRYPTION_MANAGER, "Data Encryption Manager"); + labels.put(AUTH_SCHEME_KEY, "Authentication Scheme"); + labels.put(AUTH_DATA_KEY, "Authentication Data"); + return new Pair>, Map>( + template, labels); + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.ZooInspectorManager#addWatchers + * (java.util.Collection, + * org.apache.zookeeper.inspector.manager.NodeListener) + */ + public void addWatchers(Collection selectedNodes, + NodeListener nodeListener) { + // add watcher for each node and add node to collection of + // watched nodes + if (connected) { + for (String node : selectedNodes) { + if (!watchers.containsKey(node)) { + try { + watchers.put(node, new NodeWatcher(node, nodeListener, + zooKeeper)); + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred adding node watcher for node: " + + node, e); + } + } + } + } + } + + /* + * (non-Javadoc) + * + * @see + * org.apache.zookeeper.inspector.manager.ZooInspectorManager#removeWatchers + * (java.util.Collection) + */ + public void removeWatchers(Collection selectedNodes) { + // remove watcher for each node and remove node from + // collection of watched nodes + if (connected) { + for (String node : selectedNodes) { + if (watchers.containsKey(node)) { + NodeWatcher watcher = watchers.remove(node); + if (watcher != null) { + watcher.stop(); + } + } + } + } + } + + /** + * A Watcher which will re-add itself every time an event is fired + * + */ + public class NodeWatcher implements Watcher { + + private final String nodePath; + private final NodeListener nodeListener; + private final ZooKeeper zookeeper; + private boolean closed = false; + + /** + * @param nodePath + * - the path to the node to watch + * @param nodeListener + * the {@link NodeListener} for this node + * @param zookeeper + * - a {@link ZooKeeper} to use to access zookeeper + * @throws InterruptedException + * @throws KeeperException + */ + public NodeWatcher(String nodePath, NodeListener nodeListener, + ZooKeeper zookeeper) throws KeeperException, + InterruptedException { + this.nodePath = nodePath; + this.nodeListener = nodeListener; + this.zookeeper = zookeeper; + Stat s = zooKeeper.exists(nodePath, this); + if (s != null) { + zookeeper.getChildren(nodePath, this); + } + } + + public void process(WatchedEvent event) { + if (!closed) { + try { + if (event.getType() != EventType.NodeDeleted) { + + Stat s = zooKeeper.exists(nodePath, this); + if (s != null) { + zookeeper.getChildren(nodePath, this); + } + } + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred re-adding node watcherfor node " + + nodePath, e); + } + nodeListener.processEvent(event.getPath(), event.getType() + .name(), null); + } + } + + /** + * + */ + public void stop() { + this.closed = true; + } + + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# + * loadNodeViewersFile(java.io.File) + */ + public List loadNodeViewersFile(File selectedFile) + throws IOException { + List result = new ArrayList(); + if (defaultNodeViewersFile.exists()) { + FileReader reader = new FileReader(selectedFile); + try { + BufferedReader buff = new BufferedReader(reader); + try { + while (buff.ready()) { + String line = buff.readLine(); + if (line != null && line.length() > 0 && !line.startsWith("#")) { + result.add(line); + } + } + } finally { + buff.close(); + } + } finally { + reader.close(); + } + } + return result; + } + + private void loadDefaultConnectionFile() throws IOException { + if (defaultConnectionFile.exists()) { + Properties props = new Properties(); + + FileReader reader = new FileReader(defaultConnectionFile); + try { + props.load(reader); + } finally { + reader.close(); + } + defaultEncryptionManager = props + .getProperty(DATA_ENCRYPTION_MANAGER) == null ? "org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager" + : props.getProperty(DATA_ENCRYPTION_MANAGER); + defaultTimeout = props.getProperty(SESSION_TIMEOUT) == null ? "5000" + : props.getProperty(SESSION_TIMEOUT); + defaultHosts = props.getProperty(CONNECT_STRING) == null ? "localhost:2181" + : props.getProperty(CONNECT_STRING); + defaultAuthScheme = props.getProperty(AUTH_SCHEME_KEY) == null ? "" + : props.getProperty(AUTH_SCHEME_KEY); + defaultAuthValue = props.getProperty(AUTH_DATA_KEY) == null ? "" + : props.getProperty(AUTH_DATA_KEY); + } else { + defaultEncryptionManager = "org.apache.zookeeper.inspector.encryption.BasicDataEncryptionManager"; + defaultTimeout = "5000"; + defaultHosts = "localhost:2181"; + defaultAuthScheme = ""; + defaultAuthValue = ""; + } + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# + * saveDefaultConnectionFile(java.util.Properties) + */ + public void saveDefaultConnectionFile(Properties props) throws IOException { + File defaultDir = defaultConnectionFile.getParentFile(); + if (!defaultDir.exists()) { + if (!defaultDir.mkdirs()) { + throw new IOException( + "Failed to create configuration directory: " + + defaultDir.getAbsolutePath()); + } + } + if (!defaultConnectionFile.exists()) { + if (!defaultConnectionFile.createNewFile()) { + throw new IOException( + "Failed to create default connection file: " + + defaultConnectionFile.getAbsolutePath()); + } + } + FileWriter writer = new FileWriter(defaultConnectionFile); + try { + props.store(writer, "Default connection for ZooInspector"); + } finally { + writer.close(); + } + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# + * saveNodeViewersFile(java.io.File, java.util.List) + */ + public void saveNodeViewersFile(File selectedFile, + List nodeViewersClassNames) throws IOException { + if (!selectedFile.exists()) { + if (!selectedFile.createNewFile()) { + throw new IOException( + "Failed to create node viewers configuration file: " + + selectedFile.getAbsolutePath()); + } + } + FileWriter writer = new FileWriter(selectedFile); + try { + BufferedWriter buff = new BufferedWriter(writer); + try { + for (String nodeViewersClassName : nodeViewersClassNames) { + buff.append(nodeViewersClassName); + buff.append("\n"); + } + } finally { + buff.flush(); + buff.close(); + } + } finally { + writer.close(); + } + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# + * setDefaultNodeViewerConfiguration(java.io.File, java.util.List) + */ + public void setDefaultNodeViewerConfiguration( + List nodeViewersClassNames) throws IOException { + File defaultDir = defaultNodeViewersFile.getParentFile(); + if (!defaultDir.exists()) { + if (!defaultDir.mkdirs()) { + throw new IOException( + "Failed to create configuration directory: " + + defaultDir.getAbsolutePath()); + } + } + saveNodeViewersFile(defaultNodeViewersFile, nodeViewersClassNames); + } + + public List getDefaultNodeViewerConfiguration() throws IOException { + return loadNodeViewersFile(defaultNodeViewersFile); + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# + * getLastConnectionProps() + */ + public Properties getLastConnectionProps() { + return this.lastConnectionProps; + } + + /* + * (non-Javadoc) + * + * @seeorg.apache.zookeeper.inspector.manager.ZooInspectorManager# + * setLastConnectionProps(java.util.Properties) + */ + public void setLastConnectionProps(Properties connectionProps) { + this.lastConnectionProps = connectionProps; + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorNodeManager.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorNodeManager.java index c81fa78b833..0c62547d0bb 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorNodeManager.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/ZooInspectorNodeManager.java @@ -1,33 +1,33 @@ -/** - * 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.inspector.manager; - -/** - * A Manager for all interactions between the application and the nodes in a - * Zookeeper instance - * */ -public interface ZooInspectorNodeManager extends ZooInspectorReadOnlyManager { - /** - * @param nodePath - * - the path to the node on which to set the data - * @param data - * - the data to set on the this node - * @return true if the data for the node was successfully updated - */ - public boolean setData(String nodePath, String data); -} +/** + * 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.inspector.manager; + +/** + * A Manager for all interactions between the application and the nodes in a + * Zookeeper instance + * */ +public interface ZooInspectorNodeManager extends ZooInspectorReadOnlyManager { + /** + * @param nodePath + * - the path to the node on which to set the data + * @param data + * - the data to set on the this node + * @return true if the data for the node was successfully updated + */ + public boolean setData(String nodePath, String data); +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/retry/ZooKeeperRetry.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/retry/ZooKeeperRetry.java index c5cf4458294..ce959a1a0c2 100644 --- a/src/contrib/zooinspector/src/java/org/apache/zookeeper/retry/ZooKeeperRetry.java +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/retry/ZooKeeperRetry.java @@ -1,288 +1,288 @@ -/** - * 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.retry; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.data.ACL; -import org.apache.zookeeper.data.Stat; -import org.apache.zookeeper.inspector.logger.LoggerFactory; - -/** - * A Class which extends {@link ZooKeeper} and will automatically retry calls to - * zookeeper if a {@link KeeperException.ConnectionLossException} occurs - */ -public class ZooKeeperRetry extends ZooKeeper { - - private boolean closed = false; - private final Watcher watcher; - private int limit = -1; - - /** - * @param connectString - * @param sessionTimeout - * @param watcher - * @throws IOException - */ - public ZooKeeperRetry(String connectString, int sessionTimeout, - Watcher watcher) throws IOException { - super(connectString, sessionTimeout, watcher); - this.watcher = watcher; - } - - /** - * @param connectString - * @param sessionTimeout - * @param watcher - * @param sessionId - * @param sessionPasswd - * @throws IOException - */ - public ZooKeeperRetry(String connectString, int sessionTimeout, - Watcher watcher, long sessionId, byte[] sessionPasswd) - throws IOException { - super(connectString, sessionTimeout, watcher, sessionId, sessionPasswd); - this.watcher = watcher; - } - - @Override - public synchronized void close() throws InterruptedException { - this.closed = true; - super.close(); - } - - @Override - public String create(String path, byte[] data, List acl, - CreateMode createMode) throws KeeperException, InterruptedException { - int count = 0; - do { - try { - return super.create(path, data, acl, createMode); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - if (exists(path, false) != null) { - return path; - } - } catch (KeeperException.NodeExistsException e) { - return path; - } - } while (!closed && (limit == -1 || count++ < limit)); - return null; - } - - @Override - public void delete(String path, int version) throws InterruptedException, - KeeperException { - int count = 0; - do { - try { - super.delete(path, version); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - if (exists(path, false) == null) { - return; - } - } catch (KeeperException.NoNodeException e) { - break; - } - } while (!closed && (limit == -1 || count++ < limit)); - } - - @Override - public Stat exists(String path, boolean watch) throws KeeperException, - InterruptedException { - int count = 0; - do { - try { - return super.exists(path, watch ? watcher : null); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - } - } while (!closed && (limit == -1 || count++ < limit)); - return null; - } - - @Override - public Stat exists(String path, Watcher watcher) throws KeeperException, - InterruptedException { - int count = 0; - do { - try { - return super.exists(path, watcher); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - } - } while (!closed && (limit == -1 || count++ < limit)); - return null; - } - - @Override - public List getACL(String path, Stat stat) throws KeeperException, - InterruptedException { - int count = 0; - do { - try { - return super.getACL(path, stat); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - } - } while (!closed && (limit == -1 || count++ < limit)); - return null; - } - - @Override - public List getChildren(String path, boolean watch) - throws KeeperException, InterruptedException { - int count = 0; - do { - try { - return super.getChildren(path, watch ? watcher : null); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - } - } while (!closed && (limit == -1 || count++ < limit)); - return new ArrayList(); - } - - @Override - public List getChildren(String path, Watcher watcher) - throws KeeperException, InterruptedException { - int count = 0; - do { - try { - return super.getChildren(path, watcher); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - } - } while (!closed && (limit == -1 || count++ < limit)); - return new ArrayList(); - } - - @Override - public byte[] getData(String path, boolean watch, Stat stat) - throws KeeperException, InterruptedException { - int count = 0; - do { - try { - return super.getData(path, watch ? watcher : null, stat); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - } - } while (!closed && (limit == -1 || count++ < limit)); - return null; - } - - @Override - public byte[] getData(String path, Watcher watcher, Stat stat) - throws KeeperException, InterruptedException { - int count = 0; - do { - try { - return super.getData(path, watcher, stat); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - } - } while (!closed && (limit == -1 || count++ < limit)); - return null; - } - - @Override - public Stat setACL(String path, List acl, int version) - throws KeeperException, InterruptedException { - int count = 0; - do { - try { - return super.setACL(path, acl, version); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - Stat s = exists(path, false); - if (s != null) { - if (getACL(path, s).equals(acl)) { - return s; - } - } else { - return null; - } - } - } while (!closed && (limit == -1 || count++ < limit)); - return null; - } - - @Override - public Stat setData(String path, byte[] data, int version) - throws KeeperException, InterruptedException { - int count = 0; - do { - try { - return super.setData(path, data, version); - } catch (KeeperException.ConnectionLossException e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - Stat s = exists(path, false); - if (s != null) { - if (getData(path, false, s) == data) { - return s; - } - } else { - return null; - } - } - } while (!closed && (limit == -1 || count++ < limit)); - return null; - } - - /** - * @param limit - */ - public void setRetryLimit(int limit) { - this.limit = limit; - } - - /** - * @return true if successfully connected to zookeeper - */ - public boolean testConnection() { - int count = 0; - do { - try { - return super.exists("/", null) != null; - } catch (Exception e) { - LoggerFactory.getLogger().warn( - "ZooKeeper connection lost. Trying to reconnect."); - } - } while (count++ < 5); - return false; - } - -} +/** + * 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.retry; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.inspector.logger.LoggerFactory; + +/** + * A Class which extends {@link ZooKeeper} and will automatically retry calls to + * zookeeper if a {@link KeeperException.ConnectionLossException} occurs + */ +public class ZooKeeperRetry extends ZooKeeper { + + private boolean closed = false; + private final Watcher watcher; + private int limit = -1; + + /** + * @param connectString + * @param sessionTimeout + * @param watcher + * @throws IOException + */ + public ZooKeeperRetry(String connectString, int sessionTimeout, + Watcher watcher) throws IOException { + super(connectString, sessionTimeout, watcher); + this.watcher = watcher; + } + + /** + * @param connectString + * @param sessionTimeout + * @param watcher + * @param sessionId + * @param sessionPasswd + * @throws IOException + */ + public ZooKeeperRetry(String connectString, int sessionTimeout, + Watcher watcher, long sessionId, byte[] sessionPasswd) + throws IOException { + super(connectString, sessionTimeout, watcher, sessionId, sessionPasswd); + this.watcher = watcher; + } + + @Override + public synchronized void close() throws InterruptedException { + this.closed = true; + super.close(); + } + + @Override + public String create(String path, byte[] data, List acl, + CreateMode createMode) throws KeeperException, InterruptedException { + int count = 0; + do { + try { + return super.create(path, data, acl, createMode); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + if (exists(path, false) != null) { + return path; + } + } catch (KeeperException.NodeExistsException e) { + return path; + } + } while (!closed && (limit == -1 || count++ < limit)); + return null; + } + + @Override + public void delete(String path, int version) throws InterruptedException, + KeeperException { + int count = 0; + do { + try { + super.delete(path, version); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + if (exists(path, false) == null) { + return; + } + } catch (KeeperException.NoNodeException e) { + break; + } + } while (!closed && (limit == -1 || count++ < limit)); + } + + @Override + public Stat exists(String path, boolean watch) throws KeeperException, + InterruptedException { + int count = 0; + do { + try { + return super.exists(path, watch ? watcher : null); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + } + } while (!closed && (limit == -1 || count++ < limit)); + return null; + } + + @Override + public Stat exists(String path, Watcher watcher) throws KeeperException, + InterruptedException { + int count = 0; + do { + try { + return super.exists(path, watcher); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + } + } while (!closed && (limit == -1 || count++ < limit)); + return null; + } + + @Override + public List getACL(String path, Stat stat) throws KeeperException, + InterruptedException { + int count = 0; + do { + try { + return super.getACL(path, stat); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + } + } while (!closed && (limit == -1 || count++ < limit)); + return null; + } + + @Override + public List getChildren(String path, boolean watch) + throws KeeperException, InterruptedException { + int count = 0; + do { + try { + return super.getChildren(path, watch ? watcher : null); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + } + } while (!closed && (limit == -1 || count++ < limit)); + return new ArrayList(); + } + + @Override + public List getChildren(String path, Watcher watcher) + throws KeeperException, InterruptedException { + int count = 0; + do { + try { + return super.getChildren(path, watcher); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + } + } while (!closed && (limit == -1 || count++ < limit)); + return new ArrayList(); + } + + @Override + public byte[] getData(String path, boolean watch, Stat stat) + throws KeeperException, InterruptedException { + int count = 0; + do { + try { + return super.getData(path, watch ? watcher : null, stat); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + } + } while (!closed && (limit == -1 || count++ < limit)); + return null; + } + + @Override + public byte[] getData(String path, Watcher watcher, Stat stat) + throws KeeperException, InterruptedException { + int count = 0; + do { + try { + return super.getData(path, watcher, stat); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + } + } while (!closed && (limit == -1 || count++ < limit)); + return null; + } + + @Override + public Stat setACL(String path, List acl, int aclVersion) + throws KeeperException, InterruptedException { + int count = 0; + do { + try { + return super.setACL(path, acl, aclVersion); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + Stat s = exists(path, false); + if (s != null) { + if (getACL(path, s).equals(acl)) { + return s; + } + } else { + return null; + } + } + } while (!closed && (limit == -1 || count++ < limit)); + return null; + } + + @Override + public Stat setData(String path, byte[] data, int version) + throws KeeperException, InterruptedException { + int count = 0; + do { + try { + return super.setData(path, data, version); + } catch (KeeperException.ConnectionLossException e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + Stat s = exists(path, false); + if (s != null) { + if (getData(path, false, s) == data) { + return s; + } + } else { + return null; + } + } + } while (!closed && (limit == -1 || count++ < limit)); + return null; + } + + /** + * @param limit + */ + public void setRetryLimit(int limit) { + this.limit = limit; + } + + /** + * @return true if successfully connected to zookeeper + */ + public boolean testConnection() { + int count = 0; + do { + try { + return super.exists("/", null) != null; + } catch (Exception e) { + LoggerFactory.getLogger().warn( + "ZooKeeper connection lost. Trying to reconnect."); + } + } while (count++ < 5); + return false; + } + +} diff --git a/src/contrib/zooinspector/zooInspector-dev.sh b/src/contrib/zooinspector/zooInspector-dev.sh index a57b0beba8e..50472040ad1 100755 --- a/src/contrib/zooinspector/zooInspector-dev.sh +++ b/src/contrib/zooinspector/zooInspector-dev.sh @@ -1,18 +1,18 @@ -#!/bin/sh - -# 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. - +#!/bin/sh + +# 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. + java -cp ../../../build/contrib/ZooInspector/zookeeper-dev-ZooInspector.jar:../../../build/lib/log4j-1.2.15.jar:lib/zookeeper-3.3.0.jar:lib/jtoaster-1.0.4.jar:lib org.apache.zookeeper.inspector.ZooInspector \ No newline at end of file diff --git a/src/contrib/zooinspector/zooInspector.cmd b/src/contrib/zooinspector/zooInspector.cmd index 0b298a21f8c..4fa3ab23374 100644 --- a/src/contrib/zooinspector/zooInspector.cmd +++ b/src/contrib/zooinspector/zooInspector.cmd @@ -1,18 +1,18 @@ -#!/bin/sh - -# 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. - +#!/bin/sh + +# 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. + java -cp zookeeper-dev-ZooInspector.jar;lib/log4j-1.2.15.jar;lib/zookeeper-3.3.0.jar;lib/jToaster-1.0.4.jar;lib org.apache.zookeeper.inspector.ZooInspector \ No newline at end of file diff --git a/src/docs/forrest.properties b/src/docs/forrest.properties index 16e4f9fd2cb..70cf81d7262 100644 --- a/src/docs/forrest.properties +++ b/src/docs/forrest.properties @@ -45,6 +45,7 @@ #project.status=status.xml #project.content-dir=src/documentation +project.configfile=${project.home}/src/documentation/conf/cli.xconf #project.raw-content-dir=${project.content-dir}/content #project.conf-dir=${project.content-dir}/conf #project.sitemap-dir=${project.content-dir} diff --git a/src/docs/src/documentation/conf/cli.xconf b/src/docs/src/documentation/conf/cli.xconf new file mode 100644 index 00000000000..c6713408245 --- /dev/null +++ b/src/docs/src/documentation/conf/cli.xconf @@ -0,0 +1,328 @@ + + + + + + + + . + WEB-INF/cocoon.xconf + ../tmp/cocoon-work + ../site + + + + + + + + + + + + + + + index.html + + + + + + + */* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/docs/src/documentation/content/xdocs/bookkeeperStarted.xml b/src/docs/src/documentation/content/xdocs/bookkeeperStarted.xml index e65e00a8b18..74f6f7edce6 100644 --- a/src/docs/src/documentation/content/xdocs/bookkeeperStarted.xml +++ b/src/docs/src/documentation/content/xdocs/bookkeeperStarted.xml @@ -61,7 +61,7 @@ Download BookKeeper is distributed along with ZooKeeper. To get a ZooKeeper distribution, download a recent - + stable release from one of the Apache Download Mirrors. diff --git a/src/docs/src/documentation/content/xdocs/recipes.xml b/src/docs/src/documentation/content/xdocs/recipes.xml index c911e2d4b01..ead041beb10 100644 --- a/src/docs/src/documentation/content/xdocs/recipes.xml +++ b/src/docs/src/documentation/content/xdocs/recipes.xml @@ -203,7 +203,7 @@ if p is the lowest process - node in L, wait on highest process node in P + node in L, wait on highest process node in L @@ -604,7 +604,7 @@ - Watch for changes on "ELECTION/n_j", where j is the smallest + Watch for changes on "ELECTION/n_j", where j is the largest sequence number such that j < i and n_j is a znode in C; @@ -623,14 +623,14 @@ Otherwise, watch for changes on "ELECTION/n_j", where j is the - smallest sequence number such that j < i and n_j is a znode in C; + largest sequence number such that j < i and n_j is a znode in C; Note that the znode having no preceding znode on the list of children does not imply that the creator of this znode is aware that it is - the current leader. Applications may consider creating a separate to znode + the current leader. Applications may consider creating a separate znode to acknowledge that the leader has executed the leader procedure. diff --git a/src/docs/src/documentation/content/xdocs/releasenotes.xml b/src/docs/src/documentation/content/xdocs/releasenotes.xml deleted file mode 100644 index 6bce57b6eb7..00000000000 --- a/src/docs/src/documentation/content/xdocs/releasenotes.xml +++ /dev/null @@ -1,1250 +0,0 @@ - - - - -
                                                  - 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/site.xml b/src/docs/src/documentation/content/xdocs/site.xml index 34ac4b3b8b6..e49d92cf63e 100644 --- a/src/docs/src/documentation/content/xdocs/site.xml +++ b/src/docs/src/documentation/content/xdocs/site.xml @@ -29,7 +29,7 @@ This file contains an outline of the site's information content. It is used to: See http://forrest.apache.org/docs/linking.html for more info. --> - + @@ -74,14 +74,14 @@ See http://forrest.apache.org/docs/linking.html for more info. - - - + + + - - - + + + @@ -93,10 +93,8 @@ See http://forrest.apache.org/docs/linking.html for more info. - - - - + + diff --git a/src/docs/src/documentation/content/xdocs/tabs.xml b/src/docs/src/documentation/content/xdocs/tabs.xml index 739ebf2b6c8..aef7e59b083 100644 --- a/src/docs/src/documentation/content/xdocs/tabs.xml +++ b/src/docs/src/documentation/content/xdocs/tabs.xml @@ -29,8 +29,8 @@ directory (ends in '/'), in which case /index.html will be added --> - - + + diff --git a/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml b/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml index ee212e8347b..cc5da1368d2 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml @@ -74,29 +74,90 @@
                                                  Supported Platforms + ZooKeeper consists of multiple components. Some components are + supported broadly, and other components are supported only on a smaller + set of platforms. + - GNU/Linux is supported as a development and production - platform for both server and client. - - - Sun Solaris is supported as a development and production - platform for both server and client. + Client is the Java client + library, used by applications to connect to a ZooKeeper ensemble. + - FreeBSD is supported as a development and production - platform for clients only. Java NIO selector support in - the FreeBSD JVM is broken. + Server is the Java server + that runs on the ZooKeeper ensemble nodes. - Win32 is supported as a development - platform only for both server and client. + Native Client is a client + implemented in C, similar to the Java client, used by applications + to connect to a ZooKeeper ensemble. - MacOSX is supported as a development - platform only for both server and client. + Contrib refers to multiple + optional add-on components. + + The following matrix describes the level of support committed for + running each component on different operating system platforms. + + + Support Matrix + + + + Operating System + Client + Server + Native Client + Contrib + + + + + GNU/Linux + Development and Production + Development and Production + Development and Production + Development and Production + + + Solaris + Development and Production + Development and Production + Not Supported + Not Supported + + + FreeBSD + Development and Production + Development and Production + Not Supported + Not Supported + + + Windows + Development and Production + Development and Production + Not Supported + Not Supported + + + Mac OS X + Development Only + Development Only + Not Supported + Not Supported + + + +
                                                  + + For any operating system not explicitly mentioned as supported in + the matrix, components may or may not work. The ZooKeeper community + will fix obvious bugs that are reported for other platforms, but there + is no full support.
                                                  @@ -124,7 +185,28 @@ only handle the failure of a single machine; if two machines fail, the remaining two machines do not constitute a majority. However, with five machines ZooKeeper can handle the failure of two machines. - + + + As mentioned in the + ZooKeeper Getting Started Guide + , a minimum of three servers are required for a fault tolerant + clustered setup, and it is strongly recommended that you have an + odd number of servers. + + Usually three servers is more than enough for a production + install, but for maximum reliability during maintenance, you may + wish to install five servers. With three servers, if you perform + maintenance on one of them, you are vulnerable to a failure on one + of the other two servers during that maintenance. If you have five + of them running, you can take one down for maintenance, and know + that you're still OK if one of the other four suddenly fails. + + Your redundancy considerations should include all aspects of + your environment. If you have three ZooKeeper servers, but their + network cables are all plugged into the same network switch, then + the failure of that switch will take down your entire ensemble. + + Here are the steps to setting a server that will be part of an ensemble. These steps should be performed on every host in the ensemble: @@ -152,8 +234,8 @@ from: - - http://hadoop.apache.org/zookeeper/releases.html + + http://zookeeper.apache.org/releases.html @@ -219,34 +301,10 @@ server.3=zoo3:2888:3888 Test your deployment by connecting to the hosts: - - - In Java, you can run the following command to execute - simple operations: - - $ java -cp zookeeper.jar:lib/slf4j-api-1.6.1.jar:lib/slf4j-log4j12-1.6.1.jar:lib/log4j-1.2.15.jar:conf:src/java/lib/jline-0.9.94.jar \ - org.apache.zookeeper.ZooKeeperMain -server 127.0.0.1:2181 - - - - In C, you can compile either the single threaded client or - the multithreaded client: or n the c subdirectory in the - ZooKeeper sources. This compiles the single threaded - client: - - $ make cli_st - - And this compiles the mulithreaded client: - - $ make cli_mt - - - - Running either program gives you a shell in which to execute - simple file-system-like operations. To connect to ZooKeeper with the - multithreaded client, for example, you would run: + In Java, you can run the following command to execute + simple operations: - $ cli_mt 127.0.0.1:2181 + $ bin/zkCli.sh -server 127.0.0.1:2181
                                                  @@ -714,22 +772,6 @@ server.3=zoo3:2888:3888 - - traceFile - - - (Java system property: requestTraceFile) - - If this option is defined, requests will be will logged to - a trace file named traceFile.year.month.day. Use of this option - provides useful debugging information, but will impact - performance. (Note: The system property has no zookeeper prefix, - and the configuration variable name is different from the system - property. Yes - it's not consistent, and it's annoying.) - - - maxClientCnxns @@ -783,6 +825,21 @@ server.3=zoo3:2888:3888 + + fsync.warningthresholdms + + (Java system property: zookeeper.fsync.warningthresholdms) + + New in 3.3.4: A + warning message will be output to the log whenever an + fsync in the Transactional Log (WAL) takes longer than + this value. The values is specified in milliseconds and + defaults to 1000. This value can only be set as a + system property. + + + autopurge.snapRetainCount @@ -811,6 +868,21 @@ server.3=zoo3:2888:3888 to enable the auto purging. Defaults to 0. + + + syncEnabled + + + (Java system property: zookeeper.observer.syncEnabled) + + New in 3.4.6, 3.5.0: + The observers now log transaction and write snapshot to disk + by default like the participants. This reduces the recovery time + of the observers on restart. Set to "false" to disable this + feature. Default is "true" + + @@ -970,6 +1042,83 @@ server.3=zoo3:2888:3888 + + + 4lw.commands.whitelist + + + (Java system property: zookeeper.4lw.commands.whitelist) + + New in 3.4.10: + This property contains a list of comma separated + Four Letter Words commands. It is introduced + to provide fine grained control over the set of commands ZooKeeper can execute, + so users can turn off certain commands if necessary. + By default it contains all supported four letter word commands except "wchp" and "wchc", + if the property is not specified. If the property is specified, then only commands listed + in the whitelist are enabled. + + + Here's an example of the configuration that enables stat, ruok, conf, and isro + command while disabling the rest of Four Letter Words command: + + 4lw.commands.whitelist=stat, ruok, conf, isro + + + Users can also use asterisk option so they don't have to include every command one by one in the list. + As an example, this will enable all four letter word commands: + + + 4lw.commands.whitelist=* + + + + + + + ipReachableTimeout + + + (Java system property: zookeeper.ipReachableTimeout) + + New in 3.4.11: + Set this timeout value for IP addresses reachable checking when hostname is resolved, as mesured in + milliseconds. + By default, ZooKeeper will use the first IP address of the hostname(without any reachable checking). + When zookeeper.ipReachableTimeout is set(larger than 0), ZooKeeper will will try to pick up the first + IP address which is reachable. This is done by calling Java API InetAddress.isReachable(long timeout) + function, in which this timeout value is used. If none of such reachable IP address can be found, the + first IP address of the hostname will be used anyway. + + + + + + + tcpKeepAlive + + + (Java system property: zookeeper.tcpKeepAlive) + + New in 3.4.11: + Setting this to true sets the TCP keepAlive flag on the + sockets used by quorum members to perform elections. + This will allow for connections between quorum members to + remain up when there is network infrastructure that may + otherwise break them. Some NATs and firewalls may terminate + or lose state for long running or idle connections. + + Enabling this option relies on OS level settings to work + properly, check your operating system's options regarding TCP + keepalive for more information. Defaults to + false. + + + + @@ -1012,6 +1161,142 @@ server.3=zoo3:2888:3888 connection. + + + isro + + + New in 3.4.0: Tests if + server is running in read-only mode. The server will respond with + "ro" if in read-only mode or "rw" if not in read-only mode. + + + + + gtmk + + + Gets the current trace mask as a 64-bit signed long value in + decimal format. See stmk for an explanation of + the possible values. + + + + + stmk + + + Sets the current trace mask. The trace mask is 64 bits, + where each bit enables or disables a specific category of trace + logging on the server. Log4J must be configured to enable + TRACE level first in order to see trace logging + messages. The bits of the trace mask correspond to the following + trace logging categories. + + + Trace Mask Bit Values + + + + 0b0000000000 + Unused, reserved for future use. + + + 0b0000000010 + Logs client requests, excluding ping + requests. + + + 0b0000000100 + Unused, reserved for future use. + + + 0b0000001000 + Logs client ping requests. + + + 0b0000010000 + Logs packets received from the quorum peer that is + the current leader, excluding ping requests. + + + 0b0000100000 + Logs addition, removal and validation of client + sessions. + + + 0b0001000000 + Logs delivery of watch events to client + sessions. + + + 0b0010000000 + Logs ping packets received from the quorum peer + that is the current leader. + + + 0b0100000000 + Unused, reserved for future use. + + + 0b1000000000 + Unused, reserved for future use. + + + +
                                                  + + All remaining bits in the 64-bit value are unused and + reserved for future use. Multiple trace logging categories are + specified by calculating the bitwise OR of the documented values. + The default trace mask is 0b0100110010. Thus, by default, trace + logging includes client requests, packets received from the + leader and sessions. + + To set a different trace mask, send a request containing the + stmk four-letter word followed by the trace + mask represented as a 64-bit signed long value. This example uses + the Perl pack function to construct a trace + mask that enables all trace logging categories described above and + convert it to a 64-bit signed long value with big-endian byte + order. The result is appended to stmk and sent + to the server using netcat. The server responds with the new + trace mask in decimal format. + + $ perl -e "print 'stmk', pack('q>', 0b0011111010)" | nc localhost 2181 +250 + +
                                                  +
                                                  + + + +
                                                  + Experimental Options/Features + + New features that are currently considered experimental. + + + + Read Only Mode Server + + + (Java system property: readonlymode.enabled) + + New in 3.4.0: + Setting this value to true enables Read Only Mode server + support (disabled by default). ROM allows clients + sessions which requested ROM support to connect to the + server even when the server might be partitioned from + the quorum. In this mode ROM clients can still read + values from the ZK service, but will be unable to write + values and see changes from other clients. See + ZOOKEEPER-784 for more details. + + + +
                                                  @@ -1065,6 +1350,20 @@ server.3=zoo3:2888:3888 but opens up full access to the data tree to everyone. + + + quorumListenOnAllIPs + + + When set to true the ZooKeeper server will listen + for connections from its peers on all available IP addresses, + and not only the address configured in the server list of the + configuration file. It affects the connections handling the + ZAB protocol and the Fast Leader Election protocol. Default + value is false. + + + @@ -1380,6 +1679,13 @@ imok this document for more details on setting a retention policy and maintenance of ZooKeeper storage. + + The data stored in these files is not encrypted. In the case of + storing sensitive data in ZooKeeper, necessary measures need to be + taken to prevent unauthorized access. Such measures are external to + ZooKeeper (e.g., control access to the files) and depend on the + individual settings in which it is being deployed. + @@ -1405,7 +1711,7 @@ imok - incorrect placement of transasction log + incorrect placement of transaction log The most performance critical part of ZooKeeper is the @@ -1438,6 +1744,16 @@ imok usage limit that would cause the system to swap. + + + Publicly accessible deployment + + + A ZooKeeper ensemble is expected to operate in a trusted computing environment. + It is thus recommended to deploy ZooKeeper behind a firewall. + + + diff --git a/src/docs/src/documentation/content/xdocs/zookeeperObservers.xml b/src/docs/src/documentation/content/xdocs/zookeeperObservers.xml index 99f80250438..3955f3d0476 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperObservers.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperObservers.xml @@ -102,7 +102,7 @@ to add an Observer to your ZooKeeper cluster. Now you can connect to it as though it were an ordinary Follower. Try it out, by running: - bin/zkCli.sh -server localhost:2181 + $ bin/zkCli.sh -server localhost:2181 where localhost:2181 is the hostname and port number of the Observer as diff --git a/src/docs/src/documentation/content/xdocs/zookeeperOver.xml b/src/docs/src/documentation/content/xdocs/zookeeperOver.xml index 5d2eda6c7cd..7a0444c5908 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperOver.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperOver.xml @@ -375,7 +375,7 @@ In version 3.2 r/w performance improved by ~2x compared to the previous + url="http://zookeeper.apache.org/docs/r3.1.1/zookeeperOver.html#Performance">previous 3.1 release. Benchmarks also indicate that it is reliable, too. The ZooKeeper Project ZooKeeper has been - + successfully used in many industrial applications. It is used at Yahoo! as the @@ -454,7 +454,7 @@ All users and developers are encouraged to join the community and contribute their expertise. See the - + Zookeeper Project on Apache for more information. diff --git a/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml b/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml index bfc23dc4a3f..f0c05b34981 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml @@ -139,8 +139,7 @@ The following characters are not allowed: \ud800 -uF8FFF, - \uFFF0-uFFFF, \uXFFFE - \uXFFFF (where X is a digit 1 - E), \uF0000 - - \uFFFFF. + \uFFF0 - uFFFF. @@ -162,7 +161,7 @@ znode. Znodes maintain a stat structure that includes version numbers for data changes, acl changes. The stat structure also has timestamps. The version number, together with the - timestamp allow ZooKeeper to validate the cache and to coordinate + timestamp, allows ZooKeeper to validate the cache and to coordinate updates. Each time a znode's data changes, the version number increases. For instance, whenever a client retrieves data, it also receives the version of the data. And when a client performs an update or a delete, @@ -182,7 +181,7 @@ which uses a ZooKeeper service. - Znodes are the main enitity that a programmer access. They have + A znode is the main abstraction a programmer needs to be aware of. Znodes have several characteristics that are worth mentioning here.
                                                  @@ -265,7 +264,7 @@ Version numbers - Every change to a a node will cause an increase to one of the + Every change to a node will cause an increase to one of the version numbers of that node. The three version numbers are version (number of changes to the data of a znode), cversion (number of changes to the children of a znode), and aversion (number of changes @@ -314,6 +313,12 @@ The zxid of the change that last modified this znode. + + pzxid + + The zxid of the change that last modified children of this znode. + + ctime @@ -530,7 +535,7 @@ Added in 3.2.0 -- SessionMovedException. There is an internal exception that is generally not seen by clients called the SessionMovedException. This exception occurs because a request was received on a connection for a session - which has be reestablished on a different server. The normal cause of this error is + which has been reestablished on a different server. The normal cause of this error is a client that sends a request to a server, but the network packet gets delayed, so the client times out and connects to a new server. When the delayed packet arrives at the first server, the old server detects that the session has moved, and closes the @@ -539,7 +544,7 @@ condition can be seen is when two clients try to reestablish the same connection using a saved session id and password. One of the clients will reestablish the connection and the second client will be disconnected (causing the pair to attempt to re-establish - it's connection/session indefinitely). + its connection/session indefinitely).
                                                  @@ -604,15 +609,46 @@ Watches are maintained locally at the ZooKeeper server to which the - client is connected. This allows watches to be light weight to set, + client is connected. This allows watches to be lightweight to set, maintain, and dispatch. When a client connects to a new server, the watch will be triggered for any session events. Watches will not be received while disconnected from a server. When a client reconnects, any previously registered watches will be reregistered and triggered if needed. In general this all occurs transparently. There is one case where a watch - may be missed: a watch for the existance of a znode not yet created will + may be missed: a watch for the existence of a znode not yet created will be missed if the znode is created and deleted while disconnected. +
                                                  + Semantics of Watches + + We can set watches with the three calls that read the state of + ZooKeeper: exists, getData, and getChildren. The following list details + the events that a watch can trigger and the calls that enable them: + + + + + Created event: + Enabled with a call to exists. + + + + Deleted event: + Enabled with a call to exists, getData, and getChildren. + + + + Changed event: + Enabled with a call to exists and getData. + + + + Child event: + Enabled with a call to getChildren. + + +
                                                  +
                                                  What ZooKeeper Guarantees about Watches @@ -906,7 +942,7 @@ This is a very simple example which is intended to show how to interact with ZooKeeper ACLs specifically. See .../trunk/src/c/src/cli.c - for an example of a proper C client implementation + for an example of a C client implementation @@ -1120,7 +1156,7 @@ authProvider.2=com.f.MyAuth2 have been applied. On some failures (communication errors, timeouts, etc) the client will not know if the update has applied or not. We take steps to minimize the failures, but the - only guarantee is only present with successful return codes. + guarantee is only present with successful return codes. (This is called the monotonicity condition in Paxos.) @@ -1138,7 +1174,7 @@ authProvider.2=com.f.MyAuth2 The clients view of the system is guaranteed to be up-to-date - within a certain time bound. (On the order of tens of seconds.) + within a certain time bound (on the order of tens of seconds). Either system changes will be seen by a client within this bound, or the client will detect a service outage. @@ -1370,31 +1406,7 @@ authProvider.2=com.f.MyAuth2
                                                  - Using the C Client - - You can test your client by running a ZooKeeper server (see - instructions on the project wiki page on how to run it) and connecting - to it using one of the cli applications that were built as part of the - installation procedure. cli_mt (multithreaded, built against - zookeeper_mt library) is shown in this example, but you could also use - cli_st (singlethreaded, built against zookeeper_st library): - - $ cli_mt zookeeper_host:9876 - - This is a client application that gives you a shell for - executing simple ZooKeeper commands. Once successfully started - and connected to the server it displays a shell prompt. You - can now enter ZooKeeper commands. For example, to create a - node: - - > create /my_new_node - - To verify that the node's been created: - - > ls / - - You should see a list of node who are children of the root node - "/". + Building Your Own C Client In order to be able to use the ZooKeeper API in your application you have to remember to @@ -1402,7 +1414,7 @@ authProvider.2=com.f.MyAuth2 Include ZooKeeper header: #include - <zookeeper/zookeeper.h + <zookeeper/zookeeper.h> @@ -1416,10 +1428,10 @@ authProvider.2=com.f.MyAuth2 - Refer to - for examples of usage in Java and C. - [tbd] - + + See .../trunk/src/c/src/cli.c + for an example of a C client implementation +
                                                  @@ -1494,7 +1506,7 @@ authProvider.2=com.f.MyAuth2 If you are using watches, you must look for the connected watch event. When a ZooKeeper client disconnects from a server, you will not receive notification of changes until reconnected. If you are - watching for a znode to come into existance, you will miss the event + watching for a znode to come into existence, you will miss the event if the znode is created and deleted while you are disconnected. @@ -1587,7 +1599,7 @@ authProvider.2=com.f.MyAuth2 Barrier and + url="https://cwiki.apache.org/confluence/display/ZOOKEEPER/Tutorial">Barrier and Queue Tutorial @@ -1598,7 +1610,7 @@ authProvider.2=com.f.MyAuth2 ZooKeeper + url="https://cwiki.apache.org/confluence/display/ZOOKEEPER/ZooKeeperArticles">ZooKeeper - A Reliable, Scalable Distributed Coordination System diff --git a/src/docs/src/documentation/content/xdocs/zookeeperQuotas.xml b/src/docs/src/documentation/content/xdocs/zookeeperQuotas.xml index 3be4c3fcd1e..7668e6ab190 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperQuotas.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperQuotas.xml @@ -43,8 +43,7 @@ ZooKeeper prints WARN messages if users exceed the quota assigned to them. The messages are printed in the log of the ZooKeeper.
                                                  - $java -cp zookeeper.jar:src/java/lib/log4j-1.2.15.jar/conf:src/java/lib/jline-0.9.94.jar \ - org.apache.zookeeper.ZooKeeperMain -server host:port + $ bin/zkCli.sh -server host:port The above command gives you a command line option of using quotas.
                                                  Setting Quotas diff --git a/src/docs/src/documentation/content/xdocs/zookeeperStarted.xml b/src/docs/src/documentation/content/xdocs/zookeeperStarted.xml index 482f36cb002..70c227f23ab 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperStarted.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperStarted.xml @@ -69,7 +69,7 @@ Download To get a ZooKeeper distribution, download a recent - + stable release from one of the Apache Download Mirrors.
                                                  @@ -164,39 +164,10 @@ clientPort=2181
                                                  Connecting to ZooKeeper - Once ZooKeeper is running, you have several options for connection - to it: + $ bin/zkCli.sh -server 127.0.0.1:2181 - - - Java: Use - - bin/zkCli.sh -server 127.0.0.1:2181 - - This lets you perform simple, file-like operations. - - - - C: compile cli_mt - (multi-threaded) or cli_st (single-threaded) by running - make cli_mt or make - cli_st in - the src/c subdirectory in - the ZooKeeper sources. See the README contained within - src/c for full details. + This lets you perform simple, file-like operations. - You can run the program - from src/c using: - - LD_LIBRARY_PATH=. cli_mt 127.0.0.1:2181 - - or - - LD_LIBRARY_PATH=. cli_st 127.0.0.1:2181 - This will give you a simple shell to execute file - system like operations on ZooKeeper. - - Once you have connected, you should see something like: @@ -341,8 +312,25 @@ numChildren = 0 ZooKeeper in replicated mode. A replicated group of servers in the same application is called a quorum, and in replicated mode, all servers in the quorum have copies of the same configuration - file. The file is similar to the one used in standalone mode, but with a - few differences. Here is an example: + file. + + + For replicated mode, a minimum of three servers are required, + and it is strongly recommended that you have an odd number of + servers. If you only have two servers, then you are in a + situation where if one of them fails, there are not enough + machines to form a majority quorum. Two servers is inherently + less + stable than a single server, because there are two single + points of failure. + + + + The required + conf/zoo.cfg + file for replicated mode is similar to the one used in standalone + mode, but with a few differences. Here is an example: + tickTime=2000 @@ -395,6 +383,13 @@ server.3=zoo3:2888:3888 (in the above replicated example, running on a single localhost, you would still have three config files). + Please be aware that setting up multiple servers on a single + machine will not create any redundancy. If something were to + happen which caused the machine to die, all of the zookeeper + servers would be offline. Full redundancy requires that each + server have its own machine. It must be a completely separate + physical server. Multiple virtual machines on the same physical + host are still vulnerable to the complete failure of that host.
                                                  diff --git a/src/docs/src/documentation/content/xdocs/zookeeperTutorial.xml b/src/docs/src/documentation/content/xdocs/zookeeperTutorial.xml index a155a9b3e64..10b3606c842 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperTutorial.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperTutorial.xml @@ -672,4 +672,4 @@ public class SyncPrimitive implements Watcher { - \ No newline at end of file + diff --git a/src/docs/src/documentation/skinconf.xml b/src/docs/src/documentation/skinconf.xml index 6aee6265acb..43f3a490bcd 100644 --- a/src/docs/src/documentation/skinconf.xml +++ b/src/docs/src/documentation/skinconf.xml @@ -30,7 +30,7 @@ which will be used to configure the chosen Forrest skin. In other words google will search the @domain for the query string. --> - + true @@ -66,7 +66,7 @@ which will be used to configure the chosen Forrest skin. ZooKeeper ZooKeeper: distributed coordination - http://hadoop.apache.org/zookeeper/ + http://zookeeper.apache.org/ images/zookeeper_small.gif @@ -84,7 +84,7 @@ which will be used to configure the chosen Forrest skin. images/favicon.ico - 2008 + The Apache Software Foundation. http://www.apache.org/licenses/ @@ -95,10 +95,10 @@ which will be used to configure the chosen Forrest skin. Use location="none" to not display the trail (if the skin supports it). For some skins just set the attributes to blank. --> - + - - + + + - + @@ -45,6 +62,8 @@ + + @@ -96,8 +115,9 @@ - - + + + @@ -110,7 +130,6 @@ - @@ -134,7 +153,6 @@ - @@ -143,7 +161,6 @@ - diff --git a/src/java/test/config/findbugsExcludeFile.xml b/src/java/test/config/findbugsExcludeFile.xml index cb691353d47..f1bb79db0fa 100644 --- a/src/java/test/config/findbugsExcludeFile.xml +++ b/src/java/test/config/findbugsExcludeFile.xml @@ -10,8 +10,8 @@ In particular we want to make sure we exit if this occurs Also notice logged as fatal error --> - - + + @@ -74,6 +74,11 @@ + + + + + @@ -96,10 +101,18 @@ - + + + + + + + + + @@ -125,4 +138,24 @@ + + + + + + + + + + + + + + + + + diff --git a/src/java/test/data/buffersize/create/version-2/log.1 b/src/java/test/data/buffersize/create/version-2/log.1 new file mode 100644 index 00000000000..4f05bc1cb45 Binary files /dev/null and b/src/java/test/data/buffersize/create/version-2/log.1 differ diff --git a/src/java/test/data/buffersize/create/version-2/snapshot.0 b/src/java/test/data/buffersize/create/version-2/snapshot.0 new file mode 100644 index 00000000000..49b512e65af Binary files /dev/null and b/src/java/test/data/buffersize/create/version-2/snapshot.0 differ diff --git a/src/java/test/data/buffersize/set/version-2/log.1 b/src/java/test/data/buffersize/set/version-2/log.1 new file mode 100644 index 00000000000..8586f16bf73 Binary files /dev/null and b/src/java/test/data/buffersize/set/version-2/log.1 differ diff --git a/src/java/test/data/buffersize/set/version-2/snapshot.0 b/src/java/test/data/buffersize/set/version-2/snapshot.0 new file mode 100644 index 00000000000..49b512e65af Binary files /dev/null and b/src/java/test/data/buffersize/set/version-2/snapshot.0 differ diff --git a/src/java/test/data/buffersize/snapshot/version-2/log.1 b/src/java/test/data/buffersize/snapshot/version-2/log.1 new file mode 100644 index 00000000000..2f145a68386 Binary files /dev/null and b/src/java/test/data/buffersize/snapshot/version-2/log.1 differ diff --git a/src/java/test/data/buffersize/snapshot/version-2/snapshot.0 b/src/java/test/data/buffersize/snapshot/version-2/snapshot.0 new file mode 100644 index 00000000000..49b512e65af Binary files /dev/null and b/src/java/test/data/buffersize/snapshot/version-2/snapshot.0 differ diff --git a/src/java/test/data/buffersize/snapshot/version-2/snapshot.2 b/src/java/test/data/buffersize/snapshot/version-2/snapshot.2 new file mode 100644 index 00000000000..59dfa701a83 Binary files /dev/null and b/src/java/test/data/buffersize/snapshot/version-2/snapshot.2 differ diff --git a/src/java/test/data/invalidsnap/version-2/snapshot.273 b/src/java/test/data/invalidsnap/version-2/snapshot.273 new file mode 100644 index 00000000000..3146f565663 Binary files /dev/null and b/src/java/test/data/invalidsnap/version-2/snapshot.273 differ diff --git a/src/java/test/data/kerberos/minikdc-krb5.conf b/src/java/test/data/kerberos/minikdc-krb5.conf new file mode 100644 index 00000000000..43ec7c4c4e9 --- /dev/null +++ b/src/java/test/data/kerberos/minikdc-krb5.conf @@ -0,0 +1,30 @@ +# +# 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. +# +# This resource is originally from HDFS, see the similarly named files there +# in case of bug fixing, history, etc. +# Branch : trunk +# Github Revision: 1d1ab587e4e92ce3aea4cb144811f69145cb3b33 +# +[libdefaults] + default_realm = {0} + udp_preference_limit = 1 + +[realms] + {0} = '{' + kdc = {1}:{2} + '}' \ No newline at end of file diff --git a/src/java/test/data/kerberos/minikdc.ldiff b/src/java/test/data/kerberos/minikdc.ldiff new file mode 100644 index 00000000000..20c8d7759ae --- /dev/null +++ b/src/java/test/data/kerberos/minikdc.ldiff @@ -0,0 +1,52 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This resource is originally from HDFS, see the similarly named files there +# in case of bug fixing, history, etc. +# Branch : trunk +# Github Revision: 1d1ab587e4e92ce3aea4cb144811f69145cb3b33 +# +dn: ou=users,dc=${0},dc=${1} +objectClass: organizationalUnit +objectClass: top +ou: users + +dn: uid=krbtgt,ou=users,dc=${0},dc=${1} +objectClass: top +objectClass: person +objectClass: inetOrgPerson +objectClass: krb5principal +objectClass: krb5kdcentry +cn: KDC Service +sn: Service +uid: krbtgt +userPassword: secret +krb5PrincipalName: krbtgt/${2}.${3}@${2}.${3} +krb5KeyVersionNumber: 0 + +dn: uid=ldap,ou=users,dc=${0},dc=${1} +objectClass: top +objectClass: person +objectClass: inetOrgPerson +objectClass: krb5principal +objectClass: krb5kdcentry +cn: LDAP +sn: Service +uid: ldap +userPassword: secret +krb5PrincipalName: ldap/${4}@${2}.${3} +krb5KeyVersionNumber: 0 \ No newline at end of file diff --git a/src/java/test/org/apache/jute/BinaryInputArchiveTest.java b/src/java/test/org/apache/jute/BinaryInputArchiveTest.java new file mode 100644 index 00000000000..a8d067d7d8a --- /dev/null +++ b/src/java/test/org/apache/jute/BinaryInputArchiveTest.java @@ -0,0 +1,43 @@ +/** + * 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.jute; + +import junit.framework.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + + +public class BinaryInputArchiveTest { + + @Test + public void testReadStringCheckLength() { + byte[] buf = new byte[]{ + Byte.MAX_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE}; + ByteArrayInputStream is = new ByteArrayInputStream(buf); + BinaryInputArchive ia = BinaryInputArchive.getArchive(is); + try { + ia.readString(""); + Assert.fail("Should have thrown an IOException"); + } catch (IOException e) { + Assert.assertTrue("Not 'Unreasonable length' exception: " + e, + e.getMessage().startsWith(BinaryInputArchive.UNREASONBLE_LENGTH)); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/ClientReconnectTest.java b/src/java/test/org/apache/zookeeper/ClientReconnectTest.java new file mode 100644 index 00000000000..8d66a7e6920 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/ClientReconnectTest.java @@ -0,0 +1,78 @@ +/** + * 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; + + +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.channels.SocketChannel; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.apache.zookeeper.client.HostProvider; +import org.junit.Test; + +public class ClientReconnectTest extends TestCase { + private SocketChannel sc; + private CountDownLatch countDownLatch = new CountDownLatch(3); + + class MockCnxn extends ClientCnxnSocketNIO { + MockCnxn() throws IOException { + super(); + } + + @Override + void registerAndConnect(SocketChannel sock, InetSocketAddress addr) throws + IOException { + countDownLatch.countDown(); + throw new IOException("failed to register"); + } + + @Override + SocketChannel createSock() { + return sc; + } + } + + @Test + public void testClientReconnect() throws IOException, InterruptedException { + HostProvider hostProvider = mock(HostProvider.class); + when(hostProvider.size()).thenReturn(1); + InetSocketAddress inaddr = new InetSocketAddress(1111); + when(hostProvider.next(anyLong())).thenReturn(inaddr); + ZooKeeper zk = mock(ZooKeeper.class); + sc = SocketChannel.open(); + + ClientCnxnSocketNIO nioCnxn = new MockCnxn(); + ClientWatchManager watcher = mock(ClientWatchManager.class); + ClientCnxn clientCnxn = new ClientCnxn( + "tmp", hostProvider, 5000, + zk, watcher, nioCnxn, false); + clientCnxn.start(); + countDownLatch.await(5000, TimeUnit.MILLISECONDS); + Assert.assertTrue(countDownLatch.getCount() == 0); + clientCnxn.close(); + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/JUnit4ZKTestRunner.java b/src/java/test/org/apache/zookeeper/JUnit4ZKTestRunner.java index 188c9f746af..37149ccaaf4 100644 --- a/src/java/test/org/apache/zookeeper/JUnit4ZKTestRunner.java +++ b/src/java/test/org/apache/zookeeper/JUnit4ZKTestRunner.java @@ -20,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.junit.Test; import org.junit.internal.runners.statements.InvokeMethod; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; @@ -38,23 +39,43 @@ public JUnit4ZKTestRunner(Class klass) throws InitializationError { } public class LoggedInvokeMethod extends InvokeMethod { - private String name; + private final FrameworkMethod method; + private final String name; public LoggedInvokeMethod(FrameworkMethod method, Object target) { super(method, target); + this.method = method; name = method.getName(); } @Override public void evaluate() throws Throwable { - LOG.info("RUNNING TEST METHOD " + name); + LOG.info("RUNNING TEST METHOD {}", name); try { super.evaluate(); + Runtime rt = Runtime.getRuntime(); + long usedKB = (rt.totalMemory() - rt.freeMemory()) / 1024; + LOG.info("Memory used {}", usedKB); + ThreadGroup tg = Thread.currentThread().getThreadGroup(); + while (tg.getParent() != null) { + tg = tg.getParent(); + } + LOG.info("Number of threads {}", tg.activeCount()); } catch (Throwable t) { - LOG.info("TEST METHOD FAILED " + name, t); + // The test method threw an exception, but it might be an + // expected exception as defined in the @Test annotation. + // Check the annotation and log an appropriate message. + Test annotation = this.method.getAnnotation(Test.class); + if (annotation != null && annotation.expected() != null && + annotation.expected().isAssignableFrom(t.getClass())) { + LOG.info("TEST METHOD {} THREW EXPECTED EXCEPTION {}", name, + annotation.expected()); + } else { + LOG.info("TEST METHOD FAILED {}", name, t); + } throw t; } - LOG.info("FINISHED TEST METHOD " + name); + LOG.info("FINISHED TEST METHOD {}", name); } } diff --git a/src/java/test/org/apache/zookeeper/JaasConfiguration.java b/src/java/test/org/apache/zookeeper/JaasConfiguration.java new file mode 100644 index 00000000000..bf145d0249c --- /dev/null +++ b/src/java/test/org/apache/zookeeper/JaasConfiguration.java @@ -0,0 +1,75 @@ +/** + * 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; + +import java.util.HashMap; +import java.util.Map; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; + +/** + * This helper class allows to programmatically create a JAAS configuration. + * Each section must have a name and a login module, and a set of key/values + * to describe login options. + * + * Example: + * jaas = new JaasConfiguration(); + * jaas.addSection("Server", "org.apache.zookeeper.server.auth.DigestLoginModule", + * "username", "passowrd"); + */ +public class JaasConfiguration extends javax.security.auth.login.Configuration { + private final Map sections = + new HashMap(); + + public JaasConfiguration() { + } + + /** + * Add a section to the jaas.conf + * @param name Section name + * @param loginModuleName Login module name + * @param args login key/value args + */ + public void addSection(String name, String loginModuleName, String... args) { + Map conf = new HashMap(); + // loop through the args (must be key/value sequence) + for (int i = 0; i < args.length - 1; i += 2) { + conf.put(args[i], args[i + 1]); + } + addSection(name, loginModuleName, conf); + } + + /** + * Add a section to the jaas.conf + * @param name Section name + * @param loginModuleName Login module name + * @param conf login key/value args + */ + public void addSection(String name, String loginModuleName, final Map conf) { + AppConfigurationEntry[] entries = new AppConfigurationEntry[1]; + entries[0] = new AppConfigurationEntry(loginModuleName, LoginModuleControlFlag.REQUIRED, conf); + this.sections.put(name, entries); + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String appName) { + return sections.get(appName); + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/MockPacket.java b/src/java/test/org/apache/zookeeper/MockPacket.java new file mode 100644 index 00000000000..f4bb19a5a05 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/MockPacket.java @@ -0,0 +1,46 @@ +/** + * 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; + +import org.apache.zookeeper.proto.RequestHeader; +import org.apache.zookeeper.proto.ReplyHeader; +import org.apache.jute.Record; +import org.apache.zookeeper.ZooKeeper.WatchRegistration; +import java.nio.ByteBuffer; + +public class MockPacket extends ClientCnxn.Packet { + + public MockPacket(RequestHeader requestHeader, ReplyHeader replyHeader, + Record request, Record response, + WatchRegistration watchRegistration) { + super(requestHeader, replyHeader, request, response, watchRegistration); + } + + public MockPacket(RequestHeader requestHeader, ReplyHeader replyHeader, + Record request, Record response, + WatchRegistration watchRegistration, boolean readOnly) { + super(requestHeader, replyHeader, request, response, watchRegistration, readOnly); + } + + public ByteBuffer createAndReturnBB() { + createBB(); + return this.bb; + } + +} diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthTest.java b/src/java/test/org/apache/zookeeper/SaslAuthTest.java similarity index 52% rename from src/java/test/org/apache/zookeeper/test/SaslAuthTest.java rename to src/java/test/org/apache/zookeeper/SaslAuthTest.java index fd2034652c4..eac070335ec 100644 --- a/src/java/test/org/apache/zookeeper/test/SaslAuthTest.java +++ b/src/java/test/org/apache/zookeeper/SaslAuthTest.java @@ -16,76 +16,84 @@ * limitations under the License. */ -package org.apache.zookeeper.test; +package org.apache.zookeeper; + +import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.TestableZooKeeper; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.ClientCnxn.SendThread; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.test.ClientBase; +import org.junit.AfterClass; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; public class SaslAuthTest extends ClientBase { - static { - System.setProperty("zookeeper.authProvider.1","org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); - + @BeforeClass + public static void init() { + System.setProperty("zookeeper.authProvider.1", + "org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); try { File tmpDir = createTmpDir(); File saslConfFile = new File(tmpDir, "jaas.conf"); + String jaasContent = getJaasFileContent(); FileWriter fwriter = new FileWriter(saslConfFile); - - fwriter.write("" + - "Server {\n" + - " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + - " user_super=\"test\";\n" + - "};\n" + - "Client {\n" + - " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + - " username=\"super\"\n" + - " password=\"test\";\n" + - "};" + "\n"); + fwriter.write(jaasContent); fwriter.close(); - System.setProperty("java.security.auth.login.config",saslConfFile.getAbsolutePath()); - } - catch (IOException e) { - // could not create tmp directory to hold JAAS conf file : test will fail now. + System.setProperty("java.security.auth.login.config", saslConfFile.getAbsolutePath()); + } catch (IOException e) { + // could not create tmp directory to hold JAAS conf file : test will + // fail now. } } + private static String getJaasFileContent() { + StringBuilder jaasContent=new StringBuilder(); + String newLine = System.getProperty("line.separator"); + jaasContent.append("Server {"); + jaasContent.append(newLine); + jaasContent.append("org.apache.zookeeper.server.auth.DigestLoginModule required"); + jaasContent.append(newLine); + jaasContent.append("user_super=\"test\";"); + jaasContent.append(newLine); + jaasContent.append("};"); + jaasContent.append(newLine); + jaasContent.append("Client {"); + jaasContent.append(newLine); + jaasContent.append("org.apache.zookeeper.server.auth.DigestLoginModule required"); + jaasContent.append(newLine); + jaasContent.append("username=\"super\""); + jaasContent.append(newLine); + jaasContent.append("password=\"test\";"); + jaasContent.append(newLine); + jaasContent.append("};"); + jaasContent.append(newLine); + return jaasContent.toString(); + } + + @AfterClass + public static void clean() { + System.clearProperty("zookeeper.authProvider.1"); + System.clearProperty("java.security.auth.login.config"); + } + private AtomicInteger authFailed = new AtomicInteger(0); @Override protected TestableZooKeeper createClient(String hp) throws IOException, InterruptedException { - File tmpDir = ClientBase.createTmpDir(); - File saslConfFile = new File(tmpDir, "jaas.conf"); - FileWriter fwriter = new FileWriter(saslConfFile); - - fwriter.write("" + - "Server {\n" + - " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + - " user_super=\"test\";\n" + - "};\n" + - "Client {\n" + - " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + - " username=\"super\"\n" + - " password=\"test\";\n" + - "};" + "\n"); - fwriter.close(); - System.setProperty("java.security.auth.login.config",saslConfFile.getAbsolutePath()); MyWatcher watcher = new MyWatcher(); return createClient(watcher, hp); } @@ -102,18 +110,9 @@ public synchronized void process(WatchedEvent event) { } } - @Test - public void testBadSaslAuthNotifiesWatch() throws Exception { - ZooKeeper zk = createClient(); - Thread.sleep(1000); - zk.close(); - } - - @Test public void testAuth() throws Exception { ZooKeeper zk = createClient(); - Thread.sleep(1000); try { zk.create("/path1", null, Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); Thread.sleep(1000); @@ -125,7 +124,6 @@ public void testAuth() throws Exception { @Test public void testValidSaslIds() throws Exception { ZooKeeper zk = createClient(); - Thread.sleep(1000); List validIds = new ArrayList(); validIds.add("user"); @@ -146,7 +144,6 @@ public void testValidSaslIds() throws Exception { @Test public void testInvalidSaslIds() throws Exception { ZooKeeper zk = createClient(); - Thread.sleep(1000); List invalidIds = new ArrayList(); invalidIds.add("user@KERB.REALM/server.com"); @@ -169,5 +166,48 @@ public void testInvalidSaslIds() throws Exception { } } } + + @Test + public void testZKOperationsAfterClientSaslAuthFailure() throws Exception { + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(hostPort, CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(CONNECTION_TIMEOUT); + try { + setSaslFailureFlag(zk); + + // try node creation for around 15 second, + int totalTry = 10; + int tryCount = 0; + + boolean success = false; + while (!success && tryCount++ <= totalTry) { + try { + zk.create("/saslAuthFail", "data".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + success = true; + } catch (KeeperException.ConnectionLossException e) { + Thread.sleep(1000); + // do nothing + } + } + assertTrue("ZNode creation is failing continuously after Sasl auth failure.", success); + + } finally { + zk.close(); + } + } + + // set saslLoginFailed to true to simulate the LoginException + private void setSaslFailureFlag(ZooKeeper zk) throws Exception { + Field cnxnField = zk.getClass().getDeclaredField("cnxn"); + cnxnField.setAccessible(true); + ClientCnxn clientCnxn = (ClientCnxn) cnxnField.get(zk); + Field sendThreadField = clientCnxn.getClass().getDeclaredField("sendThread"); + sendThreadField.setAccessible(true); + SendThread sendThread = (SendThread) sendThreadField.get(clientCnxn); + Field saslLoginFailedField = sendThread.getClass().getDeclaredField("saslLoginFailed"); + saslLoginFailedField.setAccessible(true); + saslLoginFailedField.setBoolean(sendThread, true); + } } diff --git a/src/java/test/org/apache/zookeeper/ServerConfigTest.java b/src/java/test/org/apache/zookeeper/ServerConfigTest.java new file mode 100644 index 00000000000..27faa745b6f --- /dev/null +++ b/src/java/test/org/apache/zookeeper/ServerConfigTest.java @@ -0,0 +1,74 @@ +/** + * 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; + +import org.apache.zookeeper.server.ServerConfig; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +public class ServerConfigTest { + + private ServerConfig serverConfig; + + @Before + public void setUp() { + serverConfig = new ServerConfig(); + } + + @Test(expected=IllegalArgumentException.class) + public void testFewArguments() { + String[] args = {"2181"}; + serverConfig.parse(args); + } + + @Test + public void testValidArguments() { + String[] args = {"2181", "/data/dir", "60000", "10000"}; + serverConfig.parse(args); + + assertEquals(2181, serverConfig.getClientPortAddress().getPort()); + assertTrue(checkEquality("/data/dir", serverConfig.getDataDir())); + assertEquals(60000, serverConfig.getTickTime()); + assertEquals(10000, serverConfig.getMaxClientCnxns()); + } + + @Test(expected=IllegalArgumentException.class) + public void testTooManyArguments() { + String[] args = {"2181", "/data/dir", "60000", "10000", "9999"}; + serverConfig.parse(args); + } + + boolean checkEquality(String a, String b) { + assertNotNull(a); + assertNotNull(b); + return a.equals(b); + } + + boolean checkEquality(String a, File b) { + assertNotNull(a); + assertNotNull(b); + return new File(a).equals(b); + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/TestableZooKeeper.java b/src/java/test/org/apache/zookeeper/TestableZooKeeper.java index f8344b6ec3b..4d46fdf738a 100644 --- a/src/java/test/org/apache/zookeeper/TestableZooKeeper.java +++ b/src/java/test/org/apache/zookeeper/TestableZooKeeper.java @@ -20,8 +20,13 @@ import java.io.IOException; import java.net.SocketAddress; -import java.nio.channels.SocketChannel; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.jute.Record; +import org.apache.zookeeper.proto.ReplyHeader; +import org.apache.zookeeper.proto.RequestHeader; public class TestableZooKeeper extends ZooKeeper { @@ -47,13 +52,24 @@ public List getExistWatches() { return super.getExistWatches(); } + /** + * Cause this ZooKeeper object to disconnect from the server. It will then + * later attempt to reconnect. + */ + public void testableConnloss() throws IOException { + synchronized(cnxn) { + cnxn.sendThread.testableCloseSocket(); + } + } /** * Cause this ZooKeeper object to stop receiving from the ZooKeeperServer * for the given number of milliseconds. * @param ms the number of milliseconds to pause. + * @return true if the connection is paused, otherwise false */ - public void pauseCnxn(final long ms) { + public boolean pauseCnxn(final long ms) { + final CountDownLatch initiatedPause = new CountDownLatch(1); new Thread() { public void run() { synchronized(cnxn) { @@ -62,6 +78,8 @@ public void run() { cnxn.sendThread.testableCloseSocket(); } catch (IOException e) { e.printStackTrace(); + } finally { + initiatedPause.countDown(); } Thread.sleep(ms); } catch (InterruptedException e) { @@ -69,6 +87,13 @@ public void run() { } } }.start(); + + try { + return initiatedPause.await(ms, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } } public boolean testableWaitForShutdown(int wait) @@ -84,4 +109,16 @@ public SocketAddress testableLocalSocketAddress() { public SocketAddress testableRemoteSocketAddress() { return super.testableRemoteSocketAddress(); } + + /** + * @return the last zxid as seen by the client session + */ + public long testableLastZxid() { + return cnxn.getLastZxid(); + } + + public ReplyHeader submitRequest(RequestHeader h, Record request, + Record response, WatchRegistration watchRegistration) throws InterruptedException { + return cnxn.submitRequest(h, request, response, watchRegistration); + } } diff --git a/src/java/test/org/apache/zookeeper/VerGenTest.java b/src/java/test/org/apache/zookeeper/VerGenTest.java index f5757136330..607edbb538c 100644 --- a/src/java/test/org/apache/zookeeper/VerGenTest.java +++ b/src/java/test/org/apache/zookeeper/VerGenTest.java @@ -43,7 +43,10 @@ public static Collection data() { {"1.2.3", new Object[] {1, 2, 3, null}}, {"1.2.3-dev", new Object[] {1, 2, 3, "dev"}}, {"1.2.3-SNAPSHOT", new Object[] {1, 2, 3, "SNAPSHOT"}}, - {"1.2.3-foo-bar+123", new Object[] {1, 2, 3, "foo-bar+123"}} + {"1.2.3-SNAPSHOT", new Object[] {1, 2, 3, "SNAPSHOT"}}, + {"1.2.3-foo-bar+123", new Object[] {1, 2, 3, "foo-bar+123"}}, + {"1.2.3.4.5-SNAPSHOT", new Object[] {1, 2, 3, "SNAPSHOT"}}, + {"1.2.3.4.5-foo-bar+123", new Object[] {1, 2, 3, "foo-bar+123"}} }); } @@ -69,7 +72,7 @@ public void testParser() { public void testGenFile() throws Exception { VerGen.Version v = VerGen.parseVersionString(input); File outputDir = ClientBase.createTmpDir(); - VerGen.generateFile(outputDir, v, 1, "Nov1"); + VerGen.generateFile(outputDir, v, "1", "Nov1"); ClientBase.recursiveDelete(outputDir); } } diff --git a/src/java/test/org/apache/zookeeper/ZKTestCase.java b/src/java/test/org/apache/zookeeper/ZKTestCase.java index b721cfca88e..9098fc47ebf 100644 --- a/src/java/test/org/apache/zookeeper/ZKTestCase.java +++ b/src/java/test/org/apache/zookeeper/ZKTestCase.java @@ -18,6 +18,7 @@ package org.apache.zookeeper; +import org.junit.Assume; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.junit.Rule; @@ -26,6 +27,10 @@ import org.junit.runner.RunWith; import org.junit.runners.model.FrameworkMethod; +import java.io.IOException; +import java.net.Inet6Address; +import java.net.InetAddress; + /** * Base class for a non-parameterized ZK test. * @@ -47,6 +52,10 @@ protected String getTestName() { @Override public void starting(FrameworkMethod method) { testName = method.getName(); + // ZOOKEEPER-2693 disables all 4lw by default. + // Here we enable the 4lw which ZooKeeper tests depends. + System.setProperty("zookeeper.4lw.commands.whitelist", "*"); + LOG.info("STARTING " + testName); } @@ -67,4 +76,13 @@ public void failed(Throwable e, FrameworkMethod method) { }; + protected void assumeIPv6Available() { + try { + InetAddress address = Inet6Address.getByName("0:0:0:0:0:0:0:1"); + Assume.assumeTrue(address.isReachable(1000)); + } catch (IOException exception) { + Assume.assumeTrue(false); + } + } + } diff --git a/src/java/test/org/apache/zookeeper/ZooKeeperTest.java b/src/java/test/org/apache/zookeeper/ZooKeeperTest.java index 9fc13c6aaf7..2cdc4cb6eac 100644 --- a/src/java/test/org/apache/zookeeper/ZooKeeperTest.java +++ b/src/java/test/org/apache/zookeeper/ZooKeeperTest.java @@ -17,7 +17,11 @@ */ package org.apache.zookeeper; +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.PrintStream; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -148,4 +152,130 @@ public void testParseWithExtraSpaces() throws Exception { Assert.assertEquals("ls is not taken as first argument", zkMain.cl.getCmdArgument(0), "ls"); Assert.assertEquals("/ is not taken as second argument", zkMain.cl.getCmdArgument(1), "/"); } + + @Test + public void testCheckInvalidAcls() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + String cmdstring = "create -s -e /node data ip:scheme:gggsd"; //invalid acl's + try{ + zkMain.executeLine(cmdstring); + }catch(KeeperException.InvalidACLException e){ + fail("For Invalid ACls should not throw exception"); + } + } + + @Test + public void testDeleteWithInvalidVersionNo() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + String cmdstring = "create -s -e /node1 data "; + String cmdstring1 = "delete /node1 2";//invalid dataversion no + zkMain.executeLine(cmdstring); + try{ + zkMain.executeLine(cmdstring1); + + }catch(KeeperException.BadVersionException e){ + fail("For Invalid dataversion number should not throw exception"); + } + } + + @Test + public void testCliCommandsNotEchoingUsage() throws Exception { + // setup redirect out/err streams to get System.in/err, use this judiciously! + final PrintStream systemErr = System.err; // get current err + final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + System.setErr(new PrintStream(errContent)); + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + String cmd1 = "printwatches"; + zkMain.executeLine(cmd1); + String cmd2 = "history"; + zkMain.executeLine(cmd2); + String cmd3 = "redo"; + zkMain.executeLine(cmd3); + // revert redirect of out/err streams - important step! + System.setErr(systemErr); + if (errContent.toString().contains("ZooKeeper -server host:port cmd args")) { + fail("CLI commands (history, redo, connect, printwatches) display usage info!"); + } + } + + @Test + public void testParseWithQuotes() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + for (String quoteChar : new String[] {"'", "\""}) { + String cmdstring = String.format("create /node %1$squoted data%1$s", quoteChar); + zkMain.cl.parseCommand(cmdstring); + Assert.assertEquals("quotes combine arguments", zkMain.cl.getNumArguments(), 3); + Assert.assertEquals("create is not taken as first argument", zkMain.cl.getCmdArgument(0), "create"); + Assert.assertEquals("/node is not taken as second argument", zkMain.cl.getCmdArgument(1), "/node"); + Assert.assertEquals("quoted data is not taken as third argument", zkMain.cl.getCmdArgument(2), "quoted data"); + } + } + + @Test + public void testParseWithMixedQuotes() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + for (String[] quoteChars : new String[][] {{"'", "\""}, {"\"", "'"}}) { + String outerQuotes = quoteChars[0]; + String innerQuotes = quoteChars[1]; + String cmdstring = String.format("create /node %1$s%2$squoted data%2$s%1$s", outerQuotes, innerQuotes); + zkMain.cl.parseCommand(cmdstring); + Assert.assertEquals("quotes combine arguments", zkMain.cl.getNumArguments(), 3); + Assert.assertEquals("create is not taken as first argument", zkMain.cl.getCmdArgument(0), "create"); + Assert.assertEquals("/node is not taken as second argument", zkMain.cl.getCmdArgument(1), "/node"); + Assert.assertEquals("quoted data is not taken as third argument", zkMain.cl.getCmdArgument(2), innerQuotes + "quoted data" + innerQuotes); + } + } + + @Test + public void testParseWithEmptyQuotes() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + String cmdstring = "create /node ''"; + zkMain.cl.parseCommand(cmdstring); + Assert.assertEquals("empty quotes should produce arguments", zkMain.cl.getNumArguments(), 3); + Assert.assertEquals("create is not taken as first argument", zkMain.cl.getCmdArgument(0), "create"); + Assert.assertEquals("/node is not taken as second argument", zkMain.cl.getCmdArgument(1), "/node"); + Assert.assertEquals("empty string is not taken as third argument", zkMain.cl.getCmdArgument(2), ""); + } + + @Test + public void testParseWithMultipleQuotes() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + String cmdstring = "create /node '' ''"; + zkMain.cl.parseCommand(cmdstring); + Assert.assertEquals("expected 5 arguments", zkMain.cl.getNumArguments(), 4); + Assert.assertEquals("create is not taken as first argument", zkMain.cl.getCmdArgument(0), "create"); + Assert.assertEquals("/node is not taken as second argument", zkMain.cl.getCmdArgument(1), "/node"); + Assert.assertEquals("empty string is not taken as third argument", zkMain.cl.getCmdArgument(2), ""); + Assert.assertEquals("empty string is not taken as fourth argument", zkMain.cl.getCmdArgument(3), ""); + } + + // ZOOKEEPER-2467 : Testing negative number for redo command + @Test + public void testRedoWithNegativeCmdNumber() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + String cmd1 = "redo -1"; + + // setup redirect out/err streams to get System.in/err, use this + // judiciously! + final PrintStream systemOut = System.out; // get current out + final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); + try { + zkMain.executeLine(cmd1); + Assert.assertEquals("Command index out of range", outContent + .toString().trim()); + } finally { + // revert redirect of out/err streams - important step! + System.setOut(systemOut); + } + } + } diff --git a/src/java/test/org/apache/zookeeper/common/TimeTest.java b/src/java/test/org/apache/zookeeper/common/TimeTest.java new file mode 100644 index 00000000000..d938556939c --- /dev/null +++ b/src/java/test/org/apache/zookeeper/common/TimeTest.java @@ -0,0 +1,109 @@ +/** + * 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.common; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Command line program for demonstrating robustness to clock + * changes. + *

                                                  + * How to run: + * ant clean compile-test + * echo build/test/lib/*.jar build/lib/*.jar build/classes build/test/classes | sed -e 's/ /:/g' > cp + * java -cp $(cat cp) org.apache.zookeeper.common.TimeTest | tee log-without-patch + *

                                                  + * After test program starts, in another window, do commands: + * date -s '+1hour' + * date -s '-1hour' + *

                                                  + * As long as there isn't any expired event, the experiment is successful. + */ +public class TimeTest extends ClientBase { + private static final long mt0 = System.currentTimeMillis(); + private static final long nt0 = Time.currentElapsedTime(); + + private static AtomicInteger watchCount = new AtomicInteger(0); + + + public static void main(String[] args) throws Exception { + System.out.printf("Starting\n"); + final TimeTest test = new TimeTest(); + System.out.printf("After construct\n"); + test.setUp(); + ZooKeeper zk = test.createClient(); + zk.create("/ephemeral", new byte[]{1, 2, 3}, + ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + while (Time.currentElapsedTime() - nt0 < 100000) { + System.out.printf("%d\t%s\n", discrepancy(), + zk.exists("/ephemeral", + watchCount.get() == 0 ? createWatcher() : null) != null); + waitByYielding(500); + } + } + + private static Watcher createWatcher() { + watchCount.incrementAndGet(); + return new Watcher() { + @Override + public void process(WatchedEvent event) { + watchCount.decrementAndGet(); + System.out.printf("%d event = %s\n", discrepancy(), event); + } + }; + + } + + private static void waitByYielding(long delay) { + long t0 = Time.currentElapsedTime(); + while (Time.currentElapsedTime() < t0 + delay) { + Thread.yield(); + } + } + + private static long discrepancy() { + return (System.currentTimeMillis() - mt0) - (Time.currentElapsedTime() - nt0); + } + + @Test + public void testElapsedTimeToDate() throws Exception { + long walltime = Time.currentWallTime(); + long elapsedTime = Time.currentElapsedTime(); + Thread.sleep(200); + + Calendar cal = Calendar.getInstance(); + cal.setTime(Time.elapsedTimeToDate(elapsedTime)); + int calculatedDate = cal.get(Calendar.HOUR_OF_DAY); + cal.setTime(new Date(walltime)); + int realDate = cal.get(Calendar.HOUR_OF_DAY); + + Assert.assertEquals(calculatedDate, realDate); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/CRCTest.java b/src/java/test/org/apache/zookeeper/server/CRCTest.java index 36f1c6285ce..2b7fb4697a1 100644 --- a/src/java/test/org/apache/zookeeper/server/CRCTest.java +++ b/src/java/test/org/apache/zookeeper/server/CRCTest.java @@ -131,6 +131,7 @@ public void testChecksums() throws Exception { zk.close(); } f.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, ClientBase.CONNECTION_TIMEOUT)); diff --git a/src/java/test/org/apache/zookeeper/server/DataNodeTest.java b/src/java/test/org/apache/zookeeper/server/DataNodeTest.java new file mode 100644 index 00000000000..628976604a3 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/DataNodeTest.java @@ -0,0 +1,65 @@ +/** + * 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; + +import static org.junit.Assert.*; + +import java.util.Set; + +import org.junit.Test; + +public class DataNodeTest { + + @Test + public void testGetChildrenShouldReturnEmptySetWhenThereAreNoChidren() { + // create DataNode and call getChildren + DataNode dataNode = new DataNode(); + Set children = dataNode.getChildren(); + assertNotNull(children); + assertEquals(0, children.size()); + + // add child,remove child and then call getChildren + String child = "child"; + dataNode.addChild(child); + dataNode.removeChild(child); + children = dataNode.getChildren(); + assertNotNull(children); + assertEquals(0, children.size()); + + // Returned empty set must not be modifiable + children = dataNode.getChildren(); + try { + children.add("new child"); + fail("UnsupportedOperationException is expected"); + } catch (UnsupportedOperationException e) { + // do nothing + } + } + + @Test + public void testGetChildrenReturnsImmutableEmptySet() { + DataNode dataNode = new DataNode(); + Set children = dataNode.getChildren(); + try { + children.add("new child"); + fail("UnsupportedOperationException is expected"); + } catch (UnsupportedOperationException e) { + // do nothing + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/DataTreeTest.java b/src/java/test/org/apache/zookeeper/server/DataTreeTest.java new file mode 100644 index 00000000000..a484cf58ef9 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/DataTreeTest.java @@ -0,0 +1,235 @@ +/** + * 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; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.KeeperException.NoNodeException; +import org.apache.zookeeper.KeeperException.NodeExistsException; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.DataTree; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.apache.zookeeper.server.DataNode; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.apache.zookeeper.Quotas; +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.common.PathTrie; +import java.lang.reflect.*; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class DataTreeTest extends ZKTestCase { + protected static final Logger LOG = LoggerFactory.getLogger(DataTreeTest.class); + + private DataTree dt; + + @Before + public void setUp() throws Exception { + dt=new DataTree(); + } + + @After + public void tearDown() throws Exception { + dt=null; + } + + /** + * For ZOOKEEPER-1755 - Test race condition when taking dumpEphemerals and + * removing the session related ephemerals from DataTree structure + */ + @Test(timeout = 60000) + public void testDumpEphemerals() throws Exception { + int count = 1000; + long session = 1000; + long zxid = 2000; + final DataTree dataTree = new DataTree(); + LOG.info("Create {} zkclient sessions and its ephemeral nodes", count); + createEphemeralNode(session, dataTree, count); + final AtomicBoolean exceptionDuringDumpEphemerals = new AtomicBoolean( + false); + final AtomicBoolean running = new AtomicBoolean(true); + Thread thread = new Thread() { + public void run() { + PrintWriter pwriter = new PrintWriter(new StringWriter()); + try { + while (running.get()) { + dataTree.dumpEphemerals(pwriter); + } + } catch (Exception e) { + LOG.error("Received exception while dumpEphemerals!", e); + exceptionDuringDumpEphemerals.set(true); + } + }; + }; + thread.start(); + LOG.debug("Killing {} zkclient sessions and its ephemeral nodes", count); + killZkClientSession(session, zxid, dataTree, count); + running.set(false); + thread.join(); + Assert.assertFalse("Should have got exception while dumpEphemerals!", + exceptionDuringDumpEphemerals.get()); + } + + private void killZkClientSession(long session, long zxid, + final DataTree dataTree, int count) { + for (int i = 0; i < count; i++) { + dataTree.killSession(session + i, zxid); + } + } + + private void createEphemeralNode(long session, final DataTree dataTree, + int count) throws NoNodeException, NodeExistsException { + for (int i = 0; i < count; i++) { + dataTree.createNode("/test" + i, new byte[0], null, session + i, + dataTree.getNode("/").stat.getCversion() + 1, 1, 1); + } + } + + @Test(timeout = 60000) + public void testRootWatchTriggered() throws Exception { + class MyWatcher implements Watcher{ + boolean fired=false; + public void process(WatchedEvent event) { + if(event.getPath().equals("/")) + fired=true; + } + } + MyWatcher watcher=new MyWatcher(); + // set a watch on the root node + dt.getChildren("/", new Stat(), watcher); + // add a new node, should trigger a watch + dt.createNode("/xyz", new byte[0], null, 0, dt.getNode("/").stat.getCversion()+1, 1, 1); + Assert.assertFalse("Root node watch not triggered",!watcher.fired); + } + + /** + * For ZOOKEEPER-1046 test if cversion is getting incremented correctly. + */ + @Test(timeout = 60000) + public void testIncrementCversion() throws Exception { + dt.createNode("/test", new byte[0], null, 0, dt.getNode("/").stat.getCversion()+1, 1, 1); + DataNode zk = dt.getNode("/test"); + int prevCversion = zk.stat.getCversion(); + long prevPzxid = zk.stat.getPzxid(); + dt.setCversionPzxid("/test/", prevCversion + 1, prevPzxid + 1); + int newCversion = zk.stat.getCversion(); + long newPzxid = zk.stat.getPzxid(); + Assert.assertTrue(" verification failed. Expected: <" + + (prevCversion + 1) + ", " + (prevPzxid + 1) + ">, found: <" + + newCversion + ", " + newPzxid + ">", + (newCversion == prevCversion + 1 && newPzxid == prevPzxid + 1)); + } + + @Test(timeout = 60000) + public void testPathTrieClearOnDeserialize() throws Exception { + + //Create a DataTree with quota nodes so PathTrie get updated + DataTree dserTree = new DataTree(); + + dserTree.createNode("/bug", new byte[20], null, -1, 1, 1, 1); + dserTree.createNode(Quotas.quotaZookeeper+"/bug", null, null, -1, 1, 1, 1); + dserTree.createNode(Quotas.quotaPath("/bug"), new byte[20], null, -1, 1, 1, 1); + dserTree.createNode(Quotas.statPath("/bug"), new byte[20], null, -1, 1, 1, 1); + + //deserialize a DataTree; this should clear the old /bug nodes and pathTrie + DataTree tree = new DataTree(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive oa = BinaryOutputArchive.getArchive(baos); + tree.serialize(oa, "test"); + baos.flush(); + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + BinaryInputArchive ia = BinaryInputArchive.getArchive(bais); + dserTree.deserialize(ia, "test"); + + Field pfield = DataTree.class.getDeclaredField("pTrie"); + pfield.setAccessible(true); + PathTrie pTrie = (PathTrie)pfield.get(dserTree); + + //Check that the node path is removed from pTrie + Assert.assertEquals("/bug is still in pTrie", "", pTrie.findMaxPrefix("/bug")); + } + + /* + * ZOOKEEPER-2201 - OutputArchive.writeRecord can block for long periods of + * time, we must call it outside of the node lock. + * We call tree.serialize, which calls our modified writeRecord method that + * blocks until it can verify that a separate thread can lock the DataNode + * currently being written, i.e. that DataTree.serializeNode does not hold + * the DataNode lock while calling OutputArchive.writeRecord. + */ + @Test(timeout = 60000) + public void testSerializeDoesntLockDataNodeWhileWriting() throws Exception { + DataTree tree = new DataTree(); + tree.createNode("/marker", new byte[] {42}, null, -1, 1, 1, 1); + final DataNode markerNode = tree.getNode("/marker"); + final AtomicBoolean ranTestCase = new AtomicBoolean(); + DataOutputStream out = new DataOutputStream(new ByteArrayOutputStream()); + BinaryOutputArchive oa = new BinaryOutputArchive(out) { + @Override + public void writeRecord(Record r, String tag) throws IOException { + DataNode node = (DataNode) r; + if (node.data.length == 1 && node.data[0] == 42) { + final Semaphore semaphore = new Semaphore(0); + new Thread(new Runnable() { + @Override + public void run() { + synchronized (markerNode) { + //When we lock markerNode, allow writeRecord to continue + semaphore.release(); + } + } + }).start(); + + try { + boolean acquired = semaphore.tryAcquire(30, TimeUnit.SECONDS); + //This is the real assertion - could another thread lock + //the DataNode we're currently writing + Assert.assertTrue("Couldn't acquire a lock on the DataNode while we were calling tree.serialize", acquired); + } catch (InterruptedException e1) { + throw new RuntimeException(e1); + } + ranTestCase.set(true); + } + super.writeRecord(r, tag); + } + }; + + tree.serialize(oa, "test"); + + //Let's make sure that we hit the code that ran the real assertion above + Assert.assertTrue("Didn't find the expected node", ranTestCase.get()); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/InvalidSnapCountTest.java b/src/java/test/org/apache/zookeeper/server/InvalidSnapCountTest.java new file mode 100644 index 00000000000..3a661647a57 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/InvalidSnapCountTest.java @@ -0,0 +1,126 @@ +/** + * 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; + +import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test stand-alone server. + * + */ +public class InvalidSnapCountTest extends ZKTestCase implements Watcher { + protected static final Logger LOG = + LoggerFactory.getLogger(InvalidSnapCountTest.class); + + public static class MainThread extends Thread { + final File confFile; + final TestMain main; + + public MainThread(int clientPort) throws IOException { + super("Standalone server with clientPort:" + clientPort); + File tmpDir = ClientBase.createTmpDir(); + confFile = new File(tmpDir, "zoo.cfg"); + + FileWriter fwriter = new FileWriter(confFile); + fwriter.write("tickTime=2000\n"); + fwriter.write("initLimit=10\n"); + fwriter.write("syncLimit=5\n"); + fwriter.write("snapCount=1\n"); + + File dataDir = new File(tmpDir, "data"); + if (!dataDir.mkdir()) { + throw new IOException("unable to mkdir " + dataDir); + } + + // Convert windows path to UNIX to avoid problems with "\" + String dir = dataDir.toString(); + String osname = java.lang.System.getProperty("os.name"); + if (osname.toLowerCase().contains("windows")) { + dir = dir.replace('\\', '/'); + } + fwriter.write("dataDir=" + dir + "\n"); + + fwriter.write("clientPort=" + clientPort + "\n"); + fwriter.flush(); + fwriter.close(); + + main = new TestMain(); + } + + public void run() { + String args[] = new String[1]; + args[0] = confFile.toString(); + try { + main.initializeAndRun(args); + } catch (Exception e) { + // test will still fail even though we just log/ignore + LOG.error("unexpected exception in run", e); + } + } + + public void shutdown() { + main.shutdown(); + } + } + + public static class TestMain extends ZooKeeperServerMain { + public void shutdown() { + super.shutdown(); + } + } + + /** + * Verify the ability to start a standalone server instance. + */ + @Test + public void testInvalidSnapCount() throws Exception { + + final int CLIENT_PORT = 3181; + + MainThread main = new MainThread(CLIENT_PORT); + main.start(); + + Assert.assertTrue("waiting for server being up", + ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT, + CONNECTION_TIMEOUT)); + + Assert.assertEquals(SyncRequestProcessor.getSnapCount(), 2); + + main.shutdown(); + + } + + public void process(WatchedEvent event) { + // ignore for this test + } +} diff --git a/src/java/test/org/apache/zookeeper/server/InvalidSnapshotTest.java b/src/java/test/org/apache/zookeeper/server/InvalidSnapshotTest.java index 738f6656905..97ad7923ed0 100644 --- a/src/java/test/org/apache/zookeeper/server/InvalidSnapshotTest.java +++ b/src/java/test/org/apache/zookeeper/server/InvalidSnapshotTest.java @@ -18,95 +18,67 @@ package org.apache.zookeeper.server; +import static org.junit.Assert.assertTrue; + import java.io.File; import java.io.RandomAccessFile; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.PortAssignment; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZKTestCase; -import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.test.ClientBase; -import org.junit.Assert; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * this test checks that the server works - * even if the last snapshot is invalidated - * by corruption or if the server crashes - * while generating the snapshot. + * This test checks that the server works even if the last snapshot is + * invalidated by corruption or if the server crashes while generating the + * snapshot. */ -public class InvalidSnapshotTest extends ZKTestCase implements Watcher { +public class InvalidSnapshotTest extends ClientBase { private static final Logger LOG = - LoggerFactory.getLogger(InvalidSnapshotTest.class); + LoggerFactory.getLogger(InvalidSnapshotTest.class); - private static final String HOSTPORT = - "127.0.0.1:" + PortAssignment.unique(); - private static final int CONNECTION_TIMEOUT = 3000; + public InvalidSnapshotTest() { + SyncRequestProcessor.setSnapCount(100); + } /** - * this test does the main work of testing - * an invalid snapshot - * @throws Exception + * Validate that the server can come up on an invalid snapshot - by + * reverting to a prior snapshot + associated logs. */ @Test public void testInvalidSnapshot() throws Exception { - File tmpDir = ClientBase.createTmpDir(); - ClientBase.setupTestEnv(); - ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); - SyncRequestProcessor.setSnapCount(100); - final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); - ServerCnxnFactory f = ServerCnxnFactory.createFactory(PORT, -1); - f.startup(zks); - Assert.assertTrue("waiting for server being up ", - ClientBase.waitForServerUp(HOSTPORT,CONNECTION_TIMEOUT)); - ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); - try { - for (int i=0; i< 2000; i++) { - zk.create("/invalidsnap-" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT); - } - } finally { - zk.close(); - } - f.shutdown(); - Assert.assertTrue("waiting for server to shutdown", - ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); - // now corrupt the snapshot - File snapFile = zks.getTxnLogFactory().findMostRecentSnapshot(); - RandomAccessFile raf = new RandomAccessFile(snapFile, "rws"); - raf.setLength(3); - raf.close(); - // now restart the server and see if it starts - zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); - SyncRequestProcessor.setSnapCount(100); - f = ServerCnxnFactory.createFactory(PORT, -1); - f.startup(zks); - Assert.assertTrue("waiting for server being up ", - ClientBase.waitForServerUp(HOSTPORT,CONNECTION_TIMEOUT)); - // the server should come up - zk = new ZooKeeper(HOSTPORT, 20000, this); - try { - Assert.assertTrue("the node should exist", - (zk.exists("/invalidsnap-1999", false) != null)); - f.shutdown(); - Assert.assertTrue("waiting for server to shutdown", - ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); - } finally { - zk.close(); - } + ZooKeeper zk = createClient(); + try { + for (int i = 0; i < 2000; i++) { + zk.create("/invalidsnap-" + i, new byte[0], + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + } finally { + zk.close(); + } + NIOServerCnxnFactory factory = (NIOServerCnxnFactory)serverFactory; + stopServer(); - f.shutdown(); - Assert.assertTrue("waiting for server to shutdown", - ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); - } + // now corrupt the snapshot + File snapFile = factory.zkServer.getTxnLogFactory().findMostRecentSnapshot(); + LOG.info("Corrupting " + snapFile); + RandomAccessFile raf = new RandomAccessFile(snapFile, "rws"); + raf.setLength(3); + raf.close(); - public void process(WatchedEvent event) { - // do nothing for now - } + // now restart the server + startServer(); + // verify that the expected data exists and wasn't lost + zk = createClient(); + try { + assertTrue("the node should exist", + (zk.exists("/invalidsnap-1999", false) != null)); + } finally { + zk.close(); + } + } } diff --git a/src/java/test/org/apache/zookeeper/server/MockNIOServerCnxn.java b/src/java/test/org/apache/zookeeper/server/MockNIOServerCnxn.java new file mode 100644 index 00000000000..53ad1f691fb --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/MockNIOServerCnxn.java @@ -0,0 +1,45 @@ +/** + * 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; + +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.io.IOException; + +public class MockNIOServerCnxn extends NIOServerCnxn { + + public MockNIOServerCnxn(ZooKeeperServer zk, SocketChannel sock, + SelectionKey sk, NIOServerCnxnFactory factory) + throws IOException { + super(zk, sock, sk, factory); + } + + /** + * Handles read/write IO on connection. + */ + public void doIO(SelectionKey k) throws InterruptedException { + super.doIO(k); + } + + @Override + protected boolean isSocketOpen() { + return true; + } + +} diff --git a/src/java/test/org/apache/zookeeper/server/NIOServerCnxnTest.java b/src/java/test/org/apache/zookeeper/server/NIOServerCnxnTest.java new file mode 100644 index 00000000000..bdee20f5f30 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/NIOServerCnxnTest.java @@ -0,0 +1,106 @@ +/** + * 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; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.CancelledKeyException; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NIOServerCnxnTest extends ClientBase { + private static final Logger LOG = LoggerFactory + .getLogger(NIOServerCnxnTest.class); + /** + * Test operations on ServerCnxn after socket closure. + */ + @Test(timeout = 60000) + public void testOperationsAfterCnxnClose() throws IOException, + InterruptedException, KeeperException { + final ZooKeeper zk = createClient(); + + final String path = "/a"; + try { + // make sure zkclient works + zk.create(path, "test".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + Assert.assertNotNull("Didn't create znode:" + path, + zk.exists(path, false)); + // Defaults ServerCnxnFactory would be instantiated with + // NIOServerCnxnFactory + Assert.assertTrue( + "Didn't instantiate ServerCnxnFactory with NIOServerCnxnFactory!", + serverFactory instanceof NIOServerCnxnFactory); + Iterable connections = serverFactory.getConnections(); + for (ServerCnxn serverCnxn : connections) { + serverCnxn.close(); + try { + serverCnxn.toString(); + } catch (Exception e) { + LOG.error("Exception while getting connection details!", e); + Assert.fail("Shouldn't throw exception while " + + "getting connection details!"); + } + } + } finally { + zk.close(); + } + } + + /** + * Mock extension of NIOServerCnxn to test for + * CancelledKeyException (ZOOKEEPER-2044). + */ + private static class MockNIOServerCnxn extends NIOServerCnxn { + public MockNIOServerCnxn(NIOServerCnxn cnxn) + throws IOException { + super(cnxn.zkServer, cnxn.sock, cnxn.sk, cnxn.factory); + } + + public void mockSendBuffer(ByteBuffer bb) throws Exception { + super.internalSendBuffer(bb); + } + } + + @Test(timeout = 30000) + public void testValidSelectionKey() throws Exception { + final ZooKeeper zk = createZKClient(hostPort, 3000); + try { + Iterable connections = serverFactory.getConnections(); + for (ServerCnxn serverCnxn : connections) { + MockNIOServerCnxn mock = new MockNIOServerCnxn((NIOServerCnxn) serverCnxn); + // Cancel key + ((NIOServerCnxn) serverCnxn).sock.keyFor(((NIOServerCnxnFactory) serverFactory).selector).cancel();; + mock.mockSendBuffer(ByteBuffer.allocate(8)); + } + } catch (CancelledKeyException e) { + LOG.error("Exception while sending bytes!", e); + Assert.fail(e.toString()); + } finally { + zk.close(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/NettyServerCnxnTest.java b/src/java/test/org/apache/zookeeper/server/NettyServerCnxnTest.java new file mode 100644 index 00000000000..52265c6607c --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/NettyServerCnxnTest.java @@ -0,0 +1,103 @@ +/** + * 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; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.Assert; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.server.NettyServerCnxnFactory.CnxnChannelHandler; +import org.apache.zookeeper.test.ClientBase; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Test verifies the behavior of NettyServerCnxn which represents a connection + * from a client to the server. + */ +public class NettyServerCnxnTest extends ClientBase { + private static final Logger LOG = LoggerFactory + .getLogger(NettyServerCnxnTest.class); + + @Override + public void setUp() throws Exception { + System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, + "org.apache.zookeeper.server.NettyServerCnxnFactory"); + super.setUp(); + } + + /** + * Test verifies the channel closure - while closing the channel + * servercnxnfactory should remove all channel references to avoid + * duplicate channel closure. Duplicate closure may result in + * indefinite hanging due to netty open issue. + * + * @see NETTY-412 + */ + @Test(timeout = 30000) + public void testSendCloseSession() throws Exception { + Assert.assertTrue( + "Didn't instantiate ServerCnxnFactory with NettyServerCnxnFactory!", + serverFactory instanceof NettyServerCnxnFactory); + + NettyServerCnxnFactory nettyServerFactory = (NettyServerCnxnFactory) serverFactory; + final CountDownLatch channelLatch = new CountDownLatch(1); + CnxnChannelHandler channelHandler = nettyServerFactory.new CnxnChannelHandler() { + @Override + public void channelDisconnected(ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + LOG.info("Recieves channel disconnected event"); + channelLatch.countDown(); + } + }; + LOG.info("Adding custom channel handler for simulation"); + nettyServerFactory.bootstrap.getPipeline().remove("servercnxnfactory"); + nettyServerFactory.bootstrap.getPipeline().addLast("servercnxnfactory", + channelHandler); + + final ZooKeeper zk = createClient(); + final String path = "/a"; + try { + // make sure zkclient works + zk.create(path, "test".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + Assert.assertNotNull("Didn't create znode:" + path, + zk.exists(path, false)); + Iterable connections = serverFactory.getConnections(); + Assert.assertEquals("Mismatch in number of live connections!", 1, + serverFactory.getNumAliveConnections()); + for (ServerCnxn serverCnxn : connections) { + serverCnxn.sendCloseSession(); + } + LOG.info("Waiting for the channel disconnected event"); + channelLatch.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); + Assert.assertEquals("Mismatch in number of live connections!", 0, + serverFactory.getNumAliveConnections()); + } finally { + zk.close(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/PrepRequestProcessorTest.java b/src/java/test/org/apache/zookeeper/server/PrepRequestProcessorTest.java new file mode 100644 index 00000000000..d86cf2a7f67 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/PrepRequestProcessorTest.java @@ -0,0 +1,262 @@ +/** + * 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; + +import org.apache.jute.BinaryOutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.KeeperException.SessionExpiredException; +import org.apache.zookeeper.KeeperException.SessionMovedException; +import org.apache.zookeeper.MultiTransactionRecord; +import org.apache.zookeeper.Op; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.proto.SetDataRequest; +import org.apache.zookeeper.server.ZooKeeperServer.ChangeRecord; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.txn.ErrorTxn; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class PrepRequestProcessorTest extends ClientBase { + private static final Logger LOG = LoggerFactory.getLogger(PrepRequestProcessorTest.class); + private static final int CONNECTION_TIMEOUT = 3000; + private static String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); + private CountDownLatch pLatch; + + private ZooKeeperServer zks; + private ServerCnxnFactory servcnxnf; + private PrepRequestProcessor processor; + private Request outcome; + + @Before + public void setup() throws Exception { + File tmpDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + SyncRequestProcessor.setSnapCount(100); + final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); + + servcnxnf = ServerCnxnFactory.createFactory(PORT, -1); + servcnxnf.startup(zks); + Assert.assertTrue("waiting for server being up ", + ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); + zks.sessionTracker = new MySessionTracker(); + } + + @After + public void teardown() throws Exception { + if (servcnxnf != null) { + servcnxnf.shutdown(); + } + if (zks != null) { + zks.shutdown(); + } + } + + @Test + public void testPRequest() throws Exception { + pLatch = new CountDownLatch(1); + processor = new PrepRequestProcessor(zks, new MyRequestProcessor()); + Request foo = new Request(null, 1l, 1, OpCode.create, ByteBuffer.allocate(3), null); + processor.pRequest(foo); + + Assert.assertEquals("Request should have marshalling error", new ErrorTxn(KeeperException.Code.MARSHALLINGERROR.intValue()), + outcome.txn); + Assert.assertTrue("request hasn't been processed in chain", pLatch.await(5, TimeUnit.SECONDS)); + } + + private Request createRequest(Record record, int opCode) throws IOException { + // encoding + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos); + record.serialize(boa, "request"); + baos.close(); + + // Id + List ids = Arrays.asList(Ids.ANYONE_ID_UNSAFE); + + return new Request(null, 1l, 0, opCode, ByteBuffer.wrap(baos.toByteArray()), ids); + } + + private void process(List ops) throws Exception { + pLatch = new CountDownLatch(1); + processor = new PrepRequestProcessor(zks, new MyRequestProcessor()); + + Record record = new MultiTransactionRecord(ops); + Request req = createRequest(record, OpCode.multi); + + processor.pRequest(req); + Assert.assertTrue("request hasn't been processed in chain", pLatch.await(5, TimeUnit.SECONDS)); + } + + /** + * This test checks that a successful multi will change outstanding record + * and failed multi shouldn't change outstanding record. + */ + @Test + public void testMultiOutstandingChange() throws Exception { + zks.getZKDatabase().dataTree.createNode("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, 0, 0, 0, 0); + + Assert.assertNull(zks.outstandingChangesForPath.get("/foo")); + + process(Arrays.asList( + Op.setData("/foo", new byte[0], -1))); + + ChangeRecord cr = zks.outstandingChangesForPath.get("/foo"); + Assert.assertNotNull("Change record wasn't set", cr); + Assert.assertEquals("Record zxid wasn't set correctly", + 1, cr.zxid); + + process(Arrays.asList( + Op.delete("/foo", -1))); + cr = zks.outstandingChangesForPath.get("/foo"); + Assert.assertEquals("Record zxid wasn't set correctly", + 2, cr.zxid); + + + // It should fail and shouldn't change outstanding record. + process(Arrays.asList( + Op.delete("/foo", -1))); + cr = zks.outstandingChangesForPath.get("/foo"); + // zxid should still be previous result because record's not changed. + Assert.assertEquals("Record zxid wasn't set correctly", + 2, cr.zxid); + } + + /** + * ZOOKEEPER-2052: + * This test checks that if a multi operation aborted, and during the multi there is side effect + * that changed outstandingChangesForPath, after aborted the side effect should be removed and + * everything should be restored correctly. + */ + @Test + public void testMultiRollbackNoLastChange() throws Exception { + zks.getZKDatabase().dataTree.createNode("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, 0, 0, 0, 0); + zks.getZKDatabase().dataTree.createNode("/foo/bar", new byte[0], Ids.OPEN_ACL_UNSAFE, 0, 0, 0, 0); + + Assert.assertNull(zks.outstandingChangesForPath.get("/foo")); + + // multi record: + // set "/foo" => succeed, leave a outstanding change + // delete "/foo" => fail, roll back change + process(Arrays.asList( + Op.setData("/foo", new byte[0], -1), + Op.delete("/foo", -1))); + + // aborting multi shouldn't leave any record. + Assert.assertNull(zks.outstandingChangesForPath.get("/foo")); + } + + /** + * It tests that PrepRequestProcessor will return BadArgument KeeperException + * if the request path (if it exists) is not valid, e.g. empty string. + */ + @Test + public void testInvalidPath() throws Exception { + pLatch = new CountDownLatch(1); + processor = new PrepRequestProcessor(zks, new MyRequestProcessor()); + + SetDataRequest record = new SetDataRequest("", new byte[0], -1); + Request req = createRequest(record, OpCode.setData); + processor.pRequest(req); + pLatch.await(); + Assert.assertEquals(outcome.hdr.getType(), OpCode.error); + Assert.assertEquals(outcome.getException().code(), KeeperException.Code.BADARGUMENTS); + } + + private class MyRequestProcessor implements RequestProcessor { + @Override + public void processRequest(Request request) { + // getting called by PrepRequestProcessor + outcome = request; + pLatch.countDown(); + } + + @Override + public void shutdown() { + // TODO Auto-generated method stub + } + } + + private class MySessionTracker implements SessionTracker { + @Override + public void addSession(long id, int to) { + // TODO Auto-generated method stub + + } + @Override + public void checkSession(long sessionId, Object owner) + throws SessionExpiredException, SessionMovedException { + // TODO Auto-generated method stub + + } + @Override + public long createSession(int sessionTimeout) { + // TODO Auto-generated method stub + return 0; + } + @Override + public void dumpSessions(PrintWriter pwriter) { + // TODO Auto-generated method stub + + } + @Override + public void removeSession(long sessionId) { + // TODO Auto-generated method stub + + } + @Override + public void setOwner(long id, Object owner) + throws SessionExpiredException { + // TODO Auto-generated method stub + + } + @Override + public void shutdown() { + // TODO Auto-generated method stub + + } + @Override + public boolean touchSession(long sessionId, int sessionTimeout) { + // TODO Auto-generated method stub + return false; + } + @Override + public void setSessionClosing(long sessionId) { + // TODO Auto-generated method stub + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/PurgeTxnTest.java b/src/java/test/org/apache/zookeeper/server/PurgeTxnTest.java new file mode 100644 index 00000000000..1685a5bba82 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/PurgeTxnTest.java @@ -0,0 +1,604 @@ +/** + * 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; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.zookeeper.data.Stat; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.server.PurgeTxnLog; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.SyncRequestProcessor; +import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.persistence.Util; +import org.apache.zookeeper.test.ClientBase; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PurgeTxnTest extends ZKTestCase implements Watcher { + private static final Logger LOG = LoggerFactory.getLogger(PurgeTxnTest.class); + private static String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); + private static final int CONNECTION_TIMEOUT = 3000; + private static final long OP_TIMEOUT_IN_MILLIS = 90000; + private File tmpDir; + + @After + public void teardown() { + if (null != tmpDir) { + ClientBase.recursiveDelete(tmpDir); + } + } + + /** + * test the purge + * @throws Exception an exception might be thrown here + */ + @Test + public void testPurge() throws Exception { + tmpDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + SyncRequestProcessor.setSnapCount(100); + final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); + ServerCnxnFactory f = ServerCnxnFactory.createFactory(PORT, -1); + f.startup(zks); + Assert.assertTrue("waiting for server being up ", + ClientBase.waitForServerUp(HOSTPORT,CONNECTION_TIMEOUT)); + ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); + try { + for (int i = 0; i< 2000; i++) { + zk.create("/invalidsnap-" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } + } finally { + zk.close(); + } + f.shutdown(); + zks.getTxnLogFactory().close(); + Assert.assertTrue("waiting for server to shutdown", + ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); + // now corrupt the snapshot + PurgeTxnLog.purge(tmpDir, tmpDir, 3); + FileTxnSnapLog snaplog = new FileTxnSnapLog(tmpDir, tmpDir); + List listLogs = snaplog.findNRecentSnapshots(4); + int numSnaps = 0; + for (File ff: listLogs) { + if (ff.getName().startsWith("snapshot")) { + numSnaps++; + } + } + Assert.assertTrue("exactly 3 snapshots ", (numSnaps == 3)); + snaplog.close(); + zks.shutdown(); + } + + /** + * Tests purge when logs are rolling or a new snapshot is created, then + * these newer files should alse be excluded in the current cycle. + * + * For frequent snapshotting, configured SnapCount to 30. There are three + * threads which will create 1000 znodes each and simultaneously do purge + * call + */ + @Test + public void testPurgeWhenLogRollingInProgress() throws Exception { + tmpDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + SyncRequestProcessor.setSnapCount(30); + final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); + ServerCnxnFactory f = ServerCnxnFactory.createFactory(PORT, -1); + f.startup(zks); + Assert.assertTrue("waiting for server being up ", + ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); + final ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); + final CountDownLatch doPurge = new CountDownLatch(1); + final CountDownLatch purgeFinished = new CountDownLatch(1); + final AtomicBoolean opFailed = new AtomicBoolean(false); + new Thread() { + public void run() { + try { + doPurge.await(OP_TIMEOUT_IN_MILLIS / 2, + TimeUnit.MILLISECONDS); + PurgeTxnLog.purge(tmpDir, tmpDir, 3); + } catch (IOException ioe) { + LOG.error("Exception when purge", ioe); + opFailed.set(true); + } catch (InterruptedException ie) { + LOG.error("Exception when purge", ie); + opFailed.set(true); + } finally { + purgeFinished.countDown(); + } + }; + }.start(); + final int thCount = 3; + List znodes = manyClientOps(zk, doPurge, thCount, + "/invalidsnap"); + Assert.assertTrue("Purging is not finished!", purgeFinished.await( + OP_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS)); + Assert.assertFalse("Purging failed!", opFailed.get()); + for (String znode : znodes) { + try { + zk.getData(znode, false, null); + } catch (Exception ke) { + LOG.error("Unexpected exception when visiting znode!", ke); + Assert.fail("Unexpected exception when visiting znode!"); + } + } + zk.close(); + f.shutdown(); + zks.shutdown(); + zks.getTxnLogFactory().close(); + } + + /** + * Tests finding n recent snapshots from set of snapshots and data logs + */ + @Test + public void testFindNRecentSnapshots() throws Exception { + int nRecentSnap = 4; // n recent snap shots + int nRecentCount = 30; + int offset = 0; + + tmpDir = ClientBase.createTmpDir(); + File version2 = new File(tmpDir.toString(), "version-2"); + Assert.assertTrue("Failed to create version_2 dir:" + version2.toString(), + version2.mkdir()); + + // Test that with no snaps, findNRecentSnapshots returns empty list + FileTxnSnapLog txnLog = new FileTxnSnapLog(tmpDir, tmpDir); + List foundSnaps = txnLog.findNRecentSnapshots(1); + assertEquals(0, foundSnaps.size()); + + List expectedNRecentSnapFiles = new ArrayList(); + int counter = offset + (2 * nRecentCount); + for (int i = 0; i < nRecentCount; i++) { + // simulate log file + File logFile = new File(version2 + "/log." + Long.toHexString(--counter)); + Assert.assertTrue("Failed to create log File:" + logFile.toString(), + logFile.createNewFile()); + // simulate snapshot file + File snapFile = new File(version2 + "/snapshot." + + Long.toHexString(--counter)); + Assert.assertTrue("Failed to create snap File:" + snapFile.toString(), + snapFile.createNewFile()); + // add the n recent snap files for assertion + if(i < nRecentSnap){ + expectedNRecentSnapFiles.add(snapFile); + } + } + + // Test that when we ask for recent snaps we get the number we asked for and + // the files we expected + List nRecentSnapFiles = txnLog.findNRecentSnapshots(nRecentSnap); + Assert.assertEquals("exactly 4 snapshots ", 4, + nRecentSnapFiles.size()); + expectedNRecentSnapFiles.removeAll(nRecentSnapFiles); + Assert.assertEquals("Didn't get the recent snap files", 0, + expectedNRecentSnapFiles.size()); + + // Test that when asking for more snaps than we created, we still only get snaps + // not logs or anything else (per ZOOKEEPER-2420) + nRecentSnapFiles = txnLog.findNRecentSnapshots(nRecentCount + 5); + assertEquals(nRecentCount, nRecentSnapFiles.size()); + for (File f: nRecentSnapFiles) { + Assert.assertTrue("findNRecentSnapshots() returned a non-snapshot: " + f.getPath(), + (Util.getZxidFromName(f.getName(), "snapshot") != -1)); + } + + txnLog.close(); + } + + /** + * Tests purge where the data directory contains old snapshots and data + * logs, newest snapshots and data logs, (newest + n) snapshots and data + * logs + */ + @Test + public void testSnapFilesGreaterThanToRetain() throws Exception { + int nRecentCount = 4; + int fileAboveRecentCount = 4; + int fileToPurgeCount = 2; + AtomicInteger offset = new AtomicInteger(0); + tmpDir = ClientBase.createTmpDir(); + File version2 = new File(tmpDir.toString(), "version-2"); + Assert.assertTrue("Failed to create version_2 dir:" + version2.toString(), + version2.mkdir()); + List snapsToPurge = new ArrayList(); + List logsToPurge = new ArrayList(); + List snaps = new ArrayList(); + List logs = new ArrayList(); + List snapsAboveRecentFiles = new ArrayList(); + List logsAboveRecentFiles = new ArrayList(); + createDataDirFiles(offset, fileToPurgeCount, false, version2, snapsToPurge, + logsToPurge); + createDataDirFiles(offset, nRecentCount, false, version2, snaps, logs); + logs.add(logsToPurge.remove(0)); // log that precedes first retained snapshot is also retained + createDataDirFiles(offset, fileAboveRecentCount, false, version2, + snapsAboveRecentFiles, logsAboveRecentFiles); + + /** + * The newest log file preceding the oldest retained snapshot is not removed as it may + * contain transactions newer than the oldest snapshot. + */ + logsToPurge.remove(0); + + FileTxnSnapLog txnLog = new FileTxnSnapLog(tmpDir, tmpDir); + PurgeTxnLog.purgeOlderSnapshots(txnLog, snaps.get(snaps.size() - 1)); + txnLog.close(); + verifyFilesAfterPurge(snapsToPurge, false); + verifyFilesAfterPurge(logsToPurge, false); + verifyFilesAfterPurge(snaps, true); + verifyFilesAfterPurge(logs, true); + verifyFilesAfterPurge(snapsAboveRecentFiles, true); + verifyFilesAfterPurge(logsAboveRecentFiles, true); + } + + /** + * Tests purge where the data directory contains snap files and log files equals to the + * number of files to be retained + */ + @Test + public void testSnapFilesEqualsToRetain() throws Exception { + internalTestSnapFilesEqualsToRetain(false); + } + + /** + * Tests purge where the data directory contains snap files equals to the + * number of files to be retained, and a log file that precedes the earliest snapshot + */ + @Test + public void testSnapFilesEqualsToRetainWithPrecedingLog() throws Exception { + internalTestSnapFilesEqualsToRetain(true); + } + + public void internalTestSnapFilesEqualsToRetain(boolean testWithPrecedingLogFile) throws Exception { + int nRecentCount = 3; + AtomicInteger offset = new AtomicInteger(0); + tmpDir = ClientBase.createTmpDir(); + File version2 = new File(tmpDir.toString(), "version-2"); + Assert.assertTrue("Failed to create version_2 dir:" + version2.toString(), + version2.mkdir()); + List snaps = new ArrayList(); + List logs = new ArrayList(); + createDataDirFiles(offset, nRecentCount, testWithPrecedingLogFile, version2, snaps, logs); + + FileTxnSnapLog txnLog = new FileTxnSnapLog(tmpDir, tmpDir); + PurgeTxnLog.purgeOlderSnapshots(txnLog, snaps.get(snaps.size() - 1)); + txnLog.close(); + verifyFilesAfterPurge(snaps, true); + verifyFilesAfterPurge(logs, true); + } + + /** + * Tests purge where the data directory contains old snapshots and data + * logs, newest snapshots and data logs + */ + @Test + public void testSnapFilesLessThanToRetain() throws Exception { + int nRecentCount = 4; + int fileToPurgeCount = 2; + AtomicInteger offset = new AtomicInteger(0); + tmpDir = ClientBase.createTmpDir(); + File version2 = new File(tmpDir.toString(), "version-2"); + Assert.assertTrue("Failed to create version_2 dir:" + version2.toString(), + version2.mkdir()); + List snapsToPurge = new ArrayList(); + List logsToPurge = new ArrayList(); + List snaps = new ArrayList(); + List logs = new ArrayList(); + createDataDirFiles(offset, fileToPurgeCount, false, version2, snapsToPurge, + logsToPurge); + createDataDirFiles(offset, nRecentCount, false, version2, snaps, logs); + logs.add(logsToPurge.remove(0)); // log that precedes first retained snapshot is also retained + + /** + * The newest log file preceding the oldest retained snapshot is not removed as it may + * contain transactions newer than the oldest snapshot. + */ + logsToPurge.remove(0); + + FileTxnSnapLog txnLog = new FileTxnSnapLog(tmpDir, tmpDir); + PurgeTxnLog.purgeOlderSnapshots(txnLog, snaps.get(snaps.size() - 1)); + txnLog.close(); + verifyFilesAfterPurge(snapsToPurge, false); + verifyFilesAfterPurge(logsToPurge, false); + verifyFilesAfterPurge(snaps, true); + verifyFilesAfterPurge(logs, true); + } + + /** + * PurgeTxnLog is called with dataLogDir snapDir -n count This test case + * verify these values are parsed properly and functionality works fine + */ + @Test + public void testPurgeTxnLogWithDataDir() + throws Exception { + tmpDir = ClientBase.createTmpDir(); + File dataDir = new File(tmpDir, "dataDir"); + File dataLogDir = new File(tmpDir, "dataLogDir"); + + File dataDirVersion2 = new File(dataDir, "version-2"); + dataDirVersion2.mkdirs(); + File dataLogDirVersion2 = new File(dataLogDir, "version-2"); + dataLogDirVersion2.mkdirs(); + + // create dummy log and transaction file + int totalFiles = 20; + + // create transaction and snapshot files in different-different + // directories + for (int i = 0; i < totalFiles; i++) { + // simulate log file + File logFile = new File(dataLogDirVersion2, "log." + + Long.toHexString(i)); + logFile.createNewFile(); + // simulate snapshot file + File snapFile = new File(dataDirVersion2, "snapshot." + + Long.toHexString(i)); + snapFile.createNewFile(); + } + + int numberOfSnapFilesToKeep = 10; + // scenario where four parameter are passed + String[] args = new String[] { dataLogDir.getAbsolutePath(), + dataDir.getAbsolutePath(), "-n", + Integer.toString(numberOfSnapFilesToKeep) }; + PurgeTxnLog.main(args); + + assertEquals(numberOfSnapFilesToKeep, dataDirVersion2.listFiles().length); + // Since for each snapshot we have a log file with same zxid, expect same # logs as snaps to be kept + assertEquals(numberOfSnapFilesToKeep, dataLogDirVersion2.listFiles().length); + ClientBase.recursiveDelete(tmpDir); + + } + + /** + * PurgeTxnLog is called with dataLogDir -n count This test case verify + * these values are parsed properly and functionality works fine + */ + @Test + public void testPurgeTxnLogWithoutDataDir() + throws Exception { + tmpDir = ClientBase.createTmpDir(); + File dataDir = new File(tmpDir, "dataDir"); + File dataLogDir = new File(tmpDir, "dataLogDir"); + + File dataDirVersion2 = new File(dataDir, "version-2"); + dataDirVersion2.mkdirs(); + File dataLogDirVersion2 = new File(dataLogDir, "version-2"); + dataLogDirVersion2.mkdirs(); + + // create dummy log and transaction file + int totalFiles = 20; + + // create transaction and snapshot files in data directory + for (int i = 0; i < totalFiles; i++) { + // simulate log file + File logFile = new File(dataLogDirVersion2, "log." + + Long.toHexString(i)); + logFile.createNewFile(); + // simulate snapshot file + File snapFile = new File(dataLogDirVersion2, "snapshot." + + Long.toHexString(i)); + snapFile.createNewFile(); + } + + int numberOfSnapFilesToKeep = 10; + // scenario where only three parameter are passed + String[] args = new String[] { dataLogDir.getAbsolutePath(), "-n", + Integer.toString(numberOfSnapFilesToKeep) }; + PurgeTxnLog.main(args); + assertEquals(numberOfSnapFilesToKeep * 2, // Since for each snapshot we have a log file with same zxid, expect same # logs as snaps to be kept + dataLogDirVersion2.listFiles().length); + ClientBase.recursiveDelete(tmpDir); + + } + + /** + * Verifies that purge does not delete any log files which started before the oldest retained + * snapshot but which might extend beyond it. + * @throws Exception an exception might be thrown here + */ + @Test + public void testPurgeDoesNotDeleteOverlappingLogFile() throws Exception { + // Setting used for snapRetainCount in this test. + final int SNAP_RETAIN_COUNT = 3; + // Number of znodes this test creates in each snapshot. + final int NUM_ZNODES_PER_SNAPSHOT = 100; + /** + * Set a sufficiently high snapCount to ensure that we don't rollover the log. Normally, + * the default value (100K at time of this writing) would ensure this, but we make that + * dependence explicit here to make the test future-proof. Not rolling over the log is + * important for this test since we are testing retention of the one and only log file which + * predates each retained snapshot. + */ + SyncRequestProcessor.setSnapCount(SNAP_RETAIN_COUNT * NUM_ZNODES_PER_SNAPSHOT * 10); + + // Create Zookeeper and connect to it. + tmpDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); + ServerCnxnFactory f = ServerCnxnFactory.createFactory(PORT, -1); + f.startup(zks); + Assert.assertTrue("waiting for server being up ", + ClientBase.waitForServerUp(HOSTPORT,CONNECTION_TIMEOUT)); + ZooKeeper zk = ClientBase.createZKClient(HOSTPORT); + + // Unique identifier for each znode that we create. + int unique = 0; + try { + /** + * Create some znodes and take a snapshot. Repeat this until we have SNAP_RETAIN_COUNT + * snapshots. Do not rollover the log. + */ + for (int snapshotCount = 0; snapshotCount < SNAP_RETAIN_COUNT; snapshotCount++) { + for (int i = 0; i< 100; i++, unique++) { + zk.create("/snap-" + unique, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } + zks.takeSnapshot(); + } + // Create some additional znodes without taking a snapshot afterwards. + for (int i = 0; i< 100; i++, unique++) { + zk.create("/snap-" + unique, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } + } finally { + zk.close(); + } + + // Shutdown Zookeeper. + f.shutdown(); + zks.getTxnLogFactory().close(); + zks.shutdown(); + Assert.assertTrue("waiting for server to shutdown", + ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); + + // Purge snapshot and log files. + PurgeTxnLog.purge(tmpDir, tmpDir, SNAP_RETAIN_COUNT); + + // Initialize Zookeeper again from the same dataDir. + zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + f = ServerCnxnFactory.createFactory(PORT, -1); + f.startup(zks); + zk = ClientBase.createZKClient(HOSTPORT); + + /** + * Verify that the last znode that was created above exists. This znode's creation was + * captured by the transaction log which was created before any of the above + * SNAP_RETAIN_COUNT snapshots were created, but it's not captured in any of these + * snapshots. So for it it exist, the (only) existing log file should not have been purged. + */ + final String lastZnode = "/snap-" + (unique - 1); + final Stat stat = zk.exists(lastZnode, false); + Assert.assertNotNull("Last znode does not exist: " + lastZnode, stat); + + // Shutdown for the last time. + f.shutdown(); + zks.getTxnLogFactory().close(); + zks.shutdown(); + } + + private File createDataDirLogFile(File version_2, int Zxid) throws IOException { + File logFile = new File(version_2 + "/log." + Long.toHexString(Zxid)); + Assert.assertTrue("Failed to create log File:" + logFile.toString(), + logFile.createNewFile()); + return logFile; + } + + private void createDataDirFiles(AtomicInteger offset, int limit, boolean createPrecedingLogFile, + File version_2, List snaps, List logs) + throws IOException { + int counter = offset.get() + (2 * limit); + if (createPrecedingLogFile) { + counter++; + } + offset.set(counter); + for (int i = 0; i < limit; i++) { + // simulate log file + logs.add(createDataDirLogFile(version_2, --counter)); + // simulate snapshot file + File snapFile = new File(version_2 + "/snapshot." + + Long.toHexString(--counter)); + Assert.assertTrue("Failed to create snap File:" + snapFile.toString(), + snapFile.createNewFile()); + snaps.add(snapFile); + } + if (createPrecedingLogFile) { + logs.add(createDataDirLogFile(version_2, --counter)); + } + } + + private void verifyFilesAfterPurge(List logs, boolean exists) { + for (File file : logs) { + Assert.assertEquals("After purging, file " + file, exists, + file.exists()); + } + } + + private List manyClientOps(final ZooKeeper zk, + final CountDownLatch doPurge, int thCount, final String prefix) { + Thread[] ths = new Thread[thCount]; + final List znodes = Collections + .synchronizedList(new ArrayList()); + final CountDownLatch finished = new CountDownLatch(thCount); + for (int indx = 0; indx < thCount; indx++) { + final String myprefix = prefix + "-" + indx; + Thread th = new Thread() { + public void run() { + for (int i = 0; i < 1000; i++) { + try { + String mynode = myprefix + "-" + i; + znodes.add(mynode); + zk.create(mynode, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } catch (Exception e) { + LOG.error("Unexpected exception occurred!", e); + } + if (i == 200) { + doPurge.countDown(); + } + } + finished.countDown(); + }; + }; + ths[indx] = th; + } + + for (Thread thread : ths) { + thread.start(); + } + try { + Assert.assertTrue("ZkClient ops is not finished!", + finished.await(OP_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ie) { + LOG.error("Unexpected exception occurred!", ie); + Assert.fail("Unexpected exception occurred!"); + } + return znodes; + } + + public void process(WatchedEvent event) { + // do nothing + } + +} diff --git a/src/java/test/org/apache/zookeeper/server/ReferenceCountedACLCacheTest.java b/src/java/test/org/apache/zookeeper/server/ReferenceCountedACLCacheTest.java new file mode 100644 index 00000000000..076657b073d --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/ReferenceCountedACLCacheTest.java @@ -0,0 +1,260 @@ +/** + * 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; + +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + +public class ReferenceCountedACLCacheTest { + @Test + public void testSameACLGivesSameID() { + List testACL = createACL("myid"); + + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + Long aclId = cache.convertAcls(testACL); + + List testACL2 = createACL("myid"); + + assertEquals(aclId, cache.convertAcls(testACL2)); + } + + @Test + public void testWhetherOrderingMatters() { + List testACL = new ArrayList(); + testACL.add(new ACL(ZooDefs.Perms.READ, new Id("scheme", "ro"))); + testACL.add(new ACL(ZooDefs.Perms.WRITE, new Id("scheme", "rw"))); + + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + Long aclId = cache.convertAcls(testACL); + + List testACL2 = new ArrayList(); + testACL2.add(new ACL(ZooDefs.Perms.WRITE, new Id("scheme", "rw"))); + testACL2.add(new ACL(ZooDefs.Perms.READ, new Id("scheme", "ro"))); + + assertFalse(aclId.equals(cache.convertAcls(testACL2))); + } + + @Test + public void testBidirectionality() { + List testACL = createACL("myid"); + + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + Long aclId = cache.convertAcls(testACL); + + assertEquals(testACL, cache.convertLong(aclId)); + } + + @Test + public void testCacheSize() { + List testACL = createACL("myid"); + + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + Long aclId = cache.convertAcls(testACL); + assertEquals(1, cache.size()); + + List testACL2 = createACL("myid"); + + assertEquals(aclId, cache.convertAcls(testACL2)); + assertEquals(1, cache.size()); + + List testACL3 = createACL("differentId"); + + Long aclId3 = cache.convertAcls(testACL3); + assertFalse(aclId3.equals(aclId)); + assertEquals(2, cache.size()); + } + + @Test + public void testAddThenRemove() { + List testACL = createACL("myid"); + + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + Long aclId = cache.convertAcls(testACL); + assertEquals(1, cache.size()); + + cache.removeUsage(aclId); + assertEquals(0, cache.size()); + } + + @Test + public void testMultipleAddsAndRemove() { + List testACL = createACL("myid"); + + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + Long aclId = cache.convertAcls(testACL); + assertEquals(1, cache.size()); + + cache.convertAcls(testACL); + assertEquals(1, cache.size()); + + List testACL2 = createACL("anotherId"); + cache.convertAcls(testACL2); + + cache.removeUsage(aclId); + assertEquals(2, cache.size()); + cache.removeUsage(aclId); + assertEquals(1, cache.size()); + + Long newId = cache.convertAcls(testACL); + assertFalse(aclId.equals(newId)); + } + + @Test + public void testAddUsage() { + List testACL = createACL("myid"); + + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + Long aclId = cache.convertAcls(testACL); + assertEquals(1, cache.size()); + + cache.addUsage(aclId); + assertEquals(1, cache.size()); + + cache.removeUsage(aclId); + assertEquals(1, cache.size()); + cache.removeUsage(aclId); + assertEquals(0, cache.size()); + } + + @Test + public void testAddNonExistentUsage() { + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + cache.addUsage(1L); + + assertEquals(0, cache.size()); + /* + On startup, it's possible that we'll try calling addUsage of an ID not in the cache. This is safe to ignore + as it'll be added later when we traverse the tranlog. See discussion here: + http://mail-archives.apache.org/mod_mbox/zookeeper-user/201507.mbox/%3CCAB5oV2_ujhvBA1sEkCG2WRakPjCy%2BNR10620WK2G1GGgmEO44g%40mail.gmail.com%3E + + This test makes sure that we don't add the ID to the cache in this case as that would result in dupes later + and consequently incorrect counts and entries that will never be cleaned out. + */ + } + + @Test + public void testSerializeDeserialize() throws IOException { + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + + List acl1 = createACL("one"); + List acl2 = createACL("two"); + List acl3 = createACL("three"); + List acl4 = createACL("four"); + List acl5 = createACL("five"); + + Long aclId1 = convertACLsNTimes(cache, acl1, 1); + Long aclId2 = convertACLsNTimes(cache, acl2, 2); + Long aclId3 = convertACLsNTimes(cache, acl3, 3); + Long aclId4 = convertACLsNTimes(cache, acl4, 4); + Long aclId5 = convertACLsNTimes(cache, acl5, 5); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive archive = BinaryOutputArchive.getArchive(baos); + cache.serialize(archive); + + BinaryInputArchive inArchive = BinaryInputArchive.getArchive(new ByteArrayInputStream(baos.toByteArray())); + ReferenceCountedACLCache deserializedCache = new ReferenceCountedACLCache(); + deserializedCache.deserialize(inArchive); + callAddUsageNTimes(deserializedCache, aclId1, 1); + callAddUsageNTimes(deserializedCache, aclId2, 2); + callAddUsageNTimes(deserializedCache, aclId3, 3); + callAddUsageNTimes(deserializedCache, aclId4, 4); + callAddUsageNTimes(deserializedCache, aclId5, 5); + + assertCachesEqual(cache, deserializedCache); + } + + private void assertCachesEqual(ReferenceCountedACLCache expected, ReferenceCountedACLCache actual){ + assertEquals(expected.aclIndex, actual.aclIndex); + assertEquals(expected.aclKeyMap, actual.aclKeyMap); + assertEquals(expected.longKeyMap, actual.longKeyMap); + assertEquals(expected.referenceCounter, actual.referenceCounter); + } + + @Test + public void testPurgeUnused() throws IOException { + ReferenceCountedACLCache cache = new ReferenceCountedACLCache(); + + List acl1 = createACL("one"); + List acl2 = createACL("two"); + List acl3 = createACL("three"); + List acl4 = createACL("four"); + List acl5 = createACL("five"); + + Long aclId1 = convertACLsNTimes(cache, acl1, 1); + Long aclId2 = convertACLsNTimes(cache, acl2, 2); + Long aclId3 = convertACLsNTimes(cache, acl3, 3); + Long aclId4 = convertACLsNTimes(cache, acl4, 4); + Long aclId5 = convertACLsNTimes(cache, acl5, 5); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive archive = BinaryOutputArchive.getArchive(baos); + cache.serialize(archive); + + BinaryInputArchive inArchive = BinaryInputArchive.getArchive(new ByteArrayInputStream(baos.toByteArray())); + ReferenceCountedACLCache deserializedCache = new ReferenceCountedACLCache(); + deserializedCache.deserialize(inArchive); + callAddUsageNTimes(deserializedCache, aclId1, 1); + callAddUsageNTimes(deserializedCache, aclId2, 2); + deserializedCache.purgeUnused(); + + assertEquals(2, deserializedCache.size()); + assertEquals(aclId1, deserializedCache.convertAcls(acl1)); + assertEquals(aclId2, deserializedCache.convertAcls(acl2)); + assertFalse(acl3.equals(deserializedCache.convertAcls(acl3))); + assertFalse(acl4.equals(deserializedCache.convertAcls(acl4))); + assertFalse(acl5.equals(deserializedCache.convertAcls(acl5))); + } + + private void callAddUsageNTimes(ReferenceCountedACLCache deserializedCache, Long aclId, int num) { + for (int i = 0; i < num; i++) { + deserializedCache.addUsage(aclId); + } + } + + private Long convertACLsNTimes(ReferenceCountedACLCache cache, List acl, int num) { + if (num <= 0) { + return -1L; + } + + for (int i = 0; i < num -1; i++) { + cache.convertAcls(acl); + } + + return cache.convertAcls(acl); + } + + private List createACL(String id) { + List acl1 = new ArrayList(); + acl1.add(new ACL(ZooDefs.Perms.ADMIN, new Id("scheme", id))); + return acl1; + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/server/SessionTrackerTest.java b/src/java/test/org/apache/zookeeper/server/SessionTrackerTest.java new file mode 100644 index 00000000000..f42710292b8 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/SessionTrackerTest.java @@ -0,0 +1,156 @@ +/** + * 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; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.Assert; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.server.SessionTrackerImpl.SessionImpl; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Test; + +/** + * Testing zk client session logic in sessiontracker + */ +public class SessionTrackerTest extends ZKTestCase { + + private final long sessionId = 339900; + private final int sessionTimeout = 3000; + private FirstProcessor firstProcessor; + private CountDownLatch latch; + + /** + * Verify the create session call in the Leader.FinalRequestProcessor after + * the session expiration. + */ + @Test(timeout = 20000) + public void testAddSessionAfterSessionExpiry() throws Exception { + ZooKeeperServer zks = setupSessionTracker(); + + latch = new CountDownLatch(1); + zks.sessionTracker.addSession(sessionId, sessionTimeout); + SessionTrackerImpl sessionTrackerImpl = (SessionTrackerImpl) zks.sessionTracker; + SessionImpl sessionImpl = sessionTrackerImpl.sessionsById + .get(sessionId); + Assert.assertNotNull("Sessionid:" + sessionId + + " doesn't exists in sessiontracker", sessionImpl); + + // verify the session existence + Object sessionOwner = new Object(); + sessionTrackerImpl.checkSession(sessionId, sessionOwner); + + // waiting for the session expiry + latch.await(sessionTimeout * 2, TimeUnit.MILLISECONDS); + + // Simulating FinalRequestProcessor logic: create session request has + // delayed and now reaches FinalRequestProcessor. Here the leader zk + // will do sessionTracker.addSession(id, timeout) + sessionTrackerImpl.addSession(sessionId, sessionTimeout); + try { + sessionTrackerImpl.checkSession(sessionId, sessionOwner); + Assert.fail("Should throw session expiry exception " + + "as the session has expired and closed"); + } catch (KeeperException.SessionExpiredException e) { + // expected behaviour + } + Assert.assertTrue("Session didn't expired", sessionImpl.isClosing()); + Assert.assertFalse("Session didn't expired", sessionTrackerImpl + .touchSession(sessionId, sessionTimeout)); + Assert.assertEquals( + "Duplicate session expiry request has been generated", 1, + firstProcessor.getCountOfCloseSessionReq()); + } + + /** + * Verify the session closure request has reached PrepRequestProcessor soon + * after session expiration by the session tracker + */ + @Test(timeout = 20000) + public void testCloseSessionRequestAfterSessionExpiry() throws Exception { + ZooKeeperServer zks = setupSessionTracker(); + + latch = new CountDownLatch(1); + zks.sessionTracker.addSession(sessionId, sessionTimeout); + SessionTrackerImpl sessionTrackerImpl = (SessionTrackerImpl) zks.sessionTracker; + SessionImpl sessionImpl = sessionTrackerImpl.sessionsById + .get(sessionId); + Assert.assertNotNull("Sessionid:" + sessionId + + " doesn't exists in sessiontracker", sessionImpl); + + // verify the session existence + Object sessionOwner = new Object(); + sessionTrackerImpl.checkSession(sessionId, sessionOwner); + + // waiting for the session expiry + latch.await(sessionTimeout * 2, TimeUnit.MILLISECONDS); + + // Simulating close session request: removeSession() will be executed + // while OpCode.closeSession + sessionTrackerImpl.removeSession(sessionId); + SessionImpl actualSession = sessionTrackerImpl.sessionsById + .get(sessionId); + Assert.assertNull("Session:" + sessionId + + " still exists after removal", actualSession); + } + + private ZooKeeperServer setupSessionTracker() throws IOException { + File tmpDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + zks.setupRequestProcessors(); + firstProcessor = new FirstProcessor(zks, null); + zks.firstProcessor = firstProcessor; + + // setup session tracker + zks.createSessionTracker(); + zks.startSessionTracker(); + return zks; + } + + // Mock processor used in zookeeper server + private class FirstProcessor extends PrepRequestProcessor { + private volatile int countOfCloseSessionReq = 0; + + public FirstProcessor(ZooKeeperServer zks, + RequestProcessor nextProcessor) { + super(zks, nextProcessor); + } + + @Override + public void processRequest(Request request) { + // check session close request + if (request.type == OpCode.closeSession) { + countOfCloseSessionReq++; + latch.countDown(); + } + } + + // return number of session expiry calls + int getCountOfCloseSessionReq() { + return countOfCloseSessionReq; + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerMainTest.java b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerMainTest.java index e3f6dc2a51c..2ffb91ef47d 100644 --- a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerMainTest.java +++ b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerMainTest.java @@ -19,20 +19,34 @@ package org.apache.zookeeper.server; import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; +import static org.junit.Assert.fail; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.jute.Record; import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZKTestCase; -import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.proto.ReplyHeader; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.persistence.Util; +import org.apache.zookeeper.server.util.ZxidUtils; import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.txn.SetDataTxn; +import org.apache.zookeeper.txn.TxnHeader; +import org.jboss.netty.channel.Channel; import org.junit.Assert; import org.junit.Test; @@ -47,30 +61,46 @@ public class ZooKeeperServerMainTest extends ZKTestCase implements Watcher { public static class MainThread extends Thread { final File confFile; final TestZKSMain main; + final File tmpDir; + final File dataDir; + final File logDir; + + public MainThread(int clientPort, boolean preCreateDirs) throws IOException { + this(clientPort, preCreateDirs, ClientBase.createTmpDir()); + } - public MainThread(int clientPort) throws IOException { + public MainThread(int clientPort, boolean preCreateDirs, File tmpDir) throws IOException { super("Standalone server with clientPort:" + clientPort); - File tmpDir = ClientBase.createTmpDir(); - confFile = new File(tmpDir, "zoo.cfg"); + this.tmpDir = tmpDir; + confFile = new File(this.tmpDir, "zoo.cfg"); FileWriter fwriter = new FileWriter(confFile); fwriter.write("tickTime=2000\n"); fwriter.write("initLimit=10\n"); fwriter.write("syncLimit=5\n"); - File dataDir = new File(tmpDir, "data"); - if (!dataDir.mkdir()) { - throw new IOException("unable to mkdir " + dataDir); + dataDir = new File(this.tmpDir, "data"); + logDir = new File(dataDir.toString() + "_txnlog"); + if (preCreateDirs) { + if (!dataDir.mkdir()) { + throw new IOException("unable to mkdir " + dataDir); + } + if (!logDir.mkdir()) { + throw new IOException("unable to mkdir " + logDir); + } } - + + String dataDirPath = dataDir.toString(); + String logDirPath = logDir.toString(); + // Convert windows path to UNIX to avoid problems with "\" - String dir = dataDir.toString(); String osname = java.lang.System.getProperty("os.name"); if (osname.toLowerCase().contains("windows")) { - dir = dir.replace('\\', '/'); + dataDirPath = dataDirPath.replace('\\', '/'); + logDirPath = logDirPath.replace('\\', '/'); } - fwriter.write("dataDir=" + dir + "\n"); - + fwriter.write("dataDir=" + dataDirPath + "\n"); + fwriter.write("dataLogDir=" + logDirPath + "\n"); fwriter.write("clientPort=" + clientPort + "\n"); fwriter.flush(); fwriter.close(); @@ -89,9 +119,29 @@ public void run() { } } - public void shutdown() { + public void shutdown() throws IOException { main.shutdown(); } + + void deleteDirs() throws IOException{ + delete(tmpDir); + } + + void delete(File f) throws IOException { + if (f.isDirectory()) { + for (File c : f.listFiles()) + delete(c); + } + if (!f.delete()) + // double check for the file existence + if (f.exists()) { + throw new IOException("Failed to delete file: " + f); + } + } + + ServerCnxnFactory getCnxnFactory() { + return main.getCnxnFactory(); + } } public static class TestZKSMain extends ZooKeeperServerMain { @@ -100,6 +150,143 @@ public void shutdown() { } } + /** + * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2247. + * Test to verify that even after non recoverable error (error while + * writing transaction log) on ZooKeeper service will be available + */ + @Test(timeout = 30000) + public void testNonRecoverableError() throws Exception { + ClientBase.setupTestEnv(); + + final int CLIENT_PORT = PortAssignment.unique(); + + MainThread main = new MainThread(CLIENT_PORT, true); + main.start(); + + Assert.assertTrue("waiting for server being up", + ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT, + CONNECTION_TIMEOUT)); + + + ZooKeeper zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT, + ClientBase.CONNECTION_TIMEOUT, this); + + zk.create("/foo1", "foobar".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + Assert.assertEquals(new String(zk.getData("/foo1", null, null)), "foobar"); + + // inject problem in server + ZooKeeperServer zooKeeperServer = main.getCnxnFactory() + .getZooKeeperServer(); + FileTxnSnapLog snapLog = zooKeeperServer.getTxnLogFactory(); + FileTxnSnapLog fileTxnSnapLogWithError = new FileTxnSnapLog( + snapLog.getDataDir(), snapLog.getSnapDir()) { + @Override + public void commit() throws IOException { + throw new IOException("Input/output error"); + } + }; + ZKDatabase newDB = new ZKDatabase(fileTxnSnapLogWithError); + zooKeeperServer.setZKDatabase(newDB); + + try { + // do create operation, so that injected IOException is thrown + zk.create("/foo2", "foobar".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + fail("IOException is expected as error is injected in transaction log commit funtionality"); + } catch (Exception e) { + // do nothing + } + zk.close(); + Assert.assertTrue("waiting for server down", + ClientBase.waitForServerDown("127.0.0.1:" + CLIENT_PORT, + ClientBase.CONNECTION_TIMEOUT)); + fileTxnSnapLogWithError.close(); + main.shutdown(); + main.deleteDirs(); + } + + /** + * Tests that the ZooKeeper server will fail to start if the + * snapshot directory is read only. + * + * This test will fail if it is executed as root user. + */ + @Test(timeout = 30000) + public void testReadOnlySnapshotDir() throws Exception { + ClientBase.setupTestEnv(); + final int CLIENT_PORT = PortAssignment.unique(); + + // Start up the ZK server to automatically create the necessary directories + // and capture the directory where data is stored + MainThread main = new MainThread(CLIENT_PORT, true); + File tmpDir = main.tmpDir; + main.start(); + Assert.assertTrue("waiting for server being up", ClientBase + .waitForServerUp("127.0.0.1:" + CLIENT_PORT, + CONNECTION_TIMEOUT / 2)); + main.shutdown(); + + // Make the snapshot directory read only + File snapDir = new File(main.dataDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION); + snapDir.setWritable(false); + + // Restart ZK and observe a failure + main = new MainThread(CLIENT_PORT, false, tmpDir); + main.start(); + + Assert.assertFalse("waiting for server being up", ClientBase + .waitForServerUp("127.0.0.1:" + CLIENT_PORT, + CONNECTION_TIMEOUT / 2)); + + main.shutdown(); + + snapDir.setWritable(true); + + main.deleteDirs(); + } + + /** + * Tests that the ZooKeeper server will fail to start if the + * transaction log directory is read only. + * + * This test will fail if it is executed as root user. + */ + @Test(timeout = 30000) + public void testReadOnlyTxnLogDir() throws Exception { + ClientBase.setupTestEnv(); + final int CLIENT_PORT = PortAssignment.unique(); + + // Start up the ZK server to automatically create the necessary directories + // and capture the directory where data is stored + MainThread main = new MainThread(CLIENT_PORT, true); + File tmpDir = main.tmpDir; + main.start(); + Assert.assertTrue("waiting for server being up", ClientBase + .waitForServerUp("127.0.0.1:" + CLIENT_PORT, + CONNECTION_TIMEOUT / 2)); + main.shutdown(); + + // Make the transaction log directory read only + File logDir = new File(main.logDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION); + logDir.setWritable(false); + + // Restart ZK and observe a failure + main = new MainThread(CLIENT_PORT, false, tmpDir); + main.start(); + + Assert.assertFalse("waiting for server being up", ClientBase + .waitForServerUp("127.0.0.1:" + CLIENT_PORT, + CONNECTION_TIMEOUT / 2)); + + main.shutdown(); + + logDir.setWritable(true); + + main.deleteDirs(); + } + /** * Verify the ability to start a standalone server instance. */ @@ -107,9 +294,9 @@ public void shutdown() { public void testStandalone() throws Exception { ClientBase.setupTestEnv(); - final int CLIENT_PORT = 3181; + final int CLIENT_PORT = PortAssignment.unique(); - MainThread main = new MainThread(CLIENT_PORT); + MainThread main = new MainThread(CLIENT_PORT, true); main.start(); Assert.assertTrue("waiting for server being up", @@ -126,12 +313,302 @@ public void testStandalone() throws Exception { zk.close(); main.shutdown(); + main.join(); + main.deleteDirs(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown("127.0.0.1:" + CLIENT_PORT, ClientBase.CONNECTION_TIMEOUT)); } + /** + * Test verifies the auto creation of data dir and data log dir. + */ + @Test(timeout = 30000) + public void testAutoCreateDataLogDir() throws Exception { + ClientBase.setupTestEnv(); + final int CLIENT_PORT = PortAssignment.unique(); + + MainThread main = new MainThread(CLIENT_PORT, false); + String args[] = new String[1]; + args[0] = main.confFile.toString(); + main.start(); + + Assert.assertTrue("waiting for server being up", + ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT, + CONNECTION_TIMEOUT)); + + ZooKeeper zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT, + ClientBase.CONNECTION_TIMEOUT, this); + + zk.create("/foo", "foobar".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + Assert.assertEquals(new String(zk.getData("/foo", null, null)), + "foobar"); + zk.close(); + + main.shutdown(); + main.join(); + main.deleteDirs(); + + Assert.assertTrue("waiting for server down", ClientBase + .waitForServerDown("127.0.0.1:" + CLIENT_PORT, + ClientBase.CONNECTION_TIMEOUT)); + } + + @Test + public void testJMXRegistrationWithNIO() throws Exception { + ClientBase.setupTestEnv(); + File tmpDir_1 = ClientBase.createTmpDir(); + ServerCnxnFactory server_1 = startServer(tmpDir_1); + File tmpDir_2 = ClientBase.createTmpDir(); + ServerCnxnFactory server_2 = startServer(tmpDir_2); + + server_1.shutdown(); + server_2.shutdown(); + + deleteFile(tmpDir_1); + deleteFile(tmpDir_2); + } + + @Test + public void testJMXRegistrationWithNetty() throws Exception { + String originalServerCnxnFactory = System + .getProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, + NettyServerCnxnFactory.class.getName()); + try { + ClientBase.setupTestEnv(); + File tmpDir_1 = ClientBase.createTmpDir(); + ServerCnxnFactory server_1 = startServer(tmpDir_1); + File tmpDir_2 = ClientBase.createTmpDir(); + ServerCnxnFactory server_2 = startServer(tmpDir_2); + + server_1.shutdown(); + server_2.shutdown(); + + deleteFile(tmpDir_1); + deleteFile(tmpDir_2); + } finally { + // setting back + if (originalServerCnxnFactory == null + || originalServerCnxnFactory.isEmpty()) { + System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + } else { + System.setProperty( + ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, + originalServerCnxnFactory); + } + } + } + + /** + * Test case to verify that ZooKeeper server is able to shutdown properly + * when there are pending request(s) in the RequestProcessor chain. + * + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-2347} + */ + @Test(timeout = 30000) + public void testRaceBetweenSyncFlushAndZKShutdown() throws Exception { + File tmpDir = ClientBase.createTmpDir(); + File testDir = File.createTempFile("test", ".dir", tmpDir); + testDir.delete(); + + // Following are the sequence of steps to simulate the deadlock + // situation - SyncRequestProcessor#shutdown holds a lock and waits on + // FinalRequestProcessor to complete a pending operation, which in turn + // also needs the ZooKeeperServer lock + + // 1. start zk server + FileTxnSnapLog ftsl = new FileTxnSnapLog(testDir, testDir); + final SimpleZooKeeperServer zkServer = new SimpleZooKeeperServer(ftsl); + zkServer.startup(); + // 2. Wait for setting up request processor chain. At the end of setup, + // it will add a mock request into the chain + // 3. Also, waiting for FinalRequestProcessor to start processing request + zkServer.waitForFinalProcessRequest(); + // 4. Above step ensures that there is a request in the processor chain. + // Now invoke shutdown, which will acquire zks lock + Thread shutdownThread = new Thread() { + public void run() { + zkServer.shutdown(); + }; + }; + shutdownThread.start(); + // 5. Wait for SyncRequestProcessor to trigger shutdown function. + // This is to ensure that zks lock is acquired + zkServer.waitForSyncReqProcessorShutdown(); + // 6. Now resume FinalRequestProcessor which in turn call + // zks#decInProcess() function and tries to acquire zks lock. + // This results in deadlock + zkServer.resumeFinalProcessRequest(); + // 7. Waiting to finish server shutdown. Testing that + // SyncRequestProcessor#shutdown holds a lock and waits on + // FinalRequestProcessor to complete a pending operation, which in turn + // also needs the ZooKeeperServer lock + shutdownThread.join(); + } + + private class SimpleZooKeeperServer extends ZooKeeperServer { + private SimpleSyncRequestProcessor syncProcessor; + private SimpleFinalRequestProcessor finalProcessor; + + SimpleZooKeeperServer(FileTxnSnapLog ftsl) throws IOException { + super(ftsl, 2000, 2000, 4000, null, new ZKDatabase(ftsl)); + } + + @Override + protected void setupRequestProcessors() { + finalProcessor = new SimpleFinalRequestProcessor(this); + syncProcessor = new SimpleSyncRequestProcessor(this, + finalProcessor); + syncProcessor.start(); + firstProcessor = new PrepRequestProcessor(this, syncProcessor); + ((PrepRequestProcessor) firstProcessor).start(); + + // add request to the chain + addRequestToSyncProcessor(); + } + + private void addRequestToSyncProcessor() { + long zxid = ZxidUtils.makeZxid(3, 7); + TxnHeader hdr = new TxnHeader(1, 1, zxid, 1, + ZooDefs.OpCode.setData); + Record txn = new SetDataTxn("/foo" + zxid, new byte[0], 1); + byte[] buf; + try { + buf = Util.marshallTxnEntry(hdr, txn); + } catch (IOException e) { + LOG.error("IOException while adding request to SyncRequestProcessor", e); + Assert.fail("IOException while adding request to SyncRequestProcessor!"); + return; + } + NettyServerCnxnFactory factory = new NettyServerCnxnFactory(); + final MockNettyServerCnxn nettyCnxn = new MockNettyServerCnxn(null, + this, factory); + Request req = new Request(nettyCnxn, 1, 1, ZooDefs.OpCode.setData, + ByteBuffer.wrap(buf), null); + req.hdr = hdr; + req.txn = txn; + syncProcessor.processRequest(req); + } + + void waitForFinalProcessRequest() throws InterruptedException { + Assert.assertTrue("Waiting for FinalRequestProcessor to start processing request", + finalProcessor.waitForProcessRequestToBeCalled()); + } + + void waitForSyncReqProcessorShutdown() throws InterruptedException { + Assert.assertTrue("Waiting for SyncRequestProcessor to shut down", + syncProcessor.waitForShutdownToBeCalled()); + } + + void resumeFinalProcessRequest() throws InterruptedException { + finalProcessor.resumeProcessRequest(); + } + } + + private class MockNettyServerCnxn extends NettyServerCnxn { + public MockNettyServerCnxn(Channel channel, ZooKeeperServer zks, + NettyServerCnxnFactory factory) { + super(null, null, factory); + } + + @Override + protected synchronized void updateStatsForResponse(long cxid, long zxid, + String op, long start, long end) { + return; + } + + @Override + public synchronized void sendResponse(ReplyHeader h, Record r, + String tag) { + return; + } + } + + private class SimpleFinalRequestProcessor extends FinalRequestProcessor { + private CountDownLatch finalReqProcessCalled = new CountDownLatch(1); + private CountDownLatch resumeFinalReqProcess = new CountDownLatch(1); + private volatile boolean interrupted = false; + public SimpleFinalRequestProcessor(ZooKeeperServer zks) { + super(zks); + } + + @Override + public void processRequest(Request request) { + finalReqProcessCalled.countDown(); + try { + resumeFinalReqProcess.await(ClientBase.CONNECTION_TIMEOUT, + TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + LOG.error("Interrupted while waiting to process request", e); + interrupted = true; // Marked as interrupted + resumeFinalReqProcess.countDown(); + return; + } + super.processRequest(request); + } + + boolean waitForProcessRequestToBeCalled() throws InterruptedException { + return finalReqProcessCalled.await(ClientBase.CONNECTION_TIMEOUT, + TimeUnit.MILLISECONDS); + } + + void resumeProcessRequest() throws InterruptedException { + resumeFinalReqProcess.countDown(); + resumeFinalReqProcess.await(ClientBase.CONNECTION_TIMEOUT, + TimeUnit.MILLISECONDS); + Assert.assertFalse("Interrupted while waiting to process request", + interrupted); + } + } + + private class SimpleSyncRequestProcessor extends SyncRequestProcessor { + private final CountDownLatch shutdownCalled = new CountDownLatch(1); + + public SimpleSyncRequestProcessor(ZooKeeperServer zks, + RequestProcessor nextProcessor) { + super(zks, nextProcessor); + } + + @Override + public void shutdown() { + shutdownCalled.countDown(); + super.shutdown(); + } + + boolean waitForShutdownToBeCalled() throws InterruptedException { + return shutdownCalled.await(ClientBase.CONNECTION_TIMEOUT / 3, + TimeUnit.MILLISECONDS); + } + } + + private void deleteFile(File f) throws IOException { + if (f.isDirectory()) { + for (File c : f.listFiles()) + deleteFile(c); + } + if (!f.delete()) + // double check for the file existence + if (f.exists()) { + throw new IOException("Failed to delete file: " + f); + } + } + + private ServerCnxnFactory startServer(File tmpDir) throws IOException, + InterruptedException { + final int CLIENT_PORT = PortAssignment.unique(); + ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + ServerCnxnFactory f = ServerCnxnFactory.createFactory(CLIENT_PORT, -1); + f.startup(zks); + Assert.assertNotNull("JMX initialization failed!", zks.jmxServerBean); + Assert.assertTrue("waiting for server being up", + ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT, + CONNECTION_TIMEOUT)); + return f; + } + public void process(WatchedEvent event) { // ignore for this test } diff --git a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerStartupTest.java b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerStartupTest.java new file mode 100644 index 00000000000..9435711b357 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerStartupTest.java @@ -0,0 +1,302 @@ +/** + * 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; + +import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class tests the startup behavior of ZooKeeper server. + */ +public class ZooKeeperServerStartupTest extends ZKTestCase { + private static final Logger LOG = LoggerFactory + .getLogger(ZooKeeperServerStartupTest.class); + private static int PORT = PortAssignment.unique(); + private static String HOST = "127.0.0.1"; + private static String HOSTPORT = HOST + ":" + PORT; + private static final String ZK_NOT_SERVING = "This ZooKeeper instance is not currently serving requests"; + + private ServerCnxnFactory servcnxnf; + private ZooKeeperServer zks; + private File tmpDir; + private CountDownLatch startupDelayLatch = new CountDownLatch(1); + + @After + public void teardown() throws Exception { + // count down to avoid infinite blocking call due to this latch, if + // any. + startupDelayLatch.countDown(); + + if (servcnxnf != null) { + servcnxnf.shutdown(); + } + if (zks != null) { + zks.shutdown(); + } + if (zks.getZKDatabase() != null) { + zks.getZKDatabase().close(); + } + if (tmpDir != null) { + ClientBase.recursiveDelete(tmpDir); + } + } + + /** + * Test case for + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-2383}. + */ + @Test(timeout = 30000) + public void testClientConnectionRequestDuringStartupWithNIOServerCnxn() + throws Exception { + tmpDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + + startSimpleZKServer(startupDelayLatch); + SimpleZooKeeperServer simplezks = (SimpleZooKeeperServer) zks; + Assert.assertTrue( + "Failed to invoke zks#startup() method during server startup", + simplezks.waitForStartupInvocation(10)); + + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zkClient = new ZooKeeper(HOSTPORT, + ClientBase.CONNECTION_TIMEOUT, watcher); + + Assert.assertFalse( + "Since server is not fully started, zks#createSession() shouldn't be invoked", + simplezks.waitForSessionCreation(5)); + + LOG.info( + "Decrements the count of the latch, so that server will proceed with startup"); + startupDelayLatch.countDown(); + + Assert.assertTrue("waiting for server being up ", ClientBase + .waitForServerUp(HOSTPORT, ClientBase.CONNECTION_TIMEOUT)); + + Assert.assertTrue( + "Failed to invoke zks#createSession() method during client session creation", + simplezks.waitForSessionCreation(5)); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zkClient.close(); + } + + /** + * Test case for + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-2383}. + */ + @Test(timeout = 30000) + public void testClientConnectionRequestDuringStartupWithNettyServerCnxn() + throws Exception { + tmpDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + + String originalServerCnxnFactory = System + .getProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + try { + System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, + NettyServerCnxnFactory.class.getName()); + startSimpleZKServer(startupDelayLatch); + SimpleZooKeeperServer simplezks = (SimpleZooKeeperServer) zks; + Assert.assertTrue( + "Failed to invoke zks#startup() method during server startup", + simplezks.waitForStartupInvocation(10)); + + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zkClient = new ZooKeeper(HOSTPORT, + ClientBase.CONNECTION_TIMEOUT, watcher); + + Assert.assertFalse( + "Since server is not fully started, zks#createSession() shouldn't be invoked", + simplezks.waitForSessionCreation(5)); + + LOG.info( + "Decrements the count of the latch, so that server will proceed with startup"); + startupDelayLatch.countDown(); + + Assert.assertTrue("waiting for server being up ", ClientBase + .waitForServerUp(HOSTPORT, ClientBase.CONNECTION_TIMEOUT)); + + Assert.assertTrue( + "Failed to invoke zks#createSession() method during client session creation", + simplezks.waitForSessionCreation(5)); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zkClient.close(); + } finally { + // reset cnxn factory + if (originalServerCnxnFactory == null) { + System.clearProperty( + ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + } else { + System.setProperty( + ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, + originalServerCnxnFactory); + } + } + } + + /** + * Test case for + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-2383}. + */ + @Test(timeout = 30000) + public void testFourLetterWordsWithNIOServerCnxn() throws Exception { + startSimpleZKServer(startupDelayLatch); + verify("conf", ZK_NOT_SERVING); + verify("crst", ZK_NOT_SERVING); + verify("cons", ZK_NOT_SERVING); + verify("dump", ZK_NOT_SERVING); + verify("mntr", ZK_NOT_SERVING); + verify("stat", ZK_NOT_SERVING); + verify("srst", ZK_NOT_SERVING); + verify("wchs", ZK_NOT_SERVING); + verify("isro", "null"); + } + + /** + * Test case for + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-2383}. + */ + @Test(timeout = 30000) + public void testFourLetterWordsWithNettyServerCnxn() throws Exception { + String originalServerCnxnFactory = System + .getProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + try { + startSimpleZKServer(startupDelayLatch); + verify("conf", ZK_NOT_SERVING); + verify("crst", ZK_NOT_SERVING); + verify("cons", ZK_NOT_SERVING); + verify("dump", ZK_NOT_SERVING); + verify("mntr", ZK_NOT_SERVING); + verify("stat", ZK_NOT_SERVING); + verify("srst", ZK_NOT_SERVING); + verify("wchs", ZK_NOT_SERVING); + verify("isro", "null"); + } finally { + // reset cnxn factory + if (originalServerCnxnFactory == null) { + System.clearProperty( + ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + } else { + System.setProperty( + ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, + originalServerCnxnFactory); + } + } + } + + private void verify(String cmd, String expected) + throws IOException { + String resp = sendRequest(cmd); + LOG.info("cmd " + cmd + " expected " + expected + " got " + resp); + Assert.assertTrue("Unexpected response: " + resp, + resp.contains(expected)); + } + + private String sendRequest(String cmd) + throws IOException { + return send4LetterWord(HOST, PORT, cmd); + } + + private void startSimpleZKServer(CountDownLatch startupDelayLatch) + throws IOException { + zks = new SimpleZooKeeperServer(tmpDir, tmpDir, 3000, + startupDelayLatch); + SyncRequestProcessor.setSnapCount(100); + final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); + + servcnxnf = ServerCnxnFactory.createFactory(PORT, -1); + Thread startupThread = new Thread() { + public void run() { + try { + servcnxnf.startup(zks); + } catch (IOException e) { + LOG.error("Unexcepted exception during server startup", e); + // Ignoring exception. If there is an ioexception + // then one of the following assertion will fail + } catch (InterruptedException e) { + LOG.error("Unexcepted exception during server startup", e); + // Ignoring exception. If there is an interrupted exception + // then one of the following assertion will fail + } + }; + }; + LOG.info("Starting zk server {}", HOSTPORT); + startupThread.start(); + } + + private static class SimpleZooKeeperServer extends ZooKeeperServer { + private CountDownLatch startupDelayLatch; + private CountDownLatch startupInvokedLatch = new CountDownLatch(1); + private CountDownLatch createSessionInvokedLatch = new CountDownLatch( + 1); + + public SimpleZooKeeperServer(File snapDir, File logDir, int tickTime, + CountDownLatch startupDelayLatch) throws IOException { + super(snapDir, logDir, tickTime); + this.startupDelayLatch = startupDelayLatch; + } + + @Override + public synchronized void startup() { + try { + startupInvokedLatch.countDown(); + // Delaying the zk server startup so that + // ZooKeeperServer#sessionTracker reference won't be + // initialized. In the defect scenario, while processing the + // connection request zkServer needs sessionTracker reference, + // but this is not yet initialized and the server is still in + // the startup phase, resulting in NPE. + startupDelayLatch.await(); + } catch (InterruptedException e) { + Assert.fail( + "Unexpected InterruptedException while startinng up!"); + } + super.startup(); + } + + @Override + long createSession(ServerCnxn cnxn, byte[] passwd, int timeout) { + createSessionInvokedLatch.countDown(); + return super.createSession(cnxn, passwd, timeout); + } + + boolean waitForStartupInvocation(long timeout) + throws InterruptedException { + return startupInvokedLatch.await(timeout, TimeUnit.SECONDS); + } + + boolean waitForSessionCreation(long timeout) + throws InterruptedException { + return createSessionInvokedLatch.await(timeout, TimeUnit.SECONDS); + } + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerTest.java b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerTest.java index 5a0759a9f6d..2d7aad17f7b 100644 --- a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerTest.java +++ b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerTest.java @@ -19,14 +19,15 @@ package org.apache.zookeeper.server; import java.io.File; +import java.io.IOException; import java.util.List; import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.server.persistence.FileTxnLog; import org.apache.zookeeper.server.persistence.Util; +import org.apache.zookeeper.test.ClientBase; import org.junit.Assert; import org.junit.Test; -import org.junit.Assert; public class ZooKeeperServerTest extends ZKTestCase { @Test @@ -114,4 +115,23 @@ public void testForceSyncDefaultDisabled() { } } + @Test + public void testInvalidSnapshot() { + File f = null; + File tmpFileDir = null; + try { + tmpFileDir = ClientBase.createTmpDir(); + f = new File(tmpFileDir, "snapshot.0"); + if (!f.exists()) { + f.createNewFile(); + } + Assert.assertFalse("Snapshot file size is greater than 9 bytes", Util.isValidSnapshot(f)); + Assert.assertTrue("Can't delete file", f.delete()); + } catch (IOException e) { + } finally { + if (null != tmpFileDir) { + ClientBase.recursiveDelete(tmpFileDir); + } + } + } } diff --git a/src/java/test/org/apache/zookeeper/server/ZooKeeperThreadTest.java b/src/java/test/org/apache/zookeeper/server/ZooKeeperThreadTest.java new file mode 100644 index 00000000000..4db02c738c0 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/ZooKeeperThreadTest.java @@ -0,0 +1,84 @@ +/** + * 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; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.Assert; + +import org.junit.Test; + +public class ZooKeeperThreadTest { + private CountDownLatch runningLatch = new CountDownLatch(1); + + public class MyThread extends ZooKeeperThread { + + public MyThread(String threadName) { + super(threadName); + } + + public void run() { + throw new Error(); + } + + @Override + protected void handleException(String thName, Throwable e) { + runningLatch.countDown(); + } + } + + public class MyCriticalThread extends ZooKeeperCriticalThread { + + public MyCriticalThread(String threadName) { + super(threadName, new ZooKeeperServerListener() { + + @Override + public void notifyStopping(String threadName, int erroCode) { + + } + }); + } + + public void run() { + throw new Error(); + } + + @Override + protected void handleException(String thName, Throwable e) { + runningLatch.countDown(); + } + } + + /** + * Test verifies uncaught exception handling of ZooKeeperThread + */ + @Test(timeout = 30000) + public void testUncaughtException() throws Exception { + MyThread t1 = new MyThread("Test-Thread"); + t1.start(); + Assert.assertTrue("Uncaught exception is not properly handled.", + runningLatch.await(10000, TimeUnit.MILLISECONDS)); + + runningLatch = new CountDownLatch(1); + MyCriticalThread t2 = new MyCriticalThread("Test-Critical-Thread"); + t2.start(); + Assert.assertTrue("Uncaught exception is not properly handled.", + runningLatch.await(10000, TimeUnit.MILLISECONDS)); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/ZxidRolloverTest.java b/src/java/test/org/apache/zookeeper/server/ZxidRolloverTest.java new file mode 100644 index 00000000000..281f9eb04e2 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/ZxidRolloverTest.java @@ -0,0 +1,444 @@ +/** + * 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; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.log4j.Logger; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.KeeperException.ConnectionLossException; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.apache.zookeeper.test.ClientTest; +import org.apache.zookeeper.test.QuorumUtil; +import org.apache.zookeeper.test.QuorumUtil.PeerStruct; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verify ZOOKEEPER-1277 - ensure that we handle epoch rollover correctly. + */ +public class ZxidRolloverTest extends TestCase { + private static final Logger LOG = Logger.getLogger(ZxidRolloverTest.class); + + private QuorumUtil qu; + private ZooKeeperServer zksLeader; + private ZooKeeper[] zkClients = new ZooKeeper[3]; + private CountdownWatcher[] zkClientWatchers = new CountdownWatcher[3]; + private int idxLeader; + private int idxFollower; + + private ZooKeeper getClient(int idx) { + return zkClients[idx-1]; + } + + @Override + protected void setUp() throws Exception { + LOG.info("STARTING " + getName()); + + // set the snap count to something low so that we force log rollover + // and verify that is working as part of the epoch rollover. + SyncRequestProcessor.setSnapCount(7); + + qu = new QuorumUtil(1); + startAll(); + + for (int i = 0; i < zkClients.length; i++) { + zkClientWatchers[i] = new CountdownWatcher(); + PeerStruct peer = qu.getPeer(i + 1); + zkClients[i] = new ZooKeeper( + "127.0.0.1:" + peer.clientPort, + ClientTest.CONNECTION_TIMEOUT, zkClientWatchers[i]); + } + waitForClientsConnected(); + } + + private void waitForClientsConnected() throws Exception { + for (int i = 0; i < zkClients.length; i++) { + zkClientWatchers[i].waitForConnected(ClientTest.CONNECTION_TIMEOUT); + zkClientWatchers[i].reset(); + } + } + + /** + * Ensure all clients are able to talk to the service. + */ + private void checkClientsConnected() throws Exception { + for (int i = 0; i < zkClients.length; i++) { + checkClientConnected(i + 1); + } + } + + /** + * Ensure the client is able to talk to the server. + * + * @param idx the idx of the server the client is talking to + */ + private void checkClientConnected(int idx) throws Exception { + ZooKeeper zk = getClient(idx); + if (zk == null) { + return; + } + try { + assertNull(zk.exists("/foofoofoo-connected", false)); + } catch (ConnectionLossException e) { + // second chance... + // in some cases, leader change in particular, the timing is + // very tricky to get right in order to assure that the client has + // disconnected and reconnected. In some cases the client will + // disconnect, then attempt to reconnect before the server is + // back, in which case we'll see another connloss on the operation + // in the try, this catches that case and waits for the server + // to come back + PeerStruct peer = qu.getPeer(idx); + Assert.assertTrue("Waiting for server down", ClientBase.waitForServerUp( + "127.0.0.1:" + peer.clientPort, ClientBase.CONNECTION_TIMEOUT)); + + assertNull(zk.exists("/foofoofoo-connected", false)); + } + } + + /** + * Ensure all clients are disconnected from the service. + */ + private void checkClientsDisconnected() throws Exception { + for (int i = 0; i < zkClients.length; i++) { + checkClientDisconnected(i + 1); + } + } + + /** + * Ensure the client is able to talk to the server + * + * @param idx the idx of the server the client is talking to + */ + private void checkClientDisconnected(int idx) throws Exception { + ZooKeeper zk = getClient(idx); + if (zk == null) { + return; + } + try { + assertNull(zk.exists("/foofoofoo-disconnected", false)); + fail("expected client to be disconnected"); + } catch (KeeperException e) { + // success + } + } + + private void startAll() throws Exception { + qu.startAll(); + checkLeader(); + // all clients should be connected + checkClientsConnected(); + } + private void start(int idx) throws Exception { + qu.start(idx); + for (String hp : qu.getConnString().split(",")) { + Assert.assertTrue("waiting for server up", ClientBase.waitForServerUp(hp, + ClientTest.CONNECTION_TIMEOUT)); + } + + checkLeader(); + // all clients should be connected + checkClientsConnected(); + } + + private void checkLeader() { + idxLeader = 1; + while(qu.getPeer(idxLeader).peer.leader == null) { + idxLeader++; + } + idxFollower = (idxLeader == 1 ? 2 : 1); + + zksLeader = qu.getPeer(idxLeader).peer.getActiveServer(); + } + + private void shutdownAll() throws Exception { + qu.shutdownAll(); + // all clients should be disconnected + checkClientsDisconnected(); + } + + private void shutdown(int idx) throws Exception { + qu.shutdown(idx); + + // leader will shutdown, remaining followers will elect a new leader + PeerStruct peer = qu.getPeer(idx); + Assert.assertTrue("Waiting for server down", ClientBase.waitForServerDown( + "127.0.0.1:" + peer.clientPort, ClientBase.CONNECTION_TIMEOUT)); + + // if idx is the the leader then everyone will get disconnected, + // otherwise if idx is a follower then just that client will get + // disconnected + if (idx == idxLeader) { + checkClientDisconnected(idx); + try { + checkClientsDisconnected(); + } catch (AssertionFailedError e) { + // the clients may or may not have already reconnected + // to the recovered cluster, force a check, but ignore + } + } else { + checkClientDisconnected(idx); + } + } + + /** Reset the next zxid to be near epoch end */ + private void adjustEpochNearEnd() { + zksLeader.setZxid((zksLeader.getZxid() & 0xffffffff00000000L) | 0xfffffffcL); + } + + @Override + protected void tearDown() throws Exception { + LOG.info("tearDown starting"); + for (int i = 0; i < zkClients.length; i++) { + zkClients[i].close(); + } + qu.shutdownAll(); + } + + /** + * Create the znodes, this may fail if the lower 32 roll over, if so + * wait for the clients to be re-connected after the re-election + */ + private int createNodes(ZooKeeper zk, int start, int count) throws Exception { + LOG.info("Creating nodes " + start + " thru " + (start + count)); + int j = 0; + try { + for (int i = start; i < start + count; i++) { + zk.create("/foo" + i, new byte[0], Ids.READ_ACL_UNSAFE, + CreateMode.EPHEMERAL); + j++; + } + } catch (ConnectionLossException e) { + // this is ok - the leader has dropped leadership + waitForClientsConnected(); + } + return j; + } + /** + * Verify the expected znodes were created and that the last znode, which + * caused the roll-over, did not. + */ + private void checkNodes(ZooKeeper zk, int start, int count) throws Exception { + LOG.info("Validating nodes " + start + " thru " + (start + count)); + for (int i = start; i < start + count; i++) { + assertNotNull(zk.exists("/foo" + i, false)); + LOG.error("Exists zxid:" + Long.toHexString(zk.exists("/foo" + i, false).getCzxid())); + } + assertNull(zk.exists("/foo" + (start + count), false)); + } + + /** + * Prior to the fix this test would hang for a while, then fail with + * connection loss. + */ + @Test + public void testSimpleRolloverFollower() throws Exception { + adjustEpochNearEnd(); + + ZooKeeper zk = getClient((idxLeader == 1 ? 2 : 1)); + int countCreated = createNodes(zk, 0, 10); + + checkNodes(zk, 0, countCreated); + } + + /** + * Similar to testSimpleRollover, but ensure the cluster comes back, + * has the right data, and is able to serve new requests. + */ + @Test + public void testRolloverThenRestart() throws Exception { + ZooKeeper zk = getClient(idxFollower); + + int countCreated = createNodes(zk, 0, 10); + + adjustEpochNearEnd(); + + countCreated += createNodes(zk, countCreated, 10); + + shutdownAll(); + startAll(); + zk = getClient(idxLeader); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + adjustEpochNearEnd(); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + shutdownAll(); + startAll(); + zk = getClient(idxFollower); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + shutdownAll(); + startAll(); + zk = getClient(idxLeader); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + // sanity check + assertTrue(countCreated > 0); + assertTrue(countCreated < 60); + } + + /** + * Similar to testRolloverThenRestart, but ensure a follower comes back, + * has the right data, and is able to serve new requests. + */ + @Test + public void testRolloverThenFollowerRestart() throws Exception { + ZooKeeper zk = getClient(idxFollower); + + int countCreated = createNodes(zk, 0, 10); + + adjustEpochNearEnd(); + + countCreated += createNodes(zk, countCreated, 10); + + shutdown(idxFollower); + start(idxFollower); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + adjustEpochNearEnd(); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + shutdown(idxFollower); + start(idxFollower); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + shutdown(idxFollower); + start(idxFollower); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + // sanity check + assertTrue(countCreated > 0); + assertTrue(countCreated < 60); + } + + /** + * Similar to testRolloverThenRestart, but ensure leadership can change, + * comes back, has the right data, and is able to serve new requests. + */ + @Test + public void testRolloverThenLeaderRestart() throws Exception { + ZooKeeper zk = getClient(idxLeader); + + int countCreated = createNodes(zk, 0, 10); + + adjustEpochNearEnd(); + + checkNodes(zk, 0, countCreated); + + shutdown(idxLeader); + start(idxLeader); + zk = getClient(idxLeader); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + adjustEpochNearEnd(); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + shutdown(idxLeader); + start(idxLeader); + zk = getClient(idxLeader); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + shutdown(idxLeader); + start(idxLeader); + zk = getClient(idxFollower); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + // sanity check + assertTrue(countCreated > 0); + assertTrue(countCreated < 50); + } + + /** + * Similar to testRolloverThenRestart, but ensure we can survive multiple + * epoch rollovers between restarts. + */ + @Test + public void testMultipleRollover() throws Exception { + ZooKeeper zk = getClient(idxFollower); + + int countCreated = createNodes(zk, 0, 10); + + adjustEpochNearEnd(); + + countCreated += createNodes(zk, countCreated, 10); + + adjustEpochNearEnd(); + + countCreated += createNodes(zk, countCreated, 10); + + adjustEpochNearEnd(); + + countCreated += createNodes(zk, countCreated, 10); + + adjustEpochNearEnd(); + + countCreated += createNodes(zk, countCreated, 10); + + shutdownAll(); + startAll(); + zk = getClient(idxFollower); + + adjustEpochNearEnd(); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + shutdown(idxLeader); + start(idxLeader); + zk = getClient(idxFollower); + + checkNodes(zk, 0, countCreated); + countCreated += createNodes(zk, countCreated, 10); + + // sanity check + assertTrue(countCreated > 0); + assertTrue(countCreated < 70); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/CnxManagerTest.java b/src/java/test/org/apache/zookeeper/server/quorum/CnxManagerTest.java similarity index 61% rename from src/java/test/org/apache/zookeeper/test/CnxManagerTest.java rename to src/java/test/org/apache/zookeeper/server/quorum/CnxManagerTest.java index fed68c16935..a82a728e26e 100644 --- a/src/java/test/org/apache/zookeeper/test/CnxManagerTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/CnxManagerTest.java @@ -16,8 +16,9 @@ * limitations under the License. */ -package org.apache.zookeeper.test; +package org.apache.zookeeper.server.quorum; +import java.io.DataOutputStream; import java.io.File; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -38,12 +39,13 @@ import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; +import org.apache.zookeeper.test.ClientBase; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class CnxManagerTest extends ZKTestCase { - protected static final Logger LOG = LoggerFactory.getLogger(FLENewEpochTest.class); + protected static final Logger LOG = LoggerFactory.getLogger(CnxManagerTest.class); protected static final int THRESHOLD = 4; int count; @@ -64,28 +66,15 @@ public void setUp() throws Exception { peerQuorumPort[i] = PortAssignment.unique(); peerClientPort[i] = PortAssignment.unique(); peers.put(Long.valueOf(i), - new QuorumServer(i, - new InetSocketAddress(peerQuorumPort[i]), - new InetSocketAddress(PortAssignment.unique()))); + new QuorumServer(i, "0.0.0.0", + peerQuorumPort[i], + PortAssignment.unique(), null)); peerTmpdir[i] = ClientBase.createTmpDir(); } } ByteBuffer createMsg(int state, long leader, long zxid, long epoch){ - byte requestBytes[] = new byte[28]; - ByteBuffer requestBuffer = ByteBuffer.wrap(requestBytes); - - /* - * Building notification packet to send - */ - - requestBuffer.clear(); - requestBuffer.putInt(state); - requestBuffer.putLong(leader); - requestBuffer.putLong(zxid); - requestBuffer.putLong(epoch); - - return requestBuffer; + return FastLeaderElection.buildMsg(state, leader, zxid, 0, epoch); } class CnxManagerThread extends Thread { @@ -98,7 +87,7 @@ class CnxManagerThread extends Thread { public void run(){ try { QuorumPeer peer = new QuorumPeer(peers, peerTmpdir[0], peerTmpdir[0], peerClientPort[0], 3, 0, 1000, 2, 2); - QuorumCnxManager cnxManager = new QuorumCnxManager(peer); + QuorumCnxManager cnxManager = peer.createCnxnManager(); QuorumCnxManager.Listener listener = cnxManager.listener; if(listener != null){ listener.start(); @@ -142,7 +131,7 @@ public void testCnxManager() throws Exception { thread.start(); QuorumPeer peer = new QuorumPeer(peers, peerTmpdir[1], peerTmpdir[1], peerClientPort[1], 3, 1, 1000, 2, 2); - QuorumCnxManager cnxManager = new QuorumCnxManager(peer); + QuorumCnxManager cnxManager = peer.createCnxnManager(); QuorumCnxManager.Listener listener = cnxManager.listener; if(listener != null){ listener.start(); @@ -175,19 +164,18 @@ public void testCnxManager() throws Exception { public void testCnxManagerTimeout() throws Exception { Random rand = new Random(); byte b = (byte) rand.nextInt(); + int finalOctet = b & 0xFF; int deadPort = PortAssignment.unique(); - String deadAddress = new String("10.1.1." + b); + String deadAddress = new String("192.0.2." + finalOctet); LOG.info("This is the dead address I'm trying: " + deadAddress); peers.put(Long.valueOf(2), - new QuorumServer(2, - new InetSocketAddress(deadAddress, deadPort), - new InetSocketAddress(deadAddress, PortAssignment.unique()))); + new QuorumServer(2, deadAddress, deadPort, PortAssignment.unique(), null)); peerTmpdir[2] = ClientBase.createTmpDir(); QuorumPeer peer = new QuorumPeer(peers, peerTmpdir[1], peerTmpdir[1], peerClientPort[1], 3, 1, 1000, 2, 2); - QuorumCnxManager cnxManager = new QuorumCnxManager(peer); + QuorumCnxManager cnxManager = peer.createCnxnManager(); QuorumCnxManager.Listener listener = cnxManager.listener; if(listener != null){ listener.start(); @@ -214,7 +202,7 @@ public void testCnxManagerTimeout() throws Exception { @Test public void testCnxManagerSpinLock() throws Exception { QuorumPeer peer = new QuorumPeer(peers, peerTmpdir[1], peerTmpdir[1], peerClientPort[1], 3, 1, 1000, 2, 2); - QuorumCnxManager cnxManager = new QuorumCnxManager(peer); + QuorumCnxManager cnxManager = peer.createCnxnManager(); QuorumCnxManager.Listener listener = cnxManager.listener; if(listener != null){ listener.start(); @@ -264,13 +252,117 @@ public void testCnxManagerSpinLock() throws Exception { cnxManager.halt(); } + /* + * Class used with testCnxFromFutureVersion + */ + class TestCnxManager extends QuorumCnxManager { + + TestCnxManager(QuorumPeer self) { + super(self.getId(), self.getView(), self.authServer, + self.authLearner, self.tickTime * self.syncLimit, + self.getQuorumListenOnAllIPs(), + self.quorumCnxnThreadsSize, false); + } + + boolean senderWorkerMapContains(Long l){ + return senderWorkerMap.containsKey(l); + } + + long getSid(Message m){ + return m.sid; + } + + String getMsgString(Message m){ + return new String(m.buffer.array()); + } + } + + /** + * Before 3.5.0 a server sends its id when connecting to another server. + * Starting with 3.5.0 a server will send a protocol version, followed by + * its id, then number of bytes in the remainder of the message and finally + * the rest of the message. The test makes sure that a 3.4.6 server is able + * to detect that a connection message has this new format, extract the id, + * and skip the remainder of the message. + * + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-1633} + * + * @throws Exception + */ + @Test + public void testCnxFromFutureVersion() throws Exception { + QuorumPeer peer = new QuorumPeer(peers, peerTmpdir[1], peerTmpdir[1], peerClientPort[1], 3, 1, 1000, 2, 20); + TestCnxManager cnxManager = new TestCnxManager(peer); + QuorumCnxManager.Listener listener = cnxManager.listener; + if(listener != null){ + listener.start(); + } else { + Assert.fail("Null listener when initializing cnx manager"); + } + + int port = peers.get(peer.getId()).electionAddr.getPort(); + LOG.info("Election port: " + port); + + Thread.sleep(1000); + + SocketChannel sc = SocketChannel.open(); + sc.socket().connect(peers.get(new Long(1)).electionAddr, 5000); + + InetSocketAddress otherAddr = peers.get(new Long(2)).electionAddr; + DataOutputStream dout = new DataOutputStream(sc.socket().getOutputStream()); + // protocol version - a negative number + dout.writeLong(0xffff0000); + // server id + dout.writeLong(new Long(2)); + // other stuff that a 3.5.0 server will send - not important for 3.4.6 + // the 3.4.6 server should just skip it + String addr = otherAddr.getHostName()+ ":" + otherAddr.getPort(); + byte[] addr_bytes = addr.getBytes(); + dout.writeInt(addr_bytes.length); + dout.write(addr_bytes); + dout.flush(); + + Thread.sleep(1000); + + Assert.assertEquals("Server 1 got connection request from server 2", + true, cnxManager.senderWorkerMapContains(new Long(2))); + + // send another message to make sure the connection message was processed + // properly (mainly that its suffix was removed from the stream) + String testStr = "this is a test message string"; + byte[] testStr_bytes = testStr.getBytes(); + dout.writeInt(testStr_bytes.length); + dout.write(testStr_bytes); + dout.flush(); + + Message m = null; + int numRetries = 1; + while((m == null) && (numRetries++ <= THRESHOLD)){ + m = cnxManager.pollRecvQueue(3000, TimeUnit.MILLISECONDS); + if(m == null) cnxManager.connectAll(); + } + + if(numRetries > THRESHOLD){ + Assert.fail("Test message hasn't been found in recvQueue"); + } + + //Assert.assertEquals("Message sender should be 2", 2, m.sid); + Assert.assertEquals("Message sender should be 2", 2, cnxManager.getSid(m)); + Assert.assertEquals("Message from 2 doesn't match test sring", testStr, + cnxManager.getMsgString(m)); + + peer.shutdown(); + cnxManager.halt(); + } + + /* * Test if a receiveConnection is able to timeout on socket errors */ @Test public void testSocketTimeout() throws Exception { QuorumPeer peer = new QuorumPeer(peers, peerTmpdir[1], peerTmpdir[1], peerClientPort[1], 3, 1, 2000, 2, 2); - QuorumCnxManager cnxManager = new QuorumCnxManager(peer); + QuorumCnxManager cnxManager = peer.createCnxnManager(); QuorumCnxManager.Listener listener = cnxManager.listener; if(listener != null){ listener.start(); @@ -297,41 +389,43 @@ public void testSocketTimeout() throws Exception { @Test public void testWorkerThreads() throws Exception { ArrayList peerList = new ArrayList(); - - for (int sid = 0; sid < 3; sid++) { - QuorumPeer peer = new QuorumPeer(peers, peerTmpdir[sid], peerTmpdir[sid], - peerClientPort[sid], 3, sid, 1000, 2, 2); - LOG.info("Starting peer " + peer.getId()); - peer.start(); - peerList.add(sid, peer); - } - String failure = verifyThreadCount(peerList, 4); - if (failure != null) { - Assert.fail(failure); - } - for (int myid = 0; myid < 3; myid++) { - for (int i = 0; i < 5; i++) { - // halt one of the listeners and verify count - QuorumPeer peer = peerList.get(myid); - LOG.info("Round " + i + ", halting peer " + peer.getId()); - peer.shutdown(); - peerList.remove(myid); - failure = verifyThreadCount(peerList, 2); - if (failure != null) { - Assert.fail(failure); - } - - // Restart halted node and verify count - peer = new QuorumPeer(peers, peerTmpdir[myid], peerTmpdir[myid], - peerClientPort[myid], 3, myid, 1000, 2, 2); - LOG.info("Round " + i + ", restarting peer " + peer.getId()); + try { + for (int sid = 0; sid < 3; sid++) { + QuorumPeer peer = new QuorumPeer(peers, peerTmpdir[sid], peerTmpdir[sid], + peerClientPort[sid], 3, sid, 1000, 2, 2); + LOG.info("Starting peer {}", peer.getId()); peer.start(); - peerList.add(myid, peer); - failure = verifyThreadCount(peerList, 4); - if (failure != null) { - Assert.fail(failure); + peerList.add(sid, peer); + } + String failure = verifyThreadCount(peerList, 4); + if (failure != null) { + Assert.fail(failure); + } + for (int myid = 0; myid < 3; myid++) { + for (int i = 0; i < 5; i++) { + // halt one of the listeners and verify count + QuorumPeer peer = peerList.get(myid); + LOG.info("Round {}, halting peer {}", new Object[] { i, + peer.getId() }); + peer.shutdown(); + peerList.remove(myid); + failure = verifyThreadCount(peerList, 2); + Assert.assertNull(failure, failure); + // Restart halted node and verify count + peer = new QuorumPeer(peers, peerTmpdir[myid], peerTmpdir[myid], + peerClientPort[myid], 3, myid, 1000, 2, 2); + LOG.info("Round {}, restarting peer {}" + + new Object[] { i, peer.getId() }); + peer.start(); + peerList.add(myid, peer); + failure = verifyThreadCount(peerList, 4); + Assert.assertNull(failure, failure); } } + } finally { + for (QuorumPeer quorumPeer : peerList) { + quorumPeer.shutdown(); + } } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/EphemeralNodeDeletionTest.java b/src/java/test/org/apache/zookeeper/server/quorum/EphemeralNodeDeletionTest.java new file mode 100644 index 00000000000..c34f243c2a5 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/EphemeralNodeDeletionTest.java @@ -0,0 +1,219 @@ +/** + * 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 static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.net.SocketTimeoutException; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +import javax.security.sasl.SaslException; + +public class EphemeralNodeDeletionTest extends QuorumPeerTestBase { + private static int SERVER_COUNT = 3; + private MainThread[] mt = new MainThread[SERVER_COUNT]; + + /** + * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2355. + * ZooKeeper ephemeral node is never deleted if follower fail while reading + * the proposal packet. + */ + + @Test(timeout = 120000) + public void testEphemeralNodeDeletion() throws Exception { + final int clientPorts[] = new int[SERVER_COUNT]; + StringBuilder sb = new StringBuilder(); + String server; + + for (int i = 0; i < SERVER_COUNT; i++) { + clientPorts[i] = PortAssignment.unique(); + server = "server." + i + "=127.0.0.1:" + PortAssignment.unique() + + ":" + PortAssignment.unique(); + sb.append(server + "\n"); + } + String currentQuorumCfgSection = sb.toString(); + System.out.println(currentQuorumCfgSection); + // start all the servers + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection) { + @Override + public TestQPMain getTestQPMain() { + return new MockTestQPMain(); + } + }; + mt[i].start(); + } + + // ensure all servers started + for (int i = 0; i < SERVER_COUNT; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], + CONNECTION_TIMEOUT)); + } + + CountdownWatcher watch = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper("127.0.0.1:" + clientPorts[1], + ClientBase.CONNECTION_TIMEOUT, watch); + watch.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + + /** + * now the problem scenario starts + */ + + // 1: create ephemeral node + String nodePath = "/e1"; + zk.create(nodePath, "1".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + + // 2: inject network problem in one of the follower + CustomQuorumPeer follower = (CustomQuorumPeer) getByServerState(mt, + ServerState.FOLLOWING); + follower.setInjectError(true); + + // 3: close the session so that ephemeral node is deleted + zk.close(); + + // remove the error + follower.setInjectError(false); + + Assert.assertTrue("Faulted Follower should have joined quorum by now", + ClientBase.waitForServerUp( + "127.0.0.1:" + follower.getClientPort(), + CONNECTION_TIMEOUT)); + + QuorumPeer leader = getByServerState(mt, ServerState.LEADING); + assertNotNull("Leader should not be null", leader); + Assert.assertTrue("Leader must be running", ClientBase.waitForServerUp( + "127.0.0.1:" + leader.getClientPort(), CONNECTION_TIMEOUT)); + + watch = new CountdownWatcher(); + zk = new ZooKeeper("127.0.0.1:" + leader.getClientPort(), + ClientBase.CONNECTION_TIMEOUT, watch); + watch.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + + Stat exists = zk.exists(nodePath, false); + assertNull("Node must have been deleted from leader", exists); + + CountdownWatcher followerWatch = new CountdownWatcher(); + ZooKeeper followerZK = new ZooKeeper( + "127.0.0.1:" + follower.getClientPort(), + ClientBase.CONNECTION_TIMEOUT, followerWatch); + followerWatch.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + Stat nodeAtFollower = followerZK.exists(nodePath, false); + + // Problem 1: Follower had one extra ephemeral node /e1 + assertNull("ephemeral node must not exist", nodeAtFollower); + + // Create the node with another session + zk.create(nodePath, "2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + + // close the session and newly created ephemeral node should be deleted + zk.close(); + + nodeAtFollower = followerZK.exists(nodePath, false); + + // Problem 2: Before fix, after session close the ephemeral node + // was not getting deleted. But now after the fix after session close + // ephemeral node is getting deleted. + assertNull("After session close ephemeral node must be deleted", + nodeAtFollower); + followerZK.close(); + } + + @After + public void tearDown() { + // stop all severs + for (int i = 0; i < mt.length; i++) { + try { + mt[i].shutdown(); + } catch (InterruptedException e) { + LOG.warn("Quorum Peer interrupted while shutting it down", e); + } + } + } + + private QuorumPeer getByServerState(MainThread[] mt, ServerState state) { + for (int i = mt.length - 1; i >= 0; i--) { + QuorumPeer quorumPeer = mt[i].getQuorumPeer(); + if (null != quorumPeer && state == quorumPeer.getPeerState()) { + return quorumPeer; + } + } + return null; + } + + static class CustomQuorumPeer extends QuorumPeer { + private boolean injectError = false; + + public CustomQuorumPeer() throws SaslException { + } + + @Override + protected Follower makeFollower(FileTxnSnapLog logFactory) + throws IOException { + return new Follower(this, new FollowerZooKeeperServer(logFactory, + this, null /*DataTreeBuilder is never used*/, + this.getZkDb())) { + + @Override + void readPacket(QuorumPacket pp) throws IOException { + /** + * In real scenario got SocketTimeoutException while reading + * the packet from leader because of network problem, but + * here throwing SocketTimeoutException based on whether + * error is injected or not + */ + super.readPacket(pp); + if (injectError && pp.getType() == Leader.PROPOSAL) { + String type = LearnerHandler.packetToString(pp); + throw new SocketTimeoutException( + "Socket timeout while reading the packet for operation " + + type); + } + } + + }; + } + + public void setInjectError(boolean injectError) { + this.injectError = injectError; + } + + } + + static class MockTestQPMain extends TestQPMain { + @Override + protected QuorumPeer getQuorumPeer() throws SaslException { + return new CustomQuorumPeer(); + } + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/server/quorum/FLEBackwardElectionRoundTest.java b/src/java/test/org/apache/zookeeper/server/quorum/FLEBackwardElectionRoundTest.java new file mode 100644 index 00000000000..c0ab3eaaf6d --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/FLEBackwardElectionRoundTest.java @@ -0,0 +1,154 @@ +/* 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 java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.HashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.server.quorum.QuorumCnxManager; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.Vote; +import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; +import org.apache.zookeeper.test.ClientBase; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + +public class FLEBackwardElectionRoundTest extends ZKTestCase { + protected static final Logger LOG = LoggerFactory.getLogger(FLELostMessageTest.class); + + int count; + HashMap peers; + File tmpdir[]; + int port[]; + + QuorumCnxManager cnxManagers[]; + + @Before + public void setUp() throws Exception { + count = 3; + + peers = new HashMap(count); + tmpdir = new File[count]; + port = new int[count]; + cnxManagers = new QuorumCnxManager[count - 1]; + } + + @After + public void tearDown() throws Exception { + for(int i = 0; i < (count - 1); i++){ + if(cnxManagers[i] != null){ + cnxManagers[i].halt(); + } + } + } + + /** + * This test is checking the following case. A server S is + * currently LOOKING and it receives notifications from + * a quorum indicating they are following S. The election + * round E of S is higher than the election round E' in the + * notification messages, so S becomes the leader and sets + * its epoch back to E'. In the meanwhile, one or more + * followers turn to LOOKING and elect S in election round E. + * Having leader and followers with different election rounds + * might prevent other servers from electing a leader because + * they can't get a consistent set of notifications from a + * quorum. + * + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-1514} + * + * + * @throws Exception + */ + + @Test + public void testBackwardElectionRound() throws Exception { + LOG.info("TestLE: " + getTestName()+ ", " + count); + for(int i = 0; i < count; i++) { + int clientport = PortAssignment.unique(); + peers.put(Long.valueOf(i), + new QuorumServer(i, "0.0.0.0", clientport, + PortAssignment.unique(), null)); + tmpdir[i] = ClientBase.createTmpDir(); + port[i] = clientport; + } + + /* + * Start server 0 + */ + + QuorumPeer peer = new QuorumPeer(peers, tmpdir[0], tmpdir[0], port[0], 3, 0, 1000, 2, 2); + peer.startLeaderElection(); + FLETestUtils.LEThread thread = new FLETestUtils.LEThread(peer, 0); + thread.start(); + + + /* + * Start mock server 1 + */ + QuorumPeer mockPeer = new QuorumPeer(peers, tmpdir[1], tmpdir[1], port[1], 3, 1, 1000, 2, 2); + cnxManagers[0] = mockPeer.createCnxnManager(); + QuorumCnxManager.Listener listener = cnxManagers[0].listener; + listener.start(); + + ByteBuffer msg = FLETestUtils.createMsg(ServerState.FOLLOWING.ordinal(), 0, 0, 1); + cnxManagers[0].toSend(0l, msg); + + /* + * Start mock server 2 + */ + mockPeer = new QuorumPeer(peers, tmpdir[2], tmpdir[2], port[2], 3, 2, 1000, 2, 2); + cnxManagers[1] = mockPeer.createCnxnManager(); + listener = cnxManagers[1].listener; + listener.start(); + + cnxManagers[1].toSend(0l, msg); + + /* + * Run another instance of leader election. + */ + thread.join(5000); + thread = new FLETestUtils.LEThread(peer, 0); + thread.start(); + + /* + * Send the same messages, this time should not make 0 the leader. + */ + cnxManagers[0].toSend(0l, msg); + cnxManagers[1].toSend(0l, msg); + + + thread.join(5000); + + if (!thread.isAlive()) { + Assert.fail("Should not have joined"); + } + + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/server/quorum/FLECompatibilityTest.java b/src/java/test/org/apache/zookeeper/server/quorum/FLECompatibilityTest.java new file mode 100644 index 00000000000..f1c04cad763 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/FLECompatibilityTest.java @@ -0,0 +1,350 @@ +/** + * 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 java.io.File; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.server.quorum.FastLeaderElection.Notification; +import org.apache.zookeeper.server.quorum.FastLeaderElection.ToSend; +import org.apache.zookeeper.server.quorum.FastLeaderElection.Messenger.WorkerReceiver; +import org.apache.zookeeper.server.quorum.QuorumCnxManager.Message; +import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; +import org.apache.zookeeper.server.util.ZxidUtils; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.FLETest; +import org.apache.zookeeper.test.QuorumBase; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class FLECompatibilityTest extends ZKTestCase { + protected static final Logger LOG = LoggerFactory.getLogger(FLECompatibilityTest.class); + + int count; + HashMap peers; + File tmpdir[]; + int port[]; + + @Before + public void setUp() throws Exception { + count = 3; + peers = new HashMap(count); + tmpdir = new File[count]; + port = new int[count]; + } + + @After + public void tearDown() throws Exception { + + } + + class MockFLEMessengerBackward { + QuorumCnxManager manager; + QuorumPeer self; + long logicalclock = 1L; + LinkedBlockingQueue sendqueue = new LinkedBlockingQueue(); + LinkedBlockingQueue internalqueue = new LinkedBlockingQueue(); + LinkedBlockingQueue recvqueue = new LinkedBlockingQueue(); + WorkerReceiver wr; + + MockFLEMessengerBackward(QuorumPeer self, QuorumCnxManager manager){ + this.manager = manager; + this.self = self; + + this.wr = new WorkerReceiver(manager); + + Thread t = new Thread(this.wr, + "WorkerReceiver[myid=" + self.getId() + "]"); + t.setDaemon(true); + t.start(); + } + + void halt() { + wr.stop = true; + } + + /* + * This class has been copied from before adding versions to notifications. + * + * {@see https://issues.apache.org/jira/browse/ZOOKEEPER-1808} + */ + class WorkerReceiver implements Runnable { + volatile boolean stop; + QuorumCnxManager manager; + final long proposedLeader = 2; + final long proposedZxid = 0x1; + final long proposedEpoch = 1; + + WorkerReceiver(QuorumCnxManager manager) { + this.stop = false; + this.manager = manager; + } + + /* + * The vote we return here is fixed for test purposes. + */ + Vote getVote(){ + return new Vote(proposedLeader, proposedZxid, proposedEpoch); + } + + public void run() { + + Message response; + while (!stop) { + // Sleeps on receive + try{ + response = manager.pollRecvQueue(3000, TimeUnit.MILLISECONDS); + if(response == null) continue; + + /* + * If it is from an observer, respond right away. + * Note that the following predicate assumes that + * if a server is not a follower, then it must be + * an observer. If we ever have any other type of + * learner in the future, we'll have to change the + * way we check for observers. + */ + if(!self.getVotingView().containsKey(response.sid)){ + Vote current = self.getCurrentVote(); + ToSend notmsg = new ToSend(ToSend.mType.notification, + current.getId(), + current.getZxid(), + logicalclock, + self.getPeerState(), + response.sid, + current.getPeerEpoch()); + + internalqueue.offer(notmsg); + } else { + // Receive new message + if (LOG.isDebugEnabled()) { + LOG.debug("Receive new notification message. My id = " + + self.getId()); + } + + /* + * We check for 28 bytes for backward compatibility + */ + if (response.buffer.capacity() < 28) { + LOG.error("Got a short response: " + + response.buffer.capacity()); + continue; + } + boolean backCompatibility = (response.buffer.capacity() == 28); + response.buffer.clear(); + + // State of peer that sent this message + QuorumPeer.ServerState ackstate = QuorumPeer.ServerState.LOOKING; + switch (response.buffer.getInt()) { + case 0: + ackstate = QuorumPeer.ServerState.LOOKING; + break; + case 1: + ackstate = QuorumPeer.ServerState.FOLLOWING; + break; + case 2: + ackstate = QuorumPeer.ServerState.LEADING; + break; + case 3: + ackstate = QuorumPeer.ServerState.OBSERVING; + break; + } + + // Instantiate Notification and set its attributes + Notification n = new Notification(); + n.leader = response.buffer.getLong(); + n.zxid = response.buffer.getLong(); + n.electionEpoch = response.buffer.getLong(); + n.state = ackstate; + n.sid = response.sid; + if(!backCompatibility){ + n.peerEpoch = response.buffer.getLong(); + } else { + if(LOG.isInfoEnabled()){ + LOG.info("Backward compatibility mode, server id=" + n.sid); + } + n.peerEpoch = ZxidUtils.getEpochFromZxid(n.zxid); + } + + /* + * If this server is looking, then send proposed leader + */ + + if(self.getPeerState() == QuorumPeer.ServerState.LOOKING){ + recvqueue.offer(n); + + /* + * Send a notification back if the peer that sent this + * message is also looking and its logical clock is + * lagging behind. + */ + if((ackstate == QuorumPeer.ServerState.LOOKING) + && (n.electionEpoch < logicalclock)){ + Vote v = getVote(); + ToSend notmsg = new ToSend(ToSend.mType.notification, + v.getId(), + v.getZxid(), + logicalclock, + self.getPeerState(), + response.sid, + v.getPeerEpoch()); + internalqueue.offer(notmsg); + } + } else { + /* + * If this server is not looking, but the one that sent the ack + * is looking, then send back what it believes to be the leader. + */ + Vote current = self.getCurrentVote(); + if(ackstate == QuorumPeer.ServerState.LOOKING){ + if(LOG.isDebugEnabled()){ + LOG.debug("Sending new notification. My id = " + + self.getId() + " recipient=" + + response.sid + " zxid=0x" + + Long.toHexString(current.getZxid()) + + " leader=" + current.getId()); + } + ToSend notmsg = new ToSend( + ToSend.mType.notification, + current.getId(), + current.getZxid(), + current.getElectionEpoch(), + self.getPeerState(), + response.sid, + current.getPeerEpoch()); + internalqueue.offer(notmsg); + } + } + } + } catch (InterruptedException e) { + System.out.println("Interrupted Exception while waiting for new message" + + e.toString()); + } + } + LOG.info("WorkerReceiver is down"); + } + } + } + + class MockFLEMessengerForward extends FastLeaderElection { + + MockFLEMessengerForward(QuorumPeer self, QuorumCnxManager manager){ + super( self, manager ); + } + + void halt() { + super.shutdown(); + } + } + + void populate() + throws Exception { + for (int i = 0; i < count; i++) { + peers.put(Long.valueOf(i), + new QuorumServer(i, "0.0.0.0", + PortAssignment.unique(), + PortAssignment.unique(), null)); + tmpdir[i] = ClientBase.createTmpDir(); + port[i] = PortAssignment.unique(); + } + } + + @Test(timeout=20000) + public void testBackwardCompatibility() + throws Exception { + populate(); + + QuorumPeer peer = new QuorumPeer(peers, tmpdir[0], tmpdir[0], port[0], 3, 0, 1000, 2, 2); + peer.setPeerState(ServerState.LOOKING); + QuorumCnxManager mng = peer.createCnxnManager(); + + /* + * Check that it generates an internal notification correctly + */ + MockFLEMessengerBackward fle = new MockFLEMessengerBackward(peer, mng); + ByteBuffer buffer = FastLeaderElection.buildMsg(ServerState.LOOKING.ordinal(), 2, 0x1, 1, 1); + fle.manager.recvQueue.add(new Message(buffer, 2)); + Notification n = fle.recvqueue.take(); + Assert.assertTrue("Wrong state", n.state == ServerState.LOOKING); + Assert.assertTrue("Wrong leader", n.leader == 2); + Assert.assertTrue("Wrong zxid", n.zxid == 0x1); + Assert.assertTrue("Wrong epoch", n.electionEpoch == 1); + Assert.assertTrue("Wrong epoch", n.peerEpoch == 1); + + /* + * Check that it sends a notification back to the sender + */ + peer.setPeerState(ServerState.FOLLOWING); + peer.setCurrentVote( new Vote(2, 0x1, 1, 1, ServerState.LOOKING) ); + buffer = FastLeaderElection.buildMsg(ServerState.LOOKING.ordinal(), 1, 0x1, 1, 1); + fle.manager.recvQueue.add(new Message(buffer, 1)); + ToSend m = fle.internalqueue.take(); + Assert.assertTrue("Wrong state", m.state == ServerState.FOLLOWING); + Assert.assertTrue("Wrong sid", m.sid == 1); + Assert.assertTrue("Wrong leader", m.leader == 2); + Assert.assertTrue("Wrong epoch", m.electionEpoch == 1); + Assert.assertTrue("Wrong epoch", m.peerEpoch == 1); + } + + @Test(timeout=20000) + public void testForwardCompatibility() + throws Exception { + populate(); + + QuorumPeer peer = new QuorumPeer(peers, tmpdir[0], tmpdir[0], port[0], 3, 0, 1000, 2, 2); + peer.setPeerState(ServerState.LOOKING); + QuorumCnxManager mng = peer.createCnxnManager(); + + /* + * Check that it generates an internal notification correctly + */ + MockFLEMessengerForward fle = new MockFLEMessengerForward(peer, mng); + ByteBuffer notBuffer = FastLeaderElection.buildMsg(ServerState.LOOKING.ordinal(), 2, 0x1, 1, 1); + ByteBuffer buffer = ByteBuffer.allocate( notBuffer.capacity() + 8 ); + notBuffer.flip(); + buffer.put(notBuffer); + buffer.putLong( Long.MAX_VALUE ); + buffer.flip(); + + fle.manager.recvQueue.add(new Message(buffer, 2)); + Notification n = fle.recvqueue.take(); + Assert.assertTrue("Wrong state", n.state == ServerState.LOOKING); + Assert.assertTrue("Wrong leader", n.leader == 2); + Assert.assertTrue("Wrong zxid", n.zxid == 0x1); + Assert.assertTrue("Wrong epoch", n.electionEpoch == 1); + Assert.assertTrue("Wrong epoch", n.peerEpoch == 1); + Assert.assertTrue("Wrong version", n.version == FastLeaderElection.Notification.CURRENTVERSION); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/FLEDontCareTest.java b/src/java/test/org/apache/zookeeper/server/quorum/FLEDontCareTest.java new file mode 100644 index 00000000000..ffc7ab10dc7 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/FLEDontCareTest.java @@ -0,0 +1,262 @@ +/** + * 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 java.io.File; +import java.net.InetSocketAddress; +import java.util.HashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.server.quorum.FastLeaderElection; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.Vote; +import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; +import org.apache.zookeeper.server.util.ZxidUtils; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.FLETest; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + +public class FLEDontCareTest { + protected static final Logger LOG = LoggerFactory.getLogger(FLEDontCareTest.class); + + class MockFLE extends FastLeaderElection { + MockFLE(QuorumPeer peer, QuorumCnxManager cnxManager) { + super(peer, cnxManager); + } + + public boolean termPredicate(HashMap votes, Vote vote) { + return super.termPredicate(votes, vote); + } + + public boolean checkLeader(HashMap votes, long leader, long electionEpoch) { + return super.checkLeader(votes, leader, electionEpoch); + } + + public boolean ooePredicate(HashMap recv, + HashMap ooe, + FastLeaderElection.Notification n) { + return super.ooePredicate(recv, ooe, n); + + } + } + + HashMap peers; + QuorumPeer peer; + File tmpdir; + + @Before + public void setUp() + throws Exception { + tmpdir = ClientBase.createTmpDir(); + peers = new HashMap(); + for(int i = 0; i < 5; i++) { + peers.put(Long.valueOf(i), + new QuorumServer(Long.valueOf(i), "127.0.0.1", PortAssignment.unique(), 0, null)); + } + peer = new QuorumPeer(peers, + tmpdir, + tmpdir, + PortAssignment.unique(), + 3, 3, 1000, 2, 2); + } + + @After + public void tearDown(){ + tmpdir.delete(); + } + + @Test + public void testDontCare() { + MockFLE fle = new MockFLE(peer, peer.createCnxnManager()); + + HashMap votes = new HashMap(); + votes.put(0L, new Vote(0x1, 4L, ZxidUtils.makeZxid(1, 1), 1, 2, ServerState.FOLLOWING)); + votes.put(1L, new Vote(0x1, 4L, ZxidUtils.makeZxid(1, 2), 1, 2, ServerState.FOLLOWING)); + votes.put(3L, new Vote(0x1, 4L, ZxidUtils.makeZxid(2, 1), 2, 2, ServerState.FOLLOWING)); + votes.put(4L, new Vote(0x1, 4L, ZxidUtils.makeZxid(2, 1), 2, 2, ServerState.LEADING)); + + Assert.assertTrue(fle.termPredicate(votes, + new Vote(4L, ZxidUtils.makeZxid(2, 1), 2, 2, ServerState.FOLLOWING))); + } + + @Test + public void testDontCareVersion() { + MockFLE fle = new MockFLE(peer, peer.createCnxnManager()); + + HashMap votes = new HashMap(); + votes.put(0L, new Vote(0x1, 4L, ZxidUtils.makeZxid(1, 1), 1, 1, ServerState.FOLLOWING)); + votes.put(1L, new Vote(0x1, 4L, ZxidUtils.makeZxid(1, 1), 1, 1, ServerState.FOLLOWING)); + votes.put(3L, new Vote(4L, ZxidUtils.makeZxid(2, 1), 2, 2, ServerState.FOLLOWING)); + votes.put(4L, new Vote(4L, ZxidUtils.makeZxid(2, 1), 2, 2, ServerState.LEADING)); + + Assert.assertTrue(fle.termPredicate(votes, + new Vote(4L, ZxidUtils.makeZxid(2, 1), 2, 2, ServerState.FOLLOWING))); + } + + @Test + public void testLookingNormal() { + MockFLE fle = new MockFLE(peer, peer.createCnxnManager()); + + HashMap votes = new HashMap(); + votes.put(0L, new Vote(4L, ZxidUtils.makeZxid(2, 1), 1, 1, ServerState.LOOKING)); + votes.put(1L, new Vote(4L, ZxidUtils.makeZxid(2, 1), 1, 1, ServerState.LOOKING)); + votes.put(3L, new Vote(4L, ZxidUtils.makeZxid(2, 1), 1, 1, ServerState.LOOKING)); + votes.put(4L, new Vote(4L, ZxidUtils.makeZxid(2, 1), 1, 1, ServerState.LEADING)); + + Assert.assertTrue(fle.termPredicate(votes, + new Vote(4L, ZxidUtils.makeZxid(2, 1), 1, 1, ServerState.LOOKING))); + } + + @Test + public void testLookingDiffRounds() { + MockFLE fle = new MockFLE(peer, peer.createCnxnManager()); + + HashMap votes = new HashMap(); + votes.put(0L, new Vote(4L, ZxidUtils.makeZxid(1, 1), 1, 1, ServerState.LOOKING)); + votes.put(1L, new Vote(4L, ZxidUtils.makeZxid(2, 1), 2, 2, ServerState.LOOKING)); + votes.put(3L, new Vote(4L, ZxidUtils.makeZxid(2, 1), 3, 2, ServerState.LOOKING)); + votes.put(4L, new Vote(4L, ZxidUtils.makeZxid(2, 1), 3, 2, ServerState.LEADING)); + + Assert.assertFalse(fle.termPredicate(votes, + new Vote(4L, ZxidUtils.makeZxid(2, 1), 2, 2, ServerState.LOOKING))); + } + + + /** + * Helper method to build notifications and populate outofelection. + * + * + * @param version + * @param leader + * @param zxid + * @param electionEpoch + * @param state + * @param sid + * @param peerEpoch + * @param outofelection + * @return + */ + FastLeaderElection.Notification genNotification(int version, + long leader, + long zxid, + long electionEpoch, + ServerState state, + long sid, + long peerEpoch, + HashMap outofelection) { + FastLeaderElection.Notification n = new FastLeaderElection.Notification(); + n.version = version; + n.leader = leader; + n.zxid = zxid; + n.electionEpoch = electionEpoch; + n.state = state; + n.sid = sid; + n.peerEpoch = peerEpoch; + + outofelection.put(n.sid, new Vote(n.version, + n.leader, + n.zxid, + n.electionEpoch, + n.peerEpoch, + n.state)); + + return n; + } + + @Test + public void testOutofElection() { + MockFLE fle = new MockFLE(peer, peer.createCnxnManager()); + HashMap outofelection = new HashMap(); + + /* + * Generates notifications emulating servers 1, 2, 4, and 5. + * Server 5 is the elected leader. + */ + + genNotification( 0x0, + 5, + ZxidUtils.makeZxid(15, 0), + 0xa, + ServerState.FOLLOWING, + 1, + 0x17, + outofelection); + + genNotification( 0x0, + 5, + ZxidUtils.makeZxid(15, 0), + 0xa, + ServerState.FOLLOWING, + 2, + 0x17, + outofelection); + + genNotification( 0x1, + 5, + ZxidUtils.makeZxid(15, 0), + 0xa, + ServerState.FOLLOWING, + 4, + 0x18, + outofelection); + + FastLeaderElection.Notification n = genNotification( 0x1, + 5, + ZxidUtils.makeZxid(15, 0), + 0xa, + ServerState.LEADING, + 5, + 0x18, + outofelection); + + /* + * fle represents the FLE instance of server 3.Here we set + * its logical clock to 1. + */ + fle.logicalclock.set(0x1); + + + /* + * Here we test the predicates we use in FLE. + */ + Assert.assertTrue("Termination predicate failed", + fle.termPredicate(outofelection, + new Vote(n.version, + n.leader, + n.zxid, + n.electionEpoch, + n.peerEpoch, + n.state))); + Assert.assertTrue("Leader check failed", + fle.checkLeader(outofelection, + n.leader, + n.electionEpoch)); + + Assert.assertTrue("Out of election predicate failed", + fle.ooePredicate( outofelection, outofelection, n )); + + } +} diff --git a/src/java/test/org/apache/zookeeper/test/FLELostMessageTest.java b/src/java/test/org/apache/zookeeper/server/quorum/FLELostMessageTest.java similarity index 56% rename from src/java/test/org/apache/zookeeper/test/FLELostMessageTest.java rename to src/java/test/org/apache/zookeeper/server/quorum/FLELostMessageTest.java index 98f8e10a872..190785c4217 100644 --- a/src/java/test/org/apache/zookeeper/test/FLELostMessageTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/FLELostMessageTest.java @@ -16,12 +16,11 @@ * limitations under the License. */ -package org.apache.zookeeper.test; +package org.apache.zookeeper.server.quorum; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.ByteBuffer; import java.util.HashMap; import org.slf4j.Logger; @@ -31,9 +30,9 @@ import org.apache.zookeeper.server.quorum.FastLeaderElection; import org.apache.zookeeper.server.quorum.QuorumCnxManager; import org.apache.zookeeper.server.quorum.QuorumPeer; -import org.apache.zookeeper.server.quorum.Vote; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; +import org.apache.zookeeper.test.ClientBase; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -42,14 +41,14 @@ public class FLELostMessageTest extends ZKTestCase { protected static final Logger LOG = LoggerFactory.getLogger(FLELostMessageTest.class); - + int count; HashMap peers; File tmpdir[]; int port[]; - + QuorumCnxManager cnxManager; - + @Before public void setUp() throws Exception { count = 3; @@ -64,69 +63,29 @@ public void tearDown() throws Exception { cnxManager.halt(); } - - class LEThread extends Thread { - int i; - QuorumPeer peer; - - LEThread(QuorumPeer peer, int i) { - this.i = i; - this.peer = peer; - LOG.info("Constructor: " + getName()); - - } - - public void run(){ - boolean flag = true; - try{ - Vote v = null; - peer.setPeerState(ServerState.LOOKING); - LOG.info("Going to call leader election: " + i); - v = peer.getElectionAlg().lookForLeader(); - - if (v == null){ - Assert.fail("Thread " + i + " got a null vote"); - } - - /* - * A real zookeeper would take care of setting the current vote. Here - * we do it manually. - */ - peer.setCurrentVote(v); - - LOG.info("Finished election: " + i + ", " + v.getId()); - - Assert.assertTrue("State is not leading.", peer.getPeerState() == ServerState.LEADING); - } catch (Exception e) { - e.printStackTrace(); - } - LOG.info("Joining"); - } - } @Test public void testLostMessage() throws Exception { FastLeaderElection le[] = new FastLeaderElection[count]; - + LOG.info("TestLE: " + getTestName()+ ", " + count); for(int i = 0; i < count; i++) { int clientport = PortAssignment.unique(); peers.put(Long.valueOf(i), - new QuorumServer(i, - new InetSocketAddress(clientport), - new InetSocketAddress(PortAssignment.unique()))); + new QuorumServer(i, "0.0.0.0", clientport, + PortAssignment.unique(), null)); tmpdir[i] = ClientBase.createTmpDir(); port[i] = clientport; } - + /* * Start server 0 */ - + QuorumPeer peer = new QuorumPeer(peers, tmpdir[1], tmpdir[1], port[1], 3, 1, 1000, 2, 2); peer.startLeaderElection(); - LEThread thread = new LEThread(peer, 1); + FLETestUtils.LEThread thread = new FLETestUtils.LEThread(peer, 1); thread.start(); - + /* * Start mock server 1 */ @@ -136,39 +95,19 @@ public void testLostMessage() throws Exception { Assert.fail("Threads didn't join"); } } - - ByteBuffer createMsg(int state, long leader, long zxid, long epoch){ - byte requestBytes[] = new byte[28]; - ByteBuffer requestBuffer = ByteBuffer.wrap(requestBytes); - - /* - * Building notification packet to send - */ - - requestBuffer.clear(); - requestBuffer.putInt(state); - requestBuffer.putLong(leader); - requestBuffer.putLong(zxid); - requestBuffer.putLong(epoch); - - return requestBuffer; - } - + void mockServer() throws InterruptedException, IOException { /* * Create an instance of the connection manager */ QuorumPeer peer = new QuorumPeer(peers, tmpdir[0], tmpdir[0], port[0], 3, 0, 1000, 2, 2); - cnxManager = new QuorumCnxManager(peer); + cnxManager = peer.createCnxnManager(); QuorumCnxManager.Listener listener = cnxManager.listener; - if(listener != null){ - listener.start(); - } else { - LOG.error("Null listener when initializing cnx manager"); - } - - cnxManager.toSend(new Long(1), createMsg(ServerState.LOOKING.ordinal(), 0, 0, 1)); + listener.start(); + + + cnxManager.toSend(1l, FLETestUtils.createMsg(ServerState.LOOKING.ordinal(), 0, 0, 0)); cnxManager.recvQueue.take(); - cnxManager.toSend(new Long(1), createMsg(ServerState.FOLLOWING.ordinal(), 1, 0, 1)); + cnxManager.toSend(1L, FLETestUtils.createMsg(ServerState.FOLLOWING.ordinal(), 1, 0, 0)); } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/FLETestUtils.java b/src/java/test/org/apache/zookeeper/server/quorum/FLETestUtils.java new file mode 100644 index 00000000000..1f0bca3d977 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/FLETestUtils.java @@ -0,0 +1,86 @@ +/* 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 java.nio.ByteBuffer; + +import org.apache.zookeeper.server.quorum.FastLeaderElection; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.Vote; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.junit.Assert; + +import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; + +public class FLETestUtils { + protected static final Logger LOG = LoggerFactory.getLogger(FLETestUtils.class); + + + /* + * Thread to run an instance of leader election for + * a given quorum peer. + */ + static class LEThread extends Thread { + private int i; + private QuorumPeer peer; + + LEThread(QuorumPeer peer, int i) { + this.i = i; + this.peer = peer; + LOG.info("Constructor: " + getName()); + + } + + public void run(){ + try{ + Vote v = null; + peer.setPeerState(ServerState.LOOKING); + LOG.info("Going to call leader election: " + i); + v = peer.getElectionAlg().lookForLeader(); + + if (v == null){ + Assert.fail("Thread " + i + " got a null vote"); + } + + /* + * A real zookeeper would take care of setting the current vote. Here + * we do it manually. + */ + peer.setCurrentVote(v); + + LOG.info("Finished election: " + i + ", " + v.getId()); + + Assert.assertTrue("State is not leading.", peer.getPeerState() == ServerState.LEADING); + } catch (Exception e) { + e.printStackTrace(); + } + LOG.info("Joining"); + } + } + + /* + * Creates a leader election notification message. + */ + + static ByteBuffer createMsg(int state, long leader, long zxid, long epoch){ + return FastLeaderElection.buildMsg(state, leader, zxid, 1, epoch); + } + +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/server/quorum/LearnerTest.java b/src/java/test/org/apache/zookeeper/server/quorum/LearnerTest.java index 7419510030a..fd08d21339d 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/LearnerTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/LearnerTest.java @@ -47,11 +47,14 @@ import org.junit.Test; public class LearnerTest extends ZKTestCase { + private static final File testData = new File( + System.getProperty("test.data.dir", "build/test/data")); + class SimpleLearnerZooKeeperServer extends LearnerZooKeeperServer { boolean startupCalled; - public SimpleLearnerZooKeeperServer(FileTxnSnapLog ftsl) throws IOException { - super(ftsl, 2000, 2000, 2000, null, new ZKDatabase(ftsl), null); + public SimpleLearnerZooKeeperServer(FileTxnSnapLog ftsl, QuorumPeer self) throws IOException { + super(ftsl, 2000, 2000, 2000, null, new ZKDatabase(ftsl), self); } Learner learner; @Override @@ -66,8 +69,8 @@ public void startup() { } class SimpleLearner extends Learner { SimpleLearner(FileTxnSnapLog ftsl) throws IOException { - self = new QuorumPeer(); - zk = new SimpleLearnerZooKeeperServer(ftsl); + self = QuorumPeer.testingQuorumPeer(); + zk = new SimpleLearnerZooKeeperServer(ftsl, self); ((SimpleLearnerZooKeeperServer)zk).learner = this; } } @@ -84,7 +87,7 @@ static private void recursiveDelete(File dir) { } @Test public void syncTest() throws Exception { - File tmpFile = File.createTempFile("test", ".dir"); + File tmpFile = File.createTempFile("test", ".dir", testData); tmpFile.delete(); try { FileTxnSnapLog ftsl = new FileTxnSnapLog(tmpFile, tmpFile); diff --git a/src/java/test/org/apache/zookeeper/server/quorum/QuorumCnxManagerTest.java b/src/java/test/org/apache/zookeeper/server/quorum/QuorumCnxManagerTest.java new file mode 100644 index 00000000000..9917cfc7388 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/QuorumCnxManagerTest.java @@ -0,0 +1,967 @@ +/** + * 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 java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import javax.security.sasl.SaslException; + +import org.apache.jute.BinaryOutputArchive; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.server.quorum.auth.NullQuorumAuthLearner; +import org.apache.zookeeper.server.quorum.auth.NullQuorumAuthServer; +import org.apache.zookeeper.server.quorum.auth.QuorumAuth; +import org.apache.zookeeper.server.quorum.auth.QuorumAuthLearner; +import org.apache.zookeeper.server.quorum.auth.QuorumAuthServer; +import org.apache.zookeeper.server.quorum.auth.SaslQuorumAuthLearner; +import org.apache.zookeeper.server.quorum.auth.SaslQuorumAuthServer; +import org.apache.zookeeper.server.quorum.flexible.QuorumMaj; +import org.apache.zookeeper.test.ClientBase; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +public class QuorumCnxManagerTest extends ZKTestCase { + private static final Logger LOG = LoggerFactory.getLogger(QuorumCnxManagerTest.class); + private int count; + private HashMap peers; + private int peerQuorumPort[]; + private int peerClientPort[]; + private ThreadPoolExecutor executor; + /** + * The maximum number of threads to allow in the connectionExecutors thread + * pool which will be used to initiate quorum server connections. Defaulting to 20. + * TODO: Need to tune this param. + */ + private final int quorumCnxnThreadsSize = 20; + private Set authzHosts; + + private static File saslConfigFile = null; + + @BeforeClass + public static void setupSasl() throws Exception { + String jaasEntries = new String("" + + "QuorumServer {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " user_test=\"mypassword\";\n" + + "};\n" + + "QuorumLearner {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"test\"\n" + + " password=\"mypassword\";\n" + + "};\n" + + "QuorumLearnerInvalid {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"test\"\n" + + " password=\"invalid\";\n" + + "};\n"); + + saslConfigFile = File.createTempFile("jaas.", ".conf"); + FileWriter fwriter = new FileWriter(saslConfigFile); + fwriter.write(jaasEntries); + fwriter.close(); + System.setProperty("java.security.auth.login.config", + saslConfigFile.getAbsolutePath()); + } + + @AfterClass + public static void cleanupSasl() throws Exception { + if (saslConfigFile != null) { + saslConfigFile.delete(); + } + } + + @Before + public void setUp() throws Exception { + this.count = 3; + this.peers = new HashMap(count); + peerQuorumPort = new int[count]; + peerClientPort = new int[count]; + authzHosts = new HashSet(); + for(int i = 0; i < count; i++) { + peerQuorumPort[i] = PortAssignment.unique(); + peerClientPort[i] = PortAssignment.unique(); + QuorumServer qs = new QuorumServer(i, "0.0.0.0", + peerQuorumPort[i], PortAssignment.unique(), null); + peers.put(Long.valueOf(i), qs); + authzHosts.add(qs.hostname); + } + executor = new ThreadPoolExecutor(3, 10, + 60, TimeUnit.SECONDS, new SynchronousQueue()); + } + + @After + public void tearDown() throws Exception { + if (executor != null) { + executor.shutdownNow(); + } + } + + @Test(timeout = 30000) + public void testNoAuthConnection() throws Exception { + QuorumCnxManager peer0 = createAndStartManager(0); + QuorumCnxManager peer1 = createAndStartManager(1); + + peer0.connectOne(1); + assertEventuallyConnected(peer0, 1); + } + + @Test(timeout = 30000) + public void testAuthConnection() throws Exception { + QuorumCnxManager peer0 = createAndStartManager(0, "QuorumServer", + "QuorumLearner", true, true); + QuorumCnxManager peer1 = createAndStartManager(1, "QuorumServer", + "QuorumLearner", true, true); + peer0.connectOne(1); + assertEventuallyConnected(peer0, 1); + } + + /** + * Peer0 has no auth configured, Peer1 has auth configured. + * Peer1 connects to peer0, because null auth server sees an auth packet and connection succeeds. + * Peer0 connects to peer1, but connection isn't initiated because + * peer0's sid is lower than peer1's + */ + @Test(timeout = 30000) + public void testClientAuthAgainstNoAuthServerWithLowerSid() + throws Exception { + QuorumCnxManager peer0 = createAndStartManager(0); + QuorumCnxManager peer1 = createAndStartManager(1, "QuorumServer", + "QuorumLearner", false, false); + peer1.connectOne(0); + peer0.connectOne(1); + assertEventuallyConnected(peer0, 1); + } + + /** + * Peer0 has auth configured, Peer1 has no auth configured. + * Peer0 connects to peer1, but is disconnected, because peer1's sid is + * higher than peer0. + * Peer1 connects to peer0, but is disconnected, because peer1 cannot + * handle auth. + */ + @Test(timeout = 30000) + public void testClientAuthAgainstNoAuthServerWithHigherSid() + throws Exception { + QuorumCnxManager peer0 = createAndStartManager(0, "QuorumServer", + "QuorumLearner", false, false); + QuorumCnxManager peer1 = createAndStartManager(1); + peer0.connectOne(1); + peer1.connectOne(0); + assertEventuallyConnected(peer0, 1); + } + + /** + * No auth learner connects to a server that requires auth, when the server + * has a higher sid. + * The connection should fail in both directions. + */ + @Test(timeout = 30000) + public void testNoAuthLearnerConnectToAuthRequiredServerWithLowerSid() + throws Exception { + QuorumCnxManager peer0 = createAndStartManager(0, "QuorumServer", + "QuorumLearner", true, true); + QuorumCnxManager peer1 = createAndStartManager(1); + peer0.connectOne(1); + peer1.connectOne(0); + assertEventuallyNotConnected(peer0, 1); + } + + /** + * No auth learner connects to a server that requires auth, when the server + * has a higher sid. + * The connection should fail in both directions. + * + * peer0 should attempt to connect to peer1, but disconnect as its sid is lower + * peer1 should attempt to connect to peer0, peer0 will accept and add an entry to + * the senderWorkerMap but peer1 will disconnect because peer1 will start speaking SASL + * and peer0 will consider this invalid. + * + * Due to the unique behavior of peer0 creating an entry + * in senderWorkerMap for peer1 and then deleting it we use mockito spies to track + * this behavior. + */ + @Test(timeout = 30000) + public void testNoAuthLearnerConnectToAuthRequiredServerWithHigherSid() + throws Exception { + ConcurrentHashMap senderWorkerMap0 = + spy(new ConcurrentHashMap()); + ConcurrentHashMap senderWorkerMap1 = + spy(new ConcurrentHashMap()); + + QuorumCnxManager peer0 = createAndStartManager(0, senderWorkerMap0); + QuorumCnxManager peer1 = createAndStartManager(1, "QuorumServer", "QuorumLearner", + true, true, senderWorkerMap1); + peer0.connectOne(1); + peer1.connectOne(0); + + verify(senderWorkerMap0, timeout(10000)).put(eq(1L), any(QuorumCnxManager.SendWorker.class)); + verify(senderWorkerMap0, timeout(10000)).remove(eq(1L), any(QuorumCnxManager.SendWorker.class)); + + verify(senderWorkerMap1, never()).put(anyLong(), any(QuorumCnxManager.SendWorker.class)); + } + + /** + * An auth learner connects to a auth server, but the credentials are bad. + * The peer with the higher sid has the bad credentials. + * The connection will be denied. + */ + @Test(timeout = 30000) + public void testAuthLearnerBadCredToAuthRequiredServerWithLowerSid() + throws Exception { + QuorumCnxManager peer0 = createAndStartManager(0, "QuorumServer", + "QuorumLearner", true, true); + QuorumCnxManager peer1 = createAndStartManager(1, "QuorumServer", + "QuorumLearnerInvalid", true, true); + peer0.connectOne(1); + peer1.connectOne(0); + + assertEventuallyNotConnected(peer0, 1); + } + + /** + * An auth learner connects to a auth server, but the credentials are bad. + * The peer with the lower sid has the bad credentials. + * The connection will work, because peer1 is connecting to peer0. + */ + @Test(timeout = 30000) + public void testAuthLearnerBadCredToAuthRequiredServerWithHigherSid() + throws Exception { + QuorumCnxManager peer0 = createAndStartManager(0, "QuorumServer", + "QuorumLearnerInvalid", true, true); + QuorumCnxManager peer1 = createAndStartManager(1, "QuorumServer", + "QuorumLearner", true, true); + peer0.connectOne(1); + peer1.connectOne(0); + assertEventuallyConnected(peer0, 1); + assertEventuallyConnected(peer1, 0); + } + + /** + * An auth learner connects to a auth server, but the credentials are bad. + * The connection should fail in both directions. + */ + @Test(timeout = 30000) + public void testAuthLearnerBadCredToNoAuthServerWithHigherSid() throws Exception { + QuorumCnxManager peer0 = createAndStartManager(0, "QuorumServer", + "QuorumLearner", false, false); + QuorumCnxManager peer1 = createAndStartManager(1, "QuorumServer", + "QuorumLearnerInvalid", true, true); + peer1.connectOne(0); + assertEventuallyNotConnected(peer1, 0); + } + + /** + * An auth learner connects to a auth server, but the credentials are bad. + * The peer with the lower sid has the bad credentials. + * The connection will work, because peer0 is connecting to peer1 and peer1 + * server doesn't require sasl + */ + @Test(timeout = 30000) + public void testAuthLearnerBadCredToNoAuthServerWithLowerSid() throws Exception { + QuorumCnxManager peer0 = createAndStartManager(0, "QuorumServer", + "QuorumLearnerInvalid", true, true); + QuorumCnxManager peer1 = createAndStartManager(1, "QuorumServer", + "QuorumLearner", false, true); + peer0.connectOne(1); + assertEventuallyConnected(peer0, 1); + assertEventuallyConnected(peer1, 0); + } + + /** + * Test verifies that the LearnerHandler should authenticate the connecting + * quorumpeer. Here its simulating authentication failure and it should throw + * SaslException + */ + @Test(timeout = 30000) + public void testLearnerHandlerAuthFailed() throws Exception { + File testData = ClientBase.createTmpDir(); + Socket leaderSocket = getSocketPair(); + File tmpDir = File.createTempFile("test", ".dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + QuorumPeer peer = createQuorumPeer(tmpDir, true, false, true, + "QuorumLearner", "QuorumServer", + QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE); + leader = createLeader(tmpDir, peer); + peer.leader = leader; + + // authentication failed as qpserver didn't get auth packet from qpclient. + try { + new LearnerHandler(leaderSocket, + new BufferedInputStream(leaderSocket.getInputStream()), leader); + Assert.fail("Must throw exception as there is an authentication failure"); + } catch (SaslException e){ + Assert.assertEquals("Mistakely added to learners", 0, + leader.getLearners().size()); + } + ClientBase.recursiveDelete(testData); + } + + /** + * Test verifies that the Leader should authenticate the connecting learner + * quorumpeer. After the successful authentication it should add this + * learner to the learnerHandler list. + */ + @Test(timeout = 30000) + public void testAuthLearnerConnectsToServerWithAuthRequired() + throws Exception { + File testDataLearner = ClientBase.createTmpDir(); + File tmpDir = File.createTempFile("test", ".dir", testDataLearner); + tmpDir.delete(); + FileTxnSnapLog ftsl = new FileTxnSnapLog(tmpDir, tmpDir); + QuorumPeer learnerPeer = createQuorumPeer(tmpDir, true, true, true, + "QuorumLearner", "QuorumServer", + QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE); + SimpleLearner sl = new SimpleLearner(ftsl, learnerPeer); + + File testDataLeader = ClientBase.createTmpDir(); + tmpDir = File.createTempFile("test", ".dir", testDataLeader); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + QuorumPeer peer = createQuorumPeer(tmpDir, true, true, true, "QuorumLearner", + "QuorumServer", + QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE); + CountDownLatch learnerLatch = new CountDownLatch(1); + leader = createSimpleLeader(tmpDir, peer, learnerLatch); + peer.leader = leader; + + startLearnerCnxAcceptorThread(leader); + LOG.info("Start establishing a connection with the Leader"); + String hostname = getLeaderHostname(peer); + sl.connectToLeader(peer.getQuorumAddress(), hostname); + // wait till leader socket soTimeout period + Assert.assertTrue("Leader should accept the auth learner connection", + learnerLatch.await(leader.self.tickTime * leader.self.initLimit + 1000, + TimeUnit.MILLISECONDS)); + Assert.assertEquals("Failed to added the learner", 1, + leader.getLearners().size()); + ClientBase.recursiveDelete(testDataLearner); + ClientBase.recursiveDelete(testDataLeader); + } + + private String getLeaderHostname(QuorumPeer peer) { + String hostname = null; + for (QuorumServer p : peer.getView().values()) { + if (p.id == peer.getId()) { + hostname = p.hostname; + break; + } + } + Assert.assertNotNull("Didn't find leader", hostname); + return hostname; + } + + /** + * Test verifies that the Leader should authenticate the connecting learner + * quorumpeer. After the successful authentication it should add this + * learner to the learnerHandler list. + */ + @Test(timeout = 30000) + public void testAuthLearnerConnectsToServerWithAuthNotRequired() + throws Exception { + File testDataLearner = ClientBase.createTmpDir(); + File tmpDir = File.createTempFile("test", ".dir", testDataLearner); + tmpDir.delete(); + FileTxnSnapLog ftsl = new FileTxnSnapLog(tmpDir, tmpDir); + QuorumPeer learnerPeer = createQuorumPeer(tmpDir, true, true, true, + "QuorumLearner", "QuorumServer", + QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE); + SimpleLearner sl = new SimpleLearner(ftsl, learnerPeer); + + File testDataLeader = ClientBase.createTmpDir(); + tmpDir = File.createTempFile("test", ".dir", testDataLeader); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + QuorumPeer peer = createQuorumPeer(tmpDir, true, true, false, "QuorumLearner", + "QuorumServer", + QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE); + CountDownLatch learnerLatch = new CountDownLatch(1); + leader = createSimpleLeader(tmpDir, peer, learnerLatch); + peer.leader = leader; + + startLearnerCnxAcceptorThread(leader); + LOG.info("Start establishing a connection with the Leader"); + String hostname = getLeaderHostname(peer); + sl.connectToLeader(peer.getQuorumAddress(), hostname); + // wait till leader socket soTimeout period + Assert.assertTrue("Leader should accept the auth learner connection", + learnerLatch.await(leader.self.tickTime * leader.self.initLimit + 1000, + TimeUnit.MILLISECONDS)); + Assert.assertEquals("Failed to added the learner", 1, + leader.getLearners().size()); + ClientBase.recursiveDelete(testDataLearner); + ClientBase.recursiveDelete(testDataLeader); + } + + private void startLearnerCnxAcceptorThread(Leader leader) + throws InterruptedException { + final CountDownLatch cnxAcceptorWatcher = new CountDownLatch(1); + leader.cnxAcceptor = leader.new LearnerCnxAcceptor(){ + @Override + public void run() { + cnxAcceptorWatcher.countDown(); + super.run(); + } + }; + leader.cnxAcceptor.start(); + // waiting to start the thread + Assert.assertTrue("Failed to start leader.cnxAcceptor thread!", + cnxAcceptorWatcher.await(15, TimeUnit.SECONDS)); + LOG.info("Started leader.cnxAcceptor:{} thread, state:{}", + leader.cnxAcceptor.getName(), leader.cnxAcceptor.getState()); + } + + /** + * Test verifies that the Auth enabled Learner is connecting to a Null Auth + * Leader server. Learner is failing to get an auth response from Null Auth + * Leader and fails the connection establishment. + */ + @Test(timeout = 30000) + public void testAuthLearnerConnectsToNullAuthServer() + throws Exception { + File testDataLearner = ClientBase.createTmpDir(); + File tmpDir = File.createTempFile("test", ".dir", testDataLearner); + tmpDir.delete(); + FileTxnSnapLog ftsl = new FileTxnSnapLog(tmpDir, tmpDir); + QuorumPeer learnerPeer = createQuorumPeer(tmpDir, true, true, true, + "QuorumLearner", "QuorumServer", + QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE); + SimpleLearner sl = new SimpleLearner(ftsl, learnerPeer); + + File testDataLeader = ClientBase.createTmpDir(); + tmpDir = File.createTempFile("test", ".dir", testDataLeader); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + QuorumPeer peer = createQuorumPeer(tmpDir, false, false, false, + "QuorumLearner", "QuorumServer", + QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE); + CountDownLatch learnerLatch = new CountDownLatch(1); + leader = createSimpleLeader(tmpDir, peer, learnerLatch); + peer.leader = leader; + + startLearnerCnxAcceptorThread(leader); + LOG.info("Start establishing a connection with the Leader"); + + try { + String hostname = getLeaderHostname(peer); + sl.connectToLeader(peer.getQuorumAddress(), hostname); + Assert.fail("Must throw exception as server doesn't supports authentication"); + } catch (IOException e) { + // expected + Assert.assertTrue("Leader should accept the auth learner connection", + learnerLatch.await(leader.self.tickTime * leader.self.initLimit + 500, + TimeUnit.MILLISECONDS)); + } + + ClientBase.recursiveDelete(testDataLearner); + ClientBase.recursiveDelete(testDataLeader); + } + + /** + * Test verifies that the No Auth enabled Learner is connecting to a No Auth + * Leader server. Learner should be able to establish a connection with + * Leader as auth is not required. + */ + @Test(timeout = 30000) + public void testNoAuthLearnerConnectsToServerWithAuthNotRequired() + throws Exception { + File testDataLearner = ClientBase.createTmpDir(); + File tmpDir = File.createTempFile("test", ".dir", testDataLearner); + tmpDir.delete(); + FileTxnSnapLog ftsl = new FileTxnSnapLog(tmpDir, tmpDir); + QuorumPeer learnerPeer = createQuorumPeer(tmpDir, true, false, false, + "QuorumLearner", "QuorumServer", ""); + SimpleLearner sl = new SimpleLearner(ftsl, learnerPeer); + + File testDataLeader = ClientBase.createTmpDir(); + tmpDir = File.createTempFile("test", ".dir", testDataLeader); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + QuorumPeer peer = createQuorumPeer(tmpDir, true, false, false, "QuorumLearner", + "QuorumServer", + QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE); + CountDownLatch learnerLatch = new CountDownLatch(1); + leader = createSimpleLeader(tmpDir, peer, learnerLatch); + peer.leader = leader; + + startLearnerCnxAcceptorThread(leader); + LOG.info("Start establishing a connection with the Leader"); + String hostname = getLeaderHostname(peer); + sl.connectToLeader(peer.getQuorumAddress(), hostname); + + Assert.assertTrue("Leader should accept no auth learner connection", + learnerLatch.await(leader.self.tickTime * leader.self.initLimit + 1000, + TimeUnit.MILLISECONDS)); + ClientBase.recursiveDelete(testDataLearner); + ClientBase.recursiveDelete(testDataLeader); + } + + /** + * Test verifies that the No Auth enabled Learner is connecting to a No Auth + * Leader server. Learner shouldn't be able to establish a connection with + * Leader as auth as auth is required. + */ + @Test(timeout = 30000) + public void testNoAuthLearnerConnectsToServerWithAuthRequired() + throws Exception { + File testDataLearner = ClientBase.createTmpDir(); + File tmpDir = File.createTempFile("test", ".dir", testDataLearner); + tmpDir.delete(); + FileTxnSnapLog ftsl = new FileTxnSnapLog(tmpDir, tmpDir); + QuorumPeer learnerPeer = createQuorumPeer(tmpDir, true, false, false, + "QuorumLearner", "QuorumServer", ""); + SimpleLearner sl = new SimpleLearner(ftsl, learnerPeer); + + File testDataLeader = ClientBase.createTmpDir(); + tmpDir = File.createTempFile("test", ".dir", testDataLeader); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + QuorumPeer peer = createQuorumPeer(tmpDir, true, true, true, "QuorumLearner", + "QuorumServer", + QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE); + CountDownLatch learnerLatch = new CountDownLatch(1); + leader = createSimpleLeader(tmpDir, peer, learnerLatch); + peer.leader = leader; + + startLearnerCnxAcceptorThread(leader); + LOG.info("Start establishing a connection with the Leader"); + String hostname = getLeaderHostname(peer); + sl.connectToLeader(peer.getQuorumAddress(), hostname); + Assert.assertFalse("Leader shouldn't accept no auth learner connection", + learnerLatch.await(leader.self.tickTime * leader.self.initLimit + 1000, + TimeUnit.MILLISECONDS)); + ClientBase.recursiveDelete(testDataLearner); + ClientBase.recursiveDelete(testDataLeader); + } + + /** + * Test verifies that the No Auth enabled Learner is connecting to a No Auth + * Leader server. Learner should be able to establish a connection with + * Leader as auth is not required. + */ + @Test(timeout = 30000) + public void testNoAuthLearnerConnectsToNullAuthServer() + throws Exception { + File testDataLearner = ClientBase.createTmpDir(); + File tmpDir = File.createTempFile("test", ".dir", testDataLearner); + tmpDir.delete(); + FileTxnSnapLog ftsl = new FileTxnSnapLog(tmpDir, tmpDir); + QuorumPeer learnerPeer = createQuorumPeer(tmpDir, true, false, false, + "QuorumLearner", "QuorumServer", ""); + SimpleLearner sl = new SimpleLearner(ftsl, learnerPeer); + + File testDataLeader = ClientBase.createTmpDir(); + tmpDir = File.createTempFile("test", ".dir", testDataLeader); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + QuorumPeer peer = createQuorumPeer(tmpDir, false, false, false, "", "", + ""); + CountDownLatch learnerLatch = new CountDownLatch(1); + leader = createSimpleLeader(tmpDir, peer, learnerLatch); + peer.leader = leader; + + startLearnerCnxAcceptorThread(leader); + LOG.info("Start establishing a connection with the Leader"); + String hostname = getLeaderHostname(peer); + sl.connectToLeader(peer.getQuorumAddress(), hostname); + + Assert.assertTrue("Leader should accept no auth learner connection", + learnerLatch.await(leader.self.tickTime * leader.self.initLimit + 1000, + TimeUnit.MILLISECONDS)); + ClientBase.recursiveDelete(testDataLearner); + ClientBase.recursiveDelete(testDataLeader); + } + + /** + * SaslQuorumAuthServer throws exception on receiving an invalid quorum + * auth packet. + */ + @Test(timeout = 30000) + public void testSaslQuorumAuthServerWithInvalidQuorumAuthPacket() + throws Exception { + Socket socket = getSocketPair(); + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + BufferedOutputStream bufferedOutput = new BufferedOutputStream(dout); + BinaryOutputArchive boa = BinaryOutputArchive + .getArchive(bufferedOutput); + QuorumAuthPacket authPacket = QuorumAuth + .createPacket(QuorumAuth.Status.IN_PROGRESS, null); + authPacket.setMagic(Long.MAX_VALUE); // invalid magic number + boa.writeRecord(authPacket, null); + bufferedOutput.flush(); + QuorumAuthServer authServer = new SaslQuorumAuthServer(true, + "QuorumServer", authzHosts); + BufferedInputStream is = new BufferedInputStream( + socket.getInputStream()); + try { + authServer.authenticate(socket, new DataInputStream(is)); + Assert.fail("Must throw exception as QuorumAuthPacket is invalid"); + } catch (SaslException e) { + // expected + } + } + + /** + * NullQuorumAuthServer should return true when no auth quorum packet + * received and timed out. + */ + @Test(timeout = 30000) + public void testNullQuorumAuthServerShouldReturnTrue() + throws Exception { + Socket socket = getSocketPair(); + QuorumAuthServer authServer = new NullQuorumAuthServer(); + BufferedInputStream is = new BufferedInputStream( + socket.getInputStream()); + // It will throw exception and fail the + // test if any unexpected error. Not adding any extra assertion. + authServer.authenticate(socket, new DataInputStream(is)); + } + + /** + * NullQuorumAuthServer should return true on receiving a valid quorum auth + * packet. + */ + @Test(timeout = 30000) + public void testNullQuorumAuthServerWithValidQuorumAuthPacket() + throws Exception { + Socket socket = getSocketPair(); + DataOutputStream dout = new DataOutputStream(socket.getOutputStream()); + BufferedOutputStream bufferedOutput = new BufferedOutputStream(dout); + BinaryOutputArchive boa = BinaryOutputArchive + .getArchive(bufferedOutput); + QuorumAuthPacket authPacket = QuorumAuth + .createPacket(QuorumAuth.Status.IN_PROGRESS, null); + boa.writeRecord(authPacket, null); + bufferedOutput.flush(); + QuorumAuthServer authServer = new NullQuorumAuthServer(); + BufferedInputStream is = new BufferedInputStream( + socket.getInputStream()); + // It will throw exception and fail the + // test if any unexpected error. Not adding any extra assertion. + authServer.authenticate(socket, new DataInputStream(is)); + } + + private QuorumCnxManager createAndStartManager(long sid) { + return createAndStartManager(sid, new ConcurrentHashMap()); + } + + private QuorumCnxManager createAndStartManager(long sid, ConcurrentHashMap senderWorkerMap) { + QuorumCnxManager peer = new QuorumCnxManager(sid, peers, + new NullQuorumAuthServer(), new NullQuorumAuthLearner(), 10000, + false, quorumCnxnThreadsSize, false, + senderWorkerMap); + executor.submit(peer.listener); + InetSocketAddress electionAddr = peer.view.get(sid).electionAddr; + waitForElectionAddrBinding(electionAddr, 15); + return peer; + } + + private QuorumCnxManager createAndStartManager(long sid, + String serverLoginContext, + String learnerLoginContext, + boolean serverRequireSasl, + boolean learnerRequireSasl) throws Exception { + return createAndStartManager(sid, serverLoginContext, learnerLoginContext, serverRequireSasl, learnerRequireSasl, new ConcurrentHashMap()); + + } + + private QuorumCnxManager createAndStartManager(long sid, + String serverLoginContext, + String learnerLoginContext, + boolean serverRequireSasl, + boolean learnerRequireSasl, + ConcurrentHashMap senderWorkerMap) + throws Exception { + QuorumAuthLearner authClient = new SaslQuorumAuthLearner(learnerRequireSasl, + "NOT_USING_KRB_PRINCIPAL", learnerLoginContext); + QuorumAuthServer authServer = new SaslQuorumAuthServer(serverRequireSasl, + serverLoginContext, authzHosts); + QuorumCnxManager peer = new QuorumCnxManager(sid, peers, + authServer, authClient, 10000, false, quorumCnxnThreadsSize, true, senderWorkerMap); + executor.submit(peer.listener); + InetSocketAddress electionAddr = peer.view.get(sid).electionAddr; + waitForElectionAddrBinding(electionAddr, 15); + return peer; + } + + private void waitForElectionAddrBinding(InetSocketAddress electionAddr, + int retries) { + boolean success = false; + while (retries > 0) { + Socket sock = new Socket(); + try { + sock.setTcpNoDelay(true); + sock.setSoTimeout(5000); + sock.connect(electionAddr, 5000); + success = true; + } catch (IOException e) { + LOG.error("IOException while checking election addr", e); + } finally { + cleanup(sock); + } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // ignore + } + retries--; + } + Assert.assertTrue("Did not connect to election port", success); + } + + private void cleanup(Socket sock) { + try { + sock.close(); + } catch (IOException ie) { + LOG.error("Exception while closing socket", ie); + } + } + + private void assertEventuallyConnected(QuorumCnxManager peer, long sid) + throws Exception { + for (int i = 0; i < 20 && !peer.connectedToPeer(sid); i++) { + Thread.sleep(1000); + } + Assert.assertTrue("Not connected to peer", peer.connectedToPeer(sid)); + } + + private void assertEventuallyNotConnected(QuorumCnxManager peer, long sid) + throws Exception { + for (int i = 0; i < 3 && !peer.connectedToPeer(sid); i++) { + Thread.sleep(1000); + } + Assert.assertFalse("Connected to peer (shouldn't be)", + peer.connectedToPeer(sid)); + } + + private QuorumPeer createQuorumPeer(File tmpDir, + boolean isQuorumAuthEnabled, boolean isQuorumLearnerAuthRequired, + boolean isQuorumServerAuthRequired, String quorumLearnerLoginContext, + String quorumServerLoginContext, String quorumServicePrincipal) + throws IOException, FileNotFoundException { + QuorumPeer peer = QuorumPeer.testingQuorumPeer(); + peer.syncLimit = 2; + peer.initLimit = 2; + peer.tickTime = 2000; + peer.quorumPeers = new HashMap(); + peer.quorumPeers.put(0L, + new QuorumServer(0, "0.0.0.0", PortAssignment.unique(), null, null)); + peer.quorumPeers.put(1L, + new QuorumServer(1, "0.0.0.0", PortAssignment.unique(), null, null)); + peer.setQuorumVerifier(new QuorumMaj(3)); + peer.setCnxnFactory(new NullServerCnxnFactory()); + // auth + if (isQuorumAuthEnabled) { + peer.authServer = new SaslQuorumAuthServer( + isQuorumServerAuthRequired, quorumServerLoginContext, authzHosts); + peer.authLearner = new SaslQuorumAuthLearner( + isQuorumLearnerAuthRequired, quorumServicePrincipal, + quorumLearnerLoginContext); + } + File version2 = new File(tmpDir, "version-2"); + version2.mkdir(); + FileOutputStream fos; + fos = new FileOutputStream(new File(version2, "currentEpoch")); + fos.write("0\n".getBytes()); + fos.close(); + fos = new FileOutputStream(new File(version2, "acceptedEpoch")); + fos.write("0\n".getBytes()); + fos.close(); + return peer; + } + + private static final class NullServerCnxnFactory extends ServerCnxnFactory { + public void startup(ZooKeeperServer zkServer) + throws IOException, InterruptedException { + } + + public void start() { + } + + public void shutdown() { + } + + public void setMaxClientCnxnsPerHost(int max) { + } + + public void join() throws InterruptedException { + } + + public int getMaxClientCnxnsPerHost() { + return 0; + } + + public int getLocalPort() { + return 0; + } + + public InetSocketAddress getLocalAddress() { + return null; + } + + public Iterable getConnections() { + return null; + } + + public void configure(InetSocketAddress addr, int maxClientCnxns) + throws IOException { + } + + public void closeSession(long sessionId) { + } + + public void closeAll() { + } + + @Override + public int getNumAliveConnections() { + return 0; + } + } + + private static Socket getSocketPair() throws IOException { + ServerSocket ss = new ServerSocket(); + ss.bind(null); + InetSocketAddress endPoint = (InetSocketAddress) ss + .getLocalSocketAddress(); + Socket s = new Socket(endPoint.getAddress(), endPoint.getPort()); + s.setSoTimeout(5000); + return s; + } + + private Leader createLeader(File tmpDir, QuorumPeer peer) throws IOException, + NoSuchFieldException, IllegalAccessException { + LeaderZooKeeperServer zk = prepareLeader(tmpDir, peer); + return new Leader(peer, zk); + } + + private Leader createSimpleLeader(File tmpDir, QuorumPeer peer, + CountDownLatch learnerLatch) throws IOException, + NoSuchFieldException, IllegalAccessException { + LeaderZooKeeperServer zk = prepareLeader(tmpDir, peer); + return new SimpleLeader(peer, zk, learnerLatch); + } + + class SimpleLeader extends Leader { + final CountDownLatch learnerLatch; + + SimpleLeader(QuorumPeer self, LeaderZooKeeperServer zk, + CountDownLatch latch) throws IOException { + super(self, zk); + this.learnerLatch = latch; + } + + @Override + void addLearnerHandler(LearnerHandler learner) { + super.addLearnerHandler(learner); + learnerLatch.countDown(); + } + } + + private LeaderZooKeeperServer prepareLeader(File tmpDir, QuorumPeer peer) + throws IOException, NoSuchFieldException, IllegalAccessException { + FileTxnSnapLog logFactory = new FileTxnSnapLog(tmpDir, tmpDir); + peer.setTxnFactory(logFactory); + Field addrField = peer.getClass().getDeclaredField("myQuorumAddr"); + addrField.setAccessible(true); + addrField.set(peer, new InetSocketAddress(PortAssignment.unique())); + ZKDatabase zkDb = new ZKDatabase(logFactory); + LeaderZooKeeperServer zk = new LeaderZooKeeperServer(logFactory, peer, + new ZooKeeperServer.BasicDataTreeBuilder(), zkDb); + return zk; + } + + class SimpleLearnerZooKeeperServer extends LearnerZooKeeperServer { + boolean startupCalled; + + public SimpleLearnerZooKeeperServer(FileTxnSnapLog ftsl, + QuorumPeer self) throws IOException { + super(ftsl, 2000, 2000, 2000, null, new ZKDatabase(ftsl), self); + } + + Learner learner; + + @Override + public Learner getLearner() { + return learner; + } + + @Override + public void startup() { + startupCalled = true; + } + } + + class SimpleLearner extends Learner { + SimpleLearner(FileTxnSnapLog ftsl, QuorumPeer learner) + throws IOException { + self = learner; + zk = new SimpleLearnerZooKeeperServer(ftsl, self); + ((SimpleLearnerZooKeeperServer) zk).learner = this; + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java index 45a61c79709..e8ba8bb591e 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java @@ -20,14 +20,20 @@ import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileReader; import java.io.LineNumberReader; +import java.io.OutputStreamWriter; import java.io.StringReader; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import org.apache.log4j.Layout; @@ -37,15 +43,16 @@ import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.PortAssignment; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs.OpCode; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper.States; +import org.apache.zookeeper.common.Time; +import org.apache.zookeeper.common.AtomicFileOutputStream; import org.apache.zookeeper.server.quorum.Leader.Proposal; -import org.apache.zookeeper.server.quorum.QuorumPeerTestBase.MainThread; +import org.apache.zookeeper.server.util.ZxidUtils; import org.apache.zookeeper.test.ClientBase; +import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -55,6 +62,25 @@ * */ public class QuorumPeerMainTest extends QuorumPeerTestBase { + protected static final Logger LOG = + Logger.getLogger(QuorumPeerMainTest.class); + + private Servers servers; + private int numServers = 0; + + @After + public void tearDown() throws Exception { + if (servers == null || servers.mt == null) { + LOG.info("No servers to shutdown!"); + return; + } + for (int i = 0; i < numServers; i++) { + if (i < servers.mt.length) { + servers.mt[i].shutdown(); + } + } + } + /** * Verify the ability to start a cluster. */ @@ -86,7 +112,7 @@ public void testQuorum() throws Exception { ZooKeeper zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT_QP1, ClientBase.CONNECTION_TIMEOUT, this); - + waitForOne(zk, States.CONNECTED); zk.create("/foo_q1", "foobar1".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); Assert.assertEquals(new String(zk.getData("/foo_q1", null, null)), "foobar1"); @@ -94,7 +120,7 @@ public void testQuorum() throws Exception { zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT_QP2, ClientBase.CONNECTION_TIMEOUT, this); - + waitForOne(zk, States.CONNECTED); zk.create("/foo_q2", "foobar2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); Assert.assertEquals(new String(zk.getData("/foo_q2", null, null)), "foobar2"); @@ -217,14 +243,14 @@ public void testEarlyLeaderAbandonment() throws Exception { */ @Test public void testHighestZxidJoinLate() throws Exception { - int numServers = 3; - Servers svrs = LaunchServers(numServers); + numServers = 3; + servers = LaunchServers(numServers); String path = "/hzxidtest"; int leader=-1; // find the leader for (int i=0; i < numServers; i++) { - if (svrs.mt[i].main.quorumPeer.leader != null) { + if (servers.mt[i].main.quorumPeer.leader != null) { leader = i; } } @@ -239,47 +265,47 @@ public void testHighestZxidJoinLate() throws Exception { byte[] output; // Create a couple of nodes - svrs.zk[leader].create(path+leader, input, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - svrs.zk[leader].create(path+nonleader, input, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + servers.zk[leader].create(path+leader, input, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + servers.zk[leader].create(path+nonleader, input, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // make sure the updates indeed committed. If it is not // the following statement will throw. - output = svrs.zk[leader].getData(path+nonleader, false, null); + output = servers.zk[leader].getData(path+nonleader, false, null); // Shutdown every one else but the leader for (int i=0; i < numServers; i++) { if (i != leader) { - svrs.mt[i].shutdown(); + servers.mt[i].shutdown(); } } input[0] = 2; // Update the node on the leader - svrs.zk[leader].setData(path+leader, input, -1, null, null); + servers.zk[leader].setData(path+leader, input, -1, null, null); // wait some time to let this get written to disk Thread.sleep(500); // shut the leader down - svrs.mt[leader].shutdown(); + servers.mt[leader].shutdown(); System.gc(); - waitForAll(svrs.zk, States.CONNECTING); + waitForAll(servers.zk, States.CONNECTING); // Start everyone but the leader for (int i=0; i < numServers; i++) { if (i != leader) { - svrs.mt[i].start(); + servers.mt[i].start(); } } // wait to connect to one of these - waitForOne(svrs.zk[nonleader], States.CONNECTED); + waitForOne(servers.zk[nonleader], States.CONNECTED); // validate that the old value is there and not the new one - output = svrs.zk[nonleader].getData(path+leader, false, null); + output = servers.zk[nonleader].getData(path+leader, false, null); Assert.assertEquals( "Expecting old value 1 since 2 isn't committed yet", @@ -287,22 +313,22 @@ public void testHighestZxidJoinLate() throws Exception { // Do some other update, so we bump the maxCommttedZxid // by setting the value to 2 - svrs.zk[nonleader].setData(path+nonleader, input, -1); + servers.zk[nonleader].setData(path+nonleader, input, -1); // start the old leader - svrs.mt[leader].start(); + servers.mt[leader].start(); // connect to it - waitForOne(svrs.zk[leader], States.CONNECTED); + waitForOne(servers.zk[leader], States.CONNECTED); // make sure it doesn't have the new value that it alone had logged - output = svrs.zk[leader].getData(path+leader, false, null); + output = servers.zk[leader].getData(path+leader, false, null); Assert.assertEquals( "Validating that the deposed leader has rolled back that change it had written", output[0], 1); // make sure the leader has the subsequent changes that were made while it was offline - output = svrs.zk[leader].getData(path+nonleader, false, null); + output = servers.zk[leader].getData(path+nonleader, false, null); Assert.assertEquals( "Validating that the deposed leader caught up on changes it missed", output[0], 2); @@ -319,7 +345,8 @@ private void waitForAll(ZooKeeper[] zks, States state) throws InterruptedExcepti boolean someoneNotConnected = true; while(someoneNotConnected) { if (iterations-- == 0) { - throw new RuntimeException("Waiting too long"); + ClientBase.logAllStackTraces(); + throw new RuntimeException("Waiting too long"); } someoneNotConnected = false; @@ -404,7 +431,7 @@ public void testBadPeerAddressInQuorum() throws Exception { boolean isup = ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_QP1, - 5000); + 30000); Assert.assertFalse("Server never came up", isup); @@ -432,6 +459,135 @@ public void testBadPeerAddressInQuorum() throws Exception { Assert.assertTrue("complains about host", found); } + @Test + public void testValidIpv6AddressInQuorum() throws Exception { + assumeIPv6Available(); + + ClientBase.setupTestEnv(); + + // setup the logger to capture all logs + Layout layout = + Logger.getRootLogger().getAppender("CONSOLE").getLayout(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + WriterAppender appender = new WriterAppender(layout, os); + appender.setImmediateFlush(true); + appender.setThreshold(Level.INFO); + Logger qlogger = Logger.getLogger("org.apache.zookeeper.server.quorum"); + qlogger.addAppender(appender); + + try { + final int CLIENT_PORT_QP1 = PortAssignment.unique(); + final int CLIENT_PORT_QP2 = PortAssignment.unique(); + + String quorumCfgSection = + "server.1=127.0.0.1:" + PortAssignment.unique() + + ":" + PortAssignment.unique() + + "\nserver.2=[0:0:0:0:0:0:0:1]:" + PortAssignment.unique() + + ":" + PortAssignment.unique(); + + MainThread q1 = new MainThread(1, CLIENT_PORT_QP1, quorumCfgSection); + MainThread q2 = new MainThread(2, CLIENT_PORT_QP2, quorumCfgSection); + + q1.start(); + q2.start(); + + Assert.assertTrue("waiting for server 1 being up", + ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_QP1, + ClientBase.CONNECTION_TIMEOUT)); + + Assert.assertTrue("waiting for server 2 being up", + ClientBase.waitForServerUp("[0:0:0:0:0:0:0:1]:" + CLIENT_PORT_QP1, + ClientBase.CONNECTION_TIMEOUT)); + + q1.shutdown(); + q2.shutdown(); + + Assert.assertTrue("waiting for server 1 down", + ClientBase.waitForServerDown("127.0.0.1:" + CLIENT_PORT_QP1, + ClientBase.CONNECTION_TIMEOUT)); + + Assert.assertTrue("waiting for server 2 down", + ClientBase.waitForServerDown("[0:0:0:0:0:0:0:1]:" + CLIENT_PORT_QP1, + ClientBase.CONNECTION_TIMEOUT)); + + } finally { + qlogger.removeAppender(appender); + } + + os.close(); + LineNumberReader r = new LineNumberReader(new StringReader(os.toString())); + String line; + boolean found = false; + Pattern p = + Pattern.compile(".*Resolved hostname: 0:0:0:0:0:0:0:1.*"); + while ((line = r.readLine()) != null) { + found = p.matcher(line).matches(); + if (found) { + break; + } + } + Assert.assertTrue("IPv6 address resolved", found); + } + + @Test + public void testInvalidIpv6AddressInQuorum() throws Exception { + assumeIPv6Available(); + + ClientBase.setupTestEnv(); + + // setup the logger to capture all logs + Layout layout = + Logger.getRootLogger().getAppender("CONSOLE").getLayout(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + WriterAppender appender = new WriterAppender(layout, os); + appender.setImmediateFlush(true); + appender.setThreshold(Level.INFO); + Logger qlogger = Logger.getLogger("org.apache.zookeeper.server.quorum"); + qlogger.addAppender(appender); + + try { + final int CLIENT_PORT_QP1 = PortAssignment.unique(); + + String quorumCfgSection = + "server.1=127.0.0.1:" + PortAssignment.unique() + + ":" + PortAssignment.unique() + + "\nserver.2=[0:0:0:0:0:0:0:1:" + PortAssignment.unique() + + ":" + PortAssignment.unique(); + + MainThread q1 = new MainThread(1, CLIENT_PORT_QP1, quorumCfgSection); + q1.start(); + + boolean isup = + ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_QP1, + 30000); + + Assert.assertFalse("Server never came up", isup); + + q1.shutdown(); + + Assert.assertTrue("waiting for server 1 down", + ClientBase.waitForServerDown("127.0.0.1:" + CLIENT_PORT_QP1, + ClientBase.CONNECTION_TIMEOUT)); + + } finally { + qlogger.removeAppender(appender); + } + + os.close(); + LineNumberReader r = new LineNumberReader(new StringReader(os.toString())); + String line; + boolean found = false; + Pattern p = + Pattern.compile(".*QuorumPeerConfig\\$ConfigException.*"); + while ((line = r.readLine()) != null) { + found = p.matcher(line).matches(); + if (found) { + break; + } + } + Assert.assertTrue("complains about configuration", found); + } + /** * Verify handling of inconsistent peer type */ @@ -565,7 +721,7 @@ public void testBadPackets() throws Exception { ZooKeeper zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT_QP1, ClientBase.CONNECTION_TIMEOUT, this); - + waitForOne(zk, States.CONNECTED); zk.create("/foo_q1", "foobar1".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); Assert.assertEquals(new String(zk.getData("/foo_q1", null, null)), "foobar1"); @@ -659,12 +815,165 @@ public void testQuorumPeerExitTime() throws Exception { q1.start(); // Let the notifications timeout Thread.sleep(30000); - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); q1.shutdown(); - long end = System.currentTimeMillis(); + long end = Time.currentElapsedTime(); if ((end - start) > maxwait) { Assert.fail("QuorumPeer took " + (end -start) + " to shutdown, expected " + maxwait); } } + + static long readLongFromFile(File file) throws IOException { + BufferedReader br = new BufferedReader(new FileReader(file)); + String line = ""; + try { + line = br.readLine(); + return Long.parseLong(line); + } catch(NumberFormatException e) { + throw new IOException("Found " + line + " in " + file); + } finally { + br.close(); + } + } + + static void writeLongToFile(File file, long value) throws IOException { + AtomicFileOutputStream out = new AtomicFileOutputStream(file); + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out)); + try { + bw.write(Long.toString(value)); + bw.flush(); + out.flush(); + out.close(); + } catch (IOException e) { + LOG.error("Failed to write new file " + file, e); + out.abort(); + throw e; + } + } + + /** + * ZOOKEEPER-1653 Make sure the server starts if the current epoch is less + * than the epoch from last logged zxid and updatingEpoch file exists. + */ + @Test + public void testUpdatingEpoch() throws Exception { + // Create a cluster and restart them multiple times to bump the epoch. + numServers = 3; + servers = LaunchServers(numServers); + File currentEpochFile; + for (int i = 0; i < 10; i++) { + for (int j = 0; j < numServers; j++) { + servers.mt[j].shutdown(); + } + waitForAll(servers.zk, States.CONNECTING); + for (int j = 0; j < numServers; j++) { + servers.mt[j].start(); + } + waitForAll(servers.zk, States.CONNECTED); + } + + // Current epoch is 11 now. + for (int i = 0; i < numServers; i++) { + currentEpochFile = new File( + new File(servers.mt[i].dataDir, "version-2"), + QuorumPeer.CURRENT_EPOCH_FILENAME); + LOG.info("Validating current epoch: " + servers.mt[i].dataDir); + Assert.assertEquals("Current epoch should be 11.", 11, + readLongFromFile(currentEpochFile)); + } + + // Find a follower and get epoch from the last logged zxid. + int followerIndex = -1; + for (int i = 0; i < numServers; i++) { + if (servers.mt[i].main.quorumPeer.leader == null) { + followerIndex = i; + break; + } + } + Assert.assertTrue("Found a valid follower", + followerIndex >= 0 && followerIndex < numServers); + MainThread follower = servers.mt[followerIndex]; + long zxid = follower.main.quorumPeer.getLastLoggedZxid(); + long epochFromZxid = ZxidUtils.getEpochFromZxid(zxid); + + // Shutdown the cluster + for (int i = 0; i < numServers; i++) { + servers.mt[i].shutdown(); + } + waitForAll(servers.zk, States.CONNECTING); + + // Make current epoch less than epoch from the last logged zxid. + // The server should fail to start. + File followerDataDir = new File(follower.dataDir, "version-2"); + currentEpochFile = new File(followerDataDir, + QuorumPeer.CURRENT_EPOCH_FILENAME); + writeLongToFile(currentEpochFile, epochFromZxid - 1); + follower.start(); + Assert.assertTrue(follower.mainFailed.await(10, TimeUnit.SECONDS)); + + // Touch the updateEpoch file. Now the server should start. + File updatingEpochFile = new File(followerDataDir, + QuorumPeer.UPDATING_EPOCH_FILENAME); + updatingEpochFile.createNewFile(); + for (int i = 0; i < numServers; i++) { + servers.mt[i].start(); + } + waitForAll(servers.zk, States.CONNECTED); + Assert.assertNotNull("Make sure the server started with acceptEpoch", + follower.main.quorumPeer.getActiveServer()); + Assert.assertFalse("updatingEpoch file should get deleted", + updatingEpochFile.exists()); + } + + @Test + public void testNewFollowerRestartAfterNewEpoch() throws Exception { + numServers = 3; + + servers = LaunchServers(numServers); + waitForAll(servers.zk, States.CONNECTED); + String inputString = "test"; + byte[] input = inputString.getBytes(); + byte[] output; + String path = "/newepochzxidtest"; + + // Create a couple of nodes + servers.zk[0].create(path, input, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + servers.zk[0].setData(path, input, -1); + + // make sure the updates indeed committed. If it is not + // the following statement will throw. + output = servers.zk[1].getData(path, false, null); + + // Shutdown every one + for (int i=0; i < numServers; i++) { + servers.mt[i].shutdown(); + } + + LOG.info("resetting follower"); + MainThread follower = servers.mt[0]; + // delete followers information + File followerDataDir = new File(follower.dataDir, "version-2"); + for(File file: followerDataDir.listFiles()) { + LOG.info("deleting " + file.getName()); + file.delete(); + } + + // Startup everyone except follower, wait for election. + for (int i=1; i < numServers; i++) { + servers.mt[i].start(); + } + for (int i=1; i < numServers; i++) { + waitForOne(servers.zk[i], States.CONNECTED); + } + + follower.start(); + waitForAll(servers.zk, States.CONNECTED); // snapshot should be recieved + + follower.shutdown(); + follower.start(); + + Assert.assertFalse(follower.mainFailed.await(10, TimeUnit.SECONDS)); + waitForAll(servers.zk, States.CONNECTED); + } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerTestBase.java b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerTestBase.java index 31d32f46b93..c19963c913c 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerTestBase.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerTestBase.java @@ -24,30 +24,35 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.CountDownLatch; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZKTestCase; -import org.apache.zookeeper.server.ZooKeeperServer; -import org.apache.zookeeper.server.persistence.FileTxnSnapLog; import org.apache.zookeeper.test.ClientBase; import org.apache.zookeeper.test.QuorumBase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Has some common functionality for tests that work with QuorumPeers. - * Override process(WatchedEvent) to implement the Watcher interface + * Has some common functionality for tests that work with QuorumPeers. Override + * process(WatchedEvent) to implement the Watcher interface */ public class QuorumPeerTestBase extends ZKTestCase implements Watcher { - protected static final Logger LOG = - LoggerFactory.getLogger(QuorumPeerTestBase.class); - + protected static final Logger LOG = LoggerFactory + .getLogger(QuorumPeerTestBase.class); + + public static final int TIMEOUT = 5000; + public void process(WatchedEvent event) { // ignore for this test } - public static class TestQPMain extends QuorumPeerMain { + public static class TestQPMain extends QuorumPeerMain { public void shutdown() { // ensure it closes - in particular wait for thread to exit if (quorumPeer != null) { @@ -59,19 +64,31 @@ public void shutdown() { public static class MainThread implements Runnable { final File confFile; volatile TestQPMain main; + final File dataDir; + CountDownLatch mainFailed; + File baseDir; + private final int myid; + private final int clientPort; + private final String quorumCfgSection; + private final Map otherConfigs; - public MainThread(int myid, int clientPort, String quorumCfgSection) - throws IOException - { - File tmpDir = ClientBase.createTmpDir(); - confFile = new File(tmpDir, "zoo.cfg"); + public MainThread(int myid, int clientPort, String quorumCfgSection, + Map otherConfigs) throws IOException { + baseDir = ClientBase.createTmpDir(); + this.myid = myid; + this.clientPort = clientPort; + this.quorumCfgSection = quorumCfgSection; + this.otherConfigs = otherConfigs; + LOG.info("id = " + myid + " tmpDir = " + baseDir + " clientPort = " + + clientPort); + confFile = new File(baseDir, "zoo.cfg"); FileWriter fwriter = new FileWriter(confFile); fwriter.write("tickTime=4000\n"); fwriter.write("initLimit=10\n"); fwriter.write("syncLimit=5\n"); - File dataDir = new File(tmpDir, "data"); + dataDir = new File(baseDir, "data"); if (!dataDir.mkdir()) { throw new IOException("Unable to mkdir " + dataDir); } @@ -83,8 +100,15 @@ public MainThread(int myid, int clientPort, String quorumCfgSection) dir = dir.replace('\\', '/'); } fwriter.write("dataDir=" + dir + "\n"); - + fwriter.write("clientPort=" + clientPort + "\n"); + + // write extra configurations + Set> entrySet = otherConfigs.entrySet(); + for (Entry entry : entrySet) { + fwriter.write(entry.getKey() + "=" + entry.getValue() + "\n"); + } + fwriter.write(quorumCfgSection + "\n"); fwriter.flush(); fwriter.close(); @@ -96,12 +120,21 @@ public MainThread(int myid, int clientPort, String quorumCfgSection) fwriter.close(); } + public MainThread(int myid, int clientPort, String quorumCfgSection) + throws IOException { + this(myid, clientPort, quorumCfgSection, + new HashMap()); + } + Thread currentThread; + synchronized public void start() { - main = new TestQPMain(); - currentThread = new Thread(this); - currentThread.start(); + main = getTestQPMain(); + currentThread = new Thread(this); + currentThread.start(); + mainFailed = new CountDownLatch(1); } + public void run() { String args[] = new String[1]; args[0] = confFile.toString(); @@ -110,27 +143,68 @@ public void run() { } catch (Exception e) { // test will still fail even though we just log/ignore LOG.error("unexpected exception in run", e); + main.shutdown(); + mainFailed.countDown(); } finally { - currentThread = null; + currentThread = null; } } public void shutdown() throws InterruptedException { - Thread t = currentThread; - if (t != null && t.isAlive()) { - main.shutdown(); - t.join(500); - } - } - public void join(long timeout) throws InterruptedException { - Thread t = currentThread; - if (t != null) { - t.join(timeout); - } - } - public boolean isAlive() { - Thread t = currentThread; - return t != null && t.isAlive(); - } + Thread t = currentThread; + if (t != null && t.isAlive()) { + main.shutdown(); + t.join(500); + } + } + + public void join(long timeout) throws InterruptedException { + Thread t = currentThread; + if (t != null) { + t.join(timeout); + } + } + + public boolean isAlive() { + Thread t = currentThread; + return t != null && t.isAlive(); + } + + public void clean() { + ClientBase.recursiveDelete(main.quorumPeer.getTxnFactory() + .getDataDir()); + } + + public QuorumPeer getQuorumPeer() { + return main.quorumPeer; + } + + public void deleteBaseDir() { + ClientBase.recursiveDelete(baseDir); + } + + public int getMyid() { + return myid; + } + + public int getClientPort() { + return clientPort; + } + + public String getQuorumCfgSection() { + return quorumCfgSection; + } + + public Map getOtherConfigs() { + return otherConfigs; + } + + public File getConfFile() { + return confFile; + } + + public TestQPMain getTestQPMain() { + return new TestQPMain(); + } } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/WatchLeakTest.java b/src/java/test/org/apache/zookeeper/server/quorum/WatchLeakTest.java new file mode 100644 index 00000000000..74713813d38 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/WatchLeakTest.java @@ -0,0 +1,306 @@ +/** + * 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.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.junit.Assert.*; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.apache.jute.InputArchive; +import org.apache.jute.OutputArchive; +import org.apache.zookeeper.MockPacket; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.proto.ConnectRequest; +import org.apache.zookeeper.proto.ReplyHeader; +import org.apache.zookeeper.proto.RequestHeader; +import org.apache.zookeeper.proto.SetWatches; +import org.apache.zookeeper.server.MockNIOServerCnxn; +import org.apache.zookeeper.server.NIOServerCnxnFactory; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.ZooTrace; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Demonstrate ZOOKEEPER-1382 : Watches leak on expired session + */ +public class WatchLeakTest { + + protected static final Logger LOG = LoggerFactory + .getLogger(WatchLeakTest.class); + + final long SESSION_ID = 0xBABEL; + + /** + * ZOOKEEPR-1382 test class + */ + @Test + public void testWatchesWithClientSessionTimeout() throws Exception { + + NIOServerCnxnFactory serverCnxnFactory = new NIOServerCnxnFactory(); + + ZKDatabase database = new ZKDatabase(null); + database.setlastProcessedZxid(2L); + QuorumPeer quorumPeer = mock(QuorumPeer.class); + FileTxnSnapLog logfactory = mock(FileTxnSnapLog.class); + // Directories are not used but we need it to avoid NPE + when(logfactory.getDataDir()).thenReturn(new File("/tmp")); + when(logfactory.getSnapDir()).thenReturn(new File("/tmp")); + FollowerZooKeeperServer fzks = null; + try { + fzks = new FollowerZooKeeperServer(logfactory, quorumPeer, null, + database); + fzks.startup(); + fzks.setServerCnxnFactory(serverCnxnFactory); + quorumPeer.follower = new MyFollower(quorumPeer, fzks); + final SelectionKey sk = new FakeSK(); + // Simulate a socket channel between a client and a follower + final SocketChannel socketChannel = createClientSocketChannel(); + // Create the NIOServerCnxn that will handle the client requests + final MockNIOServerCnxn nioCnxn = new MockNIOServerCnxn(fzks, + socketChannel, sk, serverCnxnFactory); + // Send the connection request as a client do + nioCnxn.doIO(sk); + // Send the invalid session packet to the follower + QuorumPacket qp = createInvalidSessionPacket(); + quorumPeer.follower.processPacket(qp); + // OK, now the follower knows that the session is invalid, let's try + // to + // send it the watches + nioCnxn.doIO(sk); + // wait for the the request processor to do his job + Thread.sleep(1000L); + // Session has not been re-validated ! + // If session has not been validated, there must be NO watches + int watchCount = database.getDataTree().getWatchCount(); + LOG.info("watches = " + watchCount); + assertEquals(0, watchCount); + } finally { + if (fzks != null) { + fzks.shutdown(); + } + } + } + + /** + * A follower with no real leader connection + */ + public static class MyFollower extends Follower { + /** + * Create a follower with a mocked leader connection + * + * @param self + * @param zk + */ + MyFollower(QuorumPeer self, FollowerZooKeeperServer zk) { + super(self, zk); + leaderOs = mock(OutputArchive.class); + leaderIs = mock(InputArchive.class); + bufferedOutput = mock(BufferedOutputStream.class); + } + } + + /** + * Simulate the behavior of a real selection key + */ + private static class FakeSK extends SelectionKey { + + @Override + public SelectableChannel channel() { + return null; + } + + @Override + public Selector selector() { + return mock(Selector.class); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public void cancel() { + } + + @Override + public int interestOps() { + return ops; + } + + private int ops = OP_WRITE + OP_READ; + + @Override + public SelectionKey interestOps(int ops) { + this.ops = ops; + return this; + } + + @Override + public int readyOps() { + return ops; + } + + } + + /** + * Create a watches message with a single watch on / + * + * @return + */ + private ByteBuffer createWatchesMessage() { + List dataWatches = new ArrayList(1); + dataWatches.add("/"); + List existWatches = Collections.emptyList(); + List childWatches = Collections.emptyList(); + SetWatches sw = new SetWatches(1L, dataWatches, existWatches, + childWatches); + RequestHeader h = new RequestHeader(); + h.setType(ZooDefs.OpCode.setWatches); + h.setXid(-8); + MockPacket p = new MockPacket(h, new ReplyHeader(), sw, null, null); + return p.createAndReturnBB(); + } + + /** + * This is the secret that we use to generate passwords, for the moment it + * is more of a sanity check. + */ + static final private long superSecret = 0XB3415C00L; + + /** + * Create a connection request + * + * @return + */ + private ByteBuffer createConnRequest() { + Random r = new Random(SESSION_ID ^ superSecret); + byte p[] = new byte[16]; + r.nextBytes(p); + ConnectRequest conReq = new ConnectRequest(0, 1L, 30000, SESSION_ID, p); + MockPacket packet = new MockPacket(null, null, conReq, null, null, false); + return packet.createAndReturnBB(); + } + + /** + * Mock a client channel with a connection request and a watches message + * inside. + * + * @return a socket channel + * @throws IOException + */ + private SocketChannel createClientSocketChannel() throws IOException { + + SocketChannel socketChannel = mock(SocketChannel.class); + Socket socket = mock(Socket.class); + InetSocketAddress socketAddress = new InetSocketAddress(1234); + when(socket.getRemoteSocketAddress()).thenReturn(socketAddress); + when(socketChannel.socket()).thenReturn(socket); + + // Send watches packet to server connection + final ByteBuffer connRequest = createConnRequest(); + final ByteBuffer watchesMessage = createWatchesMessage(); + final ByteBuffer request = ByteBuffer.allocate(connRequest.limit() + + watchesMessage.limit()); + request.put(connRequest); + request.put(watchesMessage); + + Answer answer = new Answer() { + int i = 0; + + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + ByteBuffer bb = (ByteBuffer) args[0]; + for (int k = 0; k < bb.limit(); k++) { + bb.put(request.get(i)); + i = i + 1; + } + return bb.limit(); + } + }; + when(socketChannel.read(any(ByteBuffer.class))).thenAnswer(answer); + return socketChannel; + } + + /** + * Forge an invalid session packet as a LEADER do + * + * @throws Exception + */ + private QuorumPacket createInvalidSessionPacket() throws Exception { + QuorumPacket qp = createValidateSessionQuorumPacket(); + ByteArrayInputStream bis = new ByteArrayInputStream(qp.getData()); + DataInputStream dis = new DataInputStream(bis); + long id = dis.readLong(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeLong(id); + // false means that the session has expired + dos.writeBoolean(false); + qp.setData(bos.toByteArray()); + return qp; + } + + /** + * Forge an validate session packet as a LEARNER do + * + * @return + * @throws Exception + */ + private QuorumPacket createValidateSessionQuorumPacket() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeLong(SESSION_ID); + dos.writeInt(3000); + dos.close(); + QuorumPacket qp = new QuorumPacket(Leader.REVALIDATE, -1, + baos.toByteArray(), null); + if (LOG.isTraceEnabled()) { + ZooTrace.logTraceMessage(LOG, ZooTrace.SESSION_TRACE_MASK, + "To validate session 0x" + Long.toHexString(2L)); + } + return qp; + } + +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/server/quorum/Zab1_0Test.java b/src/java/test/org/apache/zookeeper/server/quorum/Zab1_0Test.java index 53aa4eee4e2..3ed6097b31f 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/Zab1_0Test.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/Zab1_0Test.java @@ -18,39 +18,75 @@ package org.apache.zookeeper.server.quorum; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; +import java.io.EOFException; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.net.SocketAddress; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import org.apache.jute.BinaryInputArchive; import org.apache.jute.BinaryOutputArchive; import org.apache.jute.InputArchive; import org.apache.jute.OutputArchive; -import org.apache.zookeeper.ZKUtil; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.Watcher.Event.EventType; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper.States; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.ByteBufferInputStream; import org.apache.zookeeper.server.ByteBufferOutputStream; -import org.apache.zookeeper.server.DataTree; +import org.apache.zookeeper.server.Request; import org.apache.zookeeper.server.ServerCnxn; import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.SyncRequestProcessor; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.ZooKeeperServer; import org.apache.zookeeper.server.ZooKeeperServer.DataTreeBuilder; import org.apache.zookeeper.server.persistence.FileTxnSnapLog; -import org.apache.zookeeper.server.quorum.Leader; +import org.apache.zookeeper.server.persistence.Util; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; import org.apache.zookeeper.server.quorum.flexible.QuorumMaj; import org.apache.zookeeper.server.util.ZxidUtils; +import org.apache.zookeeper.txn.CreateSessionTxn; +import org.apache.zookeeper.txn.CreateTxn; +import org.apache.zookeeper.txn.ErrorTxn; +import org.apache.zookeeper.txn.SetDataTxn; +import org.apache.zookeeper.txn.TxnHeader; import org.junit.Assert; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Zab1_0Test { + private static final int SYNC_LIMIT = 2; + + private static final Logger LOG = LoggerFactory.getLogger(Zab1_0Test.class); + + private static final File testData = new File( + System.getProperty("test.data.dir", "build/test/data")); + private static final class LeadThread extends Thread { private final Leader leader; @@ -61,13 +97,192 @@ private LeadThread(Leader leader) { public void run() { try { leader.lead(); + } catch (InterruptedException e) { + LOG.info("Leader thread interrupted", e); } catch (Exception e) { - e.printStackTrace(); + LOG.warn("Unexpected exception in leader thread", e); } finally { leader.shutdown("lead ended"); } } } + + private static final class MockLeader extends Leader { + + MockLeader(QuorumPeer qp, LeaderZooKeeperServer zk) + throws IOException { + super(qp, zk); + } + + /** + * This method returns the value of the variable that holds the epoch + * to be proposed and that has been proposed, depending on the point + * of the execution in which it is called. + * + * @return epoch + */ + public long getCurrentEpochToPropose() { + return epoch; + } + } + + public static final class FollowerMockThread extends Thread { + private final Leader leader; + private final long followerSid; + public long epoch = -1; + public String msg = null; + private boolean onlyGetEpochToPropose; + + private FollowerMockThread(long followerSid, Leader leader, boolean onlyGetEpochToPropose) { + this.leader = leader; + this.followerSid = followerSid; + this.onlyGetEpochToPropose = onlyGetEpochToPropose; + } + + public void run() { + if (onlyGetEpochToPropose) { + try { + epoch = leader.getEpochToPropose(followerSid, 0); + } catch (Exception e) { + } + } else { + try{ + leader.waitForEpochAck(followerSid, new StateSummary(0, 0)); + msg = "FollowerMockThread (id = " + followerSid + ") returned from waitForEpochAck"; + } catch (Exception e) { + } + } + } + } + @Test + public void testLeaderInConnectingFollowers() throws Exception { + File tmpDir = File.createTempFile("test", "dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + try { + QuorumPeer peer = createQuorumPeer(tmpDir); + leader = createLeader(tmpDir, peer); + peer.leader = leader; + peer.setAcceptedEpoch(5); + + FollowerMockThread f1 = new FollowerMockThread(1, leader, true); + FollowerMockThread f2 = new FollowerMockThread(2, leader, true); + f1.start(); + f2.start(); + + // wait until followers time out in getEpochToPropose - they shouldn't return + // normally because the leader didn't execute getEpochToPropose and so its epoch was not + // accounted for + f1.join(leader.self.getInitLimit()*leader.self.getTickTime() + 5000); + f2.join(leader.self.getInitLimit()*leader.self.getTickTime() + 5000); + + // even though followers timed out, their ids are in connectingFollowers, and their + // epoch were accounted for, so the leader should not block and since it started with + // accepted epoch = 5 it should now have 6 + try { + long epoch = leader.getEpochToPropose(leader.self.getId(), leader.self.getAcceptedEpoch()); + Assert.assertEquals("leader got wrong epoch from getEpochToPropose", 6, epoch); + } catch (Exception e){ + Assert.fail("leader timed out in getEpochToPropose"); + } + } finally { + if (leader != null) { + leader.shutdown("end of test"); + } + recursiveDelete(tmpDir); + } + } + + /** + * In this test, the leader sets the last accepted epoch to 5. The call + * to getEpochToPropose should set epoch to 6 and wait until another + * follower executes it. If in getEpochToPropose we don't check if + * lastAcceptedEpoch == epoch, then the call from the subsequent + * follower with lastAcceptedEpoch = 6 doesn't change the value + * of epoch, and the test fails. It passes with the fix to predicate. + * + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-1343} + * + * + * @throws Exception + */ + + @Test + public void testLastAcceptedEpoch() throws Exception { + File tmpDir = File.createTempFile("test", "dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + LeadThread leadThread = null; + try { + QuorumPeer peer = createQuorumPeer(tmpDir); + leader = createMockLeader(tmpDir, peer); + peer.leader = leader; + peer.setAcceptedEpoch(5); + leadThread = new LeadThread(leader); + leadThread.start(); + + while(((MockLeader) leader).getCurrentEpochToPropose() != 6){ + Thread.sleep(20); + } + + try { + long epoch = leader.getEpochToPropose(1, 6); + Assert.assertEquals("New proposed epoch is wrong", 7, epoch); + } catch (Exception e){ + Assert.fail("Timed out in getEpochToPropose"); + } + + } finally { + if (leader != null) { + leader.shutdown("end of test"); + } + if (leadThread != null) { + leadThread.interrupt(); + leadThread.join(); + } + recursiveDelete(tmpDir); + } + } + + @Test + public void testLeaderInElectingFollowers() throws Exception { + File tmpDir = File.createTempFile("test", "dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + Leader leader = null; + try { + QuorumPeer peer = createQuorumPeer(tmpDir); + leader = createLeader(tmpDir, peer); + peer.leader = leader; + + FollowerMockThread f1 = new FollowerMockThread(1, leader, false); + FollowerMockThread f2 = new FollowerMockThread(2, leader, false); + + // things needed for waitForEpochAck to run (usually in leader.lead(), but we're not running leader here) + leader.readyToStart = true; + leader.leaderStateSummary = new StateSummary(leader.self.getCurrentEpoch(), leader.zk.getLastProcessedZxid()); + + f1.start(); + f2.start(); + + // wait until followers time out in waitForEpochAck - they shouldn't return + // normally because the leader didn't execute waitForEpochAck + f1.join(leader.self.getInitLimit()*leader.self.getTickTime() + 5000); + f2.join(leader.self.getInitLimit()*leader.self.getTickTime() + 5000); + + // make sure that they timed out and didn't return normally + Assert.assertTrue(f1.msg + " without waiting for leader", f1.msg == null); + Assert.assertTrue(f2.msg + " without waiting for leader", f2.msg == null); + } finally { + if (leader != null) { + leader.shutdown("end of test"); + } + recursiveDelete(tmpDir); + } + } + private static final class NullServerCnxnFactory extends ServerCnxnFactory { public void startup(ZooKeeperServer zkServer) throws IOException, InterruptedException { @@ -99,13 +314,10 @@ public void closeSession(long sessionId) { } public void closeAll() { } - } - static class MockDataTreeBuilder implements DataTreeBuilder { @Override - public DataTree build() { - return new DataTree(); + public int getNumAliveConnections() { + return 0; } - } static Socket[] getSocketPair() throws IOException { ServerSocket ss = new ServerSocket(); @@ -124,18 +336,26 @@ static void readPacketSkippingPing(InputArchive ia, QuorumPacket qp) throws IOEx } static public interface LeaderConversation { - void converseWithLeader(InputArchive ia, OutputArchive oa) throws Exception; + void converseWithLeader(InputArchive ia, OutputArchive oa, Leader l) throws Exception; + } + + static public interface PopulatedLeaderConversation { + void converseWithLeader(InputArchive ia, OutputArchive oa, Leader l, long zxid) throws Exception; } static public interface FollowerConversation { - void converseWithFollower(InputArchive ia, OutputArchive oa) throws Exception; + void converseWithFollower(InputArchive ia, OutputArchive oa, Follower f) throws Exception; } - public void testConversation(LeaderConversation conversation) throws Exception { + static public interface ObserverConversation { + void converseWithObserver(InputArchive ia, OutputArchive oa, Observer o) throws Exception; + } + + public void testLeaderConversation(LeaderConversation conversation) throws Exception { Socket pair[] = getSocketPair(); Socket leaderSocket = pair[0]; Socket followerSocket = pair[1]; - File tmpDir = File.createTempFile("test", "dir"); + File tmpDir = File.createTempFile("test", "dir", testData); tmpDir.delete(); tmpDir.mkdir(); LeadThread leadThread = null; @@ -151,7 +371,9 @@ public void testConversation(LeaderConversation conversation) throws Exception { Thread.sleep(20); } - LearnerHandler lh = new LearnerHandler(leaderSocket, leader); + LearnerHandler lh = new LearnerHandler(leaderSocket, + new BufferedInputStream(leaderSocket.getInputStream()), + leader); lh.start(); leaderSocket.setSoTimeout(4000); @@ -160,9 +382,79 @@ public void testConversation(LeaderConversation conversation) throws Exception { OutputArchive oa = BinaryOutputArchive.getArchive(followerSocket .getOutputStream()); - conversation.converseWithLeader(ia, oa); + conversation.converseWithLeader(ia, oa, leader); } finally { + if (leader != null) { + leader.shutdown("end of test"); + } + if (leadThread != null) { + leadThread.interrupt(); + leadThread.join(); + } recursiveDelete(tmpDir); + } + } + + public void testPopulatedLeaderConversation(PopulatedLeaderConversation conversation, int ops) throws Exception { + Socket pair[] = getSocketPair(); + Socket leaderSocket = pair[0]; + Socket followerSocket = pair[1]; + File tmpDir = File.createTempFile("test", "dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + LeadThread leadThread = null; + Leader leader = null; + try { + // Setup a database with two znodes + FileTxnSnapLog snapLog = new FileTxnSnapLog(tmpDir, tmpDir); + ZKDatabase zkDb = new ZKDatabase(snapLog); + + Assert.assertTrue(ops >= 1); + long zxid = ZxidUtils.makeZxid(1, 0); + for(int i = 1; i <= ops; i++){ + zxid = ZxidUtils.makeZxid(1, i); + String path = "/foo-"+ i; + zkDb.processTxn(new TxnHeader(13,1000+i,zxid,30+i,ZooDefs.OpCode.create), + new CreateTxn(path, "fpjwasalsohere".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, false, 1)); + Stat stat = new Stat(); + Assert.assertEquals("fpjwasalsohere", new String(zkDb.getData(path, stat, null))); + } + Assert.assertTrue(zxid > ZxidUtils.makeZxid(1, 0)); + + // Generate snapshot and close files. + snapLog.save(zkDb.getDataTree(), zkDb.getSessionWithTimeOuts()); + snapLog.close(); + + QuorumPeer peer = createQuorumPeer(tmpDir); + + leader = createLeader(tmpDir, peer); + peer.leader = leader; + + // Set the last accepted epoch and current epochs to be 1 + peer.setAcceptedEpoch(1); + peer.setCurrentEpoch(1); + + + leadThread = new LeadThread(leader); + leadThread.start(); + + while(leader.cnxAcceptor == null || !leader.cnxAcceptor.isAlive()) { + Thread.sleep(20); + } + + LearnerHandler lh = new LearnerHandler(leaderSocket, + new BufferedInputStream(leaderSocket.getInputStream()), + leader); + lh.start(); + leaderSocket.setSoTimeout(4000); + + InputArchive ia = BinaryInputArchive.getArchive(followerSocket + .getInputStream()); + OutputArchive oa = BinaryOutputArchive.getArchive(followerSocket + .getOutputStream()); + + conversation.converseWithLeader(ia, oa, leader, zxid); + } finally { if (leader != null) { leader.shutdown("end of test"); } @@ -170,14 +462,445 @@ public void testConversation(LeaderConversation conversation) throws Exception { leadThread.interrupt(); leadThread.join(); } + recursiveDelete(tmpDir); } } - + + public void testFollowerConversation(FollowerConversation conversation) throws Exception { + File tmpDir = File.createTempFile("test", "dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + Thread followerThread = null; + ConversableFollower follower = null; + QuorumPeer peer = null; + try { + peer = createQuorumPeer(tmpDir); + follower = createFollower(tmpDir, peer); + peer.follower = follower; + + ServerSocket ss = new ServerSocket(); + ss.bind(null); + QuorumServer leaderQS = new QuorumServer(1, + (InetSocketAddress) ss.getLocalSocketAddress()); + follower.setLeaderQuorumServer(leaderQS); + final Follower followerForThread = follower; + + followerThread = new Thread() { + public void run() { + try { + followerForThread.followLeader(); + } catch (InterruptedException e) { + LOG.info("Follower thread interrupted", e); + } catch (Exception e) { + LOG.warn("Unexpected exception in follower thread", e); + } + } + }; + followerThread.start(); + Socket leaderSocket = ss.accept(); + + InputArchive ia = BinaryInputArchive.getArchive(leaderSocket + .getInputStream()); + OutputArchive oa = BinaryOutputArchive.getArchive(leaderSocket + .getOutputStream()); + + conversation.converseWithFollower(ia, oa, follower); + } finally { + if (follower != null) { + follower.shutdown(); + } + if (followerThread != null) { + followerThread.interrupt(); + followerThread.join(); + } + if (peer != null) { + peer.shutdown(); + } + recursiveDelete(tmpDir); + } + } + + public void testObserverConversation(ObserverConversation conversation) throws Exception { + File tmpDir = File.createTempFile("test", "dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + Thread observerThread = null; + ConversableObserver observer = null; + QuorumPeer peer = null; + try { + peer = createQuorumPeer(tmpDir); + peer.setSyncEnabled(true); + observer = createObserver(tmpDir, peer); + peer.observer = observer; + + ServerSocket ss = new ServerSocket(); + ss.bind(null); + QuorumServer leaderQS = new QuorumServer(1, + (InetSocketAddress) ss.getLocalSocketAddress()); + observer.setLeaderQuorumServer(leaderQS); + final Observer observerForThread = observer; + + observerThread = new Thread() { + public void run() { + try { + observerForThread.observeLeader(); + } catch(Exception e) { + e.printStackTrace(); + } + } + }; + observerThread.start(); + Socket leaderSocket = ss.accept(); + + InputArchive ia = BinaryInputArchive.getArchive(leaderSocket + .getInputStream()); + OutputArchive oa = BinaryOutputArchive.getArchive(leaderSocket + .getOutputStream()); + + conversation.converseWithObserver(ia, oa, observer); + } finally { + if (observer != null) { + observer.shutdown(); + } + if (observerThread != null) { + observerThread.interrupt(); + observerThread.join(); + } + if (peer != null) { + peer.shutdown(); + } + recursiveDelete(tmpDir); + } + } + + @Test + public void testUnnecessarySnap() throws Exception { + testPopulatedLeaderConversation(new PopulatedLeaderConversation() { + @Override + public void converseWithLeader(InputArchive ia, OutputArchive oa, + Leader l, long zxid) throws Exception { + + Assert.assertEquals(1, l.self.getAcceptedEpoch()); + Assert.assertEquals(1, l.self.getCurrentEpoch()); + + /* we test a normal run. everything should work out well. */ + LearnerInfo li = new LearnerInfo(1, 0x10000); + byte liBytes[] = new byte[12]; + ByteBufferOutputStream.record2ByteBuffer(li, + ByteBuffer.wrap(liBytes)); + QuorumPacket qp = new QuorumPacket(Leader.FOLLOWERINFO, 1, + liBytes, null); + oa.writeRecord(qp, null); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.LEADERINFO, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(2, 0), qp.getZxid()); + Assert.assertEquals(ByteBuffer.wrap(qp.getData()).getInt(), + 0x10000); + Assert.assertEquals(2, l.self.getAcceptedEpoch()); + Assert.assertEquals(1, l.self.getCurrentEpoch()); + + byte epochBytes[] = new byte[4]; + final ByteBuffer wrappedEpochBytes = ByteBuffer.wrap(epochBytes); + wrappedEpochBytes.putInt(1); + qp = new QuorumPacket(Leader.ACKEPOCH, zxid, epochBytes, null); + oa.writeRecord(qp, null); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.DIFF, qp.getType()); + + } + }, 2); + } + + // We want to track the change with a callback rather than depending on timing + class TrackerWatcher implements Watcher { + boolean changed; + synchronized void waitForChange() throws InterruptedException { + while(!changed) { + wait(); + } + } + + @Override + public void process(WatchedEvent event) { + if (event.getType() == EventType.NodeDataChanged) { + synchronized(this) { + changed = true; + notifyAll(); + } + } + } + synchronized public boolean changed() { + return changed; + } + }; + + + @Test + public void testNormalFollowerRun() throws Exception { + testFollowerConversation(new FollowerConversation() { + @Override + public void converseWithFollower(InputArchive ia, OutputArchive oa, + Follower f) throws Exception { + File tmpDir = File.createTempFile("test", "dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + File logDir = f.fzk.getTxnLogFactory().getDataDir().getParentFile(); + File snapDir = f.fzk.getTxnLogFactory().getSnapDir().getParentFile(); + //Spy on ZK so we can check if a snapshot happened or not. + f.zk = spy(f.zk); + try { + Assert.assertEquals(0, f.self.getAcceptedEpoch()); + Assert.assertEquals(0, f.self.getCurrentEpoch()); + + // Setup a database with a single /foo node + ZKDatabase zkDb = new ZKDatabase(new FileTxnSnapLog(tmpDir, tmpDir)); + final long firstZxid = ZxidUtils.makeZxid(1, 1); + zkDb.processTxn(new TxnHeader(13, 1313, firstZxid, 33, ZooDefs.OpCode.create), new CreateTxn("/foo", "data1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, false, 1)); + Stat stat = new Stat(); + Assert.assertEquals("data1", new String(zkDb.getData("/foo", stat, null))); + + QuorumPacket qp = new QuorumPacket(); + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.FOLLOWERINFO, qp.getType()); + Assert.assertEquals(qp.getZxid(), 0); + LearnerInfo learnInfo = new LearnerInfo(); + ByteBufferInputStream.byteBuffer2Record(ByteBuffer.wrap(qp.getData()), learnInfo); + Assert.assertEquals(learnInfo.getProtocolVersion(), 0x10000); + Assert.assertEquals(learnInfo.getServerid(), 0); + + // We are simulating an established leader, so the epoch is 1 + qp.setType(Leader.LEADERINFO); + qp.setZxid(ZxidUtils.makeZxid(1, 0)); + byte protoBytes[] = new byte[4]; + ByteBuffer.wrap(protoBytes).putInt(0x10000); + qp.setData(protoBytes); + oa.writeRecord(qp, null); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACKEPOCH, qp.getType()); + Assert.assertEquals(0, qp.getZxid()); + Assert.assertEquals(ZxidUtils.makeZxid(0, 0), ByteBuffer.wrap(qp.getData()).getInt()); + Assert.assertEquals(1, f.self.getAcceptedEpoch()); + Assert.assertEquals(0, f.self.getCurrentEpoch()); + + // Send the snapshot we created earlier + qp.setType(Leader.SNAP); + qp.setData(new byte[0]); + qp.setZxid(zkDb.getDataTreeLastProcessedZxid()); + oa.writeRecord(qp, null); + zkDb.serializeSnapshot(oa); + oa.writeString("BenWasHere", null); + Thread.sleep(10); //Give it some time to process the snap + //No Snapshot taken yet, the SNAP was applied in memory + verify(f.zk, never()).takeSnapshot(); + + qp.setType(Leader.NEWLEADER); + qp.setZxid(ZxidUtils.makeZxid(1, 0)); + oa.writeRecord(qp, null); + + // Get the ack of the new leader + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACK, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + Assert.assertEquals(1, f.self.getAcceptedEpoch()); + Assert.assertEquals(1, f.self.getCurrentEpoch()); + //Make sure that we did take the snapshot now + verify(f.zk).takeSnapshot(); + Assert.assertEquals(firstZxid, f.fzk.getLastProcessedZxid()); + + // Make sure the data was recorded in the filesystem ok + ZKDatabase zkDb2 = new ZKDatabase(new FileTxnSnapLog(logDir, snapDir)); + long lastZxid = zkDb2.loadDataBase(); + Assert.assertEquals("data1", new String(zkDb2.getData("/foo", stat, null))); + Assert.assertEquals(firstZxid, lastZxid); + + // Propose an update + long proposalZxid = ZxidUtils.makeZxid(1, 1000); + proposeSetData(qp, proposalZxid, "data2", 2); + oa.writeRecord(qp, null); + + TrackerWatcher watcher = new TrackerWatcher(); + + // The change should not have happened yet, since we haven't committed + Assert.assertEquals("data1", new String(f.fzk.getZKDatabase().getData("/foo", stat, watcher))); + + // The change should happen now + qp.setType(Leader.COMMIT); + qp.setZxid(proposalZxid); + oa.writeRecord(qp, null); + + qp.setType(Leader.UPTODATE); + qp.setZxid(0); + oa.writeRecord(qp, null); + + // Read the uptodate ack + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACK, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACK, qp.getType()); + Assert.assertEquals(proposalZxid, qp.getZxid()); + + watcher.waitForChange(); + Assert.assertEquals("data2", new String(f.fzk.getZKDatabase().getData("/foo", stat, null))); + + // check and make sure the change is persisted + zkDb2 = new ZKDatabase(new FileTxnSnapLog(logDir, snapDir)); + lastZxid = zkDb2.loadDataBase(); + Assert.assertEquals("data2", new String(zkDb2.getData("/foo", stat, null))); + Assert.assertEquals(proposalZxid, lastZxid); + } finally { + recursiveDelete(tmpDir); + } + + } + + private void proposeSetData(QuorumPacket qp, long zxid, String data, int version) throws IOException { + qp.setType(Leader.PROPOSAL); + qp.setZxid(zxid); + TxnHeader hdr = new TxnHeader(4, 1414, qp.getZxid(), 55, ZooDefs.OpCode.setData); + SetDataTxn sdt = new SetDataTxn("/foo", data.getBytes(), version); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputArchive boa = BinaryOutputArchive.getArchive(baos); + boa.writeRecord(hdr, null); + boa.writeRecord(sdt, null); + qp.setData(baos.toByteArray()); + } + }); + } + + @Test + public void testNormalFollowerRunWithDiff() throws Exception { + testFollowerConversation(new FollowerConversation() { + @Override + public void converseWithFollower(InputArchive ia, OutputArchive oa, + Follower f) throws Exception { + File tmpDir = File.createTempFile("test", "dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + File logDir = f.fzk.getTxnLogFactory().getDataDir().getParentFile(); + File snapDir = f.fzk.getTxnLogFactory().getSnapDir().getParentFile(); + //Spy on ZK so we can check if a snapshot happened or not. + f.zk = spy(f.zk); + try { + Assert.assertEquals(0, f.self.getAcceptedEpoch()); + Assert.assertEquals(0, f.self.getCurrentEpoch()); + + // Setup a database with a single /foo node + ZKDatabase zkDb = new ZKDatabase(new FileTxnSnapLog(tmpDir, tmpDir)); + final long firstZxid = ZxidUtils.makeZxid(1, 1); + zkDb.processTxn(new TxnHeader(13, 1313, firstZxid, 33, ZooDefs.OpCode.create), new CreateTxn("/foo", "data1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, false, 1)); + Stat stat = new Stat(); + Assert.assertEquals("data1", new String(zkDb.getData("/foo", stat, null))); + + QuorumPacket qp = new QuorumPacket(); + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.FOLLOWERINFO, qp.getType()); + Assert.assertEquals(qp.getZxid(), 0); + LearnerInfo learnInfo = new LearnerInfo(); + ByteBufferInputStream.byteBuffer2Record(ByteBuffer.wrap(qp.getData()), learnInfo); + Assert.assertEquals(learnInfo.getProtocolVersion(), 0x10000); + Assert.assertEquals(learnInfo.getServerid(), 0); + + // We are simulating an established leader, so the epoch is 1 + qp.setType(Leader.LEADERINFO); + qp.setZxid(ZxidUtils.makeZxid(1, 0)); + byte protoBytes[] = new byte[4]; + ByteBuffer.wrap(protoBytes).putInt(0x10000); + qp.setData(protoBytes); + oa.writeRecord(qp, null); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACKEPOCH, qp.getType()); + Assert.assertEquals(0, qp.getZxid()); + Assert.assertEquals(ZxidUtils.makeZxid(0, 0), ByteBuffer.wrap(qp.getData()).getInt()); + Assert.assertEquals(1, f.self.getAcceptedEpoch()); + Assert.assertEquals(0, f.self.getCurrentEpoch()); + + // Send a diff + qp.setType(Leader.DIFF); + qp.setData(new byte[0]); + qp.setZxid(zkDb.getDataTreeLastProcessedZxid()); + oa.writeRecord(qp, null); + final long createSessionZxid = ZxidUtils.makeZxid(1, 2); + proposeNewSession(qp, createSessionZxid, 0x333); + oa.writeRecord(qp, null); + qp.setType(Leader.COMMIT); + qp.setZxid(createSessionZxid); + oa.writeRecord(qp, null); + qp.setType(Leader.NEWLEADER); + qp.setZxid(ZxidUtils.makeZxid(1, 0)); + oa.writeRecord(qp, null); + qp.setType(Leader.UPTODATE); + qp.setZxid(0); + oa.writeRecord(qp, null); + + // Read the uptodate ack + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACK, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + + + // Get the ack of the new leader + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACK, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + Assert.assertEquals(1, f.self.getAcceptedEpoch()); + Assert.assertEquals(1, f.self.getCurrentEpoch()); + + //Wait for the transactions to be written out. The thread that writes them out + // does not send anything back when it is done. + long start = System.currentTimeMillis(); + while (createSessionZxid != f.fzk.getLastProcessedZxid() && (System.currentTimeMillis() - start) < 50) { + Thread.sleep(1); + } + + Assert.assertEquals(createSessionZxid, f.fzk.getLastProcessedZxid()); + + // Make sure the data was recorded in the filesystem ok + ZKDatabase zkDb2 = new ZKDatabase(new FileTxnSnapLog(logDir, snapDir)); + start = System.currentTimeMillis(); + zkDb2.loadDataBase(); + while (zkDb2.getSessionWithTimeOuts().isEmpty() && (System.currentTimeMillis() - start) < 50) { + Thread.sleep(1); + zkDb2.loadDataBase(); + } + LOG.info("zkdb2 sessions:" + zkDb2.getSessions()); + LOG.info("zkdb2 with timeouts:" + zkDb2.getSessionWithTimeOuts()); + Assert.assertNotNull(zkDb2.getSessionWithTimeOuts().get(4L)); + //Snapshot was never taken during very simple sync + verify(f.zk, never()).takeSnapshot(); + } finally { + recursiveDelete(tmpDir); + } + + } + + private void proposeNewSession(QuorumPacket qp, long zxid, long sessionId) throws IOException { + qp.setType(Leader.PROPOSAL); + qp.setZxid(zxid); + TxnHeader hdr = new TxnHeader(4, 1414, qp.getZxid(), 55, ZooDefs.OpCode.createSession); + CreateSessionTxn cst = new CreateSessionTxn(30000); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputArchive boa = BinaryOutputArchive.getArchive(baos); + boa.writeRecord(hdr, null); + boa.writeRecord(cst, null); + qp.setData(baos.toByteArray()); + } + }); + } + @Test public void testNormalRun() throws Exception { - testConversation(new LeaderConversation() { - public void converseWithLeader(InputArchive ia, OutputArchive oa) + testLeaderConversation(new LeaderConversation() { + public void converseWithLeader(InputArchive ia, OutputArchive oa, Leader l) throws IOException { + Assert.assertEquals(0, l.self.getAcceptedEpoch()); + Assert.assertEquals(0, l.self.getCurrentEpoch()); + /* we test a normal run. everything should work out well. */ LearnerInfo li = new LearnerInfo(1, 0x10000); byte liBytes[] = new byte[12]; @@ -186,30 +909,283 @@ public void converseWithLeader(InputArchive ia, OutputArchive oa) QuorumPacket qp = new QuorumPacket(Leader.FOLLOWERINFO, 0, liBytes, null); oa.writeRecord(qp, null); + readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.LEADERINFO, qp.getType()); Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); Assert.assertEquals(ByteBuffer.wrap(qp.getData()).getInt(), 0x10000); + Assert.assertEquals(1, l.self.getAcceptedEpoch()); + Assert.assertEquals(0, l.self.getCurrentEpoch()); + qp = new QuorumPacket(Leader.ACKEPOCH, 0, new byte[4], null); oa.writeRecord(qp, null); + readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.DIFF, qp.getType()); + readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.NEWLEADER, qp.getType()); Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + Assert.assertEquals(1, l.self.getAcceptedEpoch()); + Assert.assertEquals(1, l.self.getCurrentEpoch()); + qp = new QuorumPacket(Leader.ACK, qp.getZxid(), null, null); oa.writeRecord(qp, null); + readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.UPTODATE, qp.getType()); } }); } - + + @Test + public void testTxnTimeout() throws Exception { + testLeaderConversation(new LeaderConversation() { + public void converseWithLeader(InputArchive ia, OutputArchive oa, Leader l) + throws IOException, InterruptedException, org.apache.zookeeper.server.quorum.Leader.XidRolloverException { + Assert.assertEquals(0, l.self.getAcceptedEpoch()); + Assert.assertEquals(0, l.self.getCurrentEpoch()); + + LearnerInfo li = new LearnerInfo(1, 0x10000); + byte liBytes[] = new byte[20]; + ByteBufferOutputStream.record2ByteBuffer(li, + ByteBuffer.wrap(liBytes)); + QuorumPacket qp = new QuorumPacket(Leader.FOLLOWERINFO, 0, + liBytes, null); + oa.writeRecord(qp, null); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.LEADERINFO, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + Assert.assertEquals(ByteBuffer.wrap(qp.getData()).getInt(), + 0x10000); + Assert.assertEquals(1, l.self.getAcceptedEpoch()); + Assert.assertEquals(0, l.self.getCurrentEpoch()); + + qp = new QuorumPacket(Leader.ACKEPOCH, 0, new byte[4], null); + oa.writeRecord(qp, null); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.DIFF, qp.getType()); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.NEWLEADER, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + Assert.assertEquals(1, l.self.getAcceptedEpoch()); + Assert.assertEquals(1, l.self.getCurrentEpoch()); + + qp = new QuorumPacket(Leader.ACK, qp.getZxid(), null, null); + oa.writeRecord(qp, null); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.UPTODATE, qp.getType()); + + l.propose(createNodeRequest(l.zk.getZxid())); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.PROPOSAL, qp.getType()); + + LOG.info("Proposal sent."); + + for (int i = 0; i < (2 * SYNC_LIMIT) + 2; i++) { + try { + ia.readRecord(qp, null); + LOG.info("Ping received: " + i); + qp = new QuorumPacket(Leader.PING, qp.getZxid(), "".getBytes(), null); + oa.writeRecord(qp, null); + } catch (EOFException e) { + return; + } + } + + Assert.fail("Connection hasn't been closed by leader after transaction times out."); + } + + private Request createNodeRequest(long zxid) throws IOException { + TxnHeader hdr = new TxnHeader(1, 1, zxid, 1, ZooDefs.OpCode.create); + CreateTxn ct = new CreateTxn("/foo", "data".getBytes(), null, true, 0); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputArchive boa = BinaryOutputArchive.getArchive(baos); + boa.writeRecord(hdr, "header"); + boa.writeRecord(ct, "txn"); + baos.close(); + Request rq = new Request(null, 1, 1, ZooDefs.OpCode.create, ByteBuffer.wrap(baos.toByteArray()), null); + rq.zxid = zxid; + rq.hdr = hdr; + rq.txn = ct; + return rq; + } + }); + } + + private void deserializeSnapshot(InputArchive ia) + throws IOException { + ZKDatabase zkdb = new ZKDatabase(null); + zkdb.deserializeSnapshot(ia); + String signature = ia.readString("signature"); + assertEquals("BenWasHere", signature); + } + + @Test + public void testNormalObserverRun() throws Exception { + testObserverConversation(new ObserverConversation() { + @Override + public void converseWithObserver(InputArchive ia, OutputArchive oa, + Observer o) throws Exception { + File tmpDir = File.createTempFile("test", "dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + File logDir = o.zk.getTxnLogFactory().getDataDir().getParentFile(); + File snapDir = o.zk.getTxnLogFactory().getSnapDir().getParentFile(); + try { + Assert.assertEquals(0, o.self.getAcceptedEpoch()); + Assert.assertEquals(0, o.self.getCurrentEpoch()); + + // Setup a database with a single /foo node + ZKDatabase zkDb = new ZKDatabase(new FileTxnSnapLog(tmpDir, tmpDir)); + final long foo1Zxid = ZxidUtils.makeZxid(1, 1); + final long foo2Zxid = ZxidUtils.makeZxid(1, 2); + zkDb.processTxn(new TxnHeader(13, 1313, foo1Zxid, 33, + ZooDefs.OpCode.create), new CreateTxn("/foo1", + "data1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, + false, 1)); + zkDb.processTxn(new TxnHeader(13, 1313, foo2Zxid, 33, + ZooDefs.OpCode.create), new CreateTxn("/foo2", + "data1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, + false, 1)); + Stat stat = new Stat(); + Assert.assertEquals("data1", + new String(zkDb.getData("/foo1", stat, null))); + Assert.assertEquals("data1", + new String(zkDb.getData("/foo2", stat, null))); + + QuorumPacket qp = new QuorumPacket(); + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.OBSERVERINFO, qp.getType()); + Assert.assertEquals(qp.getZxid(), 0); + LearnerInfo learnInfo = new LearnerInfo(); + ByteBufferInputStream.byteBuffer2Record( + ByteBuffer.wrap(qp.getData()), learnInfo); + Assert.assertEquals(learnInfo.getProtocolVersion(), 0x10000); + Assert.assertEquals(learnInfo.getServerid(), 0); + + // We are simulating an established leader, so the epoch is 1 + qp.setType(Leader.LEADERINFO); + qp.setZxid(ZxidUtils.makeZxid(1, 0)); + byte protoBytes[] = new byte[4]; + ByteBuffer.wrap(protoBytes).putInt(0x10000); + qp.setData(protoBytes); + oa.writeRecord(qp, null); + + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACKEPOCH, qp.getType()); + Assert.assertEquals(0, qp.getZxid()); + Assert.assertEquals(ZxidUtils.makeZxid(0, 0), ByteBuffer + .wrap(qp.getData()).getInt()); + Assert.assertEquals(1, o.self.getAcceptedEpoch()); + Assert.assertEquals(0, o.self.getCurrentEpoch()); + + // Send the snapshot we created earlier + qp.setType(Leader.SNAP); + qp.setData(new byte[0]); + qp.setZxid(zkDb.getDataTreeLastProcessedZxid()); + oa.writeRecord(qp, null); + zkDb.serializeSnapshot(oa); + oa.writeString("BenWasHere", null); + qp.setType(Leader.NEWLEADER); + qp.setZxid(ZxidUtils.makeZxid(1, 0)); + oa.writeRecord(qp, null); + + // Get the ack of the new leader + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACK, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + Assert.assertEquals(1, o.self.getAcceptedEpoch()); + Assert.assertEquals(1, o.self.getCurrentEpoch()); + + Assert.assertEquals(foo2Zxid, o.zk.getLastProcessedZxid()); + + // Make sure the data was recorded in the filesystem ok + ZKDatabase zkDb2 = new ZKDatabase(new FileTxnSnapLog( + logDir, snapDir)); + long lastZxid = zkDb2.loadDataBase(); + Assert.assertEquals("data1", + new String(zkDb2.getData("/foo1", stat, null))); + Assert.assertEquals(foo2Zxid, lastZxid); + + // Register watch + TrackerWatcher watcher = new TrackerWatcher(); + Assert.assertEquals("data1", new String(o.zk + .getZKDatabase().getData("/foo2", stat, watcher))); + + // Propose /foo1 update + long proposalZxid = ZxidUtils.makeZxid(1, 1000); + proposeSetData(qp, "/foo1", proposalZxid, "data2", 2); + oa.writeRecord(qp, null); + + // Commit /foo1 update + qp.setType(Leader.COMMIT); + qp.setZxid(proposalZxid); + oa.writeRecord(qp, null); + + // Inform /foo2 update + long informZxid = ZxidUtils.makeZxid(1, 1001); + proposeSetData(qp, "/foo2", informZxid, "data2", 2); + qp.setType(Leader.INFORM); + oa.writeRecord(qp, null); + + qp.setType(Leader.UPTODATE); + qp.setZxid(0); + oa.writeRecord(qp, null); + + // Read the uptodate ack + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.ACK, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + + // Data should get updated + watcher.waitForChange(); + Assert.assertEquals("data2", new String(o.zk + .getZKDatabase().getData("/foo1", stat, null))); + Assert.assertEquals("data2", new String(o.zk + .getZKDatabase().getData("/foo2", stat, null))); + + // Shutdown sequence guarantee that all pending requests + // in sync request processor get flush to disk + o.zk.shutdown(); + + zkDb2 = new ZKDatabase(new FileTxnSnapLog(logDir, snapDir)); + lastZxid = zkDb2.loadDataBase(); + Assert.assertEquals("data2", new String(zkDb2.getData("/foo1", stat, null))); + Assert.assertEquals("data2", new String(zkDb2.getData("/foo2", stat, null))); + Assert.assertEquals(informZxid, lastZxid); + } finally { + recursiveDelete(tmpDir); + } + + } + + private void proposeSetData(QuorumPacket qp, String path, + long zxid, String data, int version) throws IOException { + qp.setType(Leader.PROPOSAL); + qp.setZxid(zxid); + TxnHeader hdr = new TxnHeader(4, 1414, qp.getZxid(), 55, + ZooDefs.OpCode.setData); + SetDataTxn sdt = new SetDataTxn(path, data.getBytes(), version); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputArchive boa = BinaryOutputArchive.getArchive(baos); + boa.writeRecord(hdr, null); + boa.writeRecord(sdt, null); + qp.setData(baos.toByteArray()); + } + }); + } + @Test public void testLeaderBehind() throws Exception { - testConversation(new LeaderConversation() { - public void converseWithLeader(InputArchive ia, OutputArchive oa) + testLeaderConversation(new LeaderConversation() { + public void converseWithLeader(InputArchive ia, OutputArchive oa, Leader l) throws IOException { /* we test a normal run. everything should work out well. */ LearnerInfo li = new LearnerInfo(1, 0x10000); @@ -232,53 +1208,282 @@ public void converseWithLeader(InputArchive ia, OutputArchive oa) readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.NEWLEADER, qp.getType()); Assert.assertEquals(ZxidUtils.makeZxid(21, 0), qp.getZxid()); + qp = new QuorumPacket(Leader.ACK, qp.getZxid(), null, null); oa.writeRecord(qp, null); + readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.UPTODATE, qp.getType()); } }); } + /** + * Tests that when a quorum of followers send LearnerInfo but do not ack the epoch (which is sent + * by the leader upon receipt of LearnerInfo from a quorum), the leader does not start using this epoch + * as it would in the normal case (when a quorum do ack the epoch). This tests ZK-1192 + * @throws Exception + */ + @Test + public void testAbandonBeforeACKEpoch() throws Exception { + testLeaderConversation(new LeaderConversation() { + public void converseWithLeader(InputArchive ia, OutputArchive oa, Leader l) + throws IOException, InterruptedException { + /* we test a normal run. everything should work out well. */ + LearnerInfo li = new LearnerInfo(1, 0x10000); + byte liBytes[] = new byte[12]; + ByteBufferOutputStream.record2ByteBuffer(li, + ByteBuffer.wrap(liBytes)); + QuorumPacket qp = new QuorumPacket(Leader.FOLLOWERINFO, 0, + liBytes, null); + oa.writeRecord(qp, null); + readPacketSkippingPing(ia, qp); + Assert.assertEquals(Leader.LEADERINFO, qp.getType()); + Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); + Assert.assertEquals(ByteBuffer.wrap(qp.getData()).getInt(), + 0x10000); + Thread.sleep(l.self.getInitLimit()*l.self.getTickTime() + 5000); + + // The leader didn't get a quorum of acks - make sure that leader's current epoch is not advanced + Assert.assertEquals(0, l.self.getCurrentEpoch()); + } + }); + } + + /** + * verify that a peer with dirty snapshot joining an established cluster + * does not go into an inconsistent state. + * + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-1558} + */ + @Test + public void testDirtySnapshot() + throws IOException, + InterruptedException, + KeeperException, + NoSuchFieldException, + IllegalAccessException { + Socket pair[] = getSocketPair(); + Socket leaderSocket = pair[0]; + Socket followerSocket = pair[1]; + File tmpDir = File.createTempFile("test", "dir"); + tmpDir.delete(); + tmpDir.mkdir(); + LeadThread leadThread = null; + Leader leader = null; + try { + // Setup a database with two znodes + FileTxnSnapLog snapLog = new FileTxnSnapLog(tmpDir, tmpDir); + ZKDatabase zkDb = new ZKDatabase(snapLog); + + long zxid = ZxidUtils.makeZxid(0, 1); + String path = "/foo"; + zkDb.processTxn(new TxnHeader(13,1000,zxid,30,ZooDefs.OpCode.create), + new CreateTxn(path, "fpjwasalsohere".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, false, 1)); + Stat stat = new Stat(); + Assert.assertEquals("fpjwasalsohere", new String(zkDb.getData(path, stat, null))); + + // Close files + snapLog.close(); + + QuorumPeer peer = createQuorumPeer(tmpDir); + + leader = createLeader(tmpDir, peer); + peer.leader = leader; + + // Set the last accepted epoch and current epochs to be 1 + peer.setAcceptedEpoch(0); + peer.setCurrentEpoch(0); + + leadThread = new LeadThread(leader); + leadThread.start(); + + while(leader.cnxAcceptor == null || !leader.cnxAcceptor.isAlive()) { + Thread.sleep(20); + } + + leader.shutdown("Shutting down the leader"); + + // Check if there is a valid snapshot (we better not have it) + File snapDir = new File (tmpDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION); + List files = Util.sortDataDir(snapDir.listFiles(),"snapshot", false); + + for (File f : files) { + try { + Assert.assertFalse("Found a valid snapshot", Util.isValidSnapshot(f)); + } catch (IOException e) { + LOG.info("invalid snapshot " + f, e); + } + } + + } finally { + if (leader != null) { + leader.shutdown("end of test"); + } + if (leadThread != null) { + leadThread.interrupt(); + leadThread.join(); + } + recursiveDelete(tmpDir); + } + } private void recursiveDelete(File file) { if (file.isFile()) { file.delete(); } else { - for(File c: file.listFiles()) { - recursiveDelete(c); + // might return null if deleted out from under us... + File[] files = file.listFiles(); + if (files != null) { + for(File c: files) { + recursiveDelete(c); + } } file.delete(); } } private Leader createLeader(File tmpDir, QuorumPeer peer) - throws IOException, NoSuchFieldException, IllegalAccessException { + throws IOException, NoSuchFieldException, IllegalAccessException{ + LeaderZooKeeperServer zk = prepareLeader(tmpDir, peer); + return new Leader(peer, zk); + } + + private Leader createMockLeader(File tmpDir, QuorumPeer peer) + throws IOException, NoSuchFieldException, IllegalAccessException{ + LeaderZooKeeperServer zk = prepareLeader(tmpDir, peer); + return new MockLeader(peer, zk); + } + + private LeaderZooKeeperServer prepareLeader(File tmpDir, QuorumPeer peer) + throws IOException, NoSuchFieldException, IllegalAccessException { FileTxnSnapLog logFactory = new FileTxnSnapLog(tmpDir, tmpDir); peer.setTxnFactory(logFactory); Field addrField = peer.getClass().getDeclaredField("myQuorumAddr"); addrField.setAccessible(true); - addrField.set(peer, new InetSocketAddress(33556)); + addrField.set(peer, new InetSocketAddress(PortAssignment.unique())); ZKDatabase zkDb = new ZKDatabase(logFactory); - DataTreeBuilder treeBuilder = new MockDataTreeBuilder(); - LeaderZooKeeperServer zk = new LeaderZooKeeperServer(logFactory, peer, treeBuilder, zkDb); - return new Leader(peer, zk); + LeaderZooKeeperServer zk = new LeaderZooKeeperServer(logFactory, peer, new ZooKeeperServer.BasicDataTreeBuilder(), zkDb); + return zk; + } + + static class ConversableFollower extends Follower { + + ConversableFollower(QuorumPeer self, FollowerZooKeeperServer zk) { + super(self, zk); + } + + QuorumServer leaderQuorumServer; + public void setLeaderQuorumServer(QuorumServer quorumServer) { + leaderQuorumServer = quorumServer; + } + + @Override + protected QuorumServer findLeader() { + return leaderQuorumServer; + } + } + private ConversableFollower createFollower(File tmpDir, QuorumPeer peer) + throws IOException { + FileTxnSnapLog logFactory = new FileTxnSnapLog(tmpDir, tmpDir); + peer.setTxnFactory(logFactory); + ZKDatabase zkDb = new ZKDatabase(logFactory); + FollowerZooKeeperServer zk = new FollowerZooKeeperServer(logFactory, peer, new ZooKeeperServer.BasicDataTreeBuilder(), zkDb); + peer.setZKDatabase(zkDb); + return new ConversableFollower(peer, zk); + } + + static class ConversableObserver extends Observer { + + ConversableObserver(QuorumPeer self, ObserverZooKeeperServer zk) { + super(self, zk); + } + + QuorumServer leaderQuorumServer; + public void setLeaderQuorumServer(QuorumServer quorumServer) { + leaderQuorumServer = quorumServer; + } + + @Override + protected QuorumServer findLeader() { + return leaderQuorumServer; + } + } + + private ConversableObserver createObserver(File tmpDir, QuorumPeer peer) + throws IOException { + FileTxnSnapLog logFactory = new FileTxnSnapLog(tmpDir, tmpDir); + peer.setTxnFactory(logFactory); + DataTreeBuilder treeBuilder = new ZooKeeperServer.BasicDataTreeBuilder(); + ZKDatabase zkDb = new ZKDatabase(logFactory); + ObserverZooKeeperServer zk = new ObserverZooKeeperServer(logFactory, peer, treeBuilder, zkDb); + peer.setZKDatabase(zkDb); + return new ConversableObserver(peer, zk); } + + private QuorumPeer createQuorumPeer(File tmpDir) throws IOException, FileNotFoundException { - QuorumPeer peer = new QuorumPeer(); - peer.syncLimit = 2; + QuorumPeer peer = QuorumPeer.testingQuorumPeer(); + peer.syncLimit = SYNC_LIMIT; peer.initLimit = 2; peer.tickTime = 2000; peer.quorumPeers = new HashMap(); - peer.quorumPeers.put(1L, new QuorumServer(0, new InetSocketAddress(33221))); - peer.quorumPeers.put(1L, new QuorumServer(1, new InetSocketAddress(33223))); + peer.quorumPeers.put(1L, new QuorumServer(0, "0.0.0.0", 33221, 0, null)); + peer.quorumPeers.put(1L, new QuorumServer(1, "0.0.0.0", 33223, 0, null)); peer.setQuorumVerifier(new QuorumMaj(3)); peer.setCnxnFactory(new NullServerCnxnFactory()); File version2 = new File(tmpDir, "version-2"); version2.mkdir(); - new FileOutputStream(new File(version2, "currentEpoch")).write("0\n".getBytes()); - new FileOutputStream(new File(version2, "acceptedEpoch")).write("0\n".getBytes()); + FileOutputStream fos; + fos = new FileOutputStream(new File(version2, "currentEpoch")); + fos.write("0\n".getBytes()); + fos.close(); + fos = new FileOutputStream(new File(version2, "acceptedEpoch")); + fos.write("0\n".getBytes()); + fos.close(); return peer; } + + private String readContentsOfFile(File f) throws IOException { + return new BufferedReader(new FileReader(f)).readLine(); + } + + @Test + public void testInitialAcceptedCurrent() throws Exception { + File tmpDir = File.createTempFile("test", ".dir", testData); + tmpDir.delete(); + tmpDir.mkdir(); + try { + FileTxnSnapLog logFactory = new FileTxnSnapLog(tmpDir, tmpDir); + File version2 = new File(tmpDir, "version-2"); + version2.mkdir(); + long zxid = ZxidUtils.makeZxid(3, 3); + + TxnHeader hdr = new TxnHeader(1, 1, zxid, 1, ZooDefs.OpCode.error); + ErrorTxn txn = new ErrorTxn(1); + byte[] buf = Util.marshallTxnEntry(hdr, txn); + Request req = new Request(null, 1, 1, ZooDefs.OpCode.error, + ByteBuffer.wrap(buf), null); + req.hdr = hdr; + req.txn = txn; + logFactory.append(req); + logFactory.commit(); + ZKDatabase zkDb = new ZKDatabase(logFactory); + QuorumPeer peer = QuorumPeer.testingQuorumPeer(); + peer.setZKDatabase(zkDb); + peer.setTxnFactory(logFactory); + peer.getLastLoggedZxid(); + Assert.assertEquals(3, peer.getAcceptedEpoch()); + Assert.assertEquals(3, peer.getCurrentEpoch()); + Assert.assertEquals(3, Integer + .parseInt(readContentsOfFile(new File(version2, + QuorumPeer.CURRENT_EPOCH_FILENAME)))); + Assert.assertEquals(3, Integer + .parseInt(readContentsOfFile(new File(version2, + QuorumPeer.ACCEPTED_EPOCH_FILENAME)))); + } finally { + recursiveDelete(tmpDir); + } + } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/auth/KerberosSecurityTestcase.java b/src/java/test/org/apache/zookeeper/server/quorum/auth/KerberosSecurityTestcase.java new file mode 100644 index 00000000000..9617c70b332 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/KerberosSecurityTestcase.java @@ -0,0 +1,120 @@ +/** + * 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.auth; + +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +/* + * This code is originally from HDFS, see the similarly named file there + * in case of bug fixing, history, etc. + * + * Branch : trunk + * Github Revision: 1d1ab587e4e92ce3aea4cb144811f69145cb3b33 + */ + +/** + * KerberosSecurityTestcase provides a base class for using MiniKdc with other + * test cases. KerberosSecurityTestcase starts the MiniKdc (@Before) before + * running tests, and stop the MiniKdc (@After) after the testcases, using + * default settings (working dir and kdc configurations). + *

                                                  + * Users can directly inherit this class and implement their own test functions + * using the default settings, or override functions getTestDir() and + * createMiniKdcConf() to provide new settings. + */ +public class KerberosSecurityTestcase extends QuorumAuthTestBase { + private static MiniKdc kdc; + private static File workDir; + private static Properties conf; + + @BeforeClass + public static void setUpSasl() throws Exception { + startMiniKdc(); + } + + @AfterClass + public static void tearDownSasl() throws Exception { + stopMiniKdc(); + FileUtils.deleteQuietly(workDir); + } + + public static void startMiniKdc() throws Exception { + createTestDir(); + createMiniKdcConf(); + + kdc = new MiniKdc(conf, workDir); + kdc.start(); + } + + /** + * Create a working directory, it should be the build directory. Under this + * directory an ApacheDS working directory will be created, this directory + * will be deleted when the MiniKdc stops. + * + * @throws IOException + */ + public static void createTestDir() throws IOException { + workDir = createTmpDir( + new File(System.getProperty("build.test.dir", "build"))); + } + + static File createTmpDir(File parentDir) throws IOException { + File tmpFile = File.createTempFile("test", ".junit", parentDir); + // don't delete tmpFile - this ensures we don't attempt to create + // a tmpDir with a duplicate name + File tmpDir = new File(tmpFile + ".dir"); + // never true if tmpfile does it's job + Assert.assertFalse(tmpDir.exists()); + Assert.assertTrue(tmpDir.mkdirs()); + return tmpDir; + } + + /** + * Create a Kdc configuration + */ + public static void createMiniKdcConf() { + conf = MiniKdc.createConf(); + } + + public static void stopMiniKdc() { + if (kdc != null) { + kdc.stop(); + } + } + + public static MiniKdc getKdc() { + return kdc; + } + + public static File getWorkDir() { + return workDir; + } + + public static Properties getConf() { + return conf; + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/auth/KerberosTestUtils.java b/src/java/test/org/apache/zookeeper/server/quorum/auth/KerberosTestUtils.java new file mode 100644 index 00000000000..4a75f8336c1 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/KerberosTestUtils.java @@ -0,0 +1,76 @@ +/** + * 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.auth; + +import java.io.File; +import java.util.UUID; + +import org.apache.zookeeper.util.SecurityUtils; + +public class KerberosTestUtils { + private static String keytabFile = new File(System.getProperty("test.dir", "build"), UUID.randomUUID().toString()) + .getAbsolutePath(); + + public static String getRealm() { + return "EXAMPLE.COM"; + } + + public static String getLearnerPrincipal() { + return "learner@EXAMPLE.COM"; + } + + public static String getServerPrincipal() { + return "zkquorum/localhost@EXAMPLE.COM"; + } + + public static String getHostLearnerPrincipal() { + return "learner/_HOST@EXAMPLE.COM"; + } + + public static String getHostServerPrincipal() { + return "zkquorum/_HOST@EXAMPLE.COM"; + } + + public static String getHostNamedLearnerPrincipal(String myHostname) { + return "learner/" + myHostname + "@EXAMPLE.COM"; + } + + public static String getKeytabFile() { + return keytabFile; + } + + public static String replaceHostPattern(String principal) { + String[] components = principal.split("[/@]"); + if (components == null || components.length < 2 + || !components[1].equals(SecurityUtils.QUORUM_HOSTNAME_PATTERN)) { + return principal; + } else { + return replacePattern(components, "localhost"); + } + } + + public static String replacePattern(String[] components, String hostname) { + if (components.length == 3) { + return components[0] + "/" + hostname.toLowerCase() + "@" + + components[2]; + } else { + return components[0] + "/" + hostname.toLowerCase(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/auth/MiniKdc.java b/src/java/test/org/apache/zookeeper/server/quorum/auth/MiniKdc.java new file mode 100644 index 00000000000..ebe541db554 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/MiniKdc.java @@ -0,0 +1,574 @@ +/** + * 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.auth; + +import org.apache.commons.io.Charsets; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.text.StrSubstitutor; +import org.apache.directory.api.ldap.model.schema.SchemaManager; +import org.apache.directory.api.ldap.schemaextractor.SchemaLdifExtractor; +import org.apache.directory.api.ldap.schemaextractor.impl.DefaultSchemaLdifExtractor; +import org.apache.directory.api.ldap.schemaloader.LdifSchemaLoader; +import org.apache.directory.api.ldap.schemamanager.impl.DefaultSchemaManager; +import org.apache.directory.server.constants.ServerDNConstants; +import org.apache.directory.server.core.DefaultDirectoryService; +import org.apache.directory.server.core.api.CacheService; +import org.apache.directory.server.core.api.DirectoryService; +import org.apache.directory.server.core.api.InstanceLayout; +import org.apache.directory.server.core.api.schema.SchemaPartition; +import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor; +import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex; +import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition; +import org.apache.directory.server.core.partition.ldif.LdifPartition; +import org.apache.directory.server.kerberos.KerberosConfig; +import org.apache.directory.server.kerberos.kdc.KdcServer; +import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory; +import org.apache.directory.server.kerberos.shared.keytab.Keytab; +import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry; +import org.apache.directory.server.protocol.shared.transport.TcpTransport; +import org.apache.directory.server.protocol.shared.transport.UdpTransport; +import org.apache.directory.server.xdbm.Index; +import org.apache.directory.shared.kerberos.KerberosTime; +import org.apache.directory.shared.kerberos.codec.types.EncryptionType; +import org.apache.directory.shared.kerberos.components.EncryptionKey; +import org.apache.directory.api.ldap.model.entry.DefaultEntry; +import org.apache.directory.api.ldap.model.entry.Entry; +import org.apache.directory.api.ldap.model.ldif.LdifEntry; +import org.apache.directory.api.ldap.model.ldif.LdifReader; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.api.ldap.model.schema.registries.SchemaLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.UUID; + +/** + * Mini KDC based on Apache Directory Server that can be embedded in testcases + * or used from command line as a standalone KDC. + *

                                                  + * From within testcases: + *

                                                  + * MiniKdc sets 2 System properties when started and un-sets them when stopped: + *

                                                    + *
                                                  • java.security.krb5.conf: set to the MiniKDC real/host/port
                                                  • + *
                                                  • sun.security.krb5.debug: set to the debug value provided in the + * configuration
                                                  • + *
                                                  + * Because of this, multiple MiniKdc instances cannot be started in parallel. + * For example, running testcases in parallel that start a KDC each. To + * accomplish this a single MiniKdc should be used for all testcases running + * in parallel. + *

                                                  + * MiniKdc default configuration values are: + *

                                                    + *
                                                  • org.name=EXAMPLE (used to create the REALM)
                                                  • + *
                                                  • org.domain=COM (used to create the REALM)
                                                  • + *
                                                  • kdc.bind.address=localhost
                                                  • + *
                                                  • kdc.port=0 (ephemeral port)
                                                  • + *
                                                  • instance=DefaultKrbServer
                                                  • + *
                                                  • max.ticket.lifetime=86400000 (1 day)
                                                  • + *
                                                  • max.renewable.lifetime=604800000 (7 days)
                                                  • + *
                                                  • transport=TCP
                                                  • + *
                                                  • debug=false
                                                  • + *
                                                  + * The generated krb5.conf forces TCP connections. + */ +/* + * This code is originally from HDFS, see the file name MiniKdc there + * in case of bug fixing, history, etc. + * + * Branch : trunk + * Github Revision: 42e3a805117ff7cb054c2442f7b0e0cc54be63ad + */ +public class MiniKdc { + + public static final String JAVA_SECURITY_KRB5_CONF = + "java.security.krb5.conf"; + public static final String SUN_SECURITY_KRB5_DEBUG = + "sun.security.krb5.debug"; + private static final File testData = new File( + System.getProperty("test.data.dir", "build/test/data")); + + public static void main(String[] args) throws Exception { + if (args.length < 4) { + System.out.println("Arguments: " + + " []+"); + System.exit(1); + } + File workDir = new File(args[0]); + if (!workDir.exists()) { + throw new RuntimeException("Specified work directory does not exists: " + + workDir.getAbsolutePath()); + } + Properties conf = createConf(); + File file = new File(args[1]); + if (!file.exists()) { + throw new RuntimeException("Specified configuration does not exists: " + + file.getAbsolutePath()); + } + Properties userConf = new Properties(); + InputStreamReader r = null; + try { + r = new InputStreamReader(new FileInputStream(file), Charsets.UTF_8); + userConf.load(r); + } finally { + if (r != null) { + r.close(); + } + } + for (Map.Entry entry : userConf.entrySet()) { + conf.put(entry.getKey(), entry.getValue()); + } + final MiniKdc miniKdc = new MiniKdc(conf, workDir); + miniKdc.start(); + File krb5conf = new File(workDir, "krb5.conf"); + if (miniKdc.getKrb5conf().renameTo(krb5conf)) { + File keytabFile = new File(args[2]).getAbsoluteFile(); + String[] principals = new String[args.length - 3]; + System.arraycopy(args, 3, principals, 0, args.length - 3); + miniKdc.createPrincipal(keytabFile, principals); + System.out.println(); + System.out.println("Standalone MiniKdc Running"); + System.out.println("---------------------------------------------------"); + System.out.println(" Realm : " + miniKdc.getRealm()); + System.out.println(" Running at : " + miniKdc.getHost() + ":" + + miniKdc.getHost()); + System.out.println(" krb5conf : " + krb5conf); + System.out.println(); + System.out.println(" created keytab : " + keytabFile); + System.out.println(" with principals : " + Arrays.asList(principals)); + System.out.println(); + System.out.println(" Do or kill to stop it"); + System.out.println("---------------------------------------------------"); + System.out.println(); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + miniKdc.stop(); + } + }); + } else { + throw new RuntimeException("Cannot rename KDC's krb5conf to " + + krb5conf.getAbsolutePath()); + } + } + + private static final Logger LOG = LoggerFactory.getLogger(MiniKdc.class); + + public static final String ORG_NAME = "org.name"; + public static final String ORG_DOMAIN = "org.domain"; + public static final String KDC_BIND_ADDRESS = "kdc.bind.address"; + public static final String KDC_PORT = "kdc.port"; + public static final String INSTANCE = "instance"; + public static final String MAX_TICKET_LIFETIME = "max.ticket.lifetime"; + public static final String MAX_RENEWABLE_LIFETIME = "max.renewable.lifetime"; + public static final String TRANSPORT = "transport"; + public static final String DEBUG = "debug"; + + private static final Set PROPERTIES = new HashSet(); + private static final Properties DEFAULT_CONFIG = new Properties(); + + static { + PROPERTIES.add(ORG_NAME); + PROPERTIES.add(ORG_DOMAIN); + PROPERTIES.add(KDC_BIND_ADDRESS); + PROPERTIES.add(KDC_BIND_ADDRESS); + PROPERTIES.add(KDC_PORT); + PROPERTIES.add(INSTANCE); + PROPERTIES.add(TRANSPORT); + PROPERTIES.add(MAX_TICKET_LIFETIME); + PROPERTIES.add(MAX_RENEWABLE_LIFETIME); + + DEFAULT_CONFIG.setProperty(KDC_BIND_ADDRESS, "localhost"); + DEFAULT_CONFIG.setProperty(KDC_PORT, "0"); + DEFAULT_CONFIG.setProperty(INSTANCE, "DefaultKrbServer"); + DEFAULT_CONFIG.setProperty(ORG_NAME, "EXAMPLE"); + DEFAULT_CONFIG.setProperty(ORG_DOMAIN, "COM"); + DEFAULT_CONFIG.setProperty(TRANSPORT, "TCP"); + DEFAULT_CONFIG.setProperty(MAX_TICKET_LIFETIME, "86400000"); + DEFAULT_CONFIG.setProperty(MAX_RENEWABLE_LIFETIME, "604800000"); + DEFAULT_CONFIG.setProperty(DEBUG, "true"); + } + + /** + * Convenience method that returns MiniKdc default configuration. + *

                                                  + * The returned configuration is a copy, it can be customized before using + * it to create a MiniKdc. + * @return a MiniKdc default configuration. + */ + public static Properties createConf() { + return (Properties) DEFAULT_CONFIG.clone(); + } + + private Properties conf; + private DirectoryService ds; + private KdcServer kdc; + private int port; + private String realm; + private File workDir; + private File krb5conf; + + /** + * Creates a MiniKdc. + * + * @param conf MiniKdc configuration. + * @param workDir working directory, it should be the build directory. Under + * this directory an ApacheDS working directory will be created, this + * directory will be deleted when the MiniKdc stops. + * @throws Exception thrown if the MiniKdc could not be created. + */ + public MiniKdc(Properties conf, File workDir) throws Exception { + if (!conf.keySet().containsAll(PROPERTIES)) { + Set missingProperties = new HashSet(PROPERTIES); + missingProperties.removeAll(conf.keySet()); + throw new IllegalArgumentException("Missing configuration properties: " + + missingProperties); + } + this.workDir = new File(workDir, Long.toString(System.currentTimeMillis())); + if (!this.workDir.exists() + && !this.workDir.mkdirs()) { + throw new RuntimeException("Cannot create directory " + this.workDir); + } + LOG.info("Configuration:"); + LOG.info("---------------------------------------------------------------"); + for (Map.Entry entry : conf.entrySet()) { + LOG.info(" {}: {}", entry.getKey(), entry.getValue()); + } + LOG.info("---------------------------------------------------------------"); + this.conf = conf; + port = Integer.parseInt(conf.getProperty(KDC_PORT)); + if (port == 0) { + ServerSocket ss = new ServerSocket(0, 1, InetAddress.getByName(conf.getProperty(KDC_BIND_ADDRESS))); + port = ss.getLocalPort(); + ss.close(); + } + String orgName = conf.getProperty(ORG_NAME); + String orgDomain = conf.getProperty(ORG_DOMAIN); + realm = orgName.toUpperCase(Locale.ENGLISH) + "." + + orgDomain.toUpperCase(Locale.ENGLISH); + } + + /** + * Returns the port of the MiniKdc. + * + * @return the port of the MiniKdc. + */ + public int getPort() { + return port; + } + + /** + * Returns the host of the MiniKdc. + * + * @return the host of the MiniKdc. + */ + public String getHost() { + return conf.getProperty(KDC_BIND_ADDRESS); + } + + /** + * Returns the realm of the MiniKdc. + * + * @return the realm of the MiniKdc. + */ + public String getRealm() { + return realm; + } + + public File getKrb5conf() { + return krb5conf; + } + + /** + * Starts the MiniKdc. + * + * @throws Exception thrown if the MiniKdc could not be started. + */ + public synchronized void start() throws Exception { + if (kdc != null) { + throw new RuntimeException("Already started"); + } + initDirectoryService(); + initKDCServer(); + } + + private void initDirectoryService() throws Exception { + ds = new DefaultDirectoryService(); + ds.setInstanceLayout(new InstanceLayout(workDir)); + + CacheService cacheService = new CacheService(); + ds.setCacheService(cacheService); + + // first load the schema + InstanceLayout instanceLayout = ds.getInstanceLayout(); + File schemaPartitionDirectory = new File(instanceLayout.getPartitionsDirectory(), "schema"); + SchemaLdifExtractor extractor = new DefaultSchemaLdifExtractor(instanceLayout.getPartitionsDirectory()); + extractor.extractOrCopy(); + + SchemaLoader loader = new LdifSchemaLoader(schemaPartitionDirectory); + SchemaManager schemaManager = new DefaultSchemaManager(loader); + schemaManager.loadAllEnabled(); + ds.setSchemaManager(schemaManager); + // Init the LdifPartition with schema + LdifPartition schemaLdifPartition = new LdifPartition(schemaManager); + schemaLdifPartition.setPartitionPath(schemaPartitionDirectory.toURI()); + + // The schema partition + SchemaPartition schemaPartition = new SchemaPartition(schemaManager); + schemaPartition.setWrappedPartition(schemaLdifPartition); + ds.setSchemaPartition(schemaPartition); + + JdbmPartition systemPartition = new JdbmPartition(ds.getSchemaManager()); + systemPartition.setId("system"); + systemPartition.setPartitionPath( + new File(ds.getInstanceLayout().getPartitionsDirectory(), systemPartition.getId()).toURI()); + systemPartition.setSuffixDn(new Dn(ServerDNConstants.SYSTEM_DN)); + systemPartition.setSchemaManager(ds.getSchemaManager()); + ds.setSystemPartition(systemPartition); + + ds.getChangeLog().setEnabled(false); + ds.setDenormalizeOpAttrsEnabled(true); + ds.addLast(new KeyDerivationInterceptor()); + + // create one partition + String orgName = conf.getProperty(ORG_NAME).toLowerCase(Locale.ENGLISH); + String orgDomain = conf.getProperty(ORG_DOMAIN).toLowerCase(Locale.ENGLISH); + + JdbmPartition partition = new JdbmPartition(ds.getSchemaManager()); + partition.setId(orgName); + partition.setPartitionPath(new File(ds.getInstanceLayout().getPartitionsDirectory(), orgName).toURI()); + partition.setSuffixDn(new Dn("dc=" + orgName + ",dc=" + orgDomain)); + ds.addPartition(partition); + // indexes + Set> indexedAttributes = new HashSet>(); + indexedAttributes.add(new JdbmIndex("objectClass", false)); + indexedAttributes.add(new JdbmIndex("dc", false)); + indexedAttributes.add(new JdbmIndex("ou", false)); + partition.setIndexedAttributes(indexedAttributes); + + // And start the ds + ds.setInstanceId(conf.getProperty(INSTANCE)); + ds.startup(); + // context entry, after ds.startup() + Dn dn = new Dn("dc=" + orgName + ",dc=" + orgDomain); + Entry entry = ds.newEntry(dn); + entry.add("objectClass", "top", "domain"); + entry.add("dc", orgName); + ds.getAdminSession().add(entry); + } + + private void initKDCServer() throws Exception { + String orgName = conf.getProperty(ORG_NAME); + String orgDomain = conf.getProperty(ORG_DOMAIN); + String bindAddress = conf.getProperty(KDC_BIND_ADDRESS); + final Map map = new HashMap(); + map.put("0", orgName.toLowerCase(Locale.ENGLISH)); + map.put("1", orgDomain.toLowerCase(Locale.ENGLISH)); + map.put("2", orgName.toUpperCase(Locale.ENGLISH)); + map.put("3", orgDomain.toUpperCase(Locale.ENGLISH)); + map.put("4", bindAddress); + + InputStream is1 = getMinikdcResourceAsStream("minikdc.ldiff"); + + SchemaManager schemaManager = ds.getSchemaManager(); + LdifReader reader = null; + + try { + final String content = StrSubstitutor.replace(IOUtils.toString(is1), map); + reader = new LdifReader(new StringReader(content)); + + for (LdifEntry ldifEntry : reader) { + ds.getAdminSession().add(new DefaultEntry(schemaManager, ldifEntry.getEntry())); + } + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(is1); + } + + KerberosConfig kerberosConfig = new KerberosConfig(); + kerberosConfig.setMaximumRenewableLifetime(Long.parseLong(conf.getProperty(MAX_RENEWABLE_LIFETIME))); + kerberosConfig.setMaximumTicketLifetime(Long.parseLong(conf.getProperty(MAX_TICKET_LIFETIME))); + kerberosConfig.setSearchBaseDn(String.format("dc=%s,dc=%s", orgName, orgDomain)); + kerberosConfig.setPaEncTimestampRequired(false); + kdc = new KdcServer(kerberosConfig); + kdc.setDirectoryService(ds); + + // transport + String transport = conf.getProperty(TRANSPORT); + if (transport.trim().equals("TCP")) { + kdc.addTransports(new TcpTransport(bindAddress, port, 3, 50)); + } else if (transport.trim().equals("UDP")) { + kdc.addTransports(new UdpTransport(port)); + } else { + throw new IllegalArgumentException("Invalid transport: " + transport); + } + kdc.setServiceName(conf.getProperty(INSTANCE)); + kdc.start(); + + StringBuilder sb = new StringBuilder(); + InputStream is2 = getMinikdcResourceAsStream("minikdc-krb5.conf"); + + BufferedReader r = null; + + try { + r = new BufferedReader(new InputStreamReader(is2, Charsets.UTF_8)); + String line = r.readLine(); + + while (line != null) { + sb.append(line).append("{3}"); + line = r.readLine(); + } + } finally { + IOUtils.closeQuietly(r); + IOUtils.closeQuietly(is2); + } + + krb5conf = new File(workDir, "krb5.conf").getAbsoluteFile(); + FileUtils.writeStringToFile(krb5conf, MessageFormat.format(sb.toString(), getRealm(), getHost(), + Integer.toString(getPort()), System.getProperty("line.separator"))); + System.setProperty(JAVA_SECURITY_KRB5_CONF, krb5conf.getAbsolutePath()); + + System.setProperty(SUN_SECURITY_KRB5_DEBUG, conf.getProperty(DEBUG, "false")); + + // refresh the config + Class classRef; + if (System.getProperty("java.vendor").contains("IBM")) { + classRef = Class.forName("com.ibm.security.krb5.internal.Config"); + } else { + classRef = Class.forName("sun.security.krb5.Config"); + } + Method refreshMethod = classRef.getMethod("refresh", new Class[0]); + refreshMethod.invoke(classRef, new Object[0]); + + LOG.info("MiniKdc listening at port: {}", getPort()); + LOG.info("MiniKdc setting JVM krb5.conf to: {}", krb5conf.getAbsolutePath()); + } + + private InputStream getMinikdcResourceAsStream(String resourceName) + throws FileNotFoundException { + File kdcResourceFile = new File(testData, "/kerberos/" + resourceName); + return new FileInputStream(kdcResourceFile); + } + + /** + * Stops the MiniKdc + */ + public synchronized void stop() { + if (kdc != null) { + System.getProperties().remove(JAVA_SECURITY_KRB5_CONF); + System.getProperties().remove(SUN_SECURITY_KRB5_DEBUG); + kdc.stop(); + try { + ds.shutdown(); + } catch (Exception ex) { + LOG.error("Could not shutdown ApacheDS properly: {}", ex.toString(), ex); + } + } + delete(workDir); + } + + private void delete(File f) { + if (f.isFile()) { + if (! f.delete()) { + LOG.warn("WARNING: cannot delete file " + f.getAbsolutePath()); + } + } else { + for (File c: f.listFiles()) { + delete(c); + } + if (! f.delete()) { + LOG.warn("WARNING: cannot delete directory " + f.getAbsolutePath()); + } + } + } + + /** + * Creates a principal in the KDC with the specified user and password. + * + * @param principal principal name, do not include the domain. + * @param password password. + * @throws Exception thrown if the principal could not be created. + */ + public synchronized void createPrincipal(String principal, String password) throws Exception { + String orgName = conf.getProperty(ORG_NAME); + String orgDomain = conf.getProperty(ORG_DOMAIN); + String baseDn = "ou=users,dc=" + orgName.toLowerCase(Locale.ENGLISH) + ",dc=" + + orgDomain.toLowerCase(Locale.ENGLISH); + String content = "dn: uid=" + principal + "," + baseDn + "\n" + "objectClass: top\n" + "objectClass: person\n" + + "objectClass: inetOrgPerson\n" + "objectClass: krb5principal\n" + "objectClass: krb5kdcentry\n" + + "cn: " + principal + "\n" + "sn: " + principal + "\n" + "uid: " + principal + "\n" + "userPassword: " + + password + "\n" + "krb5PrincipalName: " + principal + "@" + getRealm() + "\n" + + "krb5KeyVersionNumber: 0"; + + for (LdifEntry ldifEntry : new LdifReader(new StringReader(content))) { + ds.getAdminSession().add(new DefaultEntry(ds.getSchemaManager(), ldifEntry.getEntry())); + } + } + + /** + * Creates multiple principals in the KDC and adds them to a keytab file. + * + * @param keytabFile keytab file to add the created principals. + * @param principals principals to add to the KDC, do not include the domain. + * @throws Exception thrown if the principals or the keytab file could not be + * created. + */ + public synchronized void createPrincipal(File keytabFile, + String ... principals) + throws Exception { + String generatedPassword = UUID.randomUUID().toString(); + Keytab keytab = new Keytab(); + List entries = new ArrayList(); + for (String principal : principals) { + createPrincipal(principal, generatedPassword); + principal = principal + "@" + getRealm(); + KerberosTime timestamp = new KerberosTime(); + for (Map.Entry entry : KerberosKeyFactory + .getKerberosKeys(principal, generatedPassword).entrySet()) { + EncryptionKey ekey = entry.getValue(); + byte keyVersion = (byte) ekey.getKeyVersion(); + entries.add(new KeytabEntry(principal, 1L, timestamp, keyVersion, ekey)); + } + } + keytab.setEntries(entries); + keytab.write(keytabFile); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/auth/MiniKdcTest.java b/src/java/test/org/apache/zookeeper/server/quorum/auth/MiniKdcTest.java new file mode 100644 index 00000000000..196d8bec776 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/MiniKdcTest.java @@ -0,0 +1,184 @@ +/** + * 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.auth; + +import org.apache.directory.server.kerberos.shared.keytab.Keytab; +import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry; +import org.junit.Assert; +import org.junit.Test; + +import javax.security.auth.Subject; +import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import java.io.File; +import java.security.Principal; +import java.util.Set; +import java.util.Map; +import java.util.HashSet; +import java.util.HashMap; +import java.util.Arrays; + +/* + * This code is originally from HDFS, see the file name TestMiniKdc there + * in case of bug fixing, history, etc. + * + * Branch : trunk + * Github Revision: 916140604ffef59466ba30832478311d3e6249bd + */ +public class MiniKdcTest extends KerberosSecurityTestcase { + private static final boolean IBM_JAVA = System.getProperty("java.vendor") + .contains("IBM"); + + @Test(timeout = 60000) + public void testMiniKdcStart() { + MiniKdc kdc = getKdc(); + Assert.assertNotSame(0, kdc.getPort()); + } + + @Test(timeout = 60000) + public void testKeytabGen() throws Exception { + MiniKdc kdc = getKdc(); + File workDir = getWorkDir(); + + kdc.createPrincipal(new File(workDir, "keytab"), "foo/bar", "bar/foo"); + Keytab kt = Keytab.read(new File(workDir, "keytab")); + + Set principals = new HashSet(); + for (KeytabEntry entry : kt.getEntries()) { + principals.add(entry.getPrincipalName()); + } + //here principals use \ instead of / + //because org.apache.directory.server.kerberos.shared.keytab.KeytabDecoder + // .getPrincipalName(IoBuffer buffer) use \\ when generates principal + Assert.assertEquals(new HashSet(Arrays.asList( + "foo\\bar@" + kdc.getRealm(), "bar\\foo@" + kdc.getRealm())), + principals); + } + + private static class KerberosConfiguration extends Configuration { + private String principal; + private String keytab; + private boolean isInitiator; + + private KerberosConfiguration(String principal, File keytab, + boolean client) { + this.principal = principal; + this.keytab = keytab.getAbsolutePath(); + this.isInitiator = client; + } + + public static Configuration createClientConfig(String principal, + File keytab) { + return new KerberosConfiguration(principal, keytab, true); + } + + public static Configuration createServerConfig(String principal, + File keytab) { + return new KerberosConfiguration(principal, keytab, false); + } + + private static String getKrb5LoginModuleName() { + return System.getProperty("java.vendor").contains("IBM") + ? "com.ibm.security.auth.module.Krb5LoginModule" + : "com.sun.security.auth.module.Krb5LoginModule"; + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + Map options = new HashMap(); + options.put("principal", principal); + options.put("refreshKrb5Config", "true"); + if (IBM_JAVA) { + options.put("useKeytab", keytab); + options.put("credsType", "both"); + } else { + options.put("keyTab", keytab); + options.put("useKeyTab", "true"); + options.put("storeKey", "true"); + options.put("doNotPrompt", "true"); + options.put("useTicketCache", "true"); + options.put("renewTGT", "true"); + options.put("isInitiator", Boolean.toString(isInitiator)); + } + String ticketCache = System.getenv("KRB5CCNAME"); + if (ticketCache != null) { + options.put("ticketCache", ticketCache); + } + options.put("debug", "true"); + + return new AppConfigurationEntry[] { + new AppConfigurationEntry(getKrb5LoginModuleName(), + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, + options) }; + } + } + + @Test(timeout = 60000) + public void testKerberosLogin() throws Exception { + MiniKdc kdc = getKdc(); + File workDir = getWorkDir(); + LoginContext loginContext = null; + try { + String principal = "foo"; + File keytab = new File(workDir, "foo.keytab"); + kdc.createPrincipal(keytab, principal); + + Set principals = new HashSet(); + principals.add(new KerberosPrincipal(principal)); + + // client login + Subject subject = new Subject(false, principals, + new HashSet(), new HashSet()); + loginContext = new LoginContext("", subject, null, + KerberosConfiguration.createClientConfig(principal, + keytab)); + loginContext.login(); + subject = loginContext.getSubject(); + Assert.assertEquals(1, subject.getPrincipals().size()); + Assert.assertEquals(KerberosPrincipal.class, + subject.getPrincipals().iterator().next().getClass()); + Assert.assertEquals(principal + "@" + kdc.getRealm(), + subject.getPrincipals().iterator().next().getName()); + loginContext.logout(); + + // server login + subject = new Subject(false, principals, new HashSet(), + new HashSet()); + loginContext = new LoginContext("", subject, null, + KerberosConfiguration.createServerConfig(principal, + keytab)); + loginContext.login(); + subject = loginContext.getSubject(); + Assert.assertEquals(1, subject.getPrincipals().size()); + Assert.assertEquals(KerberosPrincipal.class, + subject.getPrincipals().iterator().next().getClass()); + Assert.assertEquals(principal + "@" + kdc.getRealm(), + subject.getPrincipals().iterator().next().getName()); + loginContext.logout(); + + } finally { + if (loginContext != null) { + loginContext.logout(); + } + } + } + +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumAuthTestBase.java b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumAuthTestBase.java new file mode 100644 index 00000000000..219d5bc6f85 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumAuthTestBase.java @@ -0,0 +1,248 @@ +/** + * 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.auth; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.FileUtils; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase.MainThread; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * QuorumAuthTestBase provides a base class for testing quorum peer mutual + * authentication using SASL mechanisms. + */ +public class QuorumAuthTestBase extends ZKTestCase { + protected static final Logger LOG = LoggerFactory.getLogger(QuorumAuthTestBase.class); + protected List mt = new ArrayList(); + protected static File jaasConfigDir; + + public static void setupJaasConfig(String jaasEntries) { + try { + jaasConfigDir = ClientBase.createTmpDir(); + File saslConfFile = new File(jaasConfigDir, "jaas.conf"); + FileWriter fwriter = new FileWriter(saslConfFile); + fwriter.write(jaasEntries); + fwriter.close(); + System.setProperty("java.security.auth.login.config", + saslConfFile.getAbsolutePath()); + } catch (IOException ioe) { + LOG.error("Failed to create tmp directory to hold JAAS conf file", ioe); + // could not create tmp directory to hold JAAS conf file : test will + // fail now. + } + } + + public static void cleanupJaasConfig() { + if (jaasConfigDir != null) { + FileUtils.deleteQuietly(jaasConfigDir); + } + } + + protected String startQuorum(final int serverCount, + Map authConfigs, int authServerCount, + boolean delayedServerStartup) throws IOException { + StringBuilder connectStr = new StringBuilder(); + final int[] clientPorts = startQuorum(serverCount, 0, connectStr, + authConfigs, authServerCount, delayedServerStartup); + for (int i = 0; i < serverCount; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], + ClientBase.CONNECTION_TIMEOUT)); + } + return connectStr.toString(); + } + + /** + * Starts the given number of quorum servers and will wait for the quorum + * formation. + * + * @param serverCount + * total server count includes participants + observers + * @param observerCount + * number of observers + * @param authConfigs + * configuration parameters for authentication + * @param authServerCount + * number of auth enabled servers + * @return client port for the respective servers + * @throws IOException + */ + protected String startQuorum(final int serverCount, int observerCount, + Map authConfigs, int authServerCount) + throws IOException { + StringBuilder connectStr = new StringBuilder(); + final int[] clientPorts = startQuorum(serverCount, observerCount, + connectStr, authConfigs, authServerCount, false); + for (int i = 0; i < serverCount; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], + ClientBase.CONNECTION_TIMEOUT)); + } + return connectStr.toString(); + } + + /** + * Starts the given number of quorum servers and won't wait for the quorum + * formation. + * + * @param serverCount + * total server count includes participants + observers + * @param observerCount + * number of observers + * @param connectStr + * connection string where clients can used for connection + * establishment + * @param authConfigs + * configuration parameters for authentication + * @param authServerCount + * number of auth enabled servers + * @param delayedServerStartup + * true flag value to add delay between server's startup, false otherwise. + * @return client port for the respective servers + * @throws IOException + */ + protected int[] startQuorum(final int serverCount, int observerCount, + StringBuilder connectStr, Map authConfigs, + int authServerCount, boolean delayedServerStartup) + throws IOException { + final int clientPorts[] = new int[serverCount]; + StringBuilder sb = new StringBuilder(); + + // If there are any Observers then the Observer server details will be + // placed first in the configuration section. + for (int i = 0; i < serverCount; i++) { + clientPorts[i] = PortAssignment.unique(); + String server = ""; + if (observerCount > 0 && i < observerCount) { + // add observer learner type + server = String.format("server.%d=localhost:%d:%d:observer", + i, PortAssignment.unique(), PortAssignment.unique()); + } else { + // add participant learner type + server = String.format("server.%d=localhost:%d:%d:participant", + i, PortAssignment.unique(), PortAssignment.unique()); + } + sb.append(server + "\n"); + connectStr.append("127.0.0.1:" + clientPorts[i]); + if (i < serverCount - 1) { + connectStr.append(","); + } + } + String quorumCfg = sb.toString(); + // servers with authentication interfaces configured + int i = 0; + for (; i < authServerCount; i++) { + if (observerCount > 0 && i < observerCount) { + String obsCfgSection = quorumCfg + "\npeerType=observer"; + quorumCfg = obsCfgSection; + } + startServer(authConfigs, clientPorts[i], quorumCfg, i, delayedServerStartup); + } + // servers without any authentication configured + for (int j = 0; j < serverCount - authServerCount; j++, i++) { + if (observerCount > 0 && i < observerCount) { + String obsCfgSection = quorumCfg + "\npeerType=observer"; + quorumCfg = obsCfgSection; + } + startServer(null, clientPorts[i], quorumCfg, i, delayedServerStartup); + } + return clientPorts; + } + + private void startServer(Map authConfigs, + final int clientPort, String quorumCfg, int i, + boolean delayedServerStartup) throws IOException { + MainThread mthread; + if (authConfigs != null) { + mthread = new MainThread(i, clientPort, quorumCfg, authConfigs); + } else { + mthread = new MainThread(i, clientPort, quorumCfg); + } + mt.add(mthread); + mthread.start(); + + if (delayedServerStartup) { + addDelayBeforeStartingNextServer(mthread); + } + } + + private void addDelayBeforeStartingNextServer(MainThread mThread) { + // Refer https://issues.apache.org/jira/browse/ZOOKEEPER-2712 + LOG.info("Waiting to finish login context init(Krb login), " + + "as there are potential concurrency issues in ApacheDS " + + "if multiple servers starts together!"); + int retries = 60; // 15secs delay + while (retries > 0) { + if (mThread.getQuorumPeer() != null + && mThread.getQuorumPeer().hasAuthInitialized()) { + try { + Thread.sleep(1000); // adding 1sec grace period. + } catch (InterruptedException e) { + LOG.info("Ignore InterruptedException"); + } + break; + } + // moving to next retry cycle + retries--; + try { + Thread.sleep(250); + } catch (InterruptedException e) { + LOG.info("Ignore InterruptedException"); + } + } + } + + protected void startServer(MainThread restartPeer, + Map authConfigs) throws IOException { + MainThread mthread = new MainThread(restartPeer.getMyid(), + restartPeer.getClientPort(), restartPeer.getQuorumCfgSection(), + authConfigs); + mt.add(mthread); + mthread.start(); + } + + void shutdownAll() { + for (int i = 0; i < mt.size(); i++) { + shutdown(i); + } + } + + MainThread shutdown(int index) { + MainThread mainThread = mt.get(index); + try { + mainThread.shutdown(); + } catch (InterruptedException e) { + } finally { + mt.remove(index); + } + mainThread.deleteBaseDir(); + return mainThread; + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumAuthUpgradeTest.java b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumAuthUpgradeTest.java new file mode 100644 index 00000000000..4eeccf34a90 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumAuthUpgradeTest.java @@ -0,0 +1,239 @@ +/** + * 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.auth; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase.MainThread; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ClientTest; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +/** + * Rolling upgrade should do in three steps: + * + * step-1) Stop the server and set the flags and restart the server. + * quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=false and quorum.auth.serverRequireSasl=false + * Ensure that all the servers should complete this step. Now, move to next step. + * + * step-2) Stop the server one by one and change the flags and restart the server. + * quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=true and quorum.auth.serverRequireSasl=false + * Ensure that all the servers should complete this step. Now, move to next step. + * + * step-3) Stop the server one by one and change the flags and restart the server. + * quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=true and quorum.auth.serverRequireSasl=true + * Now, all the servers are fully upgraded and running in secured mode. + */ +public class QuorumAuthUpgradeTest extends QuorumAuthTestBase { + static { + String jaasEntries = new String("" + "QuorumServer {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " user_test=\"mypassword\";\n" + "};\n" + + "QuorumLearner {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"test\"\n" + + " password=\"mypassword\";\n" + "};\n"); + setupJaasConfig(jaasEntries); + } + + @After + public void tearDown() throws Exception { + shutdownAll(); + } + + @AfterClass + public static void cleanup() { + cleanupJaasConfig(); + } + + /** + * Test to verify that servers are able to start without any authentication. + * peer0 -> quorum.auth.enableSasl=false + * peer1 -> quorum.auth.enableSasl=false + */ + @Test(timeout = 30000) + public void testNullAuthLearnerServer() throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "false"); + + String connectStr = startQuorum(2, authConfigs, 0, false); + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, + watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk.close(); + } + + /** + * Test to verify that servers are able to form quorum. + * peer0 -> quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=false, quorum.auth.serverRequireSasl=false + * peer1 -> quorum.auth.enableSasl=false, quorum.auth.learnerRequireSasl=false, quorum.auth.serverRequireSasl=false + */ + @Test(timeout = 30000) + public void testAuthLearnerAgainstNullAuthServer() throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + + String connectStr = startQuorum(2, authConfigs, 1, false); + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, + watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk.close(); + } + + /** + * Test to verify that servers are able to form quorum. + * peer0 -> quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=false, quorum.auth.serverRequireSasl=false + * peer1 -> quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=false, quorum.auth.serverRequireSasl=false + */ + @Test(timeout = 30000) + public void testAuthLearnerAgainstNoAuthRequiredServer() throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + + String connectStr = startQuorum(2, authConfigs, 2, false); + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, + watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk.close(); + } + + /** + * Test to verify that servers are able to form quorum. + * peer0 -> quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=true, quorum.auth.serverRequireSasl=true + * peer1 -> quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=true, quorum.auth.serverRequireSasl=true + */ + @Test(timeout = 30000) + public void testAuthLearnerServer() throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + + String connectStr = startQuorum(2, authConfigs, 2, false); + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, + watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk.close(); + } + + /** + * Rolling upgrade should do in three steps: + * + * step-1) Stop the server and set the flags and restart the server. + * quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=false and quorum.auth.serverRequireSasl=false + * Ensure that all the servers should complete this step. Now, move to next step. + * + * step-2) Stop the server one by one and change the flags and restart the server. + * quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=true and quorum.auth.serverRequireSasl=false + * Ensure that all the servers should complete this step. Now, move to next step. + * + * step-3) Stop the server one by one and change the flags and restart the server. + * quorum.auth.enableSasl=true, quorum.auth.learnerRequireSasl=true and quorum.auth.serverRequireSasl=true + * Now, all the servers are fully upgraded and running in secured mode. + */ + @Test(timeout = 90000) + public void testRollingUpgrade() throws Exception { + // Start peer0,1,2 servers with quorum.auth.enableSasl=false and + // quorum.auth.learnerRequireSasl=false, quorum.auth.serverRequireSasl=false + // Assume this is an existing cluster. + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "false"); + + String connectStr = startQuorum(3, authConfigs, 0, false); + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, + watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + + //1. Upgrade peer0,1,2 with quorum.auth.enableSasl=true and + // quorum.auth.learnerRequireSasl=false, quorum.auth.serverRequireSasl=false + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "false"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "false"); + restartServer(authConfigs, 0, zk, watcher); + restartServer(authConfigs, 1, zk, watcher); + restartServer(authConfigs, 2, zk, watcher); + + //2. Upgrade peer0,1,2 with quorum.auth.enableSasl=true and + // quorum.auth.learnerRequireSasl=true, quorum.auth.serverRequireSasl=false + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "false"); + restartServer(authConfigs, 0, zk, watcher); + restartServer(authConfigs, 1, zk, watcher); + restartServer(authConfigs, 2, zk, watcher); + + //3. Upgrade peer0,1,2 with quorum.auth.enableSasl=true and + // quorum.auth.learnerRequireSasl=true, quorum.auth.serverRequireSasl=true + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + restartServer(authConfigs, 0, zk, watcher); + restartServer(authConfigs, 1, zk, watcher); + restartServer(authConfigs, 2, zk, watcher); + + //4. Restart peer2 with quorum.auth.learnerEnableSasl=false and + // quorum.auth.serverRequireSasl=false. It should fail to join the + // quorum as this needs auth. + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "false"); + MainThread m = shutdown(2); + startServer(m, authConfigs); + Assert.assertFalse("waiting for server 2 being up", ClientBase + .waitForServerUp("127.0.0.1:" + m.getClientPort(), 5000)); + } + + private void restartServer(Map authConfigs, int index, + ZooKeeper zk, CountdownWatcher watcher) throws IOException, + KeeperException, InterruptedException, TimeoutException { + LOG.info("Restarting server myid=" + index); + MainThread m = shutdown(index); + startServer(m, authConfigs); + Assert.assertTrue("waiting for server" + index + "being up", + ClientBase.waitForServerUp("127.0.0.1:" + m.getClientPort(), + ClientBase.CONNECTION_TIMEOUT)); + watcher.waitForConnected(ClientTest.CONNECTION_TIMEOUT); + zk.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumDigestAuthTest.java b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumDigestAuthTest.java new file mode 100644 index 00000000000..c2f4cc319b9 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumDigestAuthTest.java @@ -0,0 +1,380 @@ +/** + * 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.auth; + +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.QuorumPeerMain; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase; +import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase.MainThread; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +public class QuorumDigestAuthTest extends QuorumAuthTestBase { + + private ZooKeeper zk; + static { + String jaasEntries = new String("" + + "QuorumServer {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " user_test=\"mypassword\";\n" + "};\n" + + "QuorumLearner {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"test\"\n" + + " password=\"mypassword\";\n" + "};\n" + + "QuorumLearnerInvalid {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"test\"\n" + + " password=\"invalid\";\n" + "};" + "\n"); + setupJaasConfig(jaasEntries); + } + + @After + public void tearDown() throws Exception { + for (MainThread mainThread : mt) { + mainThread.shutdown(); + mainThread.deleteBaseDir(); + } + if (zk != null) { + zk.close(); + } + } + + @AfterClass + public static void cleanup(){ + cleanupJaasConfig(); + } + + /** + * Test to verify that server is able to start with valid credentials + */ + @Test(timeout = 30000) + public void testValidCredentials() throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + + String connectStr = startQuorum(3, authConfigs, 3, false); + CountdownWatcher watcher = new CountdownWatcher(); + zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + for (int i = 0; i < 10; i++) { + zk.create("/" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } + } + + /** + * Test to verify that server is able to start with invalid credentials if + * the configuration is set to quorum.auth.serverRequireSasl=false. + * Quorum will talk each other even if the authentication is not succeeded + */ + @Test(timeout = 30000) + public void testSaslNotRequiredWithInvalidCredentials() throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT, "QuorumLearnerInvalid"); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "false"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "false"); + String connectStr = startQuorum(3, authConfigs, 3, false); + CountdownWatcher watcher = new CountdownWatcher(); + zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + for (int i = 0; i < 10; i++) { + zk.create("/" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } + } + + /** + * Test to verify that server shouldn't start with invalid credentials + * if the configuration is set to quorum.auth.serverRequireSasl=true, + * quorum.auth.learnerRequireSasl=true + */ + @Test(timeout = 30000) + public void testSaslRequiredInvalidCredentials() throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT, "QuorumLearnerInvalid"); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + int serverCount = 2; + final int[] clientPorts = startQuorum(serverCount, 0, + new StringBuilder(), authConfigs, serverCount, false); + for (int i = 0; i < serverCount; i++) { + boolean waitForServerUp = ClientBase.waitForServerUp( + "127.0.0.1:" + clientPorts[i], QuorumPeerTestBase.TIMEOUT); + Assert.assertFalse("Shouldn't start server with invalid credentials", + waitForServerUp); + } + } + + /** + * If quorumpeer learner is not auth enabled then self won't be able to join + * quorum. So this test is ensuring that the quorumpeer learner is also auth + * enabled while enabling quorum server require sasl. + */ + @Test(timeout = 10000) + public void testEnableQuorumServerRequireSaslWithoutQuorumLearnerRequireSasl() + throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT, + "QuorumLearner"); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "false"); + MainThread mthread = new MainThread(1, PortAssignment.unique(), "", + authConfigs); + String args[] = new String[1]; + args[0] = mthread.getConfFile().toString(); + try { + new QuorumPeerMain() { + @Override + protected void initializeAndRun(String[] args) + throws ConfigException, IOException { + super.initializeAndRun(args); + } + }.initializeAndRun(args); + Assert.fail("Must throw exception as quorumpeer learner is not enabled!"); + } catch (ConfigException e) { + // expected + } + } + + + /** + * If quorumpeer learner is not auth enabled then self won't be able to join + * quorum. So this test is ensuring that the quorumpeer learner is also auth + * enabled while enabling quorum server require sasl. + */ + @Test(timeout = 10000) + public void testEnableQuorumAuthenticationConfigurations() + throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT, + "QuorumLearner"); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "false"); + + // case-1) 'quorum.auth.enableSasl' is off. Tries to enable server sasl. + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "false"); + MainThread mthread = new MainThread(1, PortAssignment.unique(), "", + authConfigs); + String args[] = new String[1]; + args[0] = mthread.getConfFile().toString(); + try { + new QuorumPeerMain() { + @Override + protected void initializeAndRun(String[] args) + throws ConfigException, IOException { + super.initializeAndRun(args); + } + }.initializeAndRun(args); + Assert.fail("Must throw exception as quorum sasl is not enabled!"); + } catch (ConfigException e) { + // expected + } + + // case-1) 'quorum.auth.enableSasl' is off. Tries to enable learner sasl. + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "false"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + try { + new QuorumPeerMain() { + @Override + protected void initializeAndRun(String[] args) + throws ConfigException, IOException { + super.initializeAndRun(args); + } + }.initializeAndRun(args); + Assert.fail("Must throw exception as quorum sasl is not enabled!"); + } catch (ConfigException e) { + // expected + } + } + + /** + * Test to verify that Observer server is able to join quorum. + */ + @Test(timeout = 30000) + public void testObserverWithValidCredentials() throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + + // Starting auth enabled 5-node cluster. 3-Participants and 2-Observers. + int totalServerCount = 5; + int observerCount = 2; + String connectStr = startQuorum(totalServerCount, observerCount, + authConfigs, totalServerCount); + CountdownWatcher watcher = new CountdownWatcher(); + zk = new ZooKeeper(connectStr.toString(), ClientBase.CONNECTION_TIMEOUT, + watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk.create("/myTestRoot", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } + + /** + * Test to verify that non-auth enabled Observer server should be rejected + * by the auth enabled quorum servers. + */ + @Test(timeout = 30000) + public void testNonAuthEnabledObserverJoiningAuthEnabledQuorum() + throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + + // Starting auth enabled 3-node cluster. + int totalServerCount = 3; + String connectStr = startQuorum(totalServerCount, authConfigs, + totalServerCount, false); + + CountdownWatcher watcher = new CountdownWatcher(); + zk = new ZooKeeper(connectStr.toString(), ClientBase.CONNECTION_TIMEOUT, + watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk.create("/myTestRoot", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + + // Adding a non-auth enabled Observer to the 3-node auth cluster. + String quorumCfgSection = mt.get(0).getQuorumCfgSection(); + int observerMyid = totalServerCount + 1; + StringBuilder newObsCfgSection = new StringBuilder(quorumCfgSection); + newObsCfgSection.append("\n"); + newObsCfgSection.append(String.format( + "server.%d=localhost:%d:%d:observer", observerMyid, + PortAssignment.unique(), PortAssignment.unique())); + newObsCfgSection.append("\npeerType=observer"); + newObsCfgSection.append("\n"); + int clientPort = PortAssignment.unique(); + newObsCfgSection.append("127.0.0.1:" + clientPort); + MainThread mthread = new MainThread(observerMyid, clientPort, + newObsCfgSection.toString()); + mt.add(mthread); + mthread.start(); + + boolean waitForServerUp = ClientBase.waitForServerUp( + "127.0.0.1:" + clientPort, QuorumPeerTestBase.TIMEOUT); + Assert.assertFalse( + "Non-auth enabled Observer shouldn't be able join auth-enabled quorum", + waitForServerUp); + + // quorum shouldn't be disturbed due to rejection. + zk.create("/myTestRoot", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + } + + /** + * Test to verify that server is able to reform quorum if the Leader goes + * down. + */ + @Test(timeout = 30000) + public void testRelectionWithValidCredentials() throws Exception { + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + + String connectStr = startQuorum(3, authConfigs, 3, false); + CountdownWatcher watcher = new CountdownWatcher(); + zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk.create("/myTestRoot", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + watcher.reset(); + + // Shutdown Leader to trigger re-election + QuorumPeer leaderQP = getLeaderQuorumPeer(mt); + LOG.info("Shutdown Leader sid:{} to trigger quorum leader-election", + leaderQP.getId()); + shutdownQP(leaderQP); + + // Wait for quorum formation + QuorumPeer newLeaderQP = waitForLeader(); + assertNotNull("New leader must have been elected by now", newLeaderQP); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk.create("/myTestRoot", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + } + + private QuorumPeer waitForLeader() throws InterruptedException { + int retryCnt = 0; + QuorumPeer newLeaderQP = null; + while (retryCnt < 30) { + newLeaderQP = getLeaderQuorumPeer(mt); + if (newLeaderQP != null) { + LOG.info("Number of retries:{} to findout new Leader", + retryCnt); + break; + } + retryCnt--; + Thread.sleep(500); + } + return newLeaderQP; + } + + private void shutdownQP(QuorumPeer qp) throws InterruptedException { + assertNotNull("QuorumPeer doesn't exist!", qp); + qp.shutdown(); + + int retryCnt = 30; + while (retryCnt > 0) { + if (qp.getPeerState() == ServerState.LOOKING) { + LOG.info("Number of retries:{} to change the server state to {}", + retryCnt, ServerState.LOOKING); + break; + } + Thread.sleep(500); + retryCnt--; + } + Assert.assertEquals( + "After shutdown, QuorumPeer should change its state to LOOKING", + ServerState.LOOKING, qp.getPeerState()); + } + + private QuorumPeer getLeaderQuorumPeer(List mtList) { + for (MainThread mt : mtList) { + QuorumPeer quorumPeer = mt.getQuorumPeer(); + if (null != quorumPeer + && ServerState.LEADING == quorumPeer.getPeerState()) { + return quorumPeer; + } + } + return null; + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumKerberosAuthTest.java b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumKerberosAuthTest.java new file mode 100644 index 00000000000..e3eddf7df21 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumKerberosAuthTest.java @@ -0,0 +1,115 @@ +/** + * 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.auth; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase.MainThread; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +public class QuorumKerberosAuthTest extends KerberosSecurityTestcase { + private static File keytabFile; + static { + String keytabFilePath = FilenameUtils.normalize(KerberosTestUtils.getKeytabFile(), true); + String jaasEntries = new String("" + + "QuorumServer {\n" + + " com.sun.security.auth.module.Krb5LoginModule required\n" + + " useKeyTab=true\n" + + " keyTab=\"" + keytabFilePath + "\"\n" + + " storeKey=true\n" + + " useTicketCache=false\n" + + " debug=true\n" + + " doNotPrompt=true\n" + + " refreshKrb5Config=true\n" + + " principal=\"" + KerberosTestUtils.getServerPrincipal() + "\";\n" + "};\n" + + "QuorumLearner {\n" + + " com.sun.security.auth.module.Krb5LoginModule required\n" + + " useKeyTab=true\n" + + " keyTab=\"" + keytabFilePath + "\"\n" + + " storeKey=true\n" + + " useTicketCache=false\n" + + " debug=true\n" + + " doNotPrompt=true\n" + + " refreshKrb5Config=true\n" + + " isInitiator=true\n" + + " principal=\"" + KerberosTestUtils.getLearnerPrincipal() + "\";\n" + "};\n"); + setupJaasConfig(jaasEntries); + } + + @Before + public void setUp() throws Exception { + // create keytab + keytabFile = new File(KerberosTestUtils.getKeytabFile()); + String learnerPrincipal = KerberosTestUtils.getLearnerPrincipal(); + String serverPrincipal = KerberosTestUtils.getServerPrincipal(); + learnerPrincipal = learnerPrincipal.substring(0, learnerPrincipal.lastIndexOf("@")); + serverPrincipal = serverPrincipal.substring(0, serverPrincipal.lastIndexOf("@")); + getKdc().createPrincipal(keytabFile, learnerPrincipal, serverPrincipal); + } + + @After + public void tearDown() throws Exception { + for (MainThread mainThread : mt) { + mainThread.shutdown(); + mainThread.deleteBaseDir(); + } + } + + @AfterClass + public static void cleanup() { + if(keytabFile != null){ + FileUtils.deleteQuietly(keytabFile); + } + cleanupJaasConfig(); + } + + /** + * Test to verify that server is able to start with valid credentials + */ + @Test(timeout = 120000) + public void testValidCredentials() throws Exception { + String serverPrincipal = KerberosTestUtils.getServerPrincipal(); + serverPrincipal = serverPrincipal.substring(0, serverPrincipal.lastIndexOf("@")); + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL, serverPrincipal); + String connectStr = startQuorum(3, authConfigs, 3, true); + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + for (int i = 0; i < 10; i++) { + zk.create("/" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + zk.close(); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumKerberosHostBasedAuthTest.java b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumKerberosHostBasedAuthTest.java new file mode 100644 index 00000000000..55deefb8326 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumKerberosHostBasedAuthTest.java @@ -0,0 +1,192 @@ +/** + * 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.auth; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase.MainThread; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import junit.framework.Assert; + +public class QuorumKerberosHostBasedAuthTest extends KerberosSecurityTestcase { + private static File keytabFile; + private static String hostServerPrincipal = KerberosTestUtils.getHostServerPrincipal(); + private static String hostLearnerPrincipal = KerberosTestUtils.getHostLearnerPrincipal(); + private static String hostNamedLearnerPrincipal = KerberosTestUtils.getHostNamedLearnerPrincipal("myhost"); + static { + setupJaasConfigEntries(hostServerPrincipal, hostLearnerPrincipal, hostNamedLearnerPrincipal); + } + + private static void setupJaasConfigEntries(String hostServerPrincipal, + String hostLearnerPrincipal, String hostNamedLearnerPrincipal) { + String keytabFilePath = FilenameUtils.normalize(KerberosTestUtils.getKeytabFile(), true); + String jaasEntries = new String("" + + "QuorumServer {\n" + + " com.sun.security.auth.module.Krb5LoginModule required\n" + + " useKeyTab=true\n" + + " keyTab=\"" + keytabFilePath + "\"\n" + + " storeKey=true\n" + + " useTicketCache=false\n" + + " debug=true\n" + + " doNotPrompt=true\n" + + " refreshKrb5Config=true\n" + + " principal=\"" + KerberosTestUtils.replaceHostPattern(hostServerPrincipal) + "\";\n" + "};\n" + + "QuorumLearner {\n" + + " com.sun.security.auth.module.Krb5LoginModule required\n" + + " useKeyTab=true\n" + + " keyTab=\"" + keytabFilePath + "\"\n" + + " storeKey=true\n" + + " useTicketCache=false\n" + + " debug=true\n" + + " doNotPrompt=true\n" + + " refreshKrb5Config=true\n" + + " isInitiator=true\n" + + " principal=\"" + KerberosTestUtils.replaceHostPattern(hostLearnerPrincipal) + "\";\n" + "};\n" + + "QuorumLearnerMyHost {\n" + + " com.sun.security.auth.module.Krb5LoginModule required\n" + + " useKeyTab=true\n" + + " keyTab=\"" + keytabFilePath + "\"\n" + + " storeKey=true\n" + + " useTicketCache=false\n" + + " debug=true\n" + + " doNotPrompt=true\n" + + " refreshKrb5Config=true\n" + + " isInitiator=true\n" + + " principal=\"" + hostNamedLearnerPrincipal + "\";\n" + "};\n"); + setupJaasConfig(jaasEntries); + } + + @BeforeClass + public static void setUp() throws Exception { + // create keytab + keytabFile = new File(KerberosTestUtils.getKeytabFile()); + + // Creates principals in the KDC and adds them to a keytab file. + String learnerPrincipal = hostLearnerPrincipal.substring(0, hostLearnerPrincipal.lastIndexOf("@")); + learnerPrincipal = KerberosTestUtils.replaceHostPattern(learnerPrincipal); + String serverPrincipal = hostServerPrincipal.substring(0, hostServerPrincipal.lastIndexOf("@")); + serverPrincipal = KerberosTestUtils.replaceHostPattern(serverPrincipal); + + // learner with ipaddress in principal + String learnerPrincipal2 = hostNamedLearnerPrincipal.substring(0, hostNamedLearnerPrincipal.lastIndexOf("@")); + getKdc().createPrincipal(keytabFile, learnerPrincipal, learnerPrincipal2, serverPrincipal); + } + + @After + public void tearDown() throws Exception { + for (MainThread mainThread : mt) { + mainThread.shutdown(); + mainThread.deleteBaseDir(); + } + } + + @AfterClass + public static void cleanup() { + if(keytabFile != null){ + FileUtils.deleteQuietly(keytabFile); + } + cleanupJaasConfig(); + } + + /** + * Test to verify that server is able to start with valid credentials + */ + @Test(timeout = 120000) + public void testValidCredentials() throws Exception { + String serverPrincipal = hostServerPrincipal.substring(0, hostServerPrincipal.lastIndexOf("@")); + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL, serverPrincipal); + String connectStr = startQuorum(3, authConfigs, 3, true); + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + for (int i = 0; i < 10; i++) { + zk.create("/" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + zk.close(); + } + + /** + * Test to verify that the bad server connection to the quorum should be rejected. + */ + @Test(timeout = 120000) + public void testConnectBadServer() throws Exception { + String serverPrincipal = hostServerPrincipal.substring(0, hostServerPrincipal.lastIndexOf("@")); + Map authConfigs = new HashMap(); + authConfigs.put(QuorumAuth.QUORUM_SASL_AUTH_ENABLED, "true"); + authConfigs.put(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, "true"); + authConfigs.put(QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL, serverPrincipal); + String connectStr = startQuorum(3, authConfigs, 3, true); + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + for (int i = 0; i < 10; i++) { + zk.create("/" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + zk.close(); + + String quorumCfgSection = mt.get(0).getQuorumCfgSection(); + StringBuilder sb = new StringBuilder(); + sb.append(quorumCfgSection); + + int myid = mt.size() + 1; + final int clientPort = PortAssignment.unique(); + String server = String.format("server.%d=localhost:%d:%d:participant", + myid, PortAssignment.unique(), PortAssignment.unique()); + sb.append(server + "\n"); + quorumCfgSection = sb.toString(); + authConfigs.put(QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT, + "QuorumLearnerMyHost"); + MainThread badServer = new MainThread(myid, clientPort, quorumCfgSection, + authConfigs); + badServer.start(); + watcher = new CountdownWatcher(); + connectStr = "127.0.0.1:" + clientPort; + zk = new ZooKeeper(connectStr, ClientBase.CONNECTION_TIMEOUT, watcher); + try{ + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT/3); + Assert.fail("Must throw exception as the myHost is not an authorized one!"); + } catch (TimeoutException e){ + // expected + } finally { + zk.close(); + badServer.shutdown(); + badServer.deleteBaseDir(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/util/PortForwarder.java b/src/java/test/org/apache/zookeeper/server/util/PortForwarder.java new file mode 100644 index 00000000000..acbad809d1f --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/util/PortForwarder.java @@ -0,0 +1,256 @@ +/** + * 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.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ConnectException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A utility that does bi-directional forwarding between two ports. + * Useful, for example, to simulate network failures. + * Example: + * + * Server 1 config file: + * + * server.1=127.0.0.1:7301:7401;8201 + * server.2=127.0.0.1:7302:7402;8202 + * server.3=127.0.0.1:7303:7403;8203 + * + * Server 2 and 3 config files: + * + * server.1=127.0.0.1:8301:8401;8201 + * server.2=127.0.0.1:8302:8402;8202 + * server.3=127.0.0.1:8303:8403;8203 + * + * Initially forward traffic between 730x and 830x and between 740x and 830x + * This way server 1 can communicate with servers 2 and 3 + * .... + * + * List pfs = startForwarding(); + * .... + * // simulate a network interruption for server 1 + * stopForwarding(pfs); + * .... + * // restore connection + * pfs = startForwarding(); + * + * + * private List startForwarding() throws IOException { + * List res = new ArrayList(); + * res.add(new PortForwarder(8301, 7301)); + * res.add(new PortForwarder(8401, 7401)); + * res.add(new PortForwarder(7302, 8302)); + * res.add(new PortForwarder(7402, 8402)); + * res.add(new PortForwarder(7303, 8303)); + * res.add(new PortForwarder(7403, 8403)); + * return res; + * } + * + * private void stopForwarding(List pfs) throws Exception { + * for (PortForwarder pf : pfs) { + * pf.shutdown(); + * } + * } + * + * + */ +public class PortForwarder extends Thread { + private static final Logger LOG = LoggerFactory + .getLogger(PortForwarder.class); + + private static class PortForwardWorker implements Runnable { + + private final InputStream in; + private final OutputStream out; + private final Socket toClose; + private final Socket toClose2; + + PortForwardWorker(Socket toClose, Socket toClose2, InputStream in, + OutputStream out) throws IOException { + this.toClose = toClose; + this.toClose2 = toClose2; + this.in = in; + this.out = out; + // LOG.info("starting forward for "+toClose); + } + + public void run() { + Thread.currentThread().setName(toClose.toString() + "-->" + + toClose2.toString()); + byte[] buf = new byte[1024]; + try { + while (true) { + try { + int read = this.in.read(buf); + if (read > 0) { + try { + this.out.write(buf, 0, read); + } catch (IOException e) { + LOG.warn("exception during write", e); + try { + toClose.close(); + } catch (IOException ex) { + // ignore + } + try { + toClose2.close(); + } catch (IOException ex) { + // ignore + } + break; + } + } + } catch (SocketTimeoutException e) { + LOG.error("socket timeout", e); + } + Thread.sleep(1); + } + } catch (InterruptedException e) { + LOG.warn("Interrupted", e); + try { + toClose.close(); + } catch (IOException ex) { + // ignore + } + try { + toClose2.close(); + } catch (IOException ex) { + // ignore silently + } + } catch (SocketException e) { + if (!"Socket closed".equals(e.getMessage())) { + LOG.error("Unexpected exception", e); + } + } catch (IOException e) { + LOG.error("Unexpected exception", e); + } + LOG.info("Shutting down forward for " + toClose); + } + + } + + private volatile boolean stopped = false; + private ExecutorService workers = Executors.newCachedThreadPool(); + private ServerSocket serverSocket; + private final int to; + + public PortForwarder(int from, int to) throws IOException { + this.to = to; + serverSocket = new ServerSocket(from); + serverSocket.setSoTimeout(30000); + this.start(); + } + + @Override + public void run() { + try { + while (!stopped) { + Socket sock = null; + try { + LOG.info("accepting socket local:" + + serverSocket.getLocalPort() + " to:" + to); + sock = serverSocket.accept(); + LOG.info("accepted: local:" + sock.getLocalPort() + + " from:" + sock.getPort() + + " to:" + to); + Socket target = null; + int retry = 10; + while(sock.isConnected()) { + try { + target = new Socket("localhost", to); + break; + } catch (IOException e) { + if (retry == 0) { + throw e; + } + LOG.warn("connection failed, retrying(" + retry + + "): local:" + sock.getLocalPort() + + " from:" + sock.getPort() + + " to:" + to, e); + } + Thread.sleep(TimeUnit.SECONDS.toMillis(1)); + retry--; + } + LOG.info("connected: local:" + sock.getLocalPort() + + " from:" + sock.getPort() + + " to:" + to); + sock.setSoTimeout(30000); + target.setSoTimeout(30000); + this.workers.execute(new PortForwardWorker(sock, target, + sock.getInputStream(), target.getOutputStream())); + this.workers.execute(new PortForwardWorker(target, sock, + target.getInputStream(), sock.getOutputStream())); + } catch (SocketTimeoutException e) { + LOG.warn("socket timed out local:" + + (sock != null ? sock.getLocalPort(): "") + + " from:" + (sock != null ? sock.getPort(): "") + + " to:" + to, e); + } catch (ConnectException e) { + LOG.warn("connection exception local:" + + (sock != null ? sock.getLocalPort(): "") + + " from:" + (sock != null ? sock.getPort(): "") + + " to:" + to, e); + sock.close(); + } catch (IOException e) { + if (!"Socket closed".equals(e.getMessage())) { + LOG.warn("unexpected exception local:" + + (sock != null ? sock.getLocalPort(): "") + + " from:" + (sock != null ? sock.getPort(): "") + + " to:" + to, e); + throw e; + } + } + } + } catch (IOException e) { + LOG.error("Unexpected exception to:" + to, e); + } catch (InterruptedException e) { + LOG.error("Interrupted to:" + to, e); + } + } + + public void shutdown() throws Exception { + this.stopped = true; + this.serverSocket.close(); + this.workers.shutdownNow(); + try { + if (!this.workers.awaitTermination(5, TimeUnit.SECONDS)) { + throw new Exception( + "Failed to stop forwarding within 5 seconds"); + } + } catch (InterruptedException e) { + throw new Exception("Failed to stop forwarding"); + } + this.join(); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/ACLCountTest.java b/src/java/test/org/apache/zookeeper/test/ACLCountTest.java index 772dc920d70..88b8869adf3 100644 --- a/src/java/test/org/apache/zookeeper/test/ACLCountTest.java +++ b/src/java/test/org/apache/zookeeper/test/ACLCountTest.java @@ -110,8 +110,8 @@ public void testAclCount() throws Exception { Assert.assertTrue(false); } - Assert.assertTrue(true); - + f.shutdown(); + zks.shutdown(); } diff --git a/src/java/test/org/apache/zookeeper/test/ACLTest.java b/src/java/test/org/apache/zookeeper/test/ACLTest.java index 51ef4f9d329..793265c510d 100644 --- a/src/java/test/org/apache/zookeeper/test/ACLTest.java +++ b/src/java/test/org/apache/zookeeper/test/ACLTest.java @@ -71,7 +71,7 @@ public void testDisconnectedAddAuth() throws Exception { } } finally { f.shutdown(); - + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, ClientBase.CONNECTION_TIMEOUT)); @@ -122,6 +122,7 @@ public void testAcls() throws Exception { } finally { // now shutdown the server and restart it f.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); } @@ -157,7 +158,7 @@ public void testAcls() throws Exception { zk.close(); } finally { f.shutdown(); - + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, ClientBase.CONNECTION_TIMEOUT)); diff --git a/src/java/test/org/apache/zookeeper/test/AsyncOps.java b/src/java/test/org/apache/zookeeper/test/AsyncOps.java index 328a15a8ec9..2dbe64455ea 100644 --- a/src/java/test/org/apache/zookeeper/test/AsyncOps.java +++ b/src/java/test/org/apache/zookeeper/test/AsyncOps.java @@ -19,6 +19,7 @@ package org.apache.zookeeper.test; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -147,11 +148,21 @@ public AsyncCB create() { zk.create(path, data, acl, flags, this, toString()); return this; } + + public AsyncCB createEphemeral() { + zk.create(path, data, acl, CreateMode.EPHEMERAL, this, toString()); + return this; + } public void verifyCreate() { create(); verify(); } + + public void verifyCreateEphemeral() { + createEphemeral(); + verify(); + } public void verifyCreateFailure_NodeExists() { new StringCB(zk).verifyCreate(); @@ -161,7 +172,28 @@ public void verifyCreateFailure_NodeExists() { zk.create(path, data, acl, flags, this, toString()); verify(); } - + + public void verifyCreateFailure_NoNode() { + + rc = Code.NONODE; + name = null; + path = path + "/bar"; + zk.create(path, data, acl, flags, this, toString()); + + verify(); + } + + public void verifyCreateFailure_NoChildForEphemeral() { + new StringCB(zk).verifyCreateEphemeral(); + + rc = Code.NOCHILDRENFOREPHEMERALS; + name = null; + path = path + "/bar"; + zk.create(path, data, acl, flags, this, toString()); + + verify(); + } + @Override public String toString() { return super.toString() + name; @@ -200,8 +232,21 @@ public void verifyGetACL() { zk.getACL(path, stat, this, toString()); verify(); } + + public void verifyGetACLFailure_NoNode(){ + rc = Code.NONODE; + stat = null; + acl = null; + zk.getACL(path, stat, this, toString()); + + verify(); + } public String toString(List acls) { + if (acls == null) { + return ""; + } + StringBuilder result = new StringBuilder(); for(ACL acl : acls) { result.append(acl.getPerms() + "::"); @@ -235,6 +280,7 @@ public void processResult(int rc, String path, Object ctx, { this.children = (children == null ? new ArrayList() : children); + Collections.sort(this.children); super.processResult(Code.get(rc), path, ctx); } @@ -320,6 +366,7 @@ public void processResult(int rc, String path, Object ctx, { this.children = (children == null ? new ArrayList() : children); + Collections.sort(this.children); super.processResult(Code.get(rc), path, ctx); } @@ -475,6 +522,16 @@ public void verifySetACLFailure_NoNode() { zk.setACL(path, acl, version, this, toString()); verify(); } + + public void verifySetACLFailure_BadVersion() { + new StringCB(zk).verifyCreate(); + + rc = Code.BADVERSION; + stat = null; + zk.setACL(path, acl, version + 1, this, toString()); + + verify(); + } public void setData() { zk.setData(path, data, version, this, toString()); @@ -494,6 +551,16 @@ public void verifySetDataFailure_NoNode() { zk.setData(path, data, version, this, toString()); verify(); } + + public void verifySetDataFailure_BadVersion() { + new StringCB(zk).verifyCreate(); + + rc = Code.BADVERSION; + stat = null; + zk.setData(path, data, version + 1, this, toString()); + + verify(); + } public void verifyExists() { new StringCB(zk).verifyCreate(); @@ -550,6 +617,24 @@ public void verifyDeleteFailure_NoNode() { zk.delete(path, version, this, toString()); verify(); } + + public void verifyDeleteFailure_BadVersion() { + new StringCB(zk).verifyCreate(); + rc = Code.BADVERSION; + zk.delete(path, version + 1, this, toString()); + verify(); + } + + public void verifyDeleteFailure_NotEmpty() { + StringCB scb = new StringCB(zk); + scb.create(); + scb.setPath(path + "/bar"); + scb.create(); + + rc = Code.NOTEMPTY; + zk.delete(path, version, this, toString()); + verify(); + } public void sync() { zk.sync(path, this, toString()); diff --git a/src/java/test/org/apache/zookeeper/test/AsyncOpsTest.java b/src/java/test/org/apache/zookeeper/test/AsyncOpsTest.java index 340bd735821..c807d7288c4 100644 --- a/src/java/test/org/apache/zookeeper/test/AsyncOpsTest.java +++ b/src/java/test/org/apache/zookeeper/test/AsyncOpsTest.java @@ -90,6 +90,16 @@ public void testAsyncCreateFailure_NodeExists() { new StringCB(zk).verifyCreateFailure_NodeExists(); } + @Test + public void testAsyncCreateFailure_NoNode() { + new StringCB(zk).verifyCreateFailure_NoNode(); + } + + @Test + public void testAsyncCreateFailure_NoChildForEphemeral() { + new StringCB(zk).verifyCreateFailure_NoChildForEphemeral(); + } + @Test public void testAsyncDelete() { new VoidCB(zk).verifyDelete(); @@ -100,6 +110,16 @@ public void testAsyncDeleteFailure_NoNode() { new VoidCB(zk).verifyDeleteFailure_NoNode(); } + @Test + public void testAsyncDeleteFailure_BadVersion() { + new VoidCB(zk).verifyDeleteFailure_BadVersion(); + } + + @Test + public void testAsyncDeleteFailure_NotEmpty() { + new VoidCB(zk).verifyDeleteFailure_NotEmpty(); + } + @Test public void testAsyncSync() { new VoidCB(zk).verifySync(); @@ -115,6 +135,11 @@ public void testAsyncSetACLFailure_NoNode() { new StatCB(zk).verifySetACLFailure_NoNode(); } + @Test + public void testAsyncSetACLFailure_BadVersion() { + new StatCB(zk).verifySetACLFailure_BadVersion(); + } + @Test public void testAsyncSetData() { new StatCB(zk).verifySetData(); @@ -125,6 +150,11 @@ public void testAsyncSetDataFailure_NoNode() { new StatCB(zk).verifySetDataFailure_NoNode(); } + @Test + public void testAsyncSetDataFailure_BadVersion() { + new StatCB(zk).verifySetDataFailure_BadVersion(); + } + @Test public void testAsyncExists() { new StatCB(zk).verifyExists(); @@ -140,6 +170,11 @@ public void testAsyncGetACL() { new ACLCB(zk).verifyGetACL(); } + @Test + public void testAsyncGetACLFailure_NoNode() { + new ACLCB(zk).verifyGetACLFailure_NoNode(); + } + @Test public void testAsyncGetChildrenEmpty() { new ChildrenCB(zk).verifyGetChildrenEmpty(); @@ -162,7 +197,7 @@ public void testAsyncGetChildrenFailure_NoNode() { @Test public void testAsyncGetChildren2Empty() { - new ChildrenCB(zk).verifyGetChildrenEmpty(); + new Children2CB(zk).verifyGetChildrenEmpty(); } @Test diff --git a/src/java/test/org/apache/zookeeper/test/AtomicFileOutputStreamTest.java b/src/java/test/org/apache/zookeeper/test/AtomicFileOutputStreamTest.java new file mode 100644 index 00000000000..fe86f54d0b1 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/AtomicFileOutputStreamTest.java @@ -0,0 +1,200 @@ +/** + * 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.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.common.AtomicFileOutputStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class AtomicFileOutputStreamTest extends ZKTestCase { + private static final String TEST_STRING = "hello world"; + private static final String TEST_STRING_2 = "goodbye world"; + + private File testDir; + private File dstFile; + + @Before + public void setupTestDir() throws IOException { + testDir = ClientBase.createTmpDir(); + dstFile = new File(testDir, "test.txt"); + } + @After + public void cleanupTestDir() throws IOException { + ClientBase.recursiveDelete(testDir); + } + + /** + * Test case where there is no existing file + */ + @Test + public void testWriteNewFile() throws IOException { + OutputStream fos = new AtomicFileOutputStream(dstFile); + assertFalse(dstFile.exists()); + fos.write(TEST_STRING.getBytes()); + fos.flush(); + assertFalse(dstFile.exists()); + fos.close(); + assertTrue(dstFile.exists()); + + String readBackData = ClientBase.readFile(dstFile); + assertEquals(TEST_STRING, readBackData); + } + + /** + * Test case where there is no existing file + */ + @Test + public void testOverwriteFile() throws IOException { + assertTrue("Creating empty dst file", dstFile.createNewFile()); + + OutputStream fos = new AtomicFileOutputStream(dstFile); + + assertTrue("Empty file still exists", dstFile.exists()); + fos.write(TEST_STRING.getBytes()); + fos.flush(); + + // Original contents still in place + assertEquals("", ClientBase.readFile(dstFile)); + + fos.close(); + + // New contents replace original file + String readBackData = ClientBase.readFile(dstFile); + assertEquals(TEST_STRING, readBackData); + } + + /** + * Test case where the flush() fails at close time - make sure that we clean + * up after ourselves and don't touch any existing file at the destination + */ + @Test + public void testFailToFlush() throws IOException { + // Create a file at destination + FileOutputStream fos = new FileOutputStream(dstFile); + fos.write(TEST_STRING_2.getBytes()); + fos.close(); + + OutputStream failingStream = createFailingStream(); + failingStream.write(TEST_STRING.getBytes()); + try { + failingStream.close(); + fail("Close didn't throw exception"); + } catch (IOException ioe) { + // expected + } + + // Should not have touched original file + assertEquals(TEST_STRING_2, ClientBase.readFile(dstFile)); + + assertEquals("Temporary file should have been cleaned up", + dstFile.getName(), ClientBase.join(",", testDir.list())); + } + + /** + * Create a stream that fails to flush at close time + */ + private OutputStream createFailingStream() throws FileNotFoundException { + return new AtomicFileOutputStream(dstFile) { + @Override + public void flush() throws IOException { + throw new IOException("injected failure"); + } + }; + } + + /** + * Ensure the tmp file is cleaned up and dstFile is not created when + * aborting a new file. + */ + @Test + public void testAbortNewFile() throws IOException { + AtomicFileOutputStream fos = new AtomicFileOutputStream(dstFile); + + fos.abort(); + + assertEquals(0, testDir.list().length); + } + + /** + * Ensure the tmp file is cleaned up and dstFile is not created when + * aborting a new file. + */ + @Test + public void testAbortNewFileAfterFlush() throws IOException { + AtomicFileOutputStream fos = new AtomicFileOutputStream(dstFile); + fos.write(TEST_STRING.getBytes()); + fos.flush(); + + fos.abort(); + + assertEquals(0, testDir.list().length); + } + + /** + * Ensure the tmp file is cleaned up and dstFile is untouched when + * aborting an existing file overwrite. + */ + @Test + public void testAbortExistingFile() throws IOException { + FileOutputStream fos1 = new FileOutputStream(dstFile); + fos1.write(TEST_STRING.getBytes()); + fos1.close(); + + AtomicFileOutputStream fos2 = new AtomicFileOutputStream(dstFile); + + fos2.abort(); + + // Should not have touched original file + assertEquals(TEST_STRING, ClientBase.readFile(dstFile)); + assertEquals(1, testDir.list().length); + } + + /** + * Ensure the tmp file is cleaned up and dstFile is untouched when + * aborting an existing file overwrite. + */ + @Test + public void testAbortExistingFileAfterFlush() throws IOException { + FileOutputStream fos1 = new FileOutputStream(dstFile); + fos1.write(TEST_STRING.getBytes()); + fos1.close(); + + AtomicFileOutputStream fos2 = new AtomicFileOutputStream(dstFile); + fos2.write(TEST_STRING_2.getBytes()); + fos2.flush(); + + fos2.abort(); + + // Should not have touched original file + assertEquals(TEST_STRING, ClientBase.readFile(dstFile)); + assertEquals(1, testDir.list().length); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/AuthTest.java b/src/java/test/org/apache/zookeeper/test/AuthTest.java index 300b723f8f3..60e3453bbc7 100644 --- a/src/java/test/org/apache/zookeeper/test/AuthTest.java +++ b/src/java/test/org/apache/zookeeper/test/AuthTest.java @@ -19,15 +19,16 @@ package org.apache.zookeeper.test; import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.TestableZooKeeper; import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; import org.junit.Assert; import org.junit.Test; @@ -39,8 +40,8 @@ public class AuthTest extends ClientBase { System.setProperty("zookeeper.authProvider.1", "org.apache.zookeeper.test.InvalidAuthProvider"); } - private AtomicInteger authFailed = new AtomicInteger(0); - + private final CountDownLatch authFailed = new CountDownLatch(1); + @Override protected TestableZooKeeper createClient(String hp) throws IOException, InterruptedException @@ -53,7 +54,7 @@ private class MyWatcher extends CountdownWatcher { @Override public synchronized void process(WatchedEvent event) { if (event.getState() == KeeperState.AuthFailed) { - authFailed.incrementAndGet(); + authFailed.countDown(); } else { super.process(event); @@ -69,8 +70,11 @@ public void testBadAuthNotifiesWatch() throws Exception { zk.getData("/path1", false, null); Assert.fail("Should get auth state error"); } catch(KeeperException.AuthFailedException e) { - Assert.assertEquals("Should have called my watcher", - 1, authFailed.get()); + if(!authFailed.await(CONNECTION_TIMEOUT, + TimeUnit.MILLISECONDS)) + { + Assert.fail("Should have called my watcher"); + } } finally { zk.close(); @@ -86,8 +90,11 @@ public void testBadAuthThenSendOtherCommands() throws Exception { zk.getData("/path1", false, null); Assert.fail("Should get auth state error"); } catch(KeeperException.AuthFailedException e) { - Assert.assertEquals("Should have called my watcher", - 1, authFailed.get()); + if(!authFailed.await(CONNECTION_TIMEOUT, + TimeUnit.MILLISECONDS)) + { + Assert.fail("Should have called my watcher"); + } } finally { zk.close(); diff --git a/src/java/test/org/apache/zookeeper/test/BufferSizeTest.java b/src/java/test/org/apache/zookeeper/test/BufferSizeTest.java new file mode 100644 index 00000000000..6d74e54b5d4 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/BufferSizeTest.java @@ -0,0 +1,132 @@ +/** + * 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.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; + +import org.apache.jute.BinaryInputArchive; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.junit.Before; +import org.junit.Test; + +public class BufferSizeTest extends ClientBase { + public static final int TEST_MAXBUFFER = 100; + private static final File TEST_DATA = new File( + System.getProperty("test.data.dir", "build/test/data"), + "buffersize"); + + private ZooKeeper zk; + + @Before + public void setMaxBuffer() throws IOException, InterruptedException { + System.setProperty("jute.maxbuffer", "" + TEST_MAXBUFFER); + assertEquals("Can't set jute.maxbuffer!", TEST_MAXBUFFER, BinaryInputArchive.maxBuffer); + zk = createClient(); + } + + @Test + public void testCreatesReqs() throws Exception { + testRequests(new ClientOp() { + @Override + public void execute(byte[] data) throws Exception { + zk.create("/create_test", data, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); + } + }); + } + + @Test + public void testSetReqs() throws Exception { + final String path = "/set_test"; + zk.create(path, new byte[1], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + testRequests(new ClientOp() { + @Override + public void execute(byte[] data) throws Exception { + zk.setData(path, data, -1); + } + }); + } + + /** Issues requests containing data smaller, equal, and greater than TEST_MAXBUFFER. */ + private void testRequests(ClientOp clientOp) throws Exception { + clientOp.execute(new byte[TEST_MAXBUFFER - 60]); + try { + // This should fail since the buffer size > the data size due to extra fields + clientOp.execute(new byte[TEST_MAXBUFFER]); + fail("Request exceeding jute.maxbuffer succeeded!"); + } catch (KeeperException.ConnectionLossException e) {} + try { + clientOp.execute(new byte[TEST_MAXBUFFER + 10]); + fail("Request exceeding jute.maxbuffer succeeded!"); + } catch (KeeperException.ConnectionLossException e) {} + } + + private interface ClientOp { + void execute(byte[] data) throws Exception; + } + + @Test + public void testStartup() throws Exception { + final String path = "/test_node"; + zk.create(path, new byte[TEST_MAXBUFFER - 60], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.setData(path, new byte[TEST_MAXBUFFER - 50], -1); + + stopServer(); + startServer(); + } + + @Test + public void testStartupFailureCreate() throws Exception { + // Empty snapshot and logfile containing a 5000-byte create + testStartupFailure(new File(TEST_DATA, "create"), + "Server started despite create exceeding jute.maxbuffer!"); + } + + @Test + public void testStartupFailureSet() throws Exception { + // Empty snapshot and logfile containing a 1-byte create and 5000-byte set + testStartupFailure(new File(TEST_DATA, "set"), + "Server started despite set exceeding jute.maxbuffer!"); + } + + @Test + public void testStartupFailureSnapshot() throws Exception { + // Snapshot containing 5000-byte znode and logfile containing create txn + testStartupFailure(new File(TEST_DATA, "snapshot"), + "Server started despite znode exceeding jute.maxbuffer!"); + } + + private void testStartupFailure(File testDir, String failureMsg) throws Exception { + stopServer(); + // Point server at testDir + tmpDir = testDir; + try { + startServer(); + fail(failureMsg); + } catch (IOException e) { + LOG.info("Successfully caught IOException: " + e); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/test/ClientBase.java b/src/java/test/org/apache/zookeeper/test/ClientBase.java index 0fc7d258d69..7595a029bb9 100644 --- a/src/java/test/org/apache/zookeeper/test/ClientBase.java +++ b/src/java/test/org/apache/zookeeper/test/ClientBase.java @@ -18,28 +18,31 @@ package org.apache.zookeeper.test; -import java.io.BufferedReader; +import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; -import java.net.Socket; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.management.MBeanServerConnection; +import javax.management.ObjectName; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import junit.framework.TestCase; + +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.TestableZooKeeper; @@ -48,16 +51,19 @@ import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.common.IOUtils; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ServerCnxnFactoryAccessor; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.ZooKeeperServer; import org.apache.zookeeper.server.persistence.FileTxnLog; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.util.OSMXBean; import org.junit.After; import org.junit.Assert; import org.junit.Before; - -import com.sun.management.UnixOperatingSystemMXBean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class ClientBase extends ZKTestCase { protected static final Logger LOG = LoggerFactory.getLogger(ClientBase.class); @@ -66,7 +72,9 @@ public abstract class ClientBase extends ZKTestCase { static final File BASETEST = new File(System.getProperty("build.test.dir", "build")); - protected String hostPort = "127.0.0.1:" + PortAssignment.unique(); + protected int port = PortAssignment.unique(); + protected String hostPort = "127.0.0.1:" + port; + protected String ipv6HostPort = "[0:0:0:0:0:0:0:1]:" + port; protected int maxCnxns = 0; protected ServerCnxnFactory serverFactory = null; protected File tmpDir = null; @@ -87,10 +95,15 @@ protected class NullWatcher implements Watcher { public void process(WatchedEvent event) { /* nada */ } } - protected static class CountdownWatcher implements Watcher { + public static class CountdownWatcher implements Watcher { // XXX this doesn't need to be volatile! (Should probably be final) volatile CountDownLatch clientConnected; + // Set to true when connected to a read-only server, or a read-write (quorum) server. volatile boolean connected; + // Set to true when connected to a quorum server. + volatile boolean syncConnected; + // Set to true when connected to a quorum server in read-only mode + volatile boolean readOnlyConnected; public CountdownWatcher() { reset(); @@ -98,40 +111,82 @@ public CountdownWatcher() { synchronized public void reset() { clientConnected = new CountDownLatch(1); connected = false; + syncConnected = false; + readOnlyConnected = false; } synchronized public void process(WatchedEvent event) { - if (event.getState() == KeeperState.SyncConnected || - event.getState() == KeeperState.ConnectedReadOnly) { + KeeperState state = event.getState(); + if (state == KeeperState.SyncConnected) { connected = true; - notifyAll(); - clientConnected.countDown(); + syncConnected = true; + readOnlyConnected = false; + } else if (state == KeeperState.ConnectedReadOnly) { + connected = true; + syncConnected = false; + readOnlyConnected = true; } else { connected = false; - notifyAll(); + syncConnected = false; + readOnlyConnected = false; + } + + notifyAll(); + if (connected) { + clientConnected.countDown(); } } - synchronized boolean isConnected() { + synchronized public boolean isConnected() { return connected; } - synchronized void waitForConnected(long timeout) throws InterruptedException, TimeoutException { - long expire = System.currentTimeMillis() + timeout; + synchronized public void waitForConnected(long timeout) + throws InterruptedException, TimeoutException + { + long expire = Time.currentElapsedTime() + timeout; long left = timeout; while(!connected && left > 0) { wait(left); - left = expire - System.currentTimeMillis(); + left = expire - Time.currentElapsedTime(); } if (!connected) { throw new TimeoutException("Did not connect"); } } - synchronized void waitForDisconnected(long timeout) throws InterruptedException, TimeoutException { + synchronized public void waitForSyncConnected(long timeout) + throws InterruptedException, TimeoutException + { long expire = System.currentTimeMillis() + timeout; long left = timeout; - while(connected && left > 0) { + while(!syncConnected && left > 0) { wait(left); left = expire - System.currentTimeMillis(); } + if (!syncConnected) { + throw new TimeoutException("Failed to connect to read-write ZooKeeper server."); + } + } + synchronized public void waitForReadOnlyConnected(long timeout) + throws InterruptedException, TimeoutException + { + long expire = System.currentTimeMillis() + timeout; + long left = timeout; + while(!readOnlyConnected && left > 0) { + wait(left); + left = expire - System.currentTimeMillis(); + } + if (!readOnlyConnected) { + throw new TimeoutException("Failed to connect in read-only mode to ZooKeeper server."); + } + } + synchronized public void waitForDisconnected(long timeout) + throws InterruptedException, TimeoutException + { + long expire = Time.currentElapsedTime() + timeout; + long left = timeout; + while(connected && left > 0) { + wait(left); + left = expire - Time.currentElapsedTime(); + } if (connected) { throw new TimeoutException("Did not disconnect"); @@ -152,6 +207,12 @@ protected TestableZooKeeper createClient(String hp) return createClient(watcher, hp); } + protected TestableZooKeeper createClient(CountdownWatcher watcher) + throws IOException, InterruptedException + { + return createClient(watcher, hostPort); + } + private LinkedList allClients; private boolean allClientsSetup = false; @@ -178,14 +239,13 @@ protected TestableZooKeeper createClient(CountdownWatcher watcher, } if (allClients != null) { allClients.add(zk); + JMXEnv.ensureAll(getHexSessionId(zk.getSessionId())); } else { // test done - close the zk, not needed zk.close(); } } - JMXEnv.ensureAll("0x" + Long.toHexString(zk.getSessionId())); - return zk; } @@ -213,46 +273,8 @@ public static List parseHostPortList(String hplist) { return alist; } - /** - * Send the 4letterword - * @param host the destination host - * @param port the destination port - * @param cmd the 4letterword - * @return - * @throws IOException - */ - public static String send4LetterWord(String host, int port, String cmd) - throws IOException - { - LOG.info("connecting to " + host + " " + port); - Socket sock = new Socket(host, port); - BufferedReader reader = null; - try { - OutputStream outstream = sock.getOutputStream(); - outstream.write(cmd.getBytes()); - outstream.flush(); - // this replicates NC - close the output stream before reading - sock.shutdownOutput(); - - reader = - new BufferedReader( - new InputStreamReader(sock.getInputStream())); - StringBuilder sb = new StringBuilder(); - String line; - while((line = reader.readLine()) != null) { - sb.append(line + "\n"); - } - return sb.toString(); - } finally { - sock.close(); - if (reader != null) { - reader.close(); - } - } - } - public static boolean waitForServerUp(String hp, long timeout) { - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); while (true) { try { // if there are multiple hostports, just take the first one @@ -267,7 +289,7 @@ public static boolean waitForServerUp(String hp, long timeout) { LOG.info("server " + hp + " not up " + e); } - if (System.currentTimeMillis() > start + timeout) { + if (Time.currentElapsedTime() > start + timeout) { break; } try { @@ -279,7 +301,7 @@ public static boolean waitForServerUp(String hp, long timeout) { return false; } public static boolean waitForServerDown(String hp, long timeout) { - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); while (true) { try { HostPort hpobj = parseHostPortList(hp).get(0); @@ -288,7 +310,7 @@ public static boolean waitForServerDown(String hp, long timeout) { return true; } - if (System.currentTimeMillis() > start + timeout) { + if (Time.currentElapsedTime() > start + timeout) { break; } try { @@ -300,6 +322,23 @@ public static boolean waitForServerDown(String hp, long timeout) { return false; } + public static boolean waitForServerState(QuorumPeer qp, int timeout, + String serverState) { + long start = Time.currentElapsedTime(); + while (true) { + try { + Thread.sleep(250); + } catch (InterruptedException e) { + // ignore + } + if (qp.getServerState().equals(serverState)) + return true; + if (Time.currentElapsedTime() > start + timeout) { + return false; + } + } + } + static void verifyThreadTerminated(Thread thread, long millis) throws InterruptedException { @@ -336,20 +375,42 @@ private static int getPort(String hostPort) { return Integer.parseInt(portstr); } - static ServerCnxnFactory createNewServerInstance(File dataDir, - ServerCnxnFactory factory, String hostPort, int maxCnxns) - throws IOException, InterruptedException - { + /** + * Starting the given server instance + */ + public static void startServerInstance(File dataDir, + ServerCnxnFactory factory, String hostPort) throws IOException, + InterruptedException { + final int port = getPort(hostPort); + LOG.info("STARTING server instance 127.0.0.1:{}", port); ZooKeeperServer zks = new ZooKeeperServer(dataDir, dataDir, 3000); - final int PORT = getPort(hostPort); - if (factory == null) { - factory = ServerCnxnFactory.createFactory(PORT, maxCnxns); - } factory.startup(zks); - Assert.assertTrue("waiting for server up", - ClientBase.waitForServerUp("127.0.0.1:" + PORT, - CONNECTION_TIMEOUT)); + Assert.assertTrue("waiting for server up", ClientBase.waitForServerUp( + "127.0.0.1:" + port, CONNECTION_TIMEOUT)); + } + /** + * This method instantiates a new server. Starting of the server + * instance has been moved to a separate method + * {@link ClientBase#startServerInstance(File, ServerCnxnFactory, String)}. + * Because any exception on starting the server would leave the server + * running and the caller would not be able to shutdown the instance. This + * may affect other test cases. + * + * @return newly created server instance + * + * @see ZOOKEEPER-1852 + * for more information. + */ + public static ServerCnxnFactory createNewServerInstance( + ServerCnxnFactory factory, String hostPort, int maxCnxns) + throws IOException, InterruptedException { + final int port = getPort(hostPort); + LOG.info("CREATING server instance 127.0.0.1:{}", port); + if (factory == null) { + factory = ServerCnxnFactory.createFactory(port, maxCnxns); + } return factory; } @@ -357,15 +418,18 @@ static void shutdownServerInstance(ServerCnxnFactory factory, String hostPort) { if (factory != null) { - ZKDatabase zkDb; + ZKDatabase zkDb = null; { ZooKeeperServer zs = getServer(factory); - - zkDb = zs.getZKDatabase(); + if (zs != null) { + zkDb = zs.getZKDatabase(); + } } factory.shutdown(); try { - zkDb.close(); + if (zkDb != null) { + zkDb.close(); + } } catch (IOException ie) { LOG.warn("Error closing logs ", ie); } @@ -401,12 +465,9 @@ public void setUp() throws Exception { * correctly. Unfortunately this only works on unix systems (the * only place sun has implemented as part of the mgmt bean api. */ - OperatingSystemMXBean osMbean = - ManagementFactory.getOperatingSystemMXBean(); - if (osMbean != null && osMbean instanceof UnixOperatingSystemMXBean) { - UnixOperatingSystemMXBean unixos = - (UnixOperatingSystemMXBean)osMbean; - initialFdCount = unixos.getOpenFileDescriptorCount(); + OSMXBean osMbean = new OSMXBean(); + if (osMbean.getUnix() == true) { + initialFdCount = osMbean.getOpenFileDescriptorCount(); LOG.info("Initial fdcount is: " + initialFdCount); } @@ -426,9 +487,49 @@ public void setUp() throws Exception { protected void startServer() throws Exception { LOG.info("STARTING server"); - serverFactory = createNewServerInstance(tmpDir, serverFactory, hostPort, maxCnxns); - // ensure that only server and data bean are registered - JMXEnv.ensureOnly("InMemoryDataTree", "StandaloneServer_port"); + serverFactory = createNewServerInstance(serverFactory, hostPort, + maxCnxns); + startServerInstance(tmpDir, serverFactory, hostPort); + // ensure that server and data bean are registered + Set children = JMXEnv.ensureParent("InMemoryDataTree", + "StandaloneServer_port"); + // Remove beans which are related to zk client sessions. Strong + // assertions cannot be done for these client sessions because + // registeration of these beans with server will happen only on their + // respective reconnection interval + verifyUnexpectedBeans(children); + } + + private void verifyUnexpectedBeans(Set children) { + if (allClients != null) { + for (ZooKeeper zkc : allClients) { + Iterator childItr = children.iterator(); + while (childItr.hasNext()) { + ObjectName clientBean = childItr.next(); + if (clientBean.toString().contains( + getHexSessionId(zkc.getSessionId()))) { + LOG.info("found name:" + zkc.getSessionId() + + " client bean:" + clientBean.toString()); + childItr.remove(); + } + } + } + } + for (ObjectName bean : children) { + LOG.info("unexpected:" + bean.toString()); + } + TestCase.assertEquals("Unexpected bean exists!", 0, children.size()); + } + + /** + * Returns a string representation of the given long value session id + * + * @param sessionId + * long value of session id + * @return string representation of session id + */ + protected static String getHexSessionId(long sessionId) { + return "0x" + Long.toHexString(sessionId); } protected void stopServer() throws Exception { @@ -482,12 +583,9 @@ public void tearDown() throws Exception { * correctly. Unfortunately this only works on unix systems (the * only place sun has implemented as part of the mgmt bean api. */ - OperatingSystemMXBean osMbean = - ManagementFactory.getOperatingSystemMXBean(); - if (osMbean != null && osMbean instanceof UnixOperatingSystemMXBean) { - UnixOperatingSystemMXBean unixos = - (UnixOperatingSystemMXBean)osMbean; - long fdCount = unixos.getOpenFileDescriptorCount(); + OSMXBean osMbean = new OSMXBean(); + if (osMbean.getUnix() == true) { + long fdCount = osMbean.getOpenFileDescriptorCount(); String message = "fdcount after test is: " + fdCount + " at start it was " + initialFdCount; LOG.info(message); @@ -513,7 +611,7 @@ public static boolean recursiveDelete(File d) { return d.delete(); } - private static void logAllStackTraces() { + public static void logAllStackTraces() { StringBuilder sb = new StringBuilder(); sb.append("Starting logAllStackTraces()\n"); Map threads = Thread.getAllStackTraces(); @@ -586,4 +684,56 @@ void verifyRootOfAllServersMatch(String hostPort) } } } + + public static String readFile(File file) throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + BufferedInputStream is = new BufferedInputStream(new FileInputStream(file)); + try { + IOUtils.copyBytes(is, os, 1024, true); + } finally { + is.close(); + } + return os.toString(); + } + + public static String join(String separator, Object[] parts) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Object part : parts) { + if (!first) { + sb.append(separator); + first = false; + } + sb.append(part); + } + return sb.toString(); + } + + public static ZooKeeper createZKClient(String cxnString) throws Exception { + return createZKClient(cxnString, CONNECTION_TIMEOUT); + } + + /** + * Returns ZooKeeper client after connecting to ZooKeeper Server. Session + * timeout is {@link #CONNECTION_TIMEOUT} + * + * @param cxnString + * connection string in the form of host:port + * @param sessionTimeout + * @throws IOException + * in cases of network failure + */ + public static ZooKeeper createZKClient(String cxnString, int sessionTimeout) throws IOException { + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(cxnString, sessionTimeout, watcher); + try { + watcher.waitForConnected(CONNECTION_TIMEOUT); + } catch (InterruptedException e) { + Assert.fail("ZooKeeper client can not connect to " + cxnString); + } + catch (TimeoutException e) { + Assert.fail("ZooKeeper client can not connect to " + cxnString); + } + return zk; + } } diff --git a/src/java/test/org/apache/zookeeper/test/ClientHammerTest.java b/src/java/test/org/apache/zookeeper/test/ClientHammerTest.java index 581402cfa89..025ccbe4cd7 100644 --- a/src/java/test/org/apache/zookeeper/test/ClientHammerTest.java +++ b/src/java/test/org/apache/zookeeper/test/ClientHammerTest.java @@ -22,6 +22,7 @@ import java.util.Date; import java.util.List; +import org.apache.zookeeper.common.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.CreateMode; @@ -124,7 +125,7 @@ public void runHammer(final int threadCount, final int childCount) { try { HammerThread[] threads = new HammerThread[threadCount]; - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); for (int i = 0; i < threads.length; i++) { ZooKeeper zk = createClient(); String prefix = "/test-" + i; @@ -157,7 +158,7 @@ public void testHammerSuper() throws Throwable { final int childCount = 10; HammerThread[] threads = new HammerThread[threadCount]; - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); for (int i = 0; i < threads.length; i++) { String prefix = "/test-" + i; { @@ -218,7 +219,7 @@ public void verifyHammer(long start, HammerThread[] threads, int childCount) * HAMMERTHREAD_LATENCY * safetyFactor); } LOG.info(new Date() + " Total time " - + (System.currentTimeMillis() - start)); + + (Time.currentElapsedTime() - start)); ZooKeeper zk = createClient(); try { diff --git a/src/java/test/org/apache/zookeeper/test/ClientPortBindTest.java b/src/java/test/org/apache/zookeeper/test/ClientPortBindTest.java index 43ac645f560..d6cb1e22f1d 100644 --- a/src/java/test/org/apache/zookeeper/test/ClientPortBindTest.java +++ b/src/java/test/org/apache/zookeeper/test/ClientPortBindTest.java @@ -24,6 +24,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; +import java.net.SocketException; import java.util.Enumeration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -58,13 +59,19 @@ public void testBindByAddress() throws Exception { // if we have a loopback and it has an address use it while(intfs.hasMoreElements()) { NetworkInterface i = intfs.nextElement(); - if (i.isLoopback()) { - Enumeration addrs = i.getInetAddresses(); - if (addrs.hasMoreElements()) { - bindAddress = addrs.nextElement().getHostAddress(); - // handle the ipv6 scope_id - ie remove it - bindAddress = bindAddress.split("%")[0]; + try { + if (i.isLoopback()) { + Enumeration addrs = i.getInetAddresses(); + while (addrs.hasMoreElements()) { + InetAddress a = addrs.nextElement(); + if(a.isLoopbackAddress()) { + bindAddress = a.getHostAddress(); + break; + } + } } + } catch (SocketException se) { + LOG.warn("Couldn't find loopback interface: " + se.getMessage()); } } if (bindAddress == null) { @@ -101,6 +108,7 @@ public void testBindByAddress() throws Exception { zk.close(); } finally { f.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, diff --git a/src/java/test/org/apache/zookeeper/test/ClientTest.java b/src/java/test/org/apache/zookeeper/test/ClientTest.java index e99d8df54a3..9dc48619e26 100644 --- a/src/java/test/org/apache/zookeeper/test/ClientTest.java +++ b/src/java/test/org/apache/zookeeper/test/ClientTest.java @@ -19,36 +19,38 @@ package org.apache.zookeeper.test; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.TestableZooKeeper; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.KeeperException.InvalidACLException; +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooDefs.Perms; +import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.proto.ExistsRequest; +import org.apache.zookeeper.proto.ExistsResponse; +import org.apache.zookeeper.proto.ReplyHeader; +import org.apache.zookeeper.proto.RequestHeader; import org.apache.zookeeper.server.PrepRequestProcessor; +import org.apache.zookeeper.server.util.OSMXBean; import org.junit.Assert; import org.junit.Test; - -import com.sun.management.UnixOperatingSystemMXBean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ClientTest extends ClientBase { protected static final Logger LOG = LoggerFactory.getLogger(ClientTest.class); @@ -91,14 +93,22 @@ public void testPing() throws Exception { public void testClientwithoutWatcherObj() throws IOException, InterruptedException, KeeperException { - performClientTest(false); + performClientTest(false, hostPort); } @Test public void testClientWithWatcherObj() throws IOException, InterruptedException, KeeperException { - performClientTest(true); + performClientTest(true, hostPort); + } + + @Test + public void testClientWithIPv6Address() throws IOException, + InterruptedException, KeeperException + { + assumeIPv6Available(); + performClientTest(true, ipv6HostPort); } /** Exercise the testable functions, verify tostring, etc... */ @@ -160,6 +170,12 @@ public void testACLs() throws Exception { List acls = zk.getACL("/acltest", new Stat()); Assert.assertEquals(1, acls.size()); Assert.assertEquals(Ids.OPEN_ACL_UNSAFE, acls); + + // The stat parameter should be optional. + acls = zk.getACL("/acltest", null); + Assert.assertEquals(1, acls.size()); + Assert.assertEquals(Ids.OPEN_ACL_UNSAFE, acls); + zk.close(); } finally { if (zk != null) { @@ -302,7 +318,7 @@ public void testMutipleWatcherObjs() } } - private void performClientTest(boolean withWatcherObj) + private void performClientTest(boolean withWatcherObj, String hostPort) throws IOException, InterruptedException, KeeperException { ZooKeeper zk = null; @@ -523,6 +539,22 @@ public void testSequentialNodeData() throws Exception { } + @Test + public void testLargeNodeData() throws Exception { + ZooKeeper zk= null; + String queue_handle = "/large"; + try { + zk = createClient(); + + zk.create(queue_handle, new byte[500000], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } finally { + if (zk != null) { + zk.close(); + } + } + + } private void verifyCreateFails(String path, ZooKeeper zk) throws Exception { try { @@ -693,11 +725,8 @@ public void run() { */ @Test public void testClientCleanup() throws Throwable { - OperatingSystemMXBean osMbean = - ManagementFactory.getOperatingSystemMXBean(); - if (osMbean == null - || !(osMbean instanceof UnixOperatingSystemMXBean)) - { + OSMXBean osMbean = new OSMXBean(); + if (osMbean.getUnix() == false) { LOG.warn("skipping testClientCleanup, only available on Unix"); return; } @@ -710,9 +739,7 @@ public void testClientCleanup() throws Throwable { * on unix systems (the only place sun has implemented as part of the * mgmt bean api). */ - UnixOperatingSystemMXBean unixos = - (UnixOperatingSystemMXBean) osMbean; - long initialFdCount = unixos.getOpenFileDescriptorCount(); + long initialFdCount = osMbean.getOpenFileDescriptorCount(); VerifyClientCleanup threads[] = new VerifyClientCleanup[threadCount]; @@ -728,7 +755,7 @@ public void testClientCleanup() throws Throwable { // if this Assert.fails it means we are not cleaning up after the closed // sessions. - long currentCount = unixos.getOpenFileDescriptorCount(); + long currentCount = osMbean.getOpenFileDescriptorCount(); final String logmsg = "open fds after test ({}) are not significantly higher than before ({})"; if (currentCount > initialFdCount + 10) { @@ -738,4 +765,41 @@ public void testClientCleanup() throws Throwable { LOG.info(logmsg,Long.valueOf(currentCount),Long.valueOf(initialFdCount)); } } + + + /** + * We create a perfectly valid 'exists' request, except that the opcode is wrong. + * @return + * @throws Exception + */ + @Test + public void testNonExistingOpCode() throws Exception { + final CountDownLatch clientDisconnected = new CountDownLatch(1); + Watcher watcher = new Watcher() { + @Override + public synchronized void process(WatchedEvent event) { + if (event.getState() == KeeperState.Disconnected) { + clientDisconnected.countDown(); + } + } + }; + TestableZooKeeper zk = new TestableZooKeeper(hostPort, CONNECTION_TIMEOUT, watcher); + + final String path = "/m1"; + + RequestHeader h = new RequestHeader(); + h.setType(888); // This code does not exists + ExistsRequest request = new ExistsRequest(); + request.setPath(path); + request.setWatch(false); + ExistsResponse response = new ExistsResponse(); + + ReplyHeader r = zk.submitRequest(h, request, response, null); + + Assert.assertEquals(r.getErr(), Code.UNIMPLEMENTED.intValue()); + + // Sending a nonexisting opcode should cause the server to disconnect + Assert.assertTrue("failed to disconnect", + clientDisconnected.await(5000, TimeUnit.MILLISECONDS)); + } } diff --git a/src/java/test/org/apache/zookeeper/test/DataTreeTest.java b/src/java/test/org/apache/zookeeper/test/DataTreeTest.java deleted file mode 100644 index 74c8a5e07cd..00000000000 --- a/src/java/test/org/apache/zookeeper/test/DataTreeTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * 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.test; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZKTestCase; -import org.apache.zookeeper.data.Stat; -import org.apache.zookeeper.server.DataTree; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.apache.zookeeper.server.DataNode; - -public class DataTreeTest extends ZKTestCase { - protected static final Logger LOG = LoggerFactory.getLogger(DataTreeTest.class); - - private DataTree dt; - - @Before - public void setUp() throws Exception { - dt=new DataTree(); - } - - @After - public void tearDown() throws Exception { - dt=null; - } - - @Test - public void testRootWatchTriggered() throws Exception { - class MyWatcher implements Watcher{ - boolean fired=false; - public void process(WatchedEvent event) { - if(event.getPath().equals("/")) - fired=true; - } - } - MyWatcher watcher=new MyWatcher(); - // set a watch on the root node - dt.getChildren("/", new Stat(), watcher); - // add a new node, should trigger a watch - dt.createNode("/xyz", new byte[0], null, 0, dt.getNode("/").stat.getCversion()+1, 1, 1); - Assert.assertFalse("Root node watch not triggered",!watcher.fired); - } - - /** - * For ZOOKEEPER-1046 test if cversion is getting incremented correctly. - */ - @Test - public void testIncrementCversion() throws Exception { - dt.createNode("/test", new byte[0], null, 0, dt.getNode("/").stat.getCversion()+1, 1, 1); - DataNode zk = dt.getNode("/test"); - int prevCversion = zk.stat.getCversion(); - long prevPzxid = zk.stat.getPzxid(); - dt.setCversionPzxid("/test/", prevCversion + 1, prevPzxid + 1); - int newCversion = zk.stat.getCversion(); - long newPzxid = zk.stat.getPzxid(); - Assert.assertTrue(" verification failed. Expected: <" + - (prevCversion + 1) + ", " + (prevPzxid + 1) + ">, found: <" + - newCversion + ", " + newPzxid + ">", - (newCversion == prevCversion + 1 && newPzxid == prevPzxid + 1)); - } -} diff --git a/src/java/test/org/apache/zookeeper/test/DisconnectedWatcherTest.java b/src/java/test/org/apache/zookeeper/test/DisconnectedWatcherTest.java index 4a76f124bfe..975d225e02d 100644 --- a/src/java/test/org/apache/zookeeper/test/DisconnectedWatcherTest.java +++ b/src/java/test/org/apache/zookeeper/test/DisconnectedWatcherTest.java @@ -18,6 +18,8 @@ package org.apache.zookeeper.test; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -173,4 +175,83 @@ public void testDeepChildWatcherAutoResetWithChroot() throws Exception { Assert.assertEquals(EventType.NodeChildrenChanged, e.getType()); Assert.assertEquals("/are", e.getPath()); } + + // @see jira issue ZOOKEEPER-706. Test auto reset of a large number of + // watches which require multiple SetWatches calls. + @Test + public void testManyChildWatchersAutoReset() throws Exception { + ZooKeeper zk1 = createClient(); + + MyWatcher watcher = new MyWatcher(); + ZooKeeper zk2 = createClient(watcher); + + // 110 character base path + String pathBase = "/long-path-000000000-111111111-222222222-333333333-444444444-" + + "555555555-666666666-777777777-888888888-999999999"; + + zk1.create(pathBase, null, Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + + // Create 10,000 nodes. This should ensure the length of our + // watches set below exceeds 1MB. + List paths = new ArrayList(); + for (int i = 0; i < 10000; i++) { + String path = zk1.create(pathBase + "/ch-", null, Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + paths.add(path); + } + + MyWatcher childWatcher = new MyWatcher(); + + // Set a combination of child/exists/data watches + int i = 0; + for (String path : paths) { + if (i % 3 == 0) { + zk2.getChildren(path, childWatcher); + } else if (i % 3 == 1) { + zk2.exists(path + "/foo", childWatcher); + } else if (i % 3 == 2) { + zk2.getData(path, childWatcher, null); + } + + i++; + } + + stopServer(); + watcher.waitForDisconnected(30000); + startServer(); + watcher.waitForConnected(30000); + + // Trigger the watches and ensure they properly propagate to the client + i = 0; + for (String path : paths) { + if (i % 3 == 0) { + zk1.create(path + "/ch", null, Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + + WatchedEvent e = childWatcher.events.poll(TIMEOUT, TimeUnit.MILLISECONDS); + Assert.assertNotNull(e); + Assert.assertEquals(EventType.NodeChildrenChanged, e.getType()); + Assert.assertEquals(path, e.getPath()); + } else if (i % 3 == 1) { + zk1.create(path + "/foo", null, Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + + WatchedEvent e = childWatcher.events.poll(TIMEOUT, TimeUnit.MILLISECONDS); + Assert.assertNotNull(e); + Assert.assertEquals(EventType.NodeCreated, e.getType()); + Assert.assertEquals(path + "/foo", e.getPath()); + } else if (i % 3 == 2) { + zk1.setData(path, new byte[]{1, 2, 3}, -1); + + WatchedEvent e = childWatcher.events.poll(TIMEOUT, TimeUnit.MILLISECONDS); + Assert.assertNotNull(e); + Assert.assertEquals(EventType.NodeDataChanged, e.getType()); + Assert.assertEquals(path, e.getPath()); + } + + i++; + } + } + } diff --git a/src/java/test/org/apache/zookeeper/test/FLENewEpochTest.java b/src/java/test/org/apache/zookeeper/test/FLENewEpochTest.java index 1e1abe0d155..1731e6e801a 100644 --- a/src/java/test/org/apache/zookeeper/test/FLENewEpochTest.java +++ b/src/java/test/org/apache/zookeeper/test/FLENewEpochTest.java @@ -157,9 +157,8 @@ public void testLENewEpoch() throws Exception { LOG.info("TestLE: " + getTestName()+ ", " + count); for(int i = 0; i < count; i++) { peers.put(Long.valueOf(i), - new QuorumServer(i, - new InetSocketAddress(PortAssignment.unique()), - new InetSocketAddress(PortAssignment.unique()))); + new QuorumServer(i, "0.0.0.0", PortAssignment.unique(), + PortAssignment.unique(), null)); tmpdir[i] = ClientBase.createTmpDir(); port[i] = PortAssignment.unique(); } diff --git a/src/java/test/org/apache/zookeeper/test/FLEPredicateTest.java b/src/java/test/org/apache/zookeeper/test/FLEPredicateTest.java new file mode 100644 index 00000000000..0ecac6e6c74 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/FLEPredicateTest.java @@ -0,0 +1,104 @@ +/** + * 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.test; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.HashMap; + +import org.apache.zookeeper.server.quorum.FastLeaderElection; +import org.apache.zookeeper.server.quorum.QuorumCnxManager; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZKTestCase; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.junit.Assert; +import org.junit.Test; + +public class FLEPredicateTest extends ZKTestCase { + + protected static final Logger LOG = LoggerFactory.getLogger(FLEPredicateTest.class); + + class MockFLE extends FastLeaderElection { + MockFLE(QuorumPeer peer){ + super(peer, peer.createCnxnManager()); + } + + boolean predicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch){ + return this.totalOrderPredicate(newId, newZxid, newEpoch, curId, curZxid, curEpoch); + } + } + + + HashMap peers; + + @Test + public void testPredicate() throws IOException { + + peers = new HashMap(3); + + /* + * Creates list of peers. + */ + for(int i = 0; i < 3; i++) { + peers.put(Long.valueOf(i), + new QuorumServer(i, "0.0.0.0", PortAssignment.unique(), + PortAssignment.unique(), null)); + } + + /* + * Creating peer. + */ + try{ + File tmpDir = ClientBase.createTmpDir(); + QuorumPeer peer = new QuorumPeer(peers, tmpDir, tmpDir, + PortAssignment.unique(), 3, 0, 1000, 2, 2); + + MockFLE mock = new MockFLE(peer); + + /* + * Lower epoch must return false + */ + + Assert.assertFalse (mock.predicate(4L, 0L, 0L, 3L, 0L, 2L)); + + /* + * Later epoch + */ + Assert.assertTrue (mock.predicate(0L, 0L, 1L, 1L, 0L, 0L)); + + /* + * Higher zxid + */ + Assert.assertTrue(mock.predicate(0L, 1L, 0L, 1L, 0L, 0L)); + + /* + * Higher id + */ + Assert.assertTrue(mock.predicate(1L, 1L, 0L, 0L, 1L, 0L)); + } catch (IOException e) { + LOG.error("Exception while creating quorum peer", e); + Assert.fail("Exception while creating quorum peer"); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/test/FLERestartTest.java b/src/java/test/org/apache/zookeeper/test/FLERestartTest.java index f092563fe79..a7cecf64d27 100644 --- a/src/java/test/org/apache/zookeeper/test/FLERestartTest.java +++ b/src/java/test/org/apache/zookeeper/test/FLERestartTest.java @@ -178,9 +178,8 @@ public void testLERestart() throws Exception { LOG.info("TestLE: " + getTestName()+ ", " + count); for(int i = 0; i < count; i++) { peers.put(Long.valueOf(i), - new QuorumServer(i, - new InetSocketAddress(PortAssignment.unique()), - new InetSocketAddress(PortAssignment.unique()))); + new QuorumServer(i, "0.0.0.0", PortAssignment.unique(), + PortAssignment.unique(), null)); tmpdir[i] = ClientBase.createTmpDir(); port[i] = PortAssignment.unique(); } diff --git a/src/java/test/org/apache/zookeeper/test/FLETest.java b/src/java/test/org/apache/zookeeper/test/FLETest.java index f1e050d60d7..6281a57d9e4 100644 --- a/src/java/test/org/apache/zookeeper/test/FLETest.java +++ b/src/java/test/org/apache/zookeeper/test/FLETest.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Random; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,6 +75,7 @@ int countVotes(HashSet hs, long id) { volatile long leader = -1; //volatile int round = 1; Random rand = new Random(); + Set joinedThreads; @Before public void setUp() throws Exception { @@ -87,6 +89,7 @@ public void setUp() throws Exception { port = new int[count]; successCount = 0; finalObj = new Object(); + joinedThreads = new HashSet(); } @After @@ -180,6 +183,7 @@ public void run() { if(leader == i){ synchronized(finalObj){ successCount++; + joinedThreads.add((long)i); if(successCount > (count/2)) finalObj.notify(); } @@ -224,6 +228,7 @@ public void run() { if (leader == votes[i].getId()) { synchronized(finalObj){ successCount++; + joinedThreads.add((long)i); if(successCount > (count/2)) finalObj.notify(); } break; @@ -264,9 +269,8 @@ public void testLE() throws Exception { LOG.info("TestLE: " + getTestName()+ ", " + count); for(int i = 0; i < count; i++) { peers.put(Long.valueOf(i), - new QuorumServer(i, - new InetSocketAddress(PortAssignment.unique()), - new InetSocketAddress(PortAssignment.unique()))); + new QuorumServer(i, "0.0.0.0", PortAssignment.unique(), + PortAssignment.unique(), null)); tmpdir[i] = ClientBase.createTmpDir(); port[i] = PortAssignment.unique(); } @@ -309,8 +313,10 @@ public void testLE() throws Exception { Assert.fail("Fewer than a a majority has joined"); } - if(threads.get((int) leader).isAlive()){ - Assert.fail("Leader hasn't joined: " + leader); + synchronized(finalObj){ + if(!joinedThreads.contains(leader)){ + Assert.fail("Leader hasn't joined: " + leader); + } } } @@ -359,9 +365,8 @@ public void testJoin() throws Exception { ArrayList peerList = new ArrayList(); for(sid = 0; sid < 3; sid++) { peers.put(Long.valueOf(sid), - new QuorumServer(sid, - new InetSocketAddress(PortAssignment.unique()), - new InetSocketAddress(PortAssignment.unique()))); + new QuorumServer(sid, "0.0.0.0", PortAssignment.unique(), + PortAssignment.unique(), null)); tmpdir[sid] = ClientBase.createTmpDir(); port[sid] = PortAssignment.unique(); } @@ -403,4 +408,66 @@ public void testJoin() throws Exception { } } } + + /* + * For ZOOKEEPER-1732 verify that it is possible to join an ensemble with + * inconsistent election round information. + */ + @Test + public void testJoinInconsistentEnsemble() throws Exception { + int sid; + QuorumPeer peer; + int waitTime = 10 * 1000; + ArrayList peerList = new ArrayList(); + for(sid = 0; sid < 3; sid++) { + peers.put(Long.valueOf(sid), + new QuorumServer(sid, "0.0.0.0", PortAssignment.unique(), + PortAssignment.unique(), null)); + tmpdir[sid] = ClientBase.createTmpDir(); + port[sid] = PortAssignment.unique(); + } + // start 2 peers and verify if they form the cluster + for (sid = 0; sid < 2; sid++) { + peer = new QuorumPeer(peers, tmpdir[sid], tmpdir[sid], + port[sid], 3, sid, 2000, 2, 2); + LOG.info("Starting peer " + peer.getId()); + peer.start(); + peerList.add(sid, peer); + } + peer = peerList.get(0); + VerifyState v1 = new VerifyState(peerList.get(0)); + v1.start(); + v1.join(waitTime); + Assert.assertFalse("Unable to form cluster in " + + waitTime + " ms", + !v1.isSuccess()); + // Change the election round for one of the members of the ensemble + long leaderSid = peer.getCurrentVote().getId(); + long zxid = peer.getCurrentVote().getZxid(); + long electionEpoch = peer.getCurrentVote().getElectionEpoch(); + ServerState state = peer.getCurrentVote().getState(); + long peerEpoch = peer.getCurrentVote().getPeerEpoch(); + Vote newVote = new Vote(leaderSid, zxid+100, electionEpoch+100, peerEpoch, state); + peer.setCurrentVote(newVote); + // Start 3rd peer and check if it joins the quorum + peer = new QuorumPeer(peers, tmpdir[2], tmpdir[2], + port[2], 3, 2, 2000, 2, 2); + LOG.info("Starting peer " + peer.getId()); + peer.start(); + peerList.add(sid, peer); + v1 = new VerifyState(peer); + v1.start(); + v1.join(waitTime); + if (v1.isAlive()) { + Assert.fail("Peer " + peer.getId() + " failed to join the cluster " + + "within " + waitTime + " ms"); + } + // cleanup + for (int id = 0; id < 3; id++) { + peer = peerList.get(id); + if (peer != null) { + peer.shutdown(); + } + } + } } diff --git a/src/java/test/org/apache/zookeeper/test/FLEZeroWeightTest.java b/src/java/test/org/apache/zookeeper/test/FLEZeroWeightTest.java index f3d1316dd3c..e8a8cf79272 100644 --- a/src/java/test/org/apache/zookeeper/test/FLEZeroWeightTest.java +++ b/src/java/test/org/apache/zookeeper/test/FLEZeroWeightTest.java @@ -153,9 +153,7 @@ public void testZeroWeightQuorum() throws Exception { LOG.info("TestZeroWeightQuorum: " + getTestName()+ ", " + count); for(int i = 0; i < count; i++) { peers.put(Long.valueOf(i), - new QuorumServer(i, - new InetSocketAddress(PortAssignment.unique()), - new InetSocketAddress(PortAssignment.unique()))); + new QuorumServer(i, "0.0.0.0", PortAssignment.unique(), PortAssignment.unique(), null)); tmpdir[i] = ClientBase.createTmpDir(); port[i] = PortAssignment.unique(); } diff --git a/src/java/test/org/apache/zookeeper/test/FollowerResyncConcurrencyTest.java b/src/java/test/org/apache/zookeeper/test/FollowerResyncConcurrencyTest.java index 2752d5c76d6..acd05d669d5 100644 --- a/src/java/test/org/apache/zookeeper/test/FollowerResyncConcurrencyTest.java +++ b/src/java/test/org/apache/zookeeper/test/FollowerResyncConcurrencyTest.java @@ -18,37 +18,122 @@ package org.apache.zookeeper.test; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.IOException; import java.util.Collection; import java.util.HashSet; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import org.apache.zookeeper.AsyncCallback; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.quorum.Leader; import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.Assert; import org.junit.Test; public class FollowerResyncConcurrencyTest extends ZKTestCase { - volatile int counter = 0; - volatile int errors = 0; - private static final Logger LOG = Logger.getLogger(FollowerResyncConcurrencyTest.class); public static final long CONNECTION_TIMEOUT = ClientTest.CONNECTION_TIMEOUT; + private volatile int counter = 0; + private volatile int errors = 0; + + /** + * See ZOOKEEPER-1319 - verify that a lagging follwer resyncs correctly + * + * 1) start with down quorum + * 2) start leader/follower1, add some data + * 3) restart leader/follower1 + * 4) start follower2 + * 5) verify data consistency across the ensemble + * + * @throws Exception + */ + @Test + public void testLaggingFollowerResyncsUnderNewEpoch() throws Exception { + CountdownWatcher watcher1 = new CountdownWatcher(); + CountdownWatcher watcher2 = new CountdownWatcher(); + CountdownWatcher watcher3 = new CountdownWatcher(); + + QuorumUtil qu = new QuorumUtil(1); + qu.shutdownAll(); + + qu.start(1); + qu.start(2); + Assert.assertTrue("Waiting for server up", ClientBase.waitForServerUp("127.0.0.1:" + + qu.getPeer(1).clientPort, ClientBase.CONNECTION_TIMEOUT)); + Assert.assertTrue("Waiting for server up", ClientBase.waitForServerUp("127.0.0.1:" + + qu.getPeer(2).clientPort, ClientBase.CONNECTION_TIMEOUT)); + + ZooKeeper zk1 = + createClient(qu.getPeer(1).peer.getClientPort(), watcher1); + LOG.info("zk1 has session id 0x" + Long.toHexString(zk1.getSessionId())); + + final String resyncPath = "/resyncundernewepoch"; + zk1.create(resyncPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk1.close(); + + qu.shutdown(1); + qu.shutdown(2); + Assert.assertTrue("Waiting for server down", ClientBase.waitForServerDown("127.0.0.1:" + + qu.getPeer(1).clientPort, ClientBase.CONNECTION_TIMEOUT)); + Assert.assertTrue("Waiting for server down", ClientBase.waitForServerDown("127.0.0.1:" + + qu.getPeer(2).clientPort, ClientBase.CONNECTION_TIMEOUT)); + + qu.start(1); + qu.start(2); + Assert.assertTrue("Waiting for server up", ClientBase.waitForServerUp("127.0.0.1:" + + qu.getPeer(1).clientPort, ClientBase.CONNECTION_TIMEOUT)); + Assert.assertTrue("Waiting for server up", ClientBase.waitForServerUp("127.0.0.1:" + + qu.getPeer(2).clientPort, ClientBase.CONNECTION_TIMEOUT)); + + qu.start(3); + Assert.assertTrue("Waiting for server up", ClientBase.waitForServerUp("127.0.0.1:" + + qu.getPeer(3).clientPort, ClientBase.CONNECTION_TIMEOUT)); + + zk1 = createClient(qu.getPeer(1).peer.getClientPort(), watcher1); + LOG.info("zk1 has session id 0x" + Long.toHexString(zk1.getSessionId())); + + assertNotNull("zk1 has data", zk1.exists(resyncPath, false)); + + final ZooKeeper zk2 = + createClient(qu.getPeer(2).peer.getClientPort(), watcher2); + LOG.info("zk2 has session id 0x" + Long.toHexString(zk2.getSessionId())); + + assertNotNull("zk2 has data", zk2.exists(resyncPath, false)); + + final ZooKeeper zk3 = + createClient(qu.getPeer(3).peer.getClientPort(), watcher3); + LOG.info("zk3 has session id 0x" + Long.toHexString(zk3.getSessionId())); + + assertNotNull("zk3 has data", zk3.exists(resyncPath, false)); + + zk1.close(); + zk2.close(); + zk3.close(); + + qu.shutdownAll(); + } /** * See ZOOKEEPER-962. This tests for one of the bugs hit while fixing this, @@ -64,8 +149,9 @@ public class FollowerResyncConcurrencyTest extends ZKTestCase { * @throws KeeperException */ @Test - public void testResyncBySnapThenDiffAfterFollowerCrashes () - throws IOException, InterruptedException, KeeperException, Throwable{ + public void testResyncBySnapThenDiffAfterFollowerCrashes() + throws IOException, InterruptedException, KeeperException, Throwable + { final Semaphore sem = new Semaphore(0); QuorumUtil qu = new QuorumUtil(1); @@ -75,35 +161,40 @@ public void testResyncBySnapThenDiffAfterFollowerCrashes () CountdownWatcher watcher3 = new CountdownWatcher(); int index = 1; - while(qu.getPeer(index).peer.leader == null) + while(qu.getPeer(index).peer.leader == null) { index++; + } Leader leader = qu.getPeer(index).peer.leader; - assertNotNull(leader); - /* - * Reusing the index variable to select a follower to connect to - */ + + /* Reusing the index variable to select a follower to connect to */ index = (index == 1) ? 2 : 1; + LOG.info("Connecting to follower:" + index); + qu.shutdown(index); - final ZooKeeper zk3 = new DisconnectableZooKeeper("127.0.0.1:" + qu.getPeer(3).peer.getClientPort(), 1000,watcher3); - watcher3.waitForConnected(CONNECTION_TIMEOUT); + + final ZooKeeper zk3 = + createClient(qu.getPeer(3).peer.getClientPort(), watcher3); + LOG.info("zk3 has session id 0x" + Long.toHexString(zk3.getSessionId())); + zk3.create("/mybar", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); qu.restart(index); - ZooKeeper zk = new DisconnectableZooKeeper("127.0.0.1:" + qu.getPeer(index).peer.getClientPort(), 1000, watcher1); + final ZooKeeper zk1 = + createClient(qu.getPeer(index).peer.getClientPort(), watcher1); + LOG.info("zk1 has session id 0x" + Long.toHexString(zk1.getSessionId())); - ZooKeeper zk2 = new DisconnectableZooKeeper("127.0.0.1:" + qu.getPeer(index).peer.getClientPort(), 1000, watcher2); - - watcher1.waitForConnected(CONNECTION_TIMEOUT); - watcher2.waitForConnected(CONNECTION_TIMEOUT); + final ZooKeeper zk2 = + createClient(qu.getPeer(index).peer.getClientPort(), watcher2); + LOG.info("zk2 has session id 0x" + Long.toHexString(zk2.getSessionId())); - zk.create("/first", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - Thread t = new Thread(new Runnable() { + zk1.create("/first", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + Thread mytestfooThread = new Thread(new Runnable() { @Override public void run() { - for(int i = 0; i < 1000; i++) { + for(int i = 0; i < 3000; i++) { zk3.create("/mytestfoo", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, new AsyncCallback.StringCallback() { @Override @@ -112,11 +203,9 @@ public void processResult(int rc, String path, Object ctx, String name) { if (rc != 0) { errors++; } - if(counter == 14200){ + if(counter == 16200){ sem.release(); } - - } }, null); if(i%10==0){ @@ -131,7 +220,6 @@ public void processResult(int rc, String path, Object ctx, String name) { } }); - for(int i = 0; i < 13000; i++) { zk3.create("/mybar", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, new AsyncCallback.StringCallback() { @@ -141,11 +229,9 @@ public void processResult(int rc, String path, Object ctx, String name) { if (rc != 0) { errors++; } - if(counter == 14200){ + if(counter == 16200){ sem.release(); } - - } }, null); @@ -155,10 +241,10 @@ public void processResult(int rc, String path, Object ctx, String name) { } if(i == 12000){ //Restart off of snap, then get some txns for a log, then shut down + mytestfooThread.start(); qu.restart(index); Thread.sleep(300); - qu.shutdown(index); - t.start(); + qu.shutdown(index); Thread.sleep(300); qu.restart(index); LOG.info("Setting up server: " + index); @@ -169,32 +255,37 @@ public void processResult(int rc, String path, Object ctx, String name) { if(i%50 == 0) { zk2.create("/newbaz", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, new AsyncCallback.StringCallback() { - @Override public void processResult(int rc, String path, Object ctx, String name) { counter++; if (rc != 0) { errors++; } - if(counter == 14200){ + if(counter == 16200){ sem.release(); } - - } }, null); } } // Wait until all updates return - if(!sem.tryAcquire(20000, TimeUnit.MILLISECONDS)) { + if(!sem.tryAcquire(ClientBase.CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)) { LOG.warn("Did not aquire semaphore fast enough"); } - t.join(10000); + mytestfooThread.join(ClientBase.CONNECTION_TIMEOUT); + if (mytestfooThread.isAlive()) { + LOG.error("mytestfooThread is still alive"); + } Thread.sleep(1000); - verifyState(qu, index, leader); + verifyState(qu, index, leader); + + zk1.close(); + zk2.close(); + zk3.close(); + qu.shutdownAll(); } /** @@ -222,8 +313,9 @@ public void processResult(int rc, String path, Object ctx, String name) { */ @Test - public void testResyncByDiffAfterFollowerCrashes () - throws IOException, InterruptedException, KeeperException, Throwable{ + public void testResyncByDiffAfterFollowerCrashes() + throws IOException, InterruptedException, KeeperException, Throwable + { final Semaphore sem = new Semaphore(0); QuorumUtil qu = new QuorumUtil(1); @@ -232,34 +324,35 @@ public void testResyncByDiffAfterFollowerCrashes () CountdownWatcher watcher2 = new CountdownWatcher(); CountdownWatcher watcher3 = new CountdownWatcher(); - int index = 1; - while(qu.getPeer(index).peer.leader == null) + while(qu.getPeer(index).peer.leader == null) { index++; + } Leader leader = qu.getPeer(index).peer.leader; - assertNotNull(leader); - /* - * Reusing the index variable to select a follower to connect to - */ + /* Reusing the index variable to select a follower to connect to */ index = (index == 1) ? 2 : 1; + LOG.info("Connecting to follower:" + index); - ZooKeeper zk = new DisconnectableZooKeeper("127.0.0.1:" + qu.getPeer(index).peer.getClientPort(), 1000, watcher1); + final ZooKeeper zk1 = + createClient(qu.getPeer(index).peer.getClientPort(), watcher1); + LOG.info("zk1 has session id 0x" + Long.toHexString(zk1.getSessionId())); - ZooKeeper zk2 = new DisconnectableZooKeeper("127.0.0.1:" + qu.getPeer(index).peer.getClientPort(), 1000,watcher2); - final ZooKeeper zk3 = new DisconnectableZooKeeper("127.0.0.1:" + qu.getPeer(3).peer.getClientPort(), 1000, watcher3); - watcher1.waitForConnected(CONNECTION_TIMEOUT); - watcher2.waitForConnected(CONNECTION_TIMEOUT); - watcher3.waitForConnected(CONNECTION_TIMEOUT); - zk.create("/first", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + final ZooKeeper zk2 = + createClient(qu.getPeer(index).peer.getClientPort(), watcher2); + LOG.info("zk2 has session id 0x" + Long.toHexString(zk2.getSessionId())); + + final ZooKeeper zk3 = + createClient(qu.getPeer(3).peer.getClientPort(), watcher3); + LOG.info("zk3 has session id 0x" + Long.toHexString(zk3.getSessionId())); + + zk1.create("/first", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zk2.create("/mybar", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); - final AtomicBoolean runNow = new AtomicBoolean(false); - Thread t = new Thread(new Runnable() { - + Thread mytestfooThread = new Thread(new Runnable() { @Override public void run() { int inSyncCounter = 0; @@ -276,8 +369,6 @@ public void processResult(int rc, String path, Object ctx, String name) { if(counter > 7300){ sem.release(); } - - } }, null); @@ -286,8 +377,7 @@ public void processResult(int rc, String path, Object ctx, String name) { } catch (Exception e) { } inSyncCounter++; - } - else { + } else { Thread.yield(); } } @@ -295,7 +385,7 @@ public void processResult(int rc, String path, Object ctx, String name) { } }); - t.start(); + mytestfooThread.start(); for(int i = 0; i < 5000; i++) { zk2.create("/mybar", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, new AsyncCallback.StringCallback() { @@ -308,8 +398,6 @@ public void processResult(int rc, String path, Object ctx, String name) { if(counter > 7300){ sem.release(); } - - } }, null); @@ -317,7 +405,6 @@ public void processResult(int rc, String path, Object ctx, String name) { qu.shutdown(index); Thread.sleep(1100); LOG.info("Shutting down s1"); - } if(i == 1100 || i == 1150 || i == 1200) { Thread.sleep(1000); @@ -330,7 +417,6 @@ public void processResult(int rc, String path, Object ctx, String name) { LOG.info("Setting up server: " + index); } - if(i>=1000 && i%2== 0) { zk3.create("/newbaz", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, new AsyncCallback.StringCallback() { @@ -343,8 +429,6 @@ public void processResult(int rc, String path, Object ctx, String name) { if(counter > 7300){ sem.release(); } - - } }, null); } @@ -354,17 +438,55 @@ public void processResult(int rc, String path, Object ctx, String name) { } // Wait until all updates return - if(!sem.tryAcquire(15000, TimeUnit.MILLISECONDS)) { + if(!sem.tryAcquire(ClientBase.CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)) { LOG.warn("Did not aquire semaphore fast enough"); } - t.join(10000); + mytestfooThread.join(ClientBase.CONNECTION_TIMEOUT); + if (mytestfooThread.isAlive()) { + LOG.error("mytestfooThread is still alive"); + } + Thread.sleep(1000); // Verify that server is following and has the same epoch as the leader verifyState(qu, index, leader); + zk1.close(); + zk2.close(); + zk3.close(); + + qu.shutdownAll(); } + private static DisconnectableZooKeeper createClient(int port, + CountdownWatcher watcher) + throws IOException, TimeoutException, InterruptedException + { + DisconnectableZooKeeper zk = new DisconnectableZooKeeper( + "127.0.0.1:" + port, ClientBase.CONNECTION_TIMEOUT, watcher); + + watcher.waitForConnected(CONNECTION_TIMEOUT); + return zk; + } + + private static TestableZooKeeper createTestableClient(String hp) + throws IOException, TimeoutException, InterruptedException + { + CountdownWatcher watcher = new CountdownWatcher(); + return createTestableClient(watcher, hp); + } + + private static TestableZooKeeper createTestableClient( + CountdownWatcher watcher, String hp) + throws IOException, TimeoutException, InterruptedException + { + TestableZooKeeper zk = new TestableZooKeeper( + hp, ClientBase.CONNECTION_TIMEOUT, watcher); + + watcher.waitForConnected(CONNECTION_TIMEOUT); + return zk; + } + private void verifyState(QuorumUtil qu, int index, Leader leader) { assertTrue("Not following", qu.getPeer(index).peer.follower != null); long epochF = (qu.getPeer(index).peer.getActiveServer().getZxid() >> 32L); @@ -401,4 +523,85 @@ private void verifyState(QuorumUtil qu, int index, Leader leader) { assertEquals("Leader should equal follower", lead.getEphemerals(l).size(), cleanEphemerals.size()); } } + + /** + * Verify that the server is sending the proper zxid. See ZOOKEEPER-1412. + */ + @Test + public void testFollowerSendsLastZxid() throws Exception { + QuorumUtil qu = new QuorumUtil(1); + qu.startAll(); + + int index = 1; + while(qu.getPeer(index).peer.follower == null) { + index++; + } + LOG.info("Connecting to follower:" + index); + + TestableZooKeeper zk = + createTestableClient("localhost:" + qu.getPeer(index).peer.getClientPort()); + + assertEquals(0L, zk.testableLastZxid()); + zk.exists("/", false); + long lzxid = zk.testableLastZxid(); + assertTrue("lzxid:" + lzxid + " > 0", lzxid > 0); + zk.close(); + } + + private class MyWatcher extends CountdownWatcher { + LinkedBlockingQueue events = + new LinkedBlockingQueue(); + + public void process(WatchedEvent event) { + super.process(event); + if (event.getType() != Event.EventType.None) { + try { + events.put(event); + } catch (InterruptedException e) { + LOG.warn("ignoring interrupt during event.put"); + } + } + } + } + + /** + * Verify that the server is sending the proper zxid, and as a result + * the watch doesn't fire. See ZOOKEEPER-1412. + */ + @Test + public void testFollowerWatcherResync() throws Exception { + QuorumUtil qu = new QuorumUtil(1); + qu.startAll(); + + int index = 1; + while(qu.getPeer(index).peer.follower == null) { + index++; + } + LOG.info("Connecting to follower:" + index); + + TestableZooKeeper zk1 = createTestableClient( + "localhost:" + qu.getPeer(index).peer.getClientPort()); + zk1.create("/foo", "foo".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + + MyWatcher watcher = new MyWatcher(); + TestableZooKeeper zk2 = createTestableClient(watcher, + "localhost:" + qu.getPeer(index).peer.getClientPort()); + + zk2.exists("/foo", true); + + watcher.reset(); + zk2.testableConnloss(); + if (!watcher.clientConnected.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)) + { + fail("Unable to connect to server"); + } + assertArrayEquals("foo".getBytes(), zk2.getData("/foo", false, null)); + + assertNull(watcher.events.poll(5, TimeUnit.SECONDS)); + + zk1.close(); + zk2.close(); + } + } diff --git a/src/java/test/org/apache/zookeeper/test/FollowerTest.java b/src/java/test/org/apache/zookeeper/test/FollowerTest.java new file mode 100644 index 00000000000..701d80638f0 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/FollowerTest.java @@ -0,0 +1,233 @@ +/** + * 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.test; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.zookeeper.AsyncCallback; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.Op; +import org.apache.zookeeper.OpResult; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.Watcher.Event.KeeperState; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.quorum.Leader; +import org.apache.zookeeper.server.quorum.LearnerHandler; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FollowerTest extends ZKTestCase { + private static final Logger LOG = LoggerFactory.getLogger(FollowerTest.class); + public static final long CONNECTION_TIMEOUT = ClientTest.CONNECTION_TIMEOUT; + + volatile int counter = 0; + volatile int errors = 0; + + /** + * See ZOOKEEPER-790 for details + * */ + @Test + public void testFollowersStartAfterLeader() throws Exception { + QuorumUtil qu = new QuorumUtil(1); + CountdownWatcher watcher = new CountdownWatcher(); + qu.startQuorum(); + + int index = 1; + while(qu.getPeer(index).peer.leader == null) { + index++; + } + + ZooKeeper zk = new ZooKeeper( + "127.0.0.1:" + qu.getPeer((index == 1)?2:1).peer.getClientPort(), + ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(CONNECTION_TIMEOUT); + + // break the quorum + qu.shutdown(index); + + // Wait until we disconnect to proceed + watcher.waitForDisconnected(CONNECTION_TIMEOUT); + + // try to reestablish the quorum + qu.start(index); + + try{ + watcher.waitForConnected(30000); + } catch(TimeoutException e) { + Assert.fail("client could not connect to reestablished quorum: giving up after 30+ seconds."); + } + + zk.close(); + + qu.tearDown(); + } + + /** + * Tests if closeSession can be logged before a leader gets established, which + * could lead to a locked-out follower (see ZOOKEEPER-790). + * + * The test works as follows. It has a client connecting to a follower f and + * sending batches of 1,000 updates. The goal is that f has a zxid higher than + * all other servers in the initial leader election. This way we can crash and + * recover the follower so that the follower believes it is the leader once it + * recovers (LE optimization: once a server receives a message from all other + * servers, it picks a leader. + * + * It also makes the session timeout very short so that we force the false + * leader to close the session and write it to the log in the buggy code (before + * ZOOKEEPER-790). Once f drops leadership and finds the current leader, its epoch + * is higher, and it rejects the leader. Now, if we prevent the leader from closing + * the session by only starting up (see Leader.lead()) once it obtains a quorum of + * supporters, then f will find the current leader and support it because it won't + * have a highe epoch. + * + */ + @Test + public void testNoLogBeforeLeaderEstablishment () + throws Exception { + final Semaphore sem = new Semaphore(0); + System.setProperty("zookeeper.cnxTimeout", "50"); + + QuorumUtil qu = new QuorumUtil(2, 10); + qu.startQuorum(); + + + int index = 1; + while(qu.getPeer(index).peer.leader == null) { + index++; + } + + Leader leader = qu.getPeer(index).peer.leader; + + Assert.assertNotNull(leader); + + /* + * Reusing the index variable to select a follower to connect to + */ + index = (index == 1) ? 2 : 1; + + ZooKeeper zk = new DisconnectableZooKeeper( + "127.0.0.1:" + qu.getPeer(index).peer.getClientPort(), + ClientBase.CONNECTION_TIMEOUT, new Watcher() { + public void process(WatchedEvent event) { } + }); + + zk.create("/blah", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + + for(int i = 0; i < 50000; i++) { + zk.setData("/blah", new byte[0], -1, new AsyncCallback.StatCallback() { + public void processResult(int rc, String path, Object ctx, + Stat stat) { + counter++; + if (rc != 0) { + errors++; + } + if(counter == 20000){ + sem.release(); + } + } + }, null); + + if(i == 5000){ + qu.shutdown(index); + LOG.info("Shutting down s1"); + } + if(i == 12000){ + qu.start(index); + LOG.info("Setting up server: " + index); + } + if((i % 1000) == 0){ + Thread.sleep(500); + } + } + + // Wait until all updates return + sem.tryAcquire(15, TimeUnit.SECONDS); + + // Verify that server is following and has the same epoch as the leader + Assert.assertTrue("Not following", qu.getPeer(index).peer.follower != null); + long epochF = (qu.getPeer(index).peer.getActiveServer().getZxid() >> 32L); + long epochL = (leader.getEpoch() >> 32L); + Assert.assertTrue("Zxid: " + qu.getPeer(index).peer.getActiveServer().getZxid() + + "Current epoch: " + epochF, epochF == epochL); + + qu.tearDown(); + + } + + // skip superhammer and clientcleanup as they are too expensive for quorum + + /** + * Tests if a multiop submitted to a non-leader propagates to the leader properly + * (see ZOOKEEPER-1124). + * + * The test works as follows. It has a client connect to a follower and submit a multiop + * to the follower. It then verifies that the multiop successfully gets committed by the leader. + * + * Without the fix in ZOOKEEPER-1124, this fails with a ConnectionLoss KeeperException. + */ + @Test + public void testMultiToFollower() throws Exception { + QuorumUtil qu = new QuorumUtil(1); + CountdownWatcher watcher = new CountdownWatcher(); + qu.startQuorum(); + + int index = 1; + while(qu.getPeer(index).peer.leader == null) { + index++; + } + + ZooKeeper zk = new ZooKeeper( + "127.0.0.1:" + qu.getPeer((index == 1)?2:1).peer.getClientPort(), + ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(CONNECTION_TIMEOUT); + + List results = new ArrayList(); + + results = zk.multi(Arrays.asList( + Op.create("/multi0", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), + Op.create("/multi1", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), + Op.create("/multi2", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) + )); + zk.getData("/multi0", false, null); + zk.getData("/multi1", false, null); + zk.getData("/multi2", false, null); + + zk.close(); + + qu.tearDown(); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java b/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java index 6d739942267..49d90f76e07 100644 --- a/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java +++ b/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java @@ -23,6 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.TestableZooKeeper; +import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord; import org.junit.Assert; import org.junit.Test; @@ -54,7 +55,7 @@ public void testFourLetterWords() throws Exception { verify(hp, "cons", "queued"); TestableZooKeeper zk = createClient(hp); - String sid = "0x" + Long.toHexString(zk.getSessionId()); + String sid = getHexSessionId(zk.getSessionId()); verify(hp, "stat", "queued"); verify(hp, "srvr", "Outstanding"); diff --git a/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java b/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java index 373914af21c..029b7e83dd9 100644 --- a/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java +++ b/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java @@ -19,14 +19,20 @@ package org.apache.zookeeper.test; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; import java.io.IOException; import java.io.StringReader; import java.util.regex.Pattern; import org.apache.zookeeper.TestableZooKeeper; import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.common.IOUtils; +import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.Timeout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +40,9 @@ public class FourLetterWordsTest extends ClientBase { protected static final Logger LOG = LoggerFactory.getLogger(FourLetterWordsTest.class); + @Rule + public Timeout timeout = new Timeout(30000); + /** Test the various four letter words */ @Test public void testFourLetterWords() throws Exception { @@ -56,7 +65,7 @@ public void testFourLetterWords() throws Exception { verify("cons", "queued"); TestableZooKeeper zk = createClient(); - String sid = "0x" + Long.toHexString(zk.getSessionId()); + String sid = getHexSessionId(zk.getSessionId()); verify("stat", "queued"); verify("srvr", "Outstanding"); @@ -93,12 +102,19 @@ public void testFourLetterWords() throws Exception { verify("srvr", "Outstanding"); verify("cons", "queued"); verify("mntr", "zk_server_state\tstandalone"); + verify("mntr", "num_alive_connections"); + verify("stat", "Connections"); + verify("srvr", "Connections"); } private String sendRequest(String cmd) throws IOException { HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0); - return ClientBase.send4LetterWord(hpobj.host, hpobj.port, cmd); + return send4LetterWord(hpobj.host, hpobj.port, cmd); } + private String sendRequest(String cmd, int timeout) throws IOException { + HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0); + return send4LetterWord(hpobj.host, hpobj.port, cmd, timeout); + } private void verify(String cmd, String expected) throws IOException { String resp = sendRequest(cmd); @@ -107,7 +123,7 @@ private void verify(String cmd, String expected) throws IOException { } @Test - public void validateStatOutput() throws Exception { + public void testValidateStatOutput() throws Exception { ZooKeeper zk1 = createClient(); ZooKeeper zk2 = createClient(); @@ -135,6 +151,8 @@ public void validateStatOutput() throws Exception { line = in.readLine(); Assert.assertTrue(Pattern.matches("^Sent: \\d+$", line)); line = in.readLine(); + Assert.assertTrue(Pattern.matches("^Connections: \\d+$", line)); + line = in.readLine(); Assert.assertTrue(Pattern.matches("^Outstanding: \\d+$", line)); line = in.readLine(); Assert.assertTrue(Pattern.matches("^Zxid: 0x[\\da-fA-F]+$", line)); @@ -148,7 +166,7 @@ public void validateStatOutput() throws Exception { } @Test - public void validateConsOutput() throws Exception { + public void testValidateConsOutput() throws Exception { ZooKeeper zk1 = createClient(); ZooKeeper zk2 = createClient(); @@ -167,4 +185,53 @@ public void validateConsOutput() throws Exception { zk1.close(); zk2.close(); } + + @Test(timeout=60000) + public void testValidateSocketTimeout() throws Exception { + /** + * testing positive scenario that even with timeout parameter the + * functionality works fine + */ + String resp = sendRequest("isro", 2000); + Assert.assertTrue(resp.contains("rw")); + } + + @Test + public void testSetTraceMask() throws Exception { + String gtmkResp = sendRequest("gtmk"); + Assert.assertNotNull(gtmkResp); + gtmkResp = gtmkResp.trim(); + Assert.assertFalse(gtmkResp.isEmpty()); + long formerMask = Long.valueOf(gtmkResp); + try { + verify(buildSetTraceMaskRequest(0), "0"); + verify("gtmk", "0"); + } finally { + // Restore former value. + sendRequest(buildSetTraceMaskRequest(formerMask)); + } + } + + /** + * Builds a SetTraceMask request to be sent to the server, consisting of + * "stmk" followed by the 8-byte long representation of the trace mask. + * + * @param mask trace mask to set + * @return built request + * @throws IOException if there is an I/O error + */ + private String buildSetTraceMaskRequest(long mask) throws IOException { + ByteArrayOutputStream baos = null; + DataOutputStream dos = null; + try { + baos = new ByteArrayOutputStream(); + dos = new DataOutputStream(baos); + dos.writeBytes("stmk"); + dos.writeLong(mask); + } finally { + IOUtils.closeStream(dos); + IOUtils.closeStream(baos); + } + return new String(baos.toByteArray()); + } } diff --git a/src/java/test/org/apache/zookeeper/test/FourLetterWordsWhiteListTest.java b/src/java/test/org/apache/zookeeper/test/FourLetterWordsWhiteListTest.java new file mode 100644 index 00000000000..613346f2ece --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/FourLetterWordsWhiteListTest.java @@ -0,0 +1,252 @@ +/** + * 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.test; + +import java.io.IOException; + +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.server.ServerCnxn; +import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FourLetterWordsWhiteListTest extends ClientBase { + protected static final Logger LOG = + LoggerFactory.getLogger(FourLetterWordsTest.class); + + @Rule + public Timeout timeout = new Timeout(30000); + + /* + * ZOOKEEPER-2693: test white list of four letter words. + * For 3.5.x default white list is empty. Verify that is + * the case (except 'stat' command which is enabled in ClientBase + * which other tests depend on.). + */ + @Test(timeout=30000) + public void testFourLetterWordsAllDisabledByDefault() throws Exception { + stopServer(); + ServerCnxn.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "stat"); + startServer(); + + // Default white list for 3.5.x is empty, so all command should fail. + verifyAllCommandsFail(); + + TestableZooKeeper zk = createClient(); + + verifyAllCommandsFail(); + + zk.getData("/", true, null); + + verifyAllCommandsFail(); + + zk.close(); + + verifyFuzzyMatch("stat", "Outstanding"); + verifyAllCommandsFail(); + } + + @Test(timeout=30000) + public void testFourLetterWordsEnableSomeCommands() throws Exception { + stopServer(); + ServerCnxn.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "stat, ruok, isro"); + startServer(); + // stat, ruok and isro are white listed. + verifyFuzzyMatch("stat", "Outstanding"); + verifyExactMatch("ruok", "imok"); + verifyExactMatch("isro", "rw"); + + // Rest of commands fail. + verifyExactMatch("conf", generateExpectedMessage("conf")); + verifyExactMatch("cons", generateExpectedMessage("cons")); + verifyExactMatch("crst", generateExpectedMessage("crst")); + verifyExactMatch("dump", generateExpectedMessage("dump")); + verifyExactMatch("envi", generateExpectedMessage("envi")); + verifyExactMatch("gtmk", generateExpectedMessage("gtmk")); + verifyExactMatch("stmk", generateExpectedMessage("stmk")); + verifyExactMatch("srst", generateExpectedMessage("srst")); + verifyExactMatch("wchc", generateExpectedMessage("wchc")); + verifyExactMatch("wchp", generateExpectedMessage("wchp")); + verifyExactMatch("wchs", generateExpectedMessage("wchs")); + verifyExactMatch("mntr", generateExpectedMessage("mntr")); + } + + @Test(timeout=30000) + public void testISROEnabledWhenReadOnlyModeEnabled() throws Exception { + stopServer(); + ServerCnxn.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "stat"); + System.setProperty("readonlymode.enabled", "true"); + startServer(); + verifyExactMatch("isro", "rw"); + System.clearProperty("readonlymode.enabled"); + } + + @Test(timeout=30000) + public void testFourLetterWordsInvalidConfiguration() throws Exception { + stopServer(); + ServerCnxn.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "foo bar" + + " foo,,, " + + "bar :.,@#$%^&*() , , , , bar, bar, stat, "); + startServer(); + + // Just make sure we are good when admin made some mistakes in config file. + verifyAllCommandsFail(); + // But still, what's valid in white list will get through. + verifyFuzzyMatch("stat", "Outstanding"); + } + + @Test(timeout=30000) + public void testFourLetterWordsEnableAllCommandsThroughAsterisk() throws Exception { + stopServer(); + ServerCnxn.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "*"); + startServer(); + verifyAllCommandsSuccess(); + } + + @Test(timeout=30000) + public void testFourLetterWordsEnableAllCommandsThroughExplicitList() throws Exception { + stopServer(); + ServerCnxn.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", + "ruok, envi, conf, stat, srvr, cons, dump," + + "wchs, wchp, wchc, srst, crst, " + + "mntr, gtmk, isro, stmk"); + startServer(); + verifyAllCommandsSuccess(); + } + + private void verifyAllCommandsSuccess() throws Exception { + verifyExactMatch("ruok", "imok"); + verifyFuzzyMatch("envi", "java.version"); + verifyFuzzyMatch("conf", "clientPort"); + verifyFuzzyMatch("stat", "Outstanding"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", "queued"); + verifyFuzzyMatch("dump", "Session"); + verifyFuzzyMatch("wchs", "watches"); + verifyFuzzyMatch("wchp", ""); + verifyFuzzyMatch("wchc", ""); + + verifyFuzzyMatch("srst", "reset"); + verifyFuzzyMatch("crst", "reset"); + + verifyFuzzyMatch("stat", "Outstanding"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", "queued"); + verifyFuzzyMatch("gtmk", "306"); + verifyFuzzyMatch("isro", "rw"); + + TestableZooKeeper zk = createClient(); + String sid = getHexSessionId(zk.getSessionId()); + + verifyFuzzyMatch("stat", "queued"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", sid); + verifyFuzzyMatch("dump", sid); + + zk.getData("/", true, null); + + verifyFuzzyMatch("stat", "queued"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", sid); + verifyFuzzyMatch("dump", sid); + + verifyFuzzyMatch("wchs", "watching 1"); + verifyFuzzyMatch("wchp", sid); + verifyFuzzyMatch("wchc", sid); + zk.close(); + + verifyExactMatch("ruok", "imok"); + verifyFuzzyMatch("envi", "java.version"); + verifyFuzzyMatch("conf", "clientPort"); + verifyFuzzyMatch("stat", "Outstanding"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", "queued"); + verifyFuzzyMatch("dump", "Session"); + verifyFuzzyMatch("wchs", "watch"); + verifyFuzzyMatch("wchp", ""); + verifyFuzzyMatch("wchc", ""); + + verifyFuzzyMatch("srst", "reset"); + verifyFuzzyMatch("crst", "reset"); + + verifyFuzzyMatch("stat", "Outstanding"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", "queued"); + verifyFuzzyMatch("mntr", "zk_server_state\tstandalone"); + verifyFuzzyMatch("mntr", "num_alive_connections"); + verifyFuzzyMatch("stat", "Connections"); + verifyFuzzyMatch("srvr", "Connections"); + } + + private void verifyAllCommandsFail() throws Exception { + verifyExactMatch("ruok", generateExpectedMessage("ruok")); + verifyExactMatch("conf", generateExpectedMessage("conf")); + verifyExactMatch("cons", generateExpectedMessage("cons")); + verifyExactMatch("crst", generateExpectedMessage("crst")); + verifyExactMatch("dump", generateExpectedMessage("dump")); + verifyExactMatch("envi", generateExpectedMessage("envi")); + verifyExactMatch("gtmk", generateExpectedMessage("gtmk")); + verifyExactMatch("stmk", generateExpectedMessage("stmk")); + verifyExactMatch("srst", generateExpectedMessage("srst")); + verifyExactMatch("wchc", generateExpectedMessage("wchc")); + verifyExactMatch("wchp", generateExpectedMessage("wchp")); + verifyExactMatch("wchs", generateExpectedMessage("wchs")); + verifyExactMatch("mntr", generateExpectedMessage("mntr")); + verifyExactMatch("isro", generateExpectedMessage("isro")); + + // srvr is enabled by default due to the sad fact zkServer.sh uses it. + verifyFuzzyMatch("srvr", "Outstanding"); + } + + private void verifyFuzzyMatch(String cmd, String expected) throws IOException { + String resp = sendRequest(cmd); + LOG.info("cmd " + cmd + " expected " + expected + " got " + resp); + Assert.assertTrue(resp.contains(expected)); + } + + private String generateExpectedMessage(String command) { + return command + " is not executed because it is not in the whitelist."; + } + + private void verifyExactMatch(String cmd, String expected) throws IOException { + String resp = sendRequest(cmd); + LOG.info("cmd " + cmd + " expected an exact match of " + expected + "; got " + resp); + Assert.assertTrue(resp.trim().equals(expected)); + } + + private String sendRequest(String cmd) throws IOException { + HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0); + return send4LetterWord(hpobj.host, hpobj.port, cmd); + } + + private String sendRequest(String cmd, int timeout) throws IOException { + HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0); + return send4LetterWord(hpobj.host, hpobj.port, cmd, timeout); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/HierarchicalQuorumTest.java b/src/java/test/org/apache/zookeeper/test/HierarchicalQuorumTest.java index b4f22798559..051da1330d9 100644 --- a/src/java/test/org/apache/zookeeper/test/HierarchicalQuorumTest.java +++ b/src/java/test/org/apache/zookeeper/test/HierarchicalQuorumTest.java @@ -20,9 +20,10 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; -import java.net.InetSocketAddress; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Properties; import java.util.Set; @@ -30,8 +31,11 @@ import org.slf4j.LoggerFactory; import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.jmx.CommonNames; import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.QuorumPeer.LearnerType; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; import org.apache.zookeeper.server.quorum.flexible.QuorumHierarchical; import org.junit.Assert; import org.junit.Test; @@ -128,44 +132,39 @@ void startServers(boolean withObservers) throws Exception { int initLimit = 3; int syncLimit = 3; HashMap peers = new HashMap(); - peers.put(Long.valueOf(1), new QuorumServer(1, - new InetSocketAddress("127.0.0.1", port1 + 1000), - new InetSocketAddress("127.0.0.1", leport1 + 1000))); - peers.put(Long.valueOf(2), new QuorumServer(2, - new InetSocketAddress("127.0.0.1", port2 + 1000), - new InetSocketAddress("127.0.0.1", leport2 + 1000))); - peers.put(Long.valueOf(3), new QuorumServer(3, - new InetSocketAddress("127.0.0.1", port3 + 1000), - new InetSocketAddress("127.0.0.1", leport3 + 1000))); - peers.put(Long.valueOf(4), new QuorumServer(4, - new InetSocketAddress("127.0.0.1", port4 + 1000), - new InetSocketAddress("127.0.0.1", leport4 + 1000), + peers.put(Long.valueOf(1), new QuorumServer(1, "127.0.0.1", port1 + 1000, leport1 + 1000, null)); + peers.put(Long.valueOf(2), new QuorumServer(2, "127.0.0.1", port2 + 1000, leport2 + 1000, null)); + peers.put(Long.valueOf(3), new QuorumServer(3, "127.0.0.1", port3 + 1000, leport3 + 1000, null)); + peers.put(Long.valueOf(4), new QuorumServer(4, "127.0.0.1", port4 + 1000, leport4 + 1000, withObservers ? QuorumPeer.LearnerType.OBSERVER : QuorumPeer.LearnerType.PARTICIPANT)); - peers.put(Long.valueOf(5), new QuorumServer(5, - new InetSocketAddress("127.0.0.1", port5 + 1000), - new InetSocketAddress("127.0.0.1", leport5 + 1000), + peers.put(Long.valueOf(5), new QuorumServer(5, "127.0.0.1", port5 + 1000, leport5 + 1000, withObservers ? QuorumPeer.LearnerType.OBSERVER : QuorumPeer.LearnerType.PARTICIPANT)); LOG.info("creating QuorumPeer 1 port " + port1); + List qps = new ArrayList(); QuorumHierarchical hq1 = new QuorumHierarchical(qp); s1 = new QuorumPeer(peers, s1dir, s1dir, port1, 3, 1, tickTime, initLimit, syncLimit, hq1); + qps.add(s1); Assert.assertEquals(port1, s1.getClientPort()); LOG.info("creating QuorumPeer 2 port " + port2); QuorumHierarchical hq2 = new QuorumHierarchical(qp); s2 = new QuorumPeer(peers, s2dir, s2dir, port2, 3, 2, tickTime, initLimit, syncLimit, hq2); + qps.add(s2); Assert.assertEquals(port2, s2.getClientPort()); LOG.info("creating QuorumPeer 3 port " + port3); QuorumHierarchical hq3 = new QuorumHierarchical(qp); s3 = new QuorumPeer(peers, s3dir, s3dir, port3, 3, 3, tickTime, initLimit, syncLimit, hq3); + qps.add(s3); Assert.assertEquals(port3, s3.getClientPort()); LOG.info("creating QuorumPeer 4 port " + port4); QuorumHierarchical hq4 = new QuorumHierarchical(qp); s4 = new QuorumPeer(peers, s4dir, s4dir, port4, 3, 4, tickTime, initLimit, syncLimit, hq4); + qps.add(s4); if (withObservers) { s4.setLearnerType(QuorumPeer.LearnerType.OBSERVER); } @@ -174,6 +173,7 @@ void startServers(boolean withObservers) throws Exception { LOG.info("creating QuorumPeer 5 port " + port5); QuorumHierarchical hq5 = new QuorumHierarchical(qp); s5 = new QuorumPeer(peers, s5dir, s5dir, port5, 3, 5, tickTime, initLimit, syncLimit, hq5); + qps.add(s5); if (withObservers) { s5.setLearnerType(QuorumPeer.LearnerType.OBSERVER); } @@ -229,6 +229,7 @@ void startServers(boolean withObservers) throws Exception { ensureNames.add("name0=ReplicatedServer_id" + i); } JMXEnv.ensureAll(ensureNames.toArray(new String[ensureNames.size()])); + verifyElectionTimeTakenJMXAttribute(qps); } @Override @@ -274,6 +275,33 @@ protected TestableZooKeeper createClient(String hp) return createClient(watcher, hp); } + private void verifyElectionTimeTakenJMXAttribute(List peers) + throws Exception { + LOG.info("Verify QuorumPeer#electionTimeTaken jmx bean attribute"); + + for (int i = 1; i <= peers.size(); i++) { + QuorumPeer qp = peers.get(i - 1); + if (qp.getLearnerType() == LearnerType.OBSERVER) { + continue; // Observer don't have electionTimeTaken attribute. + } + Long electionTimeTaken = -1L; + String bean = ""; + if (qp.getPeerState() == ServerState.FOLLOWING) { + bean = String.format( + "%s:name0=ReplicatedServer_id%d,name1=replica.%d,name2=Follower", + CommonNames.DOMAIN, i, i); + } else if (qp.getPeerState() == ServerState.LEADING) { + bean = String.format( + "%s:name0=ReplicatedServer_id%d,name1=replica.%d,name2=Leader", + CommonNames.DOMAIN, i, i); + } + electionTimeTaken = (Long) JMXEnv.ensureBeanAttribute(bean, + "ElectionTimeTaken"); + Assert.assertTrue("Wrong electionTimeTaken value!", + electionTimeTaken >= 0); + } + } + @Test public void testHierarchicalQuorum() throws Throwable { cht.runHammer(5, 10); diff --git a/src/java/test/org/apache/zookeeper/test/InvalidSnapshotTest.java b/src/java/test/org/apache/zookeeper/test/InvalidSnapshotTest.java index 6f18a8b0b8a..c5a7b4fcaf8 100644 --- a/src/java/test/org/apache/zookeeper/test/InvalidSnapshotTest.java +++ b/src/java/test/org/apache/zookeeper/test/InvalidSnapshotTest.java @@ -33,6 +33,7 @@ import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.server.LogFormatter; import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.SnapshotFormatter; import org.apache.zookeeper.server.SyncRequestProcessor; import org.apache.zookeeper.server.ZooKeeperServer; import org.junit.Assert; @@ -58,6 +59,29 @@ public void testLogFormatter() throws Exception { LogFormatter.main(args); } + + /** + * Verify the SnapshotFormatter by running it on a known file. + */ + @Test + public void testSnapshotFormatter() throws Exception { + File snapDir = new File(testData, "invalidsnap"); + File snapfile = new File(new File(snapDir, "version-2"), "snapshot.272"); + String[] args = {snapfile.getCanonicalFile().toString()}; + SnapshotFormatter.main(args); + } + + /** + * Verify the SnapshotFormatter by running it on a known file with one null data. + */ + @Test + public void testSnapshotFormatterWithNull() throws Exception { + File snapDir = new File(testData, "invalidsnap"); + File snapfile = new File(new File(snapDir, "version-2"), "snapshot.273"); + String[] args = {snapfile.getCanonicalFile().toString()}; + SnapshotFormatter.main(args); + } + /** * test the snapshot * @throws Exception an exception could be expected @@ -83,6 +107,7 @@ public void testSnapshot() throws Exception { zk.close(); } f.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, ClientBase.CONNECTION_TIMEOUT)); diff --git a/src/java/test/org/apache/zookeeper/test/JMXEnv.java b/src/java/test/org/apache/zookeeper/test/JMXEnv.java index 48a107f1e4d..f67c67c7744 100644 --- a/src/java/test/org/apache/zookeeper/test/JMXEnv.java +++ b/src/java/test/org/apache/zookeeper/test/JMXEnv.java @@ -37,6 +37,7 @@ import org.apache.zookeeper.jmx.CommonNames; import org.apache.zookeeper.jmx.MBeanRegistry; +import org.junit.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,14 +61,18 @@ public static void setUp() throws IOException { public static void tearDown() { try { - cc.close(); + if (cc != null) { + cc.close(); + } } catch (IOException e) { LOG.warn("Unexpected, ignoring", e); } cc = null; try { - cs.stop(); + if (cs != null) { + cs.stop(); + } } catch (IOException e) { LOG.warn("Unexpected, ignoring", e); @@ -84,34 +89,43 @@ public static MBeanServerConnection conn() throws IOException { * Note that these are components of the name, and in particular * order matters - you want the more specific name (leafs) specified * before their parent(s) (since names are hierarchical) + * It waits in a loop up to 60 seconds before failing if there is a + * mismatch. * @param expectedNames * @return * @throws IOException * @throws MalformedObjectNameException */ public static Set ensureAll(String... expectedNames) - throws IOException + throws IOException, InterruptedException { Set beans; - try { - beans = conn().queryNames( - new ObjectName(CommonNames.DOMAIN + ":*"), null); - } catch (MalformedObjectNameException e) { - throw new RuntimeException(e); - } + Set found; + int nTry = 0; + do { + if (nTry++ > 0) { + Thread.sleep(100); + } + try { + beans = conn().queryNames( + new ObjectName(CommonNames.DOMAIN + ":*"), null); + } catch (MalformedObjectNameException e) { + throw new RuntimeException(e); + } - Set found = new HashSet(); - for (String name : expectedNames) { - LOG.info("expect:" + name); - for (ObjectName bean : beans) { - if (bean.toString().contains(name)) { - LOG.info("found:" + name + " " + bean); - found.add(bean); - break; + found = new HashSet(); + for (String name : expectedNames) { + LOG.info("expect:" + name); + for (ObjectName bean : beans) { + if (bean.toString().contains(name)) { + LOG.info("found:" + name + " " + bean); + found.add(bean); + break; + } } + beans.removeAll(found); } - beans.removeAll(found); - } + } while ((expectedNames.length != found.size()) && (nTry < 600)); TestCase.assertEquals("expected " + Arrays.toString(expectedNames), expectedNames.length, found.size()); return beans; @@ -128,7 +142,7 @@ public static Set ensureAll(String... expectedNames) * @throws MalformedObjectNameException */ public static Set ensureOnly(String... expectedNames) - throws IOException + throws IOException, InterruptedException { LOG.info("ensureOnly:" + Arrays.toString(expectedNames)); Set beans = ensureAll(expectedNames); @@ -140,23 +154,44 @@ public static Set ensureOnly(String... expectedNames) } public static void ensureNone(String... expectedNames) - throws IOException + throws IOException, InterruptedException { Set beans; - try { - beans = conn().queryNames( - new ObjectName(CommonNames.DOMAIN + ":*"), null); - } catch (MalformedObjectNameException e) { - throw new RuntimeException(e); - } - - for (String name : expectedNames) { - for (ObjectName bean : beans) { - if (bean.toString().contains(name)) { - LOG.info("didntexpect:" + name); - TestCase.fail(name + " " + bean.toString()); + int nTry = 0; + boolean foundUnexpected = false; + String unexpectedName = ""; + do { + if (nTry++ > 0) { + Thread.sleep(100); + } + try { + beans = conn().queryNames( + new ObjectName(CommonNames.DOMAIN + ":*"), null); + } catch (MalformedObjectNameException e) { + throw new RuntimeException(e); + } + + foundUnexpected = false; + for (String name : expectedNames) { + for (ObjectName bean : beans) { + if (bean.toString().contains(name)) { + LOG.info("didntexpect:" + name); + foundUnexpected = true; + unexpectedName = name + " " + bean.toString(); + break; + } + } + if (foundUnexpected) { + break; } } + } while ((foundUnexpected) && (nTry < 600)); + if (foundUnexpected) { + LOG.info("List of all beans follows:"); + for (ObjectName bean : beans) { + LOG.info("bean:" + bean.toString()); + } + TestCase.fail(unexpectedName); } } @@ -174,4 +209,117 @@ public static void dump() throws IOException { } } + /** + * Ensure that the specified parent names are registered. Note that these + * are components of the name. It waits in a loop up to 60 seconds before + * failing if there is a mismatch. This will return the beans which are not + * matched. + * + * {@link https://issues.apache.org/jira/browse/ZOOKEEPER-1858} + * + * @param expectedNames + * - expected beans + * @return the beans which are not matched with the given expected names + * + * @throws IOException + * @throws InterruptedException + * + */ + public static Set ensureParent(String... expectedNames) + throws IOException, InterruptedException { + LOG.info("ensureParent:" + Arrays.toString(expectedNames)); + + Set beans; + int nTry = 0; + Set found = new HashSet(); + do { + if (nTry++ > 0) { + Thread.sleep(500); + } + try { + beans = conn().queryNames( + new ObjectName(CommonNames.DOMAIN + ":*"), null); + } catch (MalformedObjectNameException e) { + throw new RuntimeException(e); + } + found.clear(); + for (String name : expectedNames) { + LOG.info("expect:" + name); + for (ObjectName bean : beans) { + // check the existence of name in bean + if (compare(bean.toString(), name)) { + LOG.info("found:" + name + " " + bean); + found.add(bean); + break; + } + } + beans.removeAll(found); + } + } while (expectedNames.length != found.size() && nTry < 120); + TestCase.assertEquals("expected " + Arrays.toString(expectedNames), + expectedNames.length, found.size()); + return beans; + } + + /** + * Comparing that the given name exists in the bean. For component beans, + * the component name will be present at the end of the bean name + * + * For example 'StandaloneServer' will present in the bean name like + * 'org.apache.ZooKeeperService:name0=StandaloneServer_port-1' + */ + private static boolean compare(String bean, String name) { + String[] names = bean.split("="); + if (names.length > 0 && names[names.length - 1].contains(name)) { + return true; + } + return false; + } + + /** + * Ensure that the specified bean name and its attribute is registered. Note + * that these are components of the name. It waits in a loop up to 60 + * seconds before failing if there is a mismatch. This will return the beans + * which are not matched. + * + * @param expectedName + * - expected bean + * @param expectedAttribute + * - expected attribute + * @return the value of the attribute + * + * @throws Exception + */ + public static Object ensureBeanAttribute(String expectedName, + String expectedAttribute) throws Exception { + String value = ""; + LOG.info("ensure bean:{}, attribute:{}", new Object[] { expectedName, + expectedAttribute }); + + Set beans; + int nTry = 0; + do { + if (nTry++ > 0) { + Thread.sleep(500); + } + try { + beans = conn().queryNames( + new ObjectName(CommonNames.DOMAIN + ":*"), null); + } catch (MalformedObjectNameException e) { + throw new RuntimeException(e); + } + LOG.info("expect:" + expectedName); + for (ObjectName bean : beans) { + // check the existence of name in bean + if (bean.toString().equals(expectedName)) { + LOG.info("found:{} {}", new Object[] { expectedName, bean }); + return conn().getAttribute(bean, expectedAttribute); + } + } + } while (nTry < 120); + Assert.fail("Failed to find bean:" + expectedName + ", attribute:" + + expectedAttribute); + return value; + } + } diff --git a/src/java/test/org/apache/zookeeper/test/LENonTerminateTest.java b/src/java/test/org/apache/zookeeper/test/LENonTerminateTest.java index 36587143a6d..de266988f69 100644 --- a/src/java/test/org/apache/zookeeper/test/LENonTerminateTest.java +++ b/src/java/test/org/apache/zookeeper/test/LENonTerminateTest.java @@ -38,6 +38,7 @@ import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.quorum.Election; +import org.apache.zookeeper.server.quorum.FLELostMessageTest; import org.apache.zookeeper.server.quorum.LeaderElection; import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.quorum.Vote; @@ -216,7 +217,7 @@ public MockQuorumPeer(Map quorumPeers, File snapDir, throws IOException { super(quorumPeers, snapDir, logDir, electionAlg, - myid,tickTime, initLimit,syncLimit, + myid,tickTime, initLimit,syncLimit, false, ServerCnxnFactory.createFactory(clientPort, -1), new QuorumMaj(countParticipants(quorumPeers))); } @@ -299,9 +300,8 @@ public void testNonTermination() throws Exception { for(int i = 0; i < count; i++) { int clientport = PortAssignment.unique(); peers.put(Long.valueOf(i), - new QuorumServer(i, - new InetSocketAddress("127.0.0.1", clientport), - new InetSocketAddress("127.0.0.1", PortAssignment.unique()))); + new QuorumServer(i, "127.0.0.1", clientport, + PortAssignment.unique(), null)); tmpdir[i] = ClientBase.createTmpDir(); port[i] = clientport; } diff --git a/src/java/test/org/apache/zookeeper/test/LETest.java b/src/java/test/org/apache/zookeeper/test/LETest.java index 78d68f028f4..0fd34302282 100644 --- a/src/java/test/org/apache/zookeeper/test/LETest.java +++ b/src/java/test/org/apache/zookeeper/test/LETest.java @@ -98,9 +98,7 @@ public void testLE() throws Exception { votes = new Vote[count]; for(int i = 0; i < count; i++) { peers.put(Long.valueOf(i), - new QuorumServer(i, - new InetSocketAddress("127.0.0.1", - PortAssignment.unique()))); + new QuorumServer(i, "127.0.0.1", PortAssignment.unique(), 0, null)); tmpdir[i] = ClientBase.createTmpDir(); port[i] = PortAssignment.unique(); } diff --git a/src/java/test/org/apache/zookeeper/test/LoadFromLogTest.java b/src/java/test/org/apache/zookeeper/test/LoadFromLogTest.java index d92f2d38142..94b0f97ec02 100644 --- a/src/java/test/org/apache/zookeeper/test/LoadFromLogTest.java +++ b/src/java/test/org/apache/zookeeper/test/LoadFromLogTest.java @@ -18,37 +18,46 @@ package org.apache.zookeeper.test; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.zookeeper.common.Time; +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.jute.Record; import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZKTestCase; -import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.DataNode; +import org.apache.zookeeper.server.DataTree; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.SyncRequestProcessor; import org.apache.zookeeper.server.ZooKeeperServer; -import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.persistence.FileHeader; import org.apache.zookeeper.server.persistence.FileTxnLog; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; import org.apache.zookeeper.server.persistence.TxnLog.TxnIterator; +import org.apache.zookeeper.txn.CreateTxn; +import org.apache.zookeeper.txn.DeleteTxn; +import org.apache.zookeeper.txn.MultiTxn; +import org.apache.zookeeper.txn.Txn; import org.apache.zookeeper.txn.TxnHeader; import org.junit.Assert; import org.junit.Test; -import org.apache.zookeeper.server.DataTree; -import org.apache.zookeeper.server.DataNode; -import org.apache.zookeeper.txn.CreateTxn; -import org.apache.zookeeper.txn.DeleteTxn; -import org.apache.zookeeper.ZooDefs.OpCode; -import org.apache.jute.Record; -import java.io.FileInputStream; - -import org.apache.jute.BinaryInputArchive; -import org.apache.zookeeper.server.persistence.FileHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LoadFromLogTest extends ZKTestCase implements Watcher { private static String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); @@ -93,8 +102,8 @@ public void testLoad() throws Exception { ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); // now verify that the FileTxnLog reads every transaction only once - File logDir = new File(tmpDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION); - FileTxnLog txnLog = new FileTxnLog(logDir); + File logDir = new File(tmpDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION); + FileTxnLog txnLog = new FileTxnLog(logDir); TxnIterator itr = txnLog.read(0); long expectedZxid = 0; @@ -109,6 +118,7 @@ public void testLoad() throws Exception { }while(itr.next()); Assert.assertTrue("processed all transactions. " + expectedZxid + " == " + TOTAL_TRANSACTIONS, (expectedZxid == TOTAL_TRANSACTIONS)); + zks.shutdown(); } @@ -146,7 +156,7 @@ public void testTxnFailure() throws Exception { dt.createNode("/test", new byte[0], null, 0, -1, 1, 1); for (count = 1; count <= 3; count++) { dt.createNode("/test/" + count, new byte[0], null, 0, -1, count, - System.currentTimeMillis()); + Time.currentElapsedTime()); } DataNode zk = dt.getNode("/test"); @@ -157,6 +167,14 @@ public void testTxnFailure() throws Exception { LOG.info("Attempting to create " + "/test/" + (count - 1)); doOp(logFile, OpCode.create, "/test/" + (count - 1), dt, zk, zk.stat.getCversion() + 1); + + LOG.info("Attempting to create " + "/test/" + (count - 1)); + doOp(logFile, OpCode.multi, "/test/" + (count - 1), dt, zk, + zk.stat.getCversion() + 1); + + LOG.info("Attempting to create " + "/test/" + (count - 1)); + doOp(logFile, OpCode.multi, "/test/" + (count - 1), dt, zk, + -1); // Make delete fo fail, then verify cversion. // this doesn't happen anymore, we only set the cversion on create @@ -187,12 +205,27 @@ private void doOp(FileTxnSnapLog logFile, int type, String path, if (type == OpCode.delete) { txn = new DeleteTxn(path); txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, - System.currentTimeMillis(), OpCode.delete); + Time.currentElapsedTime(), OpCode.delete); } else if (type == OpCode.create) { txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, - System.currentTimeMillis(), OpCode.create); + Time.currentElapsedTime(), OpCode.create); txn = new CreateTxn(path, new byte[0], null, false, cversion); } + else if (type == OpCode.multi) { + txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, + Time.currentElapsedTime(), OpCode.create); + txn = new CreateTxn(path, new byte[0], null, false, cversion); + ArrayList txnList = new ArrayList(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos); + txn.serialize(boa, "request") ; + ByteBuffer bb = ByteBuffer.wrap(baos.toByteArray()); + Txn txact = new Txn(OpCode.create, bb.array()); + txnList.add(txact); + txn = new MultiTxn(txnList); + txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, + Time.currentElapsedTime(), OpCode.multi); + } logFile.processTransaction(txnHeader, dt, null, txn); int newCversion = parent.stat.getCversion(); @@ -218,7 +251,7 @@ public void testPad() throws Exception { File tmpDir = ClientBase.createTmpDir(); FileTxnLog txnLog = new FileTxnLog(tmpDir); TxnHeader txnHeader = new TxnHeader(0xabcd, 0x123, 0x123, - System.currentTimeMillis(), OpCode.create); + Time.currentElapsedTime(), OpCode.create); Record txn = new CreateTxn("/Test", new byte[0], null, false, 1); txnLog.append(txnHeader, txn); FileInputStream in = new FileInputStream(tmpDir.getPath() + "/log." + @@ -248,22 +281,7 @@ public void testRestore() throws Exception { f.startup(zks); Assert.assertTrue("waiting for server being up ", ClientBase .waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); - ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); - - long start = System.currentTimeMillis(); - while (!connected) { - long end = System.currentTimeMillis(); - if (end - start > 5000) { - Assert.assertTrue("Could not connect with server in 5 seconds", - false); - } - try { - Thread.sleep(200); - } catch (Exception e) { - LOG.warn("Intrrupted"); - } - - } + ZooKeeper zk = getConnectedZkClient(); // generate some transactions String lastPath = null; try { @@ -303,21 +321,7 @@ public void testRestore() throws Exception { // Verify lastProcessedZxid is set correctly Assert.assertTrue("Restore failed expected zxid=" + eZxid + " found=" + fZxid, fZxid == eZxid); - zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); - start = System.currentTimeMillis(); - while (!connected) { - long end = System.currentTimeMillis(); - if (end - start > 5000) { - Assert.assertTrue("Could not connect with server in 5 seconds", - false); - } - try { - Thread.sleep(200); - } catch (Exception e) { - LOG.warn("Intrrupted"); - } - - } + zk = getConnectedZkClient(); // Verify correctness of data and whether sequential znode creation // proceeds correctly after this point String[] children; @@ -337,5 +341,123 @@ public void testRestore() throws Exception { + " expected " + NUM_MESSAGES, (children.length == NUM_MESSAGES)); f.shutdown(); + zks.shutdown(); } -} \ No newline at end of file + + /** + * Test we can restore a snapshot that has errors and data ahead of the zxid + * of the snapshot file. + */ + @Test + public void testRestoreWithTransactionErrors() throws Exception { + // setup a single server cluster + File tmpDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + SyncRequestProcessor.setSnapCount(10000); + final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); + ServerCnxnFactory f = ServerCnxnFactory.createFactory(PORT, -1); + f.startup(zks); + Assert.assertTrue("waiting for server being up ", ClientBase + .waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); + ZooKeeper zk = getConnectedZkClient(); + // generate some transactions + try { + for (int i = 0; i < NUM_MESSAGES; i++) { + try { + zk.create("/invaliddir/test-", new byte[0], + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); + } catch(NoNodeException e) { + //Expected + } + } + } finally { + zk.close(); + } + + // force the zxid to be behind the content + zks.getZKDatabase().setlastProcessedZxid( + zks.getZKDatabase().getDataTreeLastProcessedZxid() - 10); + LOG.info("Set lastProcessedZxid to " + + zks.getZKDatabase().getDataTreeLastProcessedZxid()); + + // Force snapshot and restore + zks.takeSnapshot(); + zks.shutdown(); + f.shutdown(); + + zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + SyncRequestProcessor.setSnapCount(10000); + f = ServerCnxnFactory.createFactory(PORT, -1); + f.startup(zks); + Assert.assertTrue("waiting for server being up ", ClientBase + .waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); + + f.shutdown(); + zks.shutdown(); + } + + /** + * ZOOKEEPER-1573: test restoring a snapshot with deleted txns ahead of the + * snapshot file's zxid. + */ + @Test + public void testReloadSnapshotWithMissingParent() throws Exception { + // setup a single server cluster + File tmpDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + SyncRequestProcessor.setSnapCount(10000); + final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); + ServerCnxnFactory f = ServerCnxnFactory.createFactory(PORT, -1); + f.startup(zks); + Assert.assertTrue("waiting for server being up ", + ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); + ZooKeeper zk = getConnectedZkClient(); + + // create transactions to create the snapshot with create/delete pattern + zk.create("/a", "".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + Stat stat = zk.exists("/a", false); + long createZxId = stat.getMzxid(); + zk.create("/a/b", "".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk.delete("/a/b", -1); + zk.delete("/a", -1); + // force the zxid to be behind the content + zks.getZKDatabase().setlastProcessedZxid(createZxId); + LOG.info("Set lastProcessedZxid to {}", zks.getZKDatabase() + .getDataTreeLastProcessedZxid()); + // Force snapshot and restore + zks.takeSnapshot(); + zks.shutdown(); + f.shutdown(); + + zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + SyncRequestProcessor.setSnapCount(10000); + f = ServerCnxnFactory.createFactory(PORT, -1); + f.startup(zks); + Assert.assertTrue("waiting for server being up ", + ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); + f.shutdown(); + } + + private ZooKeeper getConnectedZkClient() throws IOException { + ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); + + long start = Time.currentElapsedTime(); + while (!connected) { + long end = Time.currentElapsedTime(); + if (end - start > 5000) { + Assert.assertTrue("Could not connect with server in 5 seconds", + false); + } + try { + Thread.sleep(200); + } catch (Exception e) { + LOG.warn("Interrupted"); + } + } + return zk; + } +} diff --git a/src/java/test/org/apache/zookeeper/test/MaxCnxnsTest.java b/src/java/test/org/apache/zookeeper/test/MaxCnxnsTest.java index 38e84fa82b3..0a31ac9b7cc 100644 --- a/src/java/test/org/apache/zookeeper/test/MaxCnxnsTest.java +++ b/src/java/test/org/apache/zookeeper/test/MaxCnxnsTest.java @@ -51,6 +51,7 @@ public CnxnThread(int i) { } public void run() { + SocketChannel sChannel = null; try { /* * For future unwary socket programmers: although connect 'blocks' it @@ -58,7 +59,7 @@ public void run() { * you can not assume that all the sockets are connected at the end of * this for loop. */ - SocketChannel sChannel = SocketChannel.open(); + sChannel = SocketChannel.open(); sChannel.connect(new InetSocketAddress(host,port)); // Construct a connection request ConnectRequest conReq = new ConnectRequest(0, 0, @@ -97,6 +98,15 @@ public void run() { catch (IOException io) { // "Connection reset by peer" } + finally { + if (sChannel != null) { + try { + sChannel.close(); + } + catch (Exception e) { + } + } + } } } diff --git a/src/java/test/org/apache/zookeeper/test/MultiTransactionTest.java b/src/java/test/org/apache/zookeeper/test/MultiTransactionTest.java index fa9c8f3b486..0277c230653 100644 --- a/src/java/test/org/apache/zookeeper/test/MultiTransactionTest.java +++ b/src/java/test/org/apache/zookeeper/test/MultiTransactionTest.java @@ -17,67 +17,456 @@ package org.apache.zookeeper.test; +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + import org.apache.log4j.Logger; -import org.apache.zookeeper.*; +import org.apache.zookeeper.AsyncCallback; +import org.apache.zookeeper.AsyncCallback.MultiCallback; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.Op; +import org.apache.zookeeper.OpResult; +import org.apache.zookeeper.Transaction; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.OpResult.CheckResult; +import org.apache.zookeeper.OpResult.CreateResult; +import org.apache.zookeeper.OpResult.DeleteResult; +import org.apache.zookeeper.OpResult.ErrorResult; +import org.apache.zookeeper.OpResult.SetDataResult; import org.apache.zookeeper.ZooDefs.Ids; -import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.SyncRequestProcessor; -import org.apache.zookeeper.server.ZooKeeperServer; -import org.apache.zookeeper.OpResult.ErrorResult; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.ArrayList; - -import org.apache.zookeeper.data.Stat; - -import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; - -public class MultiTransactionTest extends ZKTestCase implements Watcher { +@RunWith(Parameterized.class) +public class MultiTransactionTest extends ClientBase { private static final Logger LOG = Logger.getLogger(MultiTransactionTest.class); - private static final String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); - private ZooKeeper zk; - private ServerCnxnFactory serverFactory; + private ZooKeeper zk_chroot; + + private final boolean useAsync; + + public MultiTransactionTest(boolean useAsync) { + this.useAsync = useAsync; + } - @Override - public void process(WatchedEvent event) { - // ignore + @Parameters + public static Collection configs() { + return Arrays.asList(new Object[][] { + { false }, { true }, + }); } @Before - public void setupZk() throws Exception { - File tmpDir = ClientBase.createTmpDir(); - ClientBase.setupTestEnv(); - ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + public void setUp() throws Exception { SyncRequestProcessor.setSnapCount(150); - final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); - serverFactory = ServerCnxnFactory.createFactory(PORT, -1); - serverFactory.startup(zks); - LOG.info("starting up the zookeeper server .. waiting"); - Assert.assertTrue("waiting for server being up", - ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); - zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); + super.setUp(); + zk = createClient(); } - @After - public void shutdownServer() throws Exception { - zk.close(); - serverFactory.shutdown(); + static class MultiResult { + int rc; + List results; + boolean finished = false; } + private List multi(ZooKeeper zk, Iterable ops) + throws KeeperException, InterruptedException { + if (useAsync) { + final MultiResult res = new MultiResult(); + zk.multi(ops, new MultiCallback() { + @Override + public void processResult(int rc, String path, Object ctx, + List opResults) { + synchronized (res) { + res.rc = rc; + res.results = opResults; + res.finished = true; + res.notifyAll(); + } + } + }, null); + synchronized (res) { + while (!res.finished) { + res.wait(); + } + } + if (KeeperException.Code.OK.intValue() != res.rc) { + KeeperException ke = KeeperException.create(KeeperException.Code.get(res.rc)); + throw ke; + } + return res.results; + } else { + return zk.multi(ops); + } + } + + private void multiHavingErrors(ZooKeeper zk, Iterable ops, + List expectedResultCodes, String expectedErr) + throws KeeperException, InterruptedException { + if (useAsync) { + final MultiResult res = new MultiResult(); + zk.multi(ops, new MultiCallback() { + @Override + public void processResult(int rc, String path, Object ctx, + List opResults) { + synchronized (res) { + res.rc = rc; + res.results = opResults; + res.finished = true; + res.notifyAll(); + } + } + }, null); + synchronized (res) { + while (!res.finished) { + res.wait(); + } + } + for (int i = 0; i < res.results.size(); i++) { + OpResult opResult = res.results.get(i); + Assert.assertTrue("Did't recieve proper error response", + opResult instanceof ErrorResult); + ErrorResult errRes = (ErrorResult) opResult; + Assert.assertEquals("Did't recieve proper error code", + expectedResultCodes.get(i).intValue(), errRes.getErr()); + } + } else { + try { + zk.multi(ops); + Assert.fail("Shouldn't have validated in ZooKeeper client!"); + } catch (KeeperException e) { + Assert.assertEquals("Wrong exception", expectedErr, e.code() + .name()); + } catch (IllegalArgumentException e) { + Assert.assertEquals("Wrong exception", expectedErr, + e.getMessage()); + } + } + } + + private List commit(Transaction txn) + throws KeeperException, InterruptedException { + if (useAsync) { + final MultiResult res = new MultiResult(); + txn.commit(new MultiCallback() { + @Override + public void processResult(int rc, String path, Object ctx, + List opResults) { + synchronized (res) { + res.rc = rc; + res.results = opResults; + res.finished = true; + res.notifyAll(); + } + } + }, null); + synchronized (res) { + while (!res.finished) { + res.wait(); + } + } + if (KeeperException.Code.OK.intValue() != res.rc) { + KeeperException ke = KeeperException.create(KeeperException.Code.get(res.rc)); + throw ke; + } + return res.results; + } else { + return txn.commit(); + } + } + + /** + * Test verifies the multi calls with invalid znode path + */ + @Test(timeout = 90000) + public void testInvalidPath() throws Exception { + List expectedResultCodes = new ArrayList(); + expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY + .intValue()); + expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue()); + expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY + .intValue()); + // create with CreateMode + List opList = Arrays.asList(Op.create("/multi0", new byte[0], + Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT), + Op.create( + "/multi1/", new byte[0], + Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT), + Op.create("/multi2", new byte[0], + Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT)); + String expectedErr = "Path must not end with / character"; + multiHavingErrors(zk, opList, expectedResultCodes, expectedErr); + + // create with valid sequential flag + opList = Arrays.asList(Op.create("/multi0", new byte[0], + Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT), + Op.create("multi1/", new byte[0], + Ids.OPEN_ACL_UNSAFE, + CreateMode.EPHEMERAL_SEQUENTIAL.toFlag()), + Op.create("/multi2", + new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT)); + expectedErr = "Path must start with / character"; + multiHavingErrors(zk, opList, expectedResultCodes, expectedErr); + + // check + opList = Arrays.asList(Op.check("/multi0", -1), + Op.check("/multi1/", 100), + Op.check("/multi2", 5)); + expectedErr = "Path must not end with / character"; + multiHavingErrors(zk, opList, expectedResultCodes, expectedErr); + + // delete + opList = Arrays.asList(Op.delete("/multi0", -1), + Op.delete("/multi1/", 100), + Op.delete("/multi2", 5)); + multiHavingErrors(zk, opList, expectedResultCodes, expectedErr); + + // Multiple bad arguments + expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue()); + + // setdata + opList = Arrays.asList(Op.setData("/multi0", new byte[0], -1), + Op.setData("/multi1/", new byte[0], -1), + Op.setData("/multi2", new byte[0], -1), + Op.setData("multi3", new byte[0], -1)); + multiHavingErrors(zk, opList, expectedResultCodes, expectedErr); + } + + /** + * Test verifies the multi calls with blank znode path + */ + @Test(timeout = 90000) + public void testBlankPath() throws Exception { + List expectedResultCodes = new ArrayList(); + expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY + .intValue()); + expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue()); + expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY + .intValue()); + expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue()); + + // delete + String expectedErr = "Path cannot be null"; + List opList = Arrays.asList(Op.delete("/multi0", -1), + Op.delete(null, 100), + Op.delete("/multi2", 5), + Op.delete("", -1)); + multiHavingErrors(zk, opList, expectedResultCodes, expectedErr); + } + + /** + * Test verifies the multi.create with invalid createModeFlag + */ + @Test(timeout = 90000) + public void testInvalidCreateModeFlag() throws Exception { + List expectedResultCodes = new ArrayList(); + expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY + .intValue()); + expectedResultCodes.add(KeeperException.Code.BADARGUMENTS.intValue()); + expectedResultCodes.add(KeeperException.Code.RUNTIMEINCONSISTENCY + .intValue()); + + int createModeFlag = 6789; + List opList = Arrays.asList(Op.create("/multi0", new byte[0], + Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT), + Op.create("/multi1", new byte[0], + Ids.OPEN_ACL_UNSAFE, + createModeFlag), + Op.create("/multi2", new byte[0], + Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT)); + String expectedErr = KeeperException.Code.BADARGUMENTS.name(); + multiHavingErrors(zk, opList, expectedResultCodes, expectedErr); + } + + /** + * ZOOKEEPER-2052: + * Multi abort shouldn't have any side effect. + * We fix a bug in rollback and the following scenario should work: + * 1. multi delete abort because of not empty directory + * 2. ephemeral nodes under that directory are deleted + * 3. multi delete should succeed. + */ @Test - public void testCreate() throws Exception { - List results = new ArrayList(); + public void testMultiRollback() throws Exception { + zk.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + + ZooKeeper epheZk = createClient(); + epheZk.create("/foo/bar", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); + + List opList = Arrays.asList(Op.delete("/foo", -1)); + try { + multi(zk, opList); + Assert.fail("multi delete should failed for not empty directory"); + } catch (KeeperException.NotEmptyException e) { + } + + final CountDownLatch latch = new CountDownLatch(1); + + zk.exists("/foo/bar", new Watcher() { + @Override + public void process(WatchedEvent event) { + if (event.getType() == Event.EventType.NodeDeleted){ + latch.countDown(); + } + } + }); + + epheZk.close(); + + latch.await(); + + try { + zk.getData("/foo/bar", false, null); + Assert.fail("ephemeral node should have been deleted"); + } catch (KeeperException.NoNodeException e) { + } + + multi(zk, opList); + + try { + zk.getData("/foo", false, null); + Assert.fail("persistent node should have been deleted after multi"); + } catch (KeeperException.NoNodeException e) { + } + } + + @Test + public void testChRootCreateDelete() throws Exception { + // creating the subtree for chRoot clients. + String chRoot = createNameSpace(); + // Creating child using chRoot client. + zk_chroot = createClient(this.hostPort + chRoot); + Op createChild = Op.create("/myid", new byte[0], + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + multi(zk_chroot, Arrays.asList(createChild)); + + Assert.assertNotNull("zNode is not created under chroot:" + chRoot, zk + .exists(chRoot + "/myid", false)); + Assert.assertNotNull("zNode is not created under chroot:" + chRoot, + zk_chroot.exists("/myid", false)); + Assert.assertNull("zNode is created directly under '/', ignored configured chroot", + zk.exists("/myid", false)); + + // Deleting child using chRoot client. + Op deleteChild = Op.delete("/myid", 0); + multi(zk_chroot, Arrays.asList(deleteChild)); + Assert.assertNull("zNode exists under chroot:" + chRoot, zk.exists( + chRoot + "/myid", false)); + Assert.assertNull("zNode exists under chroot:" + chRoot, zk_chroot + .exists("/myid", false)); + } + + @Test + public void testChRootSetData() throws Exception { + // creating the subtree for chRoot clients. + String chRoot = createNameSpace(); + // setData using chRoot client. + zk_chroot = createClient(this.hostPort + chRoot); + String[] names = {"/multi0", "/multi1", "/multi2"}; + List ops = new ArrayList(); + + for (int i = 0; i < names.length; i++) { + ops.add(Op.create(names[i], new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT)); + ops.add(Op.setData(names[i], names[i].getBytes(), 0)); + } + + multi(zk_chroot, ops) ; + + for (int i = 0; i < names.length; i++) { + Assert.assertArrayEquals("zNode data not matching", names[i] + .getBytes(), zk_chroot.getData(names[i], false, null)); + } + } + + @Test + public void testChRootCheck() throws Exception { + // creating the subtree for chRoot clients. + String chRoot = createNameSpace(); + // checking the child version using chRoot client. + zk_chroot = createClient(this.hostPort + chRoot); + String[] names = {"/multi0", "/multi1", "/multi2"}; + List ops = new ArrayList(); + for (int i = 0; i < names.length; i++) { + zk.create(chRoot + names[i], new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } + for (int i = 0; i < names.length; i++) { + ops.add(Op.check(names[i], 0)); + } + multi(zk_chroot, ops) ; + } + + @Test + public void testChRootTransaction() throws Exception { + // creating the subtree for chRoot clients. + String chRoot = createNameSpace(); + // checking the child version using chRoot client. + zk_chroot = createClient(this.hostPort + chRoot); + String childPath = "/myid"; + Transaction transaction = zk_chroot.transaction(); + transaction.create(childPath, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + transaction.check(childPath, 0); + transaction.setData(childPath, childPath.getBytes(), 0); + commit(transaction); + + Assert.assertNotNull("zNode is not created under chroot:" + chRoot, zk + .exists(chRoot + childPath, false)); + Assert.assertNotNull("zNode is not created under chroot:" + chRoot, + zk_chroot.exists(childPath, false)); + Assert.assertNull("zNode is created directly under '/', ignored configured chroot", + zk.exists(childPath, false)); + Assert.assertArrayEquals("zNode data not matching", childPath + .getBytes(), zk_chroot.getData(childPath, false, null)); + + transaction = zk_chroot.transaction(); + // Deleting child using chRoot client. + transaction.delete(childPath, 1); + commit(transaction); + + Assert.assertNull("chroot:" + chRoot + " exists after delete", zk + .exists(chRoot + "/myid", false)); + Assert.assertNull("chroot:" + chRoot + " exists after delete", + zk_chroot.exists("/myid", false)); + } - results = zk.multi(Arrays.asList( + private String createNameSpace() throws InterruptedException, + KeeperException { + // creating the subtree for chRoot clients. + String chRoot = "/appsX"; + Op createChRoot = Op.create(chRoot, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + multi(zk, Arrays.asList(createChRoot)); + return chRoot; + } + + @Test + public void testCreate() throws Exception { + multi(zk, Arrays.asList( Op.create("/multi0", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.create("/multi1", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.create("/multi2", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) @@ -90,7 +479,7 @@ public void testCreate() throws Exception { @Test public void testCreateDelete() throws Exception { - zk.multi(Arrays.asList( + multi(zk, Arrays.asList( Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.delete("/multi", 0) )); @@ -103,7 +492,7 @@ public void testCreateDelete() throws Exception { public void testInvalidVersion() throws Exception { try { - zk.multi(Arrays.asList( + multi(zk, Arrays.asList( Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.delete("/multi", 1) )); @@ -116,7 +505,7 @@ public void testInvalidVersion() throws Exception { @Test public void testNestedCreate() throws Exception { - zk.multi(Arrays.asList( + multi(zk, Arrays.asList( /* Create */ Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.create("/multi/a", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), @@ -145,7 +534,7 @@ public void testSetData() throws Exception { ops.add(Op.setData(names[i], names[i].getBytes(), 0)); } - zk.multi(ops) ; + multi(zk, ops) ; for (int i = 0; i < names.length; i++) { Assert.assertArrayEquals(names[i].getBytes(), zk.getData(names[i], false, null)); @@ -158,7 +547,7 @@ public void testUpdateConflict() throws Exception { Assert.assertNull(zk.exists("/multi", null)); try { - zk.multi(Arrays.asList( + multi(zk, Arrays.asList( Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.setData("/multi", "X".getBytes(), 0), Op.setData("/multi", "Y".getBytes(), 0) @@ -172,7 +561,7 @@ public void testUpdateConflict() throws Exception { Assert.assertNull(zk.exists("/multi", null)); //Updating version solves conflict -- order matters - zk.multi(Arrays.asList( + multi(zk, Arrays.asList( Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.setData("/multi", "X".getBytes(), 0), Op.setData("/multi", "Y".getBytes(), 1) @@ -186,7 +575,7 @@ public void TestDeleteUpdateConflict() throws Exception { /* Delete of a node folowed by an update of the (now) deleted node */ try { - zk.multi(Arrays.asList( + multi(zk, Arrays.asList( Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), Op.delete("/multi", 0), Op.setData("/multi", "Y".getBytes(), 0) @@ -203,28 +592,214 @@ public void TestDeleteUpdateConflict() throws Exception { @Test public void TestGetResults() throws Exception { /* Delete of a node folowed by an update of the (now) deleted node */ - try { - zk.multi(Arrays.asList( - Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), - Op.delete("/multi", 0), - Op.setData("/multi", "Y".getBytes(), 0), - Op.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) - )); - Assert.fail("/multi should have been deleted so setData should have failed"); - } catch (KeeperException e) { - // '/multi' should never have been created as entire op should fail + Iterable ops = Arrays.asList( + Op.create("/multi", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), + Op.delete("/multi", 0), + Op.setData("/multi", "Y".getBytes(), 0), + Op.create("/foo", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) + ); + List results = null; + if (useAsync) { + final MultiResult res = new MultiResult(); + zk.multi(ops, new MultiCallback() { + @Override + public void processResult(int rc, String path, Object ctx, + List opResults) { + synchronized (res) { + res.rc = rc; + res.results = opResults; + res.finished = true; + res.notifyAll(); + } + } + }, null); + synchronized (res) { + while (!res.finished) { + res.wait(); + } + } + Assert.assertFalse("/multi should have been deleted so setData should have failed", + KeeperException.Code.OK.intValue() == res.rc); Assert.assertNull(zk.exists("/multi", null)); + results = res.results; + } else { + try { + zk.multi(ops); + Assert.fail("/multi should have been deleted so setData should have failed"); + } catch (KeeperException e) { + // '/multi' should never have been created as entire op should fail + Assert.assertNull(zk.exists("/multi", null)); + results = e.getResults(); + } + } - for (OpResult r : e.getResults()) { - LOG.info("RESULT==> " + r); - if (r instanceof ErrorResult) { - ErrorResult er = (ErrorResult) r; - LOG.info("ERROR RESULT: " + er + " ERR=>" + KeeperException.Code.get(er.getErr())); - } + Assert.assertNotNull(results); + for (OpResult r : results) { + LOG.info("RESULT==> " + r); + if (r instanceof ErrorResult) { + ErrorResult er = (ErrorResult) r; + LOG.info("ERROR RESULT: " + er + " ERR=>" + KeeperException.Code.get(er.getErr())); } } } + /** + * Exercise the equals methods of OpResult classes. + */ + @Test + public void testOpResultEquals() { + opEquals(new CreateResult("/foo"), + new CreateResult("/foo"), + new CreateResult("nope")); + + opEquals(new CheckResult(), + new CheckResult(), + null); + + opEquals(new SetDataResult(new Stat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)), + new SetDataResult(new Stat(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)), + new SetDataResult(new Stat(11, 12, 13, 14, 15, 16, 17, 18, 19, 110, 111))); + + opEquals(new ErrorResult(1), + new ErrorResult(1), + new ErrorResult(2)); + + opEquals(new DeleteResult(), + new DeleteResult(), + null); + + opEquals(new ErrorResult(1), + new ErrorResult(1), + new ErrorResult(2)); + } + + private void opEquals(OpResult expected, OpResult value, OpResult near) { + assertEquals(value, value); + assertFalse(value.equals(new Object())); + assertFalse(value.equals(near)); + assertFalse(value.equals(value instanceof CreateResult ? + new ErrorResult(1) : new CreateResult("nope2"))); + assertTrue(value.equals(expected)); + } + @Test + public void testWatchesTriggered() throws KeeperException, InterruptedException { + HasTriggeredWatcher watcher = new HasTriggeredWatcher(); + zk.getChildren("/", watcher); + multi(zk, Arrays.asList( + Op.create("/t", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), + Op.delete("/t", -1) + )); + assertTrue(watcher.triggered.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)); + } + @Test + public void testNoWatchesTriggeredForFailedMultiRequest() throws InterruptedException, KeeperException { + HasTriggeredWatcher watcher = new HasTriggeredWatcher(); + zk.getChildren("/", watcher); + try { + multi(zk, Arrays.asList( + Op.create("/t", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), + Op.delete("/nonexisting", -1) + )); + fail("expected previous multi op to fail!"); + } catch (KeeperException.NoNodeException e) { + // expected + } + SyncCallback cb = new SyncCallback(); + zk.sync("/", cb, null); + + // by waiting for the callback we're assured that the event queue is flushed + cb.done.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); + assertEquals(1, watcher.triggered.getCount()); + } + + @Test + public void testTransactionBuilder() throws Exception { + List results = commit(zk.transaction() + .create("/t1", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) + .create("/t1/child", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) + .create("/t2", null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL)); + assertEquals(3, results.size()); + for (OpResult r : results) { + CreateResult c = (CreateResult)r; + assertTrue(c.getPath().startsWith("/t")); + assertNotNull(c.toString()); + } + assertNotNull(zk.exists("/t1", false)); + assertNotNull(zk.exists("/t1/child", false)); + assertNotNull(zk.exists("/t2", false)); + + results = commit(zk.transaction() + .check("/t1", 0) + .check("/t1/child", 0) + .check("/t2", 0)); + assertEquals(3, results.size()); + for (OpResult r : results) { + CheckResult c = (CheckResult)r; + assertNotNull(c.toString()); + } + + try { + results = commit(zk.transaction() + .check("/t1", 0) + .check("/t1/child", 0) + .check("/t2", 1)); + fail(); + } catch (KeeperException.BadVersionException e) { + // expected + } + + results = commit(zk.transaction() + .check("/t1", 0) + .setData("/t1", new byte[0], 0)); + assertEquals(2, results.size()); + for (OpResult r : results) { + assertNotNull(r.toString()); + } + + try { + results = commit(zk.transaction() + .check("/t1", 1) + .setData("/t1", new byte[0], 2)); + fail(); + } catch (KeeperException.BadVersionException e) { + // expected + } + + results = commit(zk.transaction() + .check("/t1", 1) + .check("/t1/child", 0) + .check("/t2", 0)); + assertEquals(3, results.size()); + + results = commit(zk.transaction() + .delete("/t2", -1) + .delete("/t1/child", -1)); + assertEquals(2, results.size()); + for (OpResult r : results) { + DeleteResult d = (DeleteResult)r; + assertNotNull(d.toString()); + } + assertNotNull(zk.exists("/t1", false)); + assertNull(zk.exists("/t1/child", false)); + assertNull(zk.exists("/t2", false)); + } + + private static class HasTriggeredWatcher implements Watcher { + private final CountDownLatch triggered = new CountDownLatch(1); + + @Override + public void process(WatchedEvent event) { + triggered.countDown(); + } + } + private static class SyncCallback implements AsyncCallback.VoidCallback { + private final CountDownLatch done = new CountDownLatch(1); + + @Override + public void processResult(int rc, String path, Object ctx) { + done.countDown(); + } + } } diff --git a/src/java/test/org/apache/zookeeper/test/NonRecoverableErrorTest.java b/src/java/test/org/apache/zookeeper/test/NonRecoverableErrorTest.java new file mode 100644 index 00000000000..133d92075f3 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/NonRecoverableErrorTest.java @@ -0,0 +1,183 @@ +/** + * 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.test; + +import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.UUID; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.Assert; +import org.junit.Test; + +/** + * This class tests the non-recoverable error behavior of quorum server. + */ +public class NonRecoverableErrorTest extends QuorumPeerTestBase { + private static final String NODE_PATH = "/noLeaderIssue"; + + /** + * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2247. + * Test to verify that even after non recoverable error (error while + * writing transaction log), ZooKeeper is still available. + */ + @Test(timeout = 30000) + public void testZooKeeperServiceAvailableOnLeader() throws Exception { + int SERVER_COUNT = 3; + final int clientPorts[] = new int[SERVER_COUNT]; + StringBuilder sb = new StringBuilder(); + String server; + + for (int i = 0; i < SERVER_COUNT; i++) { + clientPorts[i] = PortAssignment.unique(); + server = "server." + i + "=127.0.0.1:" + PortAssignment.unique() + + ":" + PortAssignment.unique(); + sb.append(server + "\n"); + } + String currentQuorumCfgSection = sb.toString(); + MainThread mt[] = new MainThread[SERVER_COUNT]; + + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection); + mt[i].start(); + } + + // ensure server started + for (int i = 0; i < SERVER_COUNT; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], + CONNECTION_TIMEOUT)); + } + + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper("127.0.0.1:" + clientPorts[0], + ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + + String data = "originalData"; + zk.create(NODE_PATH, data.getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + + // get information of current leader + QuorumPeer leader = getLeaderQuorumPeer(mt); + assertNotNull("Leader must have been elected by now", leader); + + // inject problem in leader + FileTxnSnapLog snapLog = leader.getActiveServer().getTxnLogFactory(); + FileTxnSnapLog fileTxnSnapLogWithError = new FileTxnSnapLog( + snapLog.getDataDir(), snapLog.getSnapDir()) { + @Override + public void commit() throws IOException { + throw new IOException("Input/output error"); + } + }; + ZKDatabase originalZKDatabase = leader.getActiveServer() + .getZKDatabase(); + long leaderCurrentEpoch = leader.getCurrentEpoch(); + + ZKDatabase newDB = new ZKDatabase(fileTxnSnapLogWithError); + leader.getActiveServer().setZKDatabase(newDB); + + try { + // do create operation, so that injected IOException is thrown + zk.create(uniqueZnode(), data.getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + fail("IOException is expected due to error injected to transaction log commit"); + } catch (Exception e) { + // do nothing + } + + // resetting watcher so that this watcher can be again used to ensure + // that the zkClient is able to re-establish connection with the + // newly elected zookeeper quorum. + watcher.reset(); + waitForNewLeaderElection(leader, leaderCurrentEpoch); + + // ensure server started, give enough time, so that new leader election + // takes place + for (int i = 0; i < SERVER_COUNT; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], + CONNECTION_TIMEOUT)); + } + + // revert back the error + leader.getActiveServer().setZKDatabase(originalZKDatabase); + + // verify that now ZooKeeper service is up and running + leader = getLeaderQuorumPeer(mt); + assertNotNull("New leader must have been elected by now", leader); + + String uniqueNode = uniqueZnode(); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + String createNode = zk.create(uniqueNode, data.getBytes(), + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + // if node is created successfully then it means that ZooKeeper service + // is available + assertEquals("Failed to create znode", uniqueNode, createNode); + zk.close(); + // stop all severs + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i].shutdown(); + } + } + + private void waitForNewLeaderElection(QuorumPeer peer, + long leaderCurrentEpoch) throws IOException, InterruptedException { + LOG.info("Waiting for new LE cycle.."); + int count = 100; // giving a grace period of 10seconds + while (count > 0) { + if (leaderCurrentEpoch == peer.getCurrentEpoch()) { + Thread.sleep(100); + } + count--; + } + Assert.assertTrue("New LE cycle must have triggered", + leaderCurrentEpoch != peer.getCurrentEpoch()); + } + + private QuorumPeer getLeaderQuorumPeer(MainThread[] mt) { + for (int i = mt.length - 1; i >= 0; i--) { + QuorumPeer quorumPeer = mt[i].getQuorumPeer(); + if (null != quorumPeer + && ServerState.LEADING == quorumPeer.getPeerState()) { + return quorumPeer; + } + } + return null; + } + + private String uniqueZnode() { + UUID randomUUID = UUID.randomUUID(); + String node = NODE_PATH + "/" + randomUUID.toString(); + return node; + } +} diff --git a/src/java/test/org/apache/zookeeper/test/OOMTest.java b/src/java/test/org/apache/zookeeper/test/OOMTest.java index 808f7d1cb30..465df98380b 100644 --- a/src/java/test/org/apache/zookeeper/test/OOMTest.java +++ b/src/java/test/org/apache/zookeeper/test/OOMTest.java @@ -98,6 +98,7 @@ public void testOOM() throws IOException, InterruptedException, KeeperException hog.get(0)[0] = (byte) 1; f.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown("127.0.0.1:" + PORT, CONNECTION_TIMEOUT)); diff --git a/src/java/test/org/apache/zookeeper/test/OSMXBeanTest.java b/src/java/test/org/apache/zookeeper/test/OSMXBeanTest.java new file mode 100644 index 00000000000..ce21ab82539 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/OSMXBeanTest.java @@ -0,0 +1,70 @@ +/** + * 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.test; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.zookeeper.server.util.OSMXBean; + +public class OSMXBeanTest { + + private OSMXBean osMbean; + private Long ofdc = 0L; + private Long mfdc = 0L; + protected static final Logger LOG = LoggerFactory.getLogger(OSMXBeanTest.class); + + @Before + public void initialize() { + this.osMbean = new OSMXBean(); + Assert.assertNotNull("Could not initialize OSMXBean object!", osMbean); + } + + @Test + public final void testGetUnix() { + boolean isUnix = osMbean.getUnix(); + if (!isUnix) { + LOG.info("Running in a Windows system! Output won't be printed!"); + } else { + LOG.info("Running in a Unix or Linux system!"); + } + } + + @Test + public final void testGetOpenFileDescriptorCount() { + if (osMbean != null && osMbean.getUnix() == true) { + ofdc = osMbean.getOpenFileDescriptorCount(); + LOG.info("open fdcount is: " + ofdc); + } + Assert.assertFalse("The number of open file descriptor is negative",(ofdc < 0)); + } + + @Test + public final void testGetMaxFileDescriptorCount() { + if (osMbean != null && osMbean.getUnix() == true) { + mfdc = osMbean.getMaxFileDescriptorCount(); + LOG.info("max fdcount is: " + mfdc); + } + Assert.assertFalse("The max file descriptor number is negative",(mfdc < 0)); + } + +} diff --git a/src/java/test/org/apache/zookeeper/test/ObserverLETest.java b/src/java/test/org/apache/zookeeper/test/ObserverLETest.java new file mode 100644 index 00000000000..123ba0be0bd --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/ObserverLETest.java @@ -0,0 +1,76 @@ +/** + * 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.test; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.QuorumStats; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ObserverLETest extends ZKTestCase { + final QuorumBase qb = new QuorumBase(); + final ClientTest ct = new ClientTest(); + + @Before + public void establishThreeParticipantOneObserverEnsemble() throws Exception { + qb.setUp(true); + ct.hostPort = qb.hostPort; + ct.setUpAll(); + qb.s5.shutdown(); + } + + @After + public void shutdownQuorum() throws Exception { + ct.tearDownAll(); + qb.tearDown(); + } + + /** + * See ZOOKEEPER-1294. Confirms that an observer will not support the quorum + * of a leader by forming a 5-node, 2-observer ensemble (so quorum size is 2). + * When all but the leader and one observer are shut down, the leader should + * enter the 'looking' state, not stay in the 'leading' state. + */ + @Test + public void testLEWithObserver() throws Exception { + QuorumPeer leader = null; + for (QuorumPeer server : Arrays.asList(qb.s1, qb.s2, qb.s3)) { + if (server.getServerState().equals( + QuorumStats.Provider.FOLLOWING_STATE)) { + server.shutdown(); + assertTrue("Waiting for server down", ClientBase + .waitForServerDown("127.0.0.1:" + + server.getClientPort(), + ClientBase.CONNECTION_TIMEOUT)); + } else { + assertNull("More than one leader found", leader); + leader = server; + } + } + assertTrue("Leader is not in Looking state", ClientBase + .waitForServerState(leader, ClientBase.CONNECTION_TIMEOUT, + QuorumStats.Provider.LOOKING_STATE)); + } + +} diff --git a/src/java/test/org/apache/zookeeper/test/PurgeTxnTest.java b/src/java/test/org/apache/zookeeper/test/PurgeTxnTest.java deleted file mode 100644 index 8c4c9262771..00000000000 --- a/src/java/test/org/apache/zookeeper/test/PurgeTxnTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * 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.test; - -import java.io.File; -import java.util.List; - -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.PortAssignment; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZKTestCase; -import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.ZooDefs.Ids; -import org.apache.zookeeper.server.PurgeTxnLog; -import org.apache.zookeeper.server.ServerCnxnFactory; -import org.apache.zookeeper.server.SyncRequestProcessor; -import org.apache.zookeeper.server.ZooKeeperServer; -import org.apache.zookeeper.server.persistence.FileTxnSnapLog; -import org.junit.Assert; -import org.junit.Test; - -public class PurgeTxnTest extends ZKTestCase implements Watcher { - //private static final Logger LOG = LoggerFactory.getLogger(PurgeTxnTest.class); - private static String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); - private static final int CONNECTION_TIMEOUT = 3000; - /** - * test the purge - * @throws Exception an exception might be thrown here - */ - @Test - public void testPurge() throws Exception { - File tmpDir = ClientBase.createTmpDir(); - ClientBase.setupTestEnv(); - ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); - SyncRequestProcessor.setSnapCount(100); - final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); - ServerCnxnFactory f = ServerCnxnFactory.createFactory(PORT, -1); - f.startup(zks); - Assert.assertTrue("waiting for server being up ", - ClientBase.waitForServerUp(HOSTPORT,CONNECTION_TIMEOUT)); - ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); - try { - for (int i = 0; i< 2000; i++) { - zk.create("/invalidsnap-" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT); - } - } finally { - zk.close(); - } - f.shutdown(); - Assert.assertTrue("waiting for server to shutdown", - ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); - // now corrupt the snapshot - PurgeTxnLog.purge(tmpDir, tmpDir, 3); - FileTxnSnapLog snaplog = new FileTxnSnapLog(tmpDir, tmpDir); - List listLogs = snaplog.findNRecentSnapshots(4); - int numSnaps = 0; - for (File ff: listLogs) { - if (ff.getName().startsWith("snapshot")) { - numSnaps++; - } - } - Assert.assertTrue("exactly 3 snapshots ", (numSnaps == 3)); - } - - public void process(WatchedEvent event) { - // do nothing - } - -} diff --git a/src/java/test/org/apache/zookeeper/test/QuorumBase.java b/src/java/test/org/apache/zookeeper/test/QuorumBase.java index ac757ecd00d..49cff350f3c 100644 --- a/src/java/test/org/apache/zookeeper/test/QuorumBase.java +++ b/src/java/test/org/apache/zookeeper/test/QuorumBase.java @@ -20,8 +20,6 @@ import java.io.File; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.LinkedHashSet; @@ -35,10 +33,10 @@ import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.quorum.QuorumPeer.LearnerType; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.server.util.OSMXBean; import org.junit.Assert; import org.junit.Test; -import com.sun.management.UnixOperatingSystemMXBean; public class QuorumBase extends ClientBase { private static final Logger LOG = LoggerFactory.getLogger(QuorumBase.class); @@ -102,13 +100,10 @@ protected void setUp(boolean withObservers) throws Exception { startServers(withObservers); - OperatingSystemMXBean osMbean = - ManagementFactory.getOperatingSystemMXBean(); - if (osMbean != null && osMbean instanceof UnixOperatingSystemMXBean) { - UnixOperatingSystemMXBean unixos = - (UnixOperatingSystemMXBean)osMbean; + OSMXBean osMbean = new OSMXBean(); + if (osMbean.getUnix() == true) { LOG.info("Initial fdcount is: " - + unixos.getOpenFileDescriptorCount()); + + osMbean.getOpenFileDescriptorCount()); } LOG.info("Setup finished"); @@ -123,25 +118,20 @@ void startServers(boolean withObservers) throws Exception { int initLimit = 3; int syncLimit = 3; HashMap peers = new HashMap(); - peers.put(Long.valueOf(1), new QuorumServer(1, - new InetSocketAddress("127.0.0.1", port1 + 1000), - new InetSocketAddress("127.0.0.1", portLE1 + 1000), + peers.put(Long.valueOf(1), new QuorumServer(1, "127.0.0.1", port1 + 1000, + portLE1 + 1000, LearnerType.PARTICIPANT)); - peers.put(Long.valueOf(2), new QuorumServer(2, - new InetSocketAddress("127.0.0.1", port2 + 1000), - new InetSocketAddress("127.0.0.1", portLE2 + 1000), + peers.put(Long.valueOf(2), new QuorumServer(2, "127.0.0.1", port2 + 1000, + portLE2 + 1000, LearnerType.PARTICIPANT)); - peers.put(Long.valueOf(3), new QuorumServer(3, - new InetSocketAddress("127.0.0.1", port3 + 1000), - new InetSocketAddress("127.0.0.1", portLE3 + 1000), + peers.put(Long.valueOf(3), new QuorumServer(3, "127.0.0.1", port3 + 1000, + portLE3 + 1000, LearnerType.PARTICIPANT)); - peers.put(Long.valueOf(4), new QuorumServer(4, - new InetSocketAddress("127.0.0.1", port4 + 1000), - new InetSocketAddress("127.0.0.1", portLE4 + 1000), + peers.put(Long.valueOf(4), new QuorumServer(4, "127.0.0.1", port4 + 1000, + portLE4 + 1000, LearnerType.PARTICIPANT)); - peers.put(Long.valueOf(5), new QuorumServer(5, - new InetSocketAddress("127.0.0.1", port5 + 1000), - new InetSocketAddress("127.0.0.1", portLE5 + 1000), + peers.put(Long.valueOf(5), new QuorumServer(5, "127.0.0.1", port5 + 1000, + portLE5 + 1000, LearnerType.PARTICIPANT)); if (withObservers) { @@ -237,25 +227,20 @@ public void setupServer(int i) throws IOException { if(peers == null){ peers = new HashMap(); - peers.put(Long.valueOf(1), new QuorumServer(1, - new InetSocketAddress("127.0.0.1", port1 + 1000), - new InetSocketAddress("127.0.0.1", portLE1 + 1000), + peers.put(Long.valueOf(1), new QuorumServer(1, "127.0.0.1", port1 + 1000, + portLE1 + 1000, LearnerType.PARTICIPANT)); - peers.put(Long.valueOf(2), new QuorumServer(2, - new InetSocketAddress("127.0.0.1", port2 + 1000), - new InetSocketAddress("127.0.0.1", portLE2 + 1000), + peers.put(Long.valueOf(2), new QuorumServer(2, "127.0.0.1", port2 + 1000, + portLE2 + 1000, LearnerType.PARTICIPANT)); - peers.put(Long.valueOf(3), new QuorumServer(3, - new InetSocketAddress("127.0.0.1", port3 + 1000), - new InetSocketAddress("127.0.0.1", portLE3 + 1000), + peers.put(Long.valueOf(3), new QuorumServer(3, "127.0.0.1", port3 + 1000, + portLE3 + 1000, LearnerType.PARTICIPANT)); - peers.put(Long.valueOf(4), new QuorumServer(4, - new InetSocketAddress("127.0.0.1", port4 + 1000), - new InetSocketAddress("127.0.0.1", portLE4 + 1000), + peers.put(Long.valueOf(4), new QuorumServer(4, "127.0.0.1", port4 + 1000, + portLE4 + 1000, LearnerType.PARTICIPANT)); - peers.put(Long.valueOf(5), new QuorumServer(5, - new InetSocketAddress("127.0.0.1", port5 + 1000), - new InetSocketAddress("127.0.0.1", portLE5 + 1000), + peers.put(Long.valueOf(5), new QuorumServer(5, "127.0.0.1", port5 + 1000, + portLE5 + 1000, LearnerType.PARTICIPANT)); } @@ -291,13 +276,10 @@ public void setupServer(int i) throws IOException { public void tearDown() throws Exception { LOG.info("TearDown started"); - OperatingSystemMXBean osMbean = - ManagementFactory.getOperatingSystemMXBean(); - if (osMbean != null && osMbean instanceof UnixOperatingSystemMXBean) { - UnixOperatingSystemMXBean unixos = - (UnixOperatingSystemMXBean)osMbean; + OSMXBean osMbean = new OSMXBean(); + if (osMbean.getUnix() == true) { LOG.info("fdcount after test is: " - + unixos.getOpenFileDescriptorCount()); + + osMbean.getOpenFileDescriptorCount()); } shutdownServers(); @@ -331,9 +313,13 @@ public static void shutdown(QuorumPeer qp) { LOG.info("No election available to shutdown " + qp.getName()); } LOG.info("Waiting for " + qp.getName() + " to exit thread"); - qp.join(30000); + long readTimeout = qp.getTickTime() * qp.getInitLimit(); + long connectTimeout = qp.getTickTime() * qp.getSyncLimit(); + long maxTimeout = Math.max(readTimeout, connectTimeout); + maxTimeout = Math.max(maxTimeout, ClientBase.CONNECTION_TIMEOUT); + qp.join(maxTimeout * 2); if (qp.isAlive()) { - Assert.fail("QP failed to shutdown in 30 seconds: " + qp.getName()); + Assert.fail("QP failed to shutdown in " + (maxTimeout * 2) + " seconds: " + qp.getName()); } } catch (InterruptedException e) { LOG.debug("QP interrupted: " + qp.getName(), e); diff --git a/src/java/test/org/apache/zookeeper/test/QuorumTest.java b/src/java/test/org/apache/zookeeper/test/QuorumTest.java index 13d0e83e8e8..6f827ec7dba 100644 --- a/src/java/test/org/apache/zookeeper/test/QuorumTest.java +++ b/src/java/test/org/apache/zookeeper/test/QuorumTest.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.apache.zookeeper.AsyncCallback; import org.apache.zookeeper.CreateMode; @@ -151,8 +152,7 @@ public void processResult(int rc, String path, Object ctx, } }, null); } - ArrayList fhs = new ArrayList(leader.forwardingFollowers); - for(LearnerHandler f: fhs) { + for(LearnerHandler f : leader.getForwardingFollowers()) { f.getSocket().shutdownInput(); } for(int i = 0; i < 5000; i++) { @@ -293,173 +293,4 @@ ClientBase.CONNECTION_TIMEOUT, new DiscoWatcher(), zk.close(); } - /** - * See ZOOKEEPER-790 for details - * */ - @Test - public void testFollowersStartAfterLeader() throws Exception { - QuorumUtil qu = new QuorumUtil(1); - CountdownWatcher watcher = new CountdownWatcher(); - qu.startQuorum(); - - int index = 1; - while(qu.getPeer(index).peer.leader == null) - index++; - - ZooKeeper zk = new ZooKeeper( - "127.0.0.1:" + qu.getPeer((index == 1)?2:1).peer.getClientPort(), - ClientBase.CONNECTION_TIMEOUT, watcher); - watcher.waitForConnected(CONNECTION_TIMEOUT); - - // break the quorum - qu.shutdown(index); - - // try to reestablish the quorum - qu.start(index); - Assert.assertTrue("quorum reestablishment failed", - QuorumBase.waitForServerUp( - "127.0.0.1:" + qu.getPeer(2).clientPort, - CONNECTION_TIMEOUT)); - - for (int i = 0; i < 30; i++) { - try { - zk.create("/test", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT); - break; - } catch(KeeperException.ConnectionLossException e) { - Thread.sleep(1000); - } - // test fails if we still can't connect to the quorum after 30 seconds. - Assert.fail("client could not connect to reestablished quorum: giving up after 30+ seconds."); - } - - zk.close(); - } - - /** - * Tests if closeSession can be logged before a leader gets established, which - * could lead to a locked-out follower (see ZOOKEEPER-790). - * - * The test works as follows. It has a client connecting to a follower f and - * sending batches of 1,000 updates. The goal is that f has a zxid higher than - * all other servers in the initial leader election. This way we can crash and - * recover the follower so that the follower believes it is the leader once it - * recovers (LE optimization: once a server receives a message from all other - * servers, it picks a leader. - * - * It also makes the session timeout very short so that we force the false - * leader to close the session and write it to the log in the buggy code (before - * ZOOKEEPER-790). Once f drops leadership and finds the current leader, its epoch - * is higher, and it rejects the leader. Now, if we prevent the leader from closing - * the session by only starting up (see Leader.lead()) once it obtains a quorum of - * supporters, then f will find the current leader and support it because it won't - * have a highe epoch. - * - */ - @Test - public void testNoLogBeforeLeaderEstablishment () - throws IOException, InterruptedException, KeeperException{ - final Semaphore sem = new Semaphore(0); - - QuorumUtil qu = new QuorumUtil(2); - qu.startQuorum(); - - - int index = 1; - while(qu.getPeer(index).peer.leader == null) - index++; - - Leader leader = qu.getPeer(index).peer.leader; - - Assert.assertNotNull(leader); - - /* - * Reusing the index variable to select a follower to connect to - */ - index = (index == 1) ? 2 : 1; - - ZooKeeper zk = new DisconnectableZooKeeper("127.0.0.1:" + qu.getPeer(index).peer.getClientPort(), 1000, new Watcher() { - public void process(WatchedEvent event) { - }}); - - zk.create("/blah", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - - for(int i = 0; i < 50000; i++) { - zk.setData("/blah", new byte[0], -1, new AsyncCallback.StatCallback() { - public void processResult(int rc, String path, Object ctx, - Stat stat) { - counter++; - if (rc != 0) { - errors++; - } - if(counter == 20000){ - sem.release(); - } - } - }, null); - - if(i == 5000){ - qu.shutdown(index); - LOG.info("Shutting down s1"); - } - if(i == 12000){ - qu.start(index); - LOG.info("Setting up server: " + index); - } - if((i % 1000) == 0){ - Thread.sleep(500); - } - } - - // Wait until all updates return - sem.tryAcquire(15000, TimeUnit.MILLISECONDS); - - // Verify that server is following and has the same epoch as the leader - Assert.assertTrue("Not following", qu.getPeer(index).peer.follower != null); - long epochF = (qu.getPeer(index).peer.getActiveServer().getZxid() >> 32L); - long epochL = (leader.getEpoch() >> 32L); - Assert.assertTrue("Zxid: " + qu.getPeer(index).peer.getActiveServer().getZxid() + - "Current epoch: " + epochF, epochF == epochL); - - } - - // skip superhammer and clientcleanup as they are too expensive for quorum - - /** - * Tests if a multiop submitted to a non-leader propagates to the leader properly - * (see ZOOKEEPER-1124). - * - * The test works as follows. It has a client connect to a follower and submit a multiop - * to the follower. It then verifies that the multiop successfully gets committed by the leader. - * - * Without the fix in ZOOKEEPER-1124, this fails with a ConnectionLoss KeeperException. - */ - @Test - public void testMultiToFollower() throws Exception { - QuorumUtil qu = new QuorumUtil(1); - CountdownWatcher watcher = new CountdownWatcher(); - qu.startQuorum(); - - int index = 1; - while(qu.getPeer(index).peer.leader == null) - index++; - - ZooKeeper zk = new ZooKeeper( - "127.0.0.1:" + qu.getPeer((index == 1)?2:1).peer.getClientPort(), - ClientBase.CONNECTION_TIMEOUT, watcher); - watcher.waitForConnected(CONNECTION_TIMEOUT); - - List results = new ArrayList(); - - results = zk.multi(Arrays.asList( - Op.create("/multi0", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), - Op.create("/multi1", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), - Op.create("/multi2", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) - )); - zk.getData("/multi0", false, null); - zk.getData("/multi1", false, null); - zk.getData("/multi2", false, null); - - zk.close(); - } } diff --git a/src/java/test/org/apache/zookeeper/test/QuorumUtil.java b/src/java/test/org/apache/zookeeper/test/QuorumUtil.java index a68ebec27f2..1f2dbbc7ed8 100644 --- a/src/java/test/org/apache/zookeeper/test/QuorumUtil.java +++ b/src/java/test/org/apache/zookeeper/test/QuorumUtil.java @@ -20,8 +20,6 @@ import java.io.File; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.LinkedHashSet; @@ -35,10 +33,9 @@ import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.quorum.QuorumPeer.LearnerType; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.server.util.OSMXBean; import org.junit.Assert; -import com.sun.management.UnixOperatingSystemMXBean; - /** * Utility for quorum testing. Setups 2n+1 peers and allows to start/stop all * peers, particular peer, n peers etc. @@ -82,7 +79,7 @@ public class PeerStruct { * @param n * number of peers in the ensemble will be 2n+1 */ - public QuorumUtil(int n) throws RuntimeException { + public QuorumUtil(int n, int syncLimit) throws RuntimeException { try { ClientBase.setupTestEnv(); JMXEnv.setUp(); @@ -91,7 +88,7 @@ public QuorumUtil(int n) throws RuntimeException { ALL = 2 * N + 1; tickTime = 2000; initLimit = 3; - syncLimit = 3; + this.syncLimit = syncLimit; electionAlg = 3; hostPort = ""; @@ -102,9 +99,10 @@ public QuorumUtil(int n) throws RuntimeException { ps.clientPort = PortAssignment.unique(); peers.put(i, ps); - peersView.put(Long.valueOf(i), new QuorumServer(i, new InetSocketAddress( - "127.0.0.1", ps.clientPort + 1000), new InetSocketAddress("127.0.0.1", - PortAssignment.unique() + 1000), LearnerType.PARTICIPANT)); + peersView.put(Long.valueOf(i), + new QuorumServer(i, "127.0.0.1", ps.clientPort + 1000, + PortAssignment.unique() + 1000, + LearnerType.PARTICIPANT)); hostPort += "127.0.0.1:" + ps.clientPort + ((i == ALL) ? "" : ","); } for (int i = 1; i <= ALL; ++i) { @@ -119,6 +117,10 @@ public QuorumUtil(int n) throws RuntimeException { } } + public QuorumUtil(int n) throws RuntimeException { + this(n, 3); + } + public PeerStruct getPeer(int id) { return peers.get(id); } @@ -160,6 +162,8 @@ public void startAll() throws IOException { JMXEnv.ensureAll(ensureNames.toArray(new String[ensureNames.size()])); } catch (IOException e) { LOG.warn("IOException during JMXEnv operation", e); + } catch (InterruptedException e) { + LOG.warn("InterruptedException during JMXEnv operation", e); } } @@ -246,10 +250,9 @@ public String getConnString() { public void tearDown() throws Exception { LOG.info("TearDown started"); - OperatingSystemMXBean osMbean = ManagementFactory.getOperatingSystemMXBean(); - if (osMbean != null && osMbean instanceof UnixOperatingSystemMXBean) { - UnixOperatingSystemMXBean unixos = (UnixOperatingSystemMXBean) osMbean; - LOG.info("fdcount after test is: " + unixos.getOpenFileDescriptorCount()); + OSMXBean osMbean = new OSMXBean(); + if (osMbean.getUnix() == true) { + LOG.info("fdcount after test is: " + osMbean.getOpenFileDescriptorCount()); } shutdownAll(); diff --git a/src/java/test/org/apache/zookeeper/test/ReadOnlyModeTest.java b/src/java/test/org/apache/zookeeper/test/ReadOnlyModeTest.java index 0b4157f67ae..a99e4aead8e 100644 --- a/src/java/test/org/apache/zookeeper/test/ReadOnlyModeTest.java +++ b/src/java/test/org/apache/zookeeper/test/ReadOnlyModeTest.java @@ -34,6 +34,7 @@ import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.NotReadOnlyException; +import org.apache.zookeeper.Transaction; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.KeeperState; @@ -41,30 +42,78 @@ import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooKeeper.States; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.test.ClientBase.CountdownWatcher; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.slf4j.LoggerFactory; public class ReadOnlyModeTest extends ZKTestCase { + private static final org.slf4j.Logger LOG = LoggerFactory + .getLogger(ReadOnlyModeTest.class); private static int CONNECTION_TIMEOUT = QuorumBase.CONNECTION_TIMEOUT; private QuorumUtil qu = new QuorumUtil(1); @Before public void setUp() throws Exception { + System.setProperty("readonlymode.enabled", "true"); qu.startQuorum(); } @After public void tearDown() throws Exception { + System.setProperty("readonlymode.enabled", "false"); qu.tearDown(); } + /** + * Test write operations using multi request. + */ + @Test(timeout = 90000) + public void testMultiTransaction() throws Exception { + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zk = new ZooKeeper(qu.getConnString(), CONNECTION_TIMEOUT, + watcher, true); + watcher.waitForConnected(CONNECTION_TIMEOUT); // ensure zk got connected + + final String data = "Data to be read in RO mode"; + final String node1 = "/tnode1"; + final String node2 = "/tnode2"; + zk.create(node1, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + + watcher.reset(); + qu.shutdown(2); + watcher.waitForConnected(CONNECTION_TIMEOUT); + Assert.assertEquals("Should be in r-o mode", States.CONNECTEDREADONLY, + zk.getState()); + + // read operation during r/o mode + String remoteData = new String(zk.getData(node1, false, null)); + Assert.assertEquals("Failed to read data in r-o mode", data, remoteData); + + try { + Transaction transaction = zk.transaction(); + transaction.setData(node1, "no way".getBytes(), -1); + transaction.create(node2, data.getBytes(), + ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + transaction.commit(); + Assert.fail("Write operation using multi-transaction" + + " api has succeeded during RO mode"); + } catch (NotReadOnlyException e) { + // ok + } + + Assert.assertNull("Should have created the znode:" + node2, + zk.exists(node2, false)); + } + /** * Basic test of read-only client functionality. Tries to read and write * during read-only mode, then regains a quorum and tries to write again. */ - @Test + @Test(timeout = 90000) public void testReadOnlyClient() throws Exception { CountdownWatcher watcher = new CountdownWatcher(); ZooKeeper zk = new ZooKeeper(qu.getConnString(), CONNECTION_TIMEOUT, @@ -105,15 +154,11 @@ public void testReadOnlyClient() throws Exception { * Ensures that upon connection to a read-only server client receives * ConnectedReadOnly state notification. */ - @Test + @Test(timeout = 90000) public void testConnectionEvents() throws Exception { - final List states = new ArrayList(); + CountdownWatcher watcher = new CountdownWatcher(); ZooKeeper zk = new ZooKeeper(qu.getConnString(), CONNECTION_TIMEOUT, - new Watcher() { - public void process(WatchedEvent event) { - states.add(event.getState()); - } - }, true); + watcher, true); boolean success = false; for (int i = 0; i < 30; i++) { try { @@ -126,22 +171,20 @@ public void process(WatchedEvent event) { } } Assert.assertTrue("Did not succeed in connecting in 30s", success); + Assert.assertFalse("The connection should not be read-only yet", watcher.readOnlyConnected); // kill peer and wait no more than 5 seconds for read-only server // to be started (which should take one tickTime (2 seconds)) qu.shutdown(2); - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); while (!(zk.getState() == States.CONNECTEDREADONLY)) { Thread.sleep(200); - Assert.assertTrue("Can't connect to the server", System - .currentTimeMillis() - - start < 5000); + // FIXME this was originally 5 seconds, but realistically, on random/slow/virt hosts, there is no way to guarantee this + Assert.assertTrue("Can't connect to the server", + Time.currentElapsedTime() - start < 30000); } - // At this point states list should contain, in the given order, - // SyncConnected, Disconnected, and ConnectedReadOnly states - Assert.assertTrue("ConnectedReadOnly event wasn't received", states - .get(2) == KeeperState.ConnectedReadOnly); + watcher.waitForReadOnlyConnected(5000); zk.close(); } @@ -150,7 +193,7 @@ public void process(WatchedEvent event) { * then connects to a majority server. Transition should be transparent for * the user. */ - @Test + @Test(timeout = 90000) public void testSessionEstablishment() throws Exception { qu.shutdown(2); @@ -161,17 +204,29 @@ public void testSessionEstablishment() throws Exception { Assert.assertSame("should be in r/o mode", States.CONNECTEDREADONLY, zk .getState()); long fakeId = zk.getSessionId(); + LOG.info("Connected as r/o mode with state {} and session id {}", + zk.getState(), fakeId); watcher.reset(); qu.start(2); Assert.assertTrue("waiting for server up", ClientBase.waitForServerUp( "127.0.0.1:" + qu.getPeer(2).clientPort, CONNECTION_TIMEOUT)); - watcher.waitForConnected(CONNECTION_TIMEOUT); + LOG.info("Server 127.0.0.1:{} is up", qu.getPeer(2).clientPort); + // ZOOKEEPER-2722: wait until we can connect to a read-write server after the quorum + // is formed. Otherwise, it is possible that client first connects to a read-only server, + // then drops the connection because of shutting down of the read-only server caused + // by leader election / quorum forming between the read-only server and the newly started + // server. If we happen to execute the zk.create after the read-only server is shutdown and + // before the quorum is formed, we will get a ConnectLossException. + watcher.waitForSyncConnected(CONNECTION_TIMEOUT); + Assert.assertEquals("Should be in read-write mode", States.CONNECTED, + zk.getState()); + LOG.info("Connected as rw mode with state {} and session id {}", + zk.getState(), zk.getSessionId()); zk.create("/test", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); Assert.assertFalse("fake session and real session have same id", zk .getSessionId() == fakeId); - zk.close(); } @@ -180,7 +235,7 @@ public void testSessionEstablishment() throws Exception { * server. */ @SuppressWarnings("deprecation") - @Test + @Test(timeout = 90000) public void testSeekForRwServer() throws Exception { // setup the logger to capture all logs diff --git a/src/java/test/org/apache/zookeeper/test/RecoveryTest.java b/src/java/test/org/apache/zookeeper/test/RecoveryTest.java index 2127e98c93d..c084a68fcb3 100644 --- a/src/java/test/org/apache/zookeeper/test/RecoveryTest.java +++ b/src/java/test/org/apache/zookeeper/test/RecoveryTest.java @@ -105,6 +105,7 @@ public void testRecovery() throws Exception { } f.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); @@ -142,6 +143,7 @@ public void testRecovery() throws Exception { } } f.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, @@ -181,6 +183,7 @@ public void testRecovery() throws Exception { zk.close(); f.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, diff --git a/src/java/test/org/apache/zookeeper/test/RepeatStartupTest.java b/src/java/test/org/apache/zookeeper/test/RepeatStartupTest.java index 3421004f78f..f714a2caf13 100644 --- a/src/java/test/org/apache/zookeeper/test/RepeatStartupTest.java +++ b/src/java/test/org/apache/zookeeper/test/RepeatStartupTest.java @@ -60,6 +60,7 @@ public void testFail() throws Exception { ClientBase.waitForServerUp("127.0.0.1:" + PORT, QuorumTest.CONNECTION_TIMEOUT)); factory.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown("127.0.0.1:" + PORT, QuorumTest.CONNECTION_TIMEOUT)); diff --git a/src/java/test/org/apache/zookeeper/test/RestoreCommittedLogTest.java b/src/java/test/org/apache/zookeeper/test/RestoreCommittedLogTest.java index 9a9ff831386..65cc0e2f3f4 100644 --- a/src/java/test/org/apache/zookeeper/test/RestoreCommittedLogTest.java +++ b/src/java/test/org/apache/zookeeper/test/RestoreCommittedLogTest.java @@ -70,6 +70,7 @@ public void testRestoreCommittedLog() throws Exception { zk.close(); } f.shutdown(); + zks.shutdown(); Assert.assertTrue("waiting for server to shutdown", ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); @@ -80,6 +81,7 @@ public void testRestoreCommittedLog() throws Exception { int logsize = committedLog.size(); LOG.info("committedLog size = " + logsize); Assert.assertTrue("log size != 0", (logsize != 0)); + zks.shutdown(); } public void process(WatchedEvent event) { diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedClientTest.java b/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedClientTest.java new file mode 100644 index 00000000000..9e28278f5fc --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedClientTest.java @@ -0,0 +1,104 @@ +/** + * 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.test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.client.ZooKeeperSaslClient; +import org.junit.Assert; +import org.junit.Test; + +public class SaslAuthDesignatedClientTest extends ClientBase { + static { + System.setProperty("zookeeper.authProvider.1","org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); + System.setProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "MyZookeeperClient"); + + try { + File tmpDir = createTmpDir(); + File saslConfFile = new File(tmpDir, "jaas.conf"); + FileWriter fwriter = new FileWriter(saslConfFile); + + fwriter.write("" + + "Server {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " user_myuser=\"mypassword\";\n" + + "};\n" + + "Client {\n" + /* this 'Client' section has an incorrect password, but we're not configured + to use it (we're configured by the above System.setProperty(...LOGIN_CONTEXT_NAME_KEY...) to + use the 'MyZookeeperClient' section below, which has the correct password).*/ + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"myuser\"\n" + + " password=\"wrongpassword\";\n" + + "};" + + "MyZookeeperClient {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"myuser\"\n" + + " password=\"mypassword\";\n" + + "};" + "\n"); + fwriter.close(); + System.setProperty("java.security.auth.login.config",saslConfFile.getAbsolutePath()); + } + catch (IOException e) { + // could not create tmp directory to hold JAAS conf file : test will fail now. + } + } + + @Test + public void testAuth() throws Exception { + ZooKeeper zk = createClient(); + try { + zk.create("/path1", null, Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); + Thread.sleep(1000); + } catch (KeeperException e) { + Assert.fail("test failed :" + e); + } + finally { + zk.close(); + } + } + + @Test + public void testSaslConfig() throws Exception { + ZooKeeper zk = createClient(); + try { + zk.getChildren("/", false); + Assert.assertFalse(zk.getSaslClient(). + clientTunneledAuthenticationInProgress()); + Assert.assertEquals(zk.getSaslClient().getSaslState(), + ZooKeeperSaslClient.SaslState.COMPLETE); + Assert.assertNotNull( + javax.security.auth.login.Configuration.getConfiguration(). + getAppConfigurationEntry("MyZookeeperClient")); + Assert.assertSame(zk.getSaslClient().getLoginContext(), + "MyZookeeperClient"); + } catch (KeeperException e) { + Assert.fail("test failed :" + e); + } finally { + zk.close(); + } + } + + +} diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedServerTest.java b/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedServerTest.java new file mode 100644 index 00000000000..aa30870da28 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedServerTest.java @@ -0,0 +1,104 @@ +/** + * 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.test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.Watcher.Event.KeeperState; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.server.ZooKeeperSaslServer; +import org.apache.zookeeper.JaasConfiguration; +import org.junit.Assert; +import org.junit.Test; + +public class SaslAuthDesignatedServerTest extends ClientBase { + public static int AUTHENTICATION_TIMEOUT = 30000; + + static { + System.setProperty("zookeeper.authProvider.1","org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); + System.setProperty(ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY, "MyZookeeperServer"); + + JaasConfiguration conf = new JaasConfiguration(); + + /* this 'Server' section has an incorrect password, but we're not configured + * to use it (we're configured by the above System.setProperty(...LOGIN_CONTEXT_NAME_KEY...) + * to use the 'MyZookeeperServer' section below, which has the correct password). + */ + conf.addSection("Server", "org.apache.zookeeper.server.auth.DigestLoginModule", + "user_myuser", "wrongpassword"); + + conf.addSection("MyZookeeperServer", "org.apache.zookeeper.server.auth.DigestLoginModule", + "user_myuser", "mypassword"); + + conf.addSection("Client", "org.apache.zookeeper.server.auth.DigestLoginModule", + "username", "myuser", "password", "mypassword"); + + javax.security.auth.login.Configuration.setConfiguration(conf); + } + + private AtomicInteger authFailed = new AtomicInteger(0); + + private class MyWatcher extends CountdownWatcher { + volatile CountDownLatch authCompleted; + + @Override + synchronized public void reset() { + authCompleted = new CountDownLatch(1); + super.reset(); + } + + @Override + public synchronized void process(WatchedEvent event) { + if (event.getState() == KeeperState.AuthFailed) { + authFailed.incrementAndGet(); + authCompleted.countDown(); + } else if (event.getState() == KeeperState.SaslAuthenticated) { + authCompleted.countDown(); + } else { + super.process(event); + } + } + } + + @Test + public void testAuth() throws Exception { + MyWatcher watcher = new MyWatcher(); + ZooKeeper zk = createClient(watcher); + watcher.authCompleted.await(AUTHENTICATION_TIMEOUT, TimeUnit.MILLISECONDS); + Assert.assertEquals(authFailed.get(), 0); + + try { + zk.create("/path1", null, Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); + } catch (KeeperException e) { + Assert.fail("test failed :" + e); + } + finally { + zk.close(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthFailDesignatedClientTest.java b/src/java/test/org/apache/zookeeper/test/SaslAuthFailDesignatedClientTest.java new file mode 100644 index 00000000000..5291141cf8f --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SaslAuthFailDesignatedClientTest.java @@ -0,0 +1,108 @@ +/** + * 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.test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.Watcher.Event.KeeperState; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.client.ZooKeeperSaslClient; +import org.junit.Assert; +import org.junit.Test; + +public class SaslAuthFailDesignatedClientTest extends ClientBase { + static { + System.setProperty("zookeeper.authProvider.1","org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); + System.setProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "MyZookeeperClient"); + + try { + File tmpDir = createTmpDir(); + File saslConfFile = new File(tmpDir, "jaas.conf"); + FileWriter fwriter = new FileWriter(saslConfFile); + + fwriter.write("" + + "Server {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " user_myuser=\"mypassword\";\n" + + "};\n" + + "Client {\n" + /* this 'Client' section has the correct password, but we're not configured + to use it (we're configured by the above System.setProperty(...LOGIN_CONTEXT_NAME_KEY...) to + use the 'MyZookeeperClient' section, which has an incorrect password).*/ + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"myuser\"\n" + + " password=\"mypassword\";\n" + + "};" + + "MyZookeeperClient {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"myuser\"\n" + + " password=\"wrongpassword\";\n" + + "};" + "\n"); + fwriter.close(); + System.setProperty("java.security.auth.login.config",saslConfFile.getAbsolutePath()); + } + catch (IOException e) { + // could not create tmp directory to hold JAAS conf file : test will fail now. + } + } + + private AtomicInteger authFailed = new AtomicInteger(0); + + private class MyWatcher extends CountdownWatcher { + @Override + public synchronized void process(WatchedEvent event) { + if (event.getState() == KeeperState.AuthFailed) { + authFailed.incrementAndGet(); + } + else { + super.process(event); + } + } + } + + @Test + public void testAuth() throws Exception { + // Cannot use createClient here because server may close session before + // JMXEnv.ensureAll is called which will fail the test case + CountdownWatcher watcher = new CountdownWatcher(); + TestableZooKeeper zk = new TestableZooKeeper(hostPort, CONNECTION_TIMEOUT, watcher); + if (!watcher.clientConnected.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)) + { + Assert.fail("Unable to connect to server"); + } + try { + zk.create("/path1", null, Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); + Assert.fail("Should have gotten exception."); + } catch (KeeperException e) { + // ok, exception as expected. + LOG.info("Got exception as expected: " + e); + } + finally { + zk.close(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthFailNotifyTest.java b/src/java/test/org/apache/zookeeper/test/SaslAuthFailNotifyTest.java new file mode 100644 index 00000000000..2b00d862dfe --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SaslAuthFailNotifyTest.java @@ -0,0 +1,98 @@ +/** + * 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.test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.Watcher.Event.KeeperState; +import org.apache.zookeeper.ZooDefs.Ids; +import org.junit.Test; +import org.junit.Assert; + +public class SaslAuthFailNotifyTest extends ClientBase { + static { + System.setProperty("zookeeper.authProvider.1","org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); + System.setProperty("zookeeper.allowSaslFailedClients","true"); + + try { + File tmpDir = createTmpDir(); + File saslConfFile = new File(tmpDir, "jaas.conf"); + FileWriter fwriter = new FileWriter(saslConfFile); + + fwriter.write("" + + "Server {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " user_super=\"test\";\n" + + "};\n" + + "Client {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"super\"\n" + + " password=\"test1\";\n" + // NOTE: wrong password ('test' != 'test1') : this is to test SASL authentication failure. + "};" + "\n"); + fwriter.close(); + System.setProperty("java.security.auth.login.config",saslConfFile.getAbsolutePath()); + } + catch (IOException e) { + // could not create tmp directory to hold JAAS conf file. + } + } + + private AtomicInteger authFailed = new AtomicInteger(0); + + @Override + protected TestableZooKeeper createClient(String hp) + throws IOException, InterruptedException + { + MyWatcher watcher = new MyWatcher(); + return createClient(watcher, hp); + } + + private class MyWatcher extends CountdownWatcher { + @Override + public synchronized void process(WatchedEvent event) { + if (event.getState() == KeeperState.AuthFailed) { + synchronized(authFailed) { + authFailed.incrementAndGet(); + authFailed.notify(); + } + } + else { + super.process(event); + } + } + } + + @Test + public void testBadSaslAuthNotifiesWatch() throws Exception { + ZooKeeper zk = createClient(); + // wait for authFailed event from client's EventThread. + synchronized(authFailed) { + authFailed.wait(); + } + Assert.assertEquals(authFailed.get(),1); + zk.close(); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthFailTest.java b/src/java/test/org/apache/zookeeper/test/SaslAuthFailTest.java index 8de7c2a120d..33a505ea2a7 100644 --- a/src/java/test/org/apache/zookeeper/test/SaslAuthFailTest.java +++ b/src/java/test/org/apache/zookeeper/test/SaslAuthFailTest.java @@ -59,57 +59,10 @@ public class SaslAuthFailTest extends ClientBase { // could not create tmp directory to hold JAAS conf file. } } - - private AtomicInteger authFailed = new AtomicInteger(0); - - @Override - protected TestableZooKeeper createClient(String hp) - throws IOException, InterruptedException - { - File tmpDir = ClientBase.createTmpDir(); - File saslConfFile = new File(tmpDir, "jaas_bad_password.conf"); - FileWriter fwriter = new FileWriter(saslConfFile); - - fwriter.write("" + - "Server {\n" + - " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + - " user_super=\"test\";\n" + - "};\n" + - "Client {\n" + - " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + - " username=\"super\"\n" + - " password=\"test1\";\n" + // NOTE: wrong password to cause authentication failure : 'test' != 'test1'. - "};" + "\n"); - fwriter.close(); - System.setProperty("java.security.auth.login.config",saslConfFile.getAbsolutePath()); - MyWatcher watcher = new MyWatcher(); - return createClient(watcher, hp); - } - - private class MyWatcher extends CountdownWatcher { - @Override - public synchronized void process(WatchedEvent event) { - if (event.getState() == KeeperState.AuthFailed) { - authFailed.incrementAndGet(); - } - else { - super.process(event); - } - } - } - - @Test - public void testBadSaslAuthNotifiesWatch() throws Exception { - ZooKeeper zk = createClient(); - Thread.sleep(1000); - zk.close(); - } - @Test public void testAuthFail() throws Exception { ZooKeeper zk = createClient(); - Thread.sleep(1000); try { zk.create("/path1", null, Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); Assert.fail("Should have gotten exception."); diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthMissingClientConfigTest.java b/src/java/test/org/apache/zookeeper/test/SaslAuthMissingClientConfigTest.java new file mode 100644 index 00000000000..98be0be3386 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SaslAuthMissingClientConfigTest.java @@ -0,0 +1,97 @@ +/** + * 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.test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.Watcher.Event.KeeperState; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.client.ZooKeeperSaslClient; +import org.junit.Assert; +import org.junit.Test; + +public class SaslAuthMissingClientConfigTest extends ClientBase { + static { + System.setProperty("zookeeper.authProvider.1","org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); + // This configuration section 'MyZookeeperClient', is missing from the JAAS configuration. + // As a result, SASL authentication should fail, which is tested by this test (testAuth()). + System.setProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "MyZookeeperClient"); + + try { + File tmpDir = createTmpDir(); + File saslConfFile = new File(tmpDir, "jaas.conf"); + FileWriter fwriter = new FileWriter(saslConfFile); + + fwriter.write("" + + "Server {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " user_myuser=\"mypassword\";\n" + + "};\n" + + "Client {\n" + /* this 'Client' section has the correct password, but we're not configured + to use it - we're configured instead by the above + System.setProperty(...LOGIN_CONTEXT_NAME_KEY...) to + use the (nonexistent) 'MyZookeeperClient' section. */ + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"myuser\"\n" + + " password=\"mypassword\";\n" + + "};\n"); + fwriter.close(); + System.setProperty("java.security.auth.login.config",saslConfFile.getAbsolutePath()); + } + catch (IOException e) { + // could not create tmp directory to hold JAAS conf file : test will fail now. + } + } + + private AtomicInteger authFailed = new AtomicInteger(0); + + private class MyWatcher extends CountdownWatcher { + @Override + public synchronized void process(WatchedEvent event) { + if (event.getState() == KeeperState.AuthFailed) { + authFailed.incrementAndGet(); + } + else { + super.process(event); + } + } + } + + @Test + public void testAuth() throws Exception { + ZooKeeper zk = createClient(); + try { + zk.create("/path1", null, Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); + Assert.fail("Should have gotten exception."); + } catch (KeeperException e) { + // ok, exception as expected. + LOG.info("Got exception as expected: " + e); + } + finally { + zk.close(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/test/SaslClientTest.java b/src/java/test/org/apache/zookeeper/test/SaslClientTest.java new file mode 100644 index 00000000000..8213abca455 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SaslClientTest.java @@ -0,0 +1,62 @@ +/** + * 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.test; + +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.client.ZooKeeperSaslClient; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +public class SaslClientTest extends ZKTestCase { + + private String existingPropertyValue = null; + + @Before + public void setUp() { + existingPropertyValue = System.getProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY); + } + + @After + public void tearDown() { + // Restore the System property if it was set previously + if (existingPropertyValue != null) { + System.setProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY, existingPropertyValue); + } + } + + @Test + public void testSaslClientDisabled() { + System.clearProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY); + Assert.assertTrue("SASL client disabled", ZooKeeperSaslClient.isEnabled()); + + for (String value : Arrays.asList("true", "TRUE")) { + System.setProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY, value); + Assert.assertTrue("SASL client disabled", ZooKeeperSaslClient.isEnabled()); + } + + for (String value : Arrays.asList("false", "FALSE")) { + System.setProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY, value); + Assert.assertFalse("SASL client disabled", ZooKeeperSaslClient.isEnabled()); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/test/SessionInvalidationTest.java b/src/java/test/org/apache/zookeeper/test/SessionInvalidationTest.java new file mode 100644 index 00000000000..3a0b2ee6018 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SessionInvalidationTest.java @@ -0,0 +1,105 @@ +/** + * 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.test; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import junit.framework.Assert; + +import org.apache.jute.BinaryOutputArchive; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.proto.ConnectRequest; +import org.apache.zookeeper.proto.CreateRequest; +import org.apache.zookeeper.proto.RequestHeader; +import org.junit.Test; + +public class SessionInvalidationTest extends ClientBase { + /** + * Test solution for ZOOKEEPER-1208. Verify that operations are not + * accepted after a close session. + * + * We're using our own marshalling here in order to force an operation + * after the session is closed (ZooKeeper.class will not allow this). Also + * by filling the pipe with operations it increases the likelyhood that + * the server will process the create before FinalRequestProcessor + * removes the session from the tracker. + */ + @Test + public void testCreateAfterCloseShouldFail() throws Exception { + for (int i = 0; i < 10; i++) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos); + + // open a connection + boa.writeInt(44, "len"); + ConnectRequest conReq = new ConnectRequest(0, 0, 30000, 0, new byte[16]); + conReq.serialize(boa, "connect"); + + // close connection + boa.writeInt(8, "len"); + RequestHeader h = new RequestHeader(1, ZooDefs.OpCode.closeSession); + h.serialize(boa, "header"); + + // create ephemeral znode + boa.writeInt(52, "len"); // We'll fill this in later + RequestHeader header = new RequestHeader(2, OpCode.create); + header.serialize(boa, "header"); + CreateRequest createReq = new CreateRequest("/foo" + i, new byte[0], + Ids.OPEN_ACL_UNSAFE, 1); + createReq.serialize(boa, "request"); + baos.close(); + + System.out.println("Length:" + baos.toByteArray().length); + + String hp[] = hostPort.split(":"); + Socket sock = new Socket(hp[0], Integer.parseInt(hp[1])); + InputStream resultStream = null; + try { + OutputStream outstream = sock.getOutputStream(); + byte[] data = baos.toByteArray(); + outstream.write(data); + outstream.flush(); + + resultStream = sock.getInputStream(); + byte[] b = new byte[10000]; + int len; + while ((len = resultStream.read(b)) >= 0) { + // got results + System.out.println("gotlen:" + len); + } + } finally { + if (resultStream != null) { + resultStream.close(); + } + sock.close(); + } + } + + ZooKeeper zk = createClient(); + Assert.assertEquals(1, zk.getChildren("/", false).size()); + + zk.close(); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/SessionTest.java b/src/java/test/org/apache/zookeeper/test/SessionTest.java index df9bd547c31..0740ebbd1e9 100644 --- a/src/java/test/org/apache/zookeeper/test/SessionTest.java +++ b/src/java/test/org/apache/zookeeper/test/SessionTest.java @@ -57,6 +57,7 @@ public class SessionTest extends ZKTestCase { PortAssignment.unique(); private ServerCnxnFactory serverFactory; + private ZooKeeperServer zs; private CountDownLatch startSignal; @@ -71,7 +72,7 @@ public void setUp() throws Exception { } ClientBase.setupTestEnv(); - ZooKeeperServer zs = new ZooKeeperServer(tmpDir, tmpDir, TICK_TIME); + zs = new ZooKeeperServer(tmpDir, tmpDir, TICK_TIME); final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); serverFactory = ServerCnxnFactory.createFactory(PORT, -1); @@ -85,6 +86,7 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { serverFactory.shutdown(); + zs.shutdown(); Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); diff --git a/src/java/test/org/apache/zookeeper/test/StaticHostProviderTest.java b/src/java/test/org/apache/zookeeper/test/StaticHostProviderTest.java index fa0128cd24d..8414be97887 100644 --- a/src/java/test/org/apache/zookeeper/test/StaticHostProviderTest.java +++ b/src/java/test/org/apache/zookeeper/test/StaticHostProviderTest.java @@ -21,20 +21,30 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.client.HostProvider; import org.apache.zookeeper.client.StaticHostProvider; +import org.apache.zookeeper.common.Time; import org.junit.Test; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Collection; +import java.util.Random; public class StaticHostProviderTest extends ZKTestCase { - + private static final Logger LOG = LoggerFactory.getLogger(StaticHostProviderTest.class); + @Test - public void testNextGoesRound() throws UnknownHostException { - HostProvider hostProvider = getHostProvider(2); + public void testNextGoesRound() { + HostProvider hostProvider = getHostProvider((byte) 2); InetSocketAddress first = hostProvider.next(0); assertTrue(first instanceof InetSocketAddress); hostProvider.next(0); @@ -42,55 +52,107 @@ public void testNextGoesRound() throws UnknownHostException { } @Test - public void testNextGoesRoundAndSleeps() throws UnknownHostException { - int size = 2; + public void testNextGoesRoundAndSleeps() { + byte size = 2; HostProvider hostProvider = getHostProvider(size); while (size > 0) { hostProvider.next(0); --size; } - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); hostProvider.next(1000); - long stop = System.currentTimeMillis(); + long stop = Time.currentElapsedTime(); assertTrue(900 <= stop - start); } @Test - public void testNextDoesNotSleepForZero() throws UnknownHostException { - int size = 2; + public void testNextDoesNotSleepForZero() { + byte size = 2; HostProvider hostProvider = getHostProvider(size); while (size > 0) { hostProvider.next(0); --size; } - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); hostProvider.next(0); - long stop = System.currentTimeMillis(); - assertTrue(10000 > stop - start); + long stop = Time.currentElapsedTime(); + assertTrue(5 > stop - start); } @Test public void testTwoConsequitiveCallsToNextReturnDifferentElement() throws UnknownHostException { - HostProvider hostProvider = getHostProvider(2); + HostProvider hostProvider = getHostProvider((byte) 2); assertNotSame(hostProvider.next(0), hostProvider.next(0)); } @Test - public void testOnConnectDoesNotReset() throws UnknownHostException { - HostProvider hostProvider = getHostProvider(2); + public void testOnConnectDoesNotReset() { + HostProvider hostProvider = getHostProvider((byte) 2); InetSocketAddress first = hostProvider.next(0); hostProvider.onConnected(); InetSocketAddress second = hostProvider.next(0); assertNotSame(first, second); } - private StaticHostProvider getHostProvider(int size) - throws UnknownHostException { + @Test + public void testLiteralIPNoReverseNS() throws Exception { + byte size = 30; + HostProvider hostProvider = getHostProviderUnresolved(size); + for (int i = 0; i < size; i++) { + InetSocketAddress next = hostProvider.next(0); + assertTrue(next instanceof InetSocketAddress); + assertTrue(!next.isUnresolved()); + assertTrue("Did not match "+ next.toString(), !next.toString().startsWith("/")); + // Do NOT trigger the reverse name service lookup. + String hostname = next.getHostName(); + // In this case, the hostname equals literal IP address. + hostname.equals(next.getAddress().getHostAddress()); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testTwoInvalidHostAddresses() { + ArrayList list = new ArrayList(); + list.add(new InetSocketAddress("a", 2181)); + list.add(new InetSocketAddress("b", 2181)); + new StaticHostProvider(list); + } + + public void testOneInvalidHostAddresses() { + Collection addr = getUnresolvedServerAddresses((byte) 1); + addr.add(new InetSocketAddress("a", 2181)); + + StaticHostProvider sp = new StaticHostProvider(addr); + InetSocketAddress n1 = sp.next(0); + InetSocketAddress n2 = sp.next(0); + + assertEquals(n2, n1); + } + + private StaticHostProvider getHostProviderUnresolved(byte size) { + return new StaticHostProvider(getUnresolvedServerAddresses(size)); + } + + private Collection getUnresolvedServerAddresses(byte size) { + ArrayList list = new ArrayList(size); + while (size > 0) { + list.add(InetSocketAddress.createUnresolved("10.10.10." + size, 1234 + size)); + --size; + } + return list; + } + + private StaticHostProvider getHostProvider(byte size) { ArrayList list = new ArrayList( size); while (size > 0) { - list.add(new InetSocketAddress("10.10.10." + size, 1234)); + try { + list.add(new InetSocketAddress(InetAddress.getByAddress(new byte[]{10, 10, 10, size}), 1234 + size)); + } catch (UnknownHostException e) { + LOG.error("Exception while resolving address", e); + fail("Failed to resolve address"); + } --size; } return new StaticHostProvider(list); diff --git a/src/java/test/org/apache/zookeeper/test/TestHammer.java b/src/java/test/org/apache/zookeeper/test/TestHammer.java index 09a678b28c1..a73d6df35df 100644 --- a/src/java/test/org/apache/zookeeper/test/TestHammer.java +++ b/src/java/test/org/apache/zookeeper/test/TestHammer.java @@ -24,6 +24,7 @@ import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.AsyncCallback.VoidCallback; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.common.Time; public class TestHammer implements VoidCallback { @@ -32,7 +33,7 @@ public class TestHammer implements VoidCallback { */ static int REPS = 50000; public static void main(String[] args) { - long startTime = System.currentTimeMillis(); + long startTime = Time.currentElapsedTime(); ZooKeeper zk = null; try { zk = new ZooKeeper(args[0], 10000, null); @@ -51,7 +52,7 @@ public static void main(String[] args) { e.printStackTrace(); } } - System.out.println("creates/sec=" + (REPS*1000/(System.currentTimeMillis()-startTime))); + System.out.println("creates/sec=" + (REPS*1000/(Time.currentElapsedTime()-startTime))); } public void processResult(int rc, String path, Object ctx) { diff --git a/src/java/test/org/apache/zookeeper/test/TruncateTest.java b/src/java/test/org/apache/zookeeper/test/TruncateTest.java index 4800ce54c4d..66ff63d0ffa 100644 --- a/src/java/test/org/apache/zookeeper/test/TruncateTest.java +++ b/src/java/test/org/apache/zookeeper/test/TruncateTest.java @@ -23,30 +23,39 @@ import java.net.InetSocketAddress; import java.util.HashMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import junit.framework.Assert; + +import org.apache.jute.Record; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.Request; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.persistence.FileTxnLog; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.persistence.TxnLog.TxnIterator; import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.txn.SetDataTxn; +import org.apache.zookeeper.txn.TxnHeader; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class TruncateTest extends ZKTestCase { private static final Logger LOG = LoggerFactory.getLogger(TruncateTest.class); File dataDir1, dataDir2, dataDir3; - final int baseHostPort = 12233; + final int baseHostPort = PortAssignment.unique(); @Before public void setUp() throws IOException { @@ -69,12 +78,103 @@ public void process(WatchedEvent event) { connected = event.getState() == Watcher.Event.KeeperState.SyncConnected; } }; + + @Test + public void testTruncationStreamReset() throws Exception { + File tmpdir = ClientBase.createTmpDir(); + FileTxnSnapLog snaplog = new FileTxnSnapLog(tmpdir, tmpdir); + ZKDatabase zkdb = new ZKDatabase(snaplog); + + for (int i = 1; i <= 100; i++) { + append(zkdb, i); + } + + zkdb.truncateLog(1); + + append(zkdb, 200); + + zkdb.close(); + + // verify that the truncation and subsequent append were processed + // correctly + FileTxnLog txnlog = new FileTxnLog(new File(tmpdir, "version-2")); + TxnIterator iter = txnlog.read(1); + + TxnHeader hdr = iter.getHeader(); + Record txn = iter.getTxn(); + Assert.assertEquals(1, hdr.getZxid()); + Assert.assertTrue(txn instanceof SetDataTxn); + + iter.next(); + + hdr = iter.getHeader(); + txn = iter.getTxn(); + Assert.assertEquals(200, hdr.getZxid()); + Assert.assertTrue(txn instanceof SetDataTxn); + iter.close(); + ClientBase.recursiveDelete(tmpdir); + } + + private void append(ZKDatabase zkdb, int i) throws IOException { + TxnHeader hdr = new TxnHeader(1, 1, i, 1, ZooDefs.OpCode.setData); + Record txn = new SetDataTxn("/foo" + i, new byte[0], 1); + Request req = new Request(null, 0, 0, 0, null, null); + req.hdr = hdr; + req.txn = txn; + + zkdb.append(req); + zkdb.commit(); + } + + + @Test + public void testTruncationNullLog() throws Exception { + File tmpdir = ClientBase.createTmpDir(); + FileTxnSnapLog snaplog = new FileTxnSnapLog(tmpdir, tmpdir); + ZKDatabase zkdb = new ZKDatabase(snaplog); + + for (int i = 1; i <= 100; i++) { + append(zkdb, i); + } + zkdb.close(); + File[] logs = snaplog.getDataDir().listFiles(); + for(int i = 0; i < logs.length; i++) { + LOG.debug("Deleting: {}", logs[i].getName()); + Assert.assertTrue("Failed to delete log file: " + logs[i].getName(), logs[i].delete()); + } + try { + zkdb.truncateLog(1); + Assert.assertTrue("Should not get here", false); + } + catch(IOException e) { + Assert.assertTrue("Should have received an IOException", true); + } + catch(NullPointerException npe) { + Assert.fail("This should not throw NPE!"); + } + + ClientBase.recursiveDelete(tmpdir); + } @Test public void testTruncate() throws IOException, InterruptedException, KeeperException { // Prime the server that is going to come in late with 50 txns - ServerCnxnFactory factory = ClientBase.createNewServerInstance(dataDir1, null, "127.0.0.1:" + baseHostPort, 100); - ZooKeeper zk = new ZooKeeper("127.0.0.1:" + baseHostPort, 15000, nullWatcher); + String hostPort = "127.0.0.1:" + baseHostPort; + int maxCnxns = 100; + ServerCnxnFactory factory = ClientBase.createNewServerInstance(null, + hostPort, maxCnxns); + ClientBase.startServerInstance(dataDir1, factory, hostPort); + ClientBase.shutdownServerInstance(factory, hostPort); + + // standalone starts with 0 epoch while quorum starts with 1 + File origfile = new File(new File(dataDir1, "version-2"), "snapshot.0"); + File newfile = new File(new File(dataDir1, "version-2"), "snapshot.100000000"); + origfile.renameTo(newfile); + + factory = ClientBase.createNewServerInstance(null, hostPort, maxCnxns); + ClientBase.startServerInstance(dataDir1, factory, hostPort); + + ZooKeeper zk = new ZooKeeper(hostPort, 15000, nullWatcher); for(int i = 0; i < 50; i++) { zk.create("/" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } @@ -101,9 +201,9 @@ public void testTruncate() throws IOException, InterruptedException, KeeperExcep // Start up two of the quorum and add 10 txns HashMap peers = new HashMap(); - peers.put(Long.valueOf(1), new QuorumServer(1, new InetSocketAddress("127.0.0.1", port1 + 1000))); - peers.put(Long.valueOf(2), new QuorumServer(2, new InetSocketAddress("127.0.0.1", port2 + 1000))); - peers.put(Long.valueOf(3), new QuorumServer(3, new InetSocketAddress("127.0.0.1", port3 + 1000))); + peers.put(Long.valueOf(1), new QuorumServer(1, "127.0.0.1", port1 + 1000, 0, null)); + peers.put(Long.valueOf(2), new QuorumServer(2, "127.0.0.1", port2 + 1000, 0, null)); + peers.put(Long.valueOf(3), new QuorumServer(3, "127.0.0.1", port3 + 1000, 0, null)); QuorumPeer s2 = new QuorumPeer(peers, dataDir2, dataDir2, port2, 0, 2, tickTime, initLimit, syncLimit); s2.start(); @@ -137,11 +237,13 @@ public void testTruncate() throws IOException, InterruptedException, KeeperExcep } zk1.getData("/9", false, new Stat()); try { - // 10 wont work because the session expiration - // will match the zxid for 10 and so we wont - // actually truncate the zxid for 10 creation - // but for 11 we will for sure - zk1.getData("/11", false, new Stat()); + // /10 wont work because the session expiration + // will match the zxid for /10 and so we wont + // actually truncate the zxid for /10 creation + // due to an artifact of switching the xid of the standalone + // /11 is the last entry in the log for the xid + // as a result /12 is the first of the truncated znodes to check for + zk1.getData("/12", false, new Stat()); Assert.fail("Should have gotten an error"); } catch(KeeperException.NoNodeException e) { // this is what we want diff --git a/src/java/test/org/apache/zookeeper/test/WatchEventWhenAutoReset.java b/src/java/test/org/apache/zookeeper/test/WatchEventWhenAutoReset.java new file mode 100644 index 00000000000..eed02c5845b --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/WatchEventWhenAutoReset.java @@ -0,0 +1,212 @@ +/** + * 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.test; + +import java.io.IOException; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.Watcher.Event.EventType; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import junit.framework.TestCase; + +public class WatchEventWhenAutoReset extends TestCase { + protected static final Logger LOG = LoggerFactory + .getLogger(WatchEventWhenAutoReset.class); + + // waiting time for expected condition + private static final int TIMEOUT = 30000; + + static public class EventsWatcher extends CountdownWatcher { + private LinkedBlockingQueue dataEvents = new LinkedBlockingQueue(); + + @Override + public void process(WatchedEvent event) { + super.process(event); + try { + if (event.getType() != Event.EventType.None) { + dataEvents.put(event); + } + } catch (InterruptedException e) { + LOG.warn("ignoring interrupt during EventsWatcher process"); + } + } + + public void assertEvent(long timeout, EventType eventType) { + try { + WatchedEvent event = dataEvents.poll(timeout, + TimeUnit.MILLISECONDS); + Assert.assertNotNull("do not receive a " + eventType, event); + Assert.assertEquals(eventType, event.getType()); + } catch (InterruptedException e) { + LOG.warn("ignoring interrupt during EventsWatcher assertEvent"); + } + } + } + + private ZooKeeper createClient(QuorumUtil qu, int id, EventsWatcher watcher) + throws IOException { + String hostPort = "127.0.0.1:" + qu.getPeer(id).clientPort; + ZooKeeper zk = new ZooKeeper(hostPort, TIMEOUT, watcher); + try { + watcher.waitForConnected(TIMEOUT); + } catch (InterruptedException e) { + // ignoring the interrupt + } catch (TimeoutException e) { + fail("can not connect to " + hostPort); + } + return zk; + } + + private ZooKeeper createClient(QuorumUtil qu, int id) throws IOException { + return createClient(qu, id, new EventsWatcher()); + } + + @Test + public void testNodeDataChanged() throws Exception { + QuorumUtil qu = new QuorumUtil(1); + qu.startAll(); + + EventsWatcher watcher = new EventsWatcher(); + ZooKeeper zk1 = createClient(qu, 1, watcher); + ZooKeeper zk2 = createClient(qu, 2); + + String path = "/test-changed"; + + zk1.create(path, new byte[1], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk1.getData(path, watcher, null); + qu.shutdown(1); + zk2.delete(path, -1); + zk2.create(path, new byte[2], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + qu.start(1); + watcher.waitForConnected(TIMEOUT); + watcher.assertEvent(TIMEOUT, EventType.NodeDataChanged); + + zk1.exists(path, watcher); + qu.shutdown(1); + zk2.delete(path, -1); + zk2.create(path, new byte[2], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + qu.start(1); + watcher.waitForConnected(TIMEOUT * 1000L); + watcher.assertEvent(TIMEOUT, EventType.NodeDataChanged); + + qu.shutdownAll(); + } + + @Test + public void testNodeCreated() throws Exception { + QuorumUtil qu = new QuorumUtil(1); + qu.startAll(); + + EventsWatcher watcher = new EventsWatcher(); + ZooKeeper zk1 = createClient(qu, 1, watcher); + ZooKeeper zk2 = createClient(qu, 2); + + String path = "/test1-created"; + + zk1.exists(path, watcher); + qu.shutdown(1); + zk2.create(path, new byte[2], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + qu.start(1); + watcher.waitForConnected(TIMEOUT * 1000L); + watcher.assertEvent(TIMEOUT, EventType.NodeCreated); + + qu.shutdownAll(); + } + + @Test + public void testNodeDeleted() throws Exception { + QuorumUtil qu = new QuorumUtil(1); + qu.startAll(); + + EventsWatcher watcher = new EventsWatcher(); + ZooKeeper zk1 = createClient(qu, 1, watcher); + ZooKeeper zk2 = createClient(qu, 2); + + String path = "/test-deleted"; + + zk1.create(path, new byte[1], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk1.getData(path, watcher, null); + qu.shutdown(1); + zk2.delete(path, -1); + qu.start(1); + watcher.waitForConnected(TIMEOUT * 1000L); + watcher.assertEvent(TIMEOUT, EventType.NodeDeleted); + + zk1.create(path, new byte[1], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk1.exists(path, watcher); + qu.shutdown(1); + zk2.delete(path, -1); + qu.start(1); + watcher.waitForConnected(TIMEOUT * 1000L); + watcher.assertEvent(TIMEOUT, EventType.NodeDeleted); + + zk1.create(path, new byte[1], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk1.getChildren(path, watcher); + qu.shutdown(1); + zk2.delete(path, -1); + qu.start(1); + watcher.waitForConnected(TIMEOUT * 1000L); + watcher.assertEvent(TIMEOUT, EventType.NodeDeleted); + + qu.shutdownAll(); + } + + @Test + public void testNodeChildrenChanged() throws Exception { + QuorumUtil qu = new QuorumUtil(1); + qu.startAll(); + + EventsWatcher watcher = new EventsWatcher(); + ZooKeeper zk1 = createClient(qu, 1, watcher); + ZooKeeper zk2 = createClient(qu, 2); + + String path = "/test-children-changed"; + + zk1.create(path, new byte[1], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk1.getChildren(path, watcher); + qu.shutdown(1); + zk2.create(path + "/children-1", new byte[2], + ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + qu.start(1); + watcher.waitForConnected(TIMEOUT * 1000L); + watcher.assertEvent(TIMEOUT, EventType.NodeChildrenChanged); + + qu.shutdownAll(); + } +} + diff --git a/src/java/test/org/apache/zookeeper/test/WatcherTest.java b/src/java/test/org/apache/zookeeper/test/WatcherTest.java index 90ec5136ae4..1c06690cd51 100644 --- a/src/java/test/org/apache/zookeeper/test/WatcherTest.java +++ b/src/java/test/org/apache/zookeeper/test/WatcherTest.java @@ -39,11 +39,14 @@ import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.Stat; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; public class WatcherTest extends ClientBase { protected static final Logger LOG = LoggerFactory.getLogger(WatcherTest.class); + private long timeOfLastWatcherInvocation; + private final class MyStatCallback implements StatCallback { int rc; public void processResult(int rc, String path, Object ctx, Stat stat) { @@ -59,6 +62,7 @@ private class MyWatcher extends CountdownWatcher { public void process(WatchedEvent event) { super.process(event); if (event.getType() != Event.EventType.None) { + timeOfLastWatcherInvocation = System.currentTimeMillis(); try { events.put(event); } catch (InterruptedException e) { @@ -68,6 +72,14 @@ public void process(WatchedEvent event) { } } + @Before + public void setUp() throws Exception { + super.setUp(); + // Reset to default value since some test cases set this to true. + // Needed for JDK7 since unit test can run is random order + ClientCnxn.setDisableAutoResetWatch(false); + } + /** * Verify that we get all of the events we expect to get. This particular * case verifies that we see all of the data events on a particular node. @@ -164,7 +176,6 @@ public void testWatcherCount() } final static int COUNT = 100; - boolean hasSeenDelete = true; /** * This test checks that watches for pending requests do not get triggered, * but watches set by previous requests do. @@ -186,8 +197,7 @@ public void testWatchAutoResetWithPending() throws Exception { zk.exists("/test", watches[i], cbs[i], count); } zk.exists("/test", false); - zk.pauseCnxn(3000); - Thread.sleep(50); + Assert.assertTrue("Failed to pause the connection!", zk.pauseCnxn(3000)); zk2.close(); stopServer(); watches[0].waitForDisconnected(60000); @@ -199,7 +209,7 @@ public void testWatchAutoResetWithPending() throws Exception { startServer(); watches[COUNT/2-1].waitForConnected(60000); Assert.assertEquals(null, zk.exists("/test", false)); - Thread.sleep(10); + waitForAllWatchers(); for(int i = 0; i < COUNT/2; i++) { Assert.assertEquals("For " + i, 1, watches[i].events.size()); } @@ -213,6 +223,18 @@ public void testWatchAutoResetWithPending() throws Exception { Assert.assertEquals(COUNT, count[0]); zk.close(); } + + /** + * Wait until no watcher has been fired in the last second to ensure that all watches + * that are waiting to be fired have been fired + * @throws Exception + */ + private void waitForAllWatchers() throws Exception { + timeOfLastWatcherInvocation = System.currentTimeMillis(); + while (System.currentTimeMillis() - timeOfLastWatcherInvocation < 1000) { + Thread.sleep(1000); + } + } final int TIMEOUT = 5000; diff --git a/src/java/test/org/apache/zookeeper/test/ZkDatabaseCorruptionTest.java b/src/java/test/org/apache/zookeeper/test/ZkDatabaseCorruptionTest.java index d8f4138123e..c213b2a26f4 100644 --- a/src/java/test/org/apache/zookeeper/test/ZkDatabaseCorruptionTest.java +++ b/src/java/test/org/apache/zookeeper/test/ZkDatabaseCorruptionTest.java @@ -21,7 +21,9 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import java.util.Arrays; +import org.apache.zookeeper.AsyncCallback; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; @@ -71,6 +73,13 @@ private void corruptAllSnapshots(File snapDir) throws IOException { } } + private class NoopStringCallback implements AsyncCallback.StringCallback { + @Override + public void processResult(int rc, String path, Object ctx, + String name) { + } + } + @Test public void testCorruption() throws Exception { ClientBase.waitForServerUp(qb.hostPort, 10000); @@ -80,52 +89,72 @@ public void process(WatchedEvent event) { }}); SyncRequestProcessor.setSnapCount(100); for (int i = 0; i < 2000; i++) { - zk.create("/0-" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.create("/0-" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT, new NoopStringCallback(), null); } zk.close(); - QuorumPeer leader; + + long leaderSid = 1; + QuorumPeer leader = null; //find out who is the leader and kill it - if ( qb.s5.getPeerState() != ServerState.LEADING) { - throw new Exception("the last server is not the leader"); + for (QuorumPeer quorumPeer : Arrays.asList(qb.s1, qb.s2, qb.s3, qb.s4, qb.s5)) { + if (quorumPeer.getPeerState() == ServerState.LEADING) { + leader = quorumPeer; + break; + } + ++leaderSid; } - leader = qb.s5; - // now corrupt the qurompeer database + + Assert.assertNotNull("Cannot find the leader.", leader); + leader.shutdown(); + + // now corrupt the leader's database FileTxnSnapLog snapLog = leader.getTxnFactory(); File snapDir= snapLog.getSnapDir(); //corrupt all the snapshot in the snapshot directory corruptAllSnapshots(snapDir); qb.shutdownServers(); qb.setupServers(); - qb.s1.start(); - qb.s2.start(); - qb.s3.start(); - qb.s4.start(); + + if (leaderSid != 1)qb.s1.start(); else leader = qb.s1; + if (leaderSid != 2)qb.s2.start(); else leader = qb.s2; + if (leaderSid != 3)qb.s3.start(); else leader = qb.s3; + if (leaderSid != 4)qb.s4.start(); else leader = qb.s4; + if (leaderSid != 5)qb.s5.start(); else leader = qb.s5; + try { - qb.s5.start(); + leader.start(); Assert.assertTrue(false); } catch(RuntimeException re) { LOG.info("Got an error: expected", re); } - //waut for servers to be up + //wait for servers to be up String[] list = qb.hostPort.split(","); - for (int i =0; i < 4; i++) { - String hp = list[i]; - Assert.assertTrue("waiting for server up", - ClientBase.waitForServerUp(hp, - CONNECTION_TIMEOUT)); - LOG.info(hp + " is accepting client connections"); + for (int i = 0; i < 5; i++) { + if(leaderSid != (i + 1)) { + String hp = list[i]; + Assert.assertTrue("waiting for server up", + ClientBase.waitForServerUp(hp, + CONNECTION_TIMEOUT)); + LOG.info("{} is accepting client connections", hp); + } else { + LOG.info("Skipping the leader"); + } } zk = qb.createClient(); SyncRequestProcessor.setSnapCount(100); for (int i = 2000; i < 4000; i++) { - zk.create("/0-" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.create("/0-" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT, new NoopStringCallback(), null); } zk.close(); - QuorumBase.shutdown(qb.s1); - QuorumBase.shutdown(qb.s2); - QuorumBase.shutdown(qb.s3); - QuorumBase.shutdown(qb.s4); + + if (leaderSid != 1)QuorumBase.shutdown(qb.s1); + if (leaderSid != 2)QuorumBase.shutdown(qb.s2); + if (leaderSid != 3)QuorumBase.shutdown(qb.s3); + if (leaderSid != 4)QuorumBase.shutdown(qb.s4); + if (leaderSid != 5)QuorumBase.shutdown(qb.s5); } diff --git a/src/java/test/org/apache/zookeeper/test/ZooKeeperTestClient.java b/src/java/test/org/apache/zookeeper/test/ZooKeeperTestClient.java index 67ca52faed2..0bbba61d5d9 100644 --- a/src/java/test/org/apache/zookeeper/test/ZooKeeperTestClient.java +++ b/src/java/test/org/apache/zookeeper/test/ZooKeeperTestClient.java @@ -32,6 +32,7 @@ import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.data.Stat; import org.junit.Assert; @@ -40,7 +41,7 @@ public class ZooKeeperTestClient extends ZKTestCase implements Watcher { protected static final String dirOnZK = "/test_dir"; - protected String testDirOnZK = dirOnZK + "/" + System.currentTimeMillis(); + protected String testDirOnZK = dirOnZK + "/" + Time.currentElapsedTime(); LinkedBlockingQueue events = new LinkedBlockingQueue(); diff --git a/src/lastRevision.bat b/src/lastRevision.bat index e31a6b96c34..68999471db6 100644 --- a/src/lastRevision.bat +++ b/src/lastRevision.bat @@ -16,8 +16,7 @@ rem See the License for the specific language governing permissions and rem limitations under the License. rem Find the current revision, store it in a file, for DOS -svn info | findstr Revision > %1 -For /F "tokens=1,2 delims= " %%a In (%1) Do ( - echo lastRevision=%%b> %1 +for /f "delims=" %%i in ('git rev-parse HEAD') do set rev=%%i + echo lastRevision=%rev% > %1 ) diff --git a/src/lastRevision.sh b/src/lastRevision.sh index a462990e742..0690c7da916 100755 --- a/src/lastRevision.sh +++ b/src/lastRevision.sh @@ -16,6 +16,6 @@ # Find the current revision, store it in a file FILE=$1 -LASTREV=`svn info | grep '^Revision' | sed -e 's/Revision: *//'` +LASTREV=`git rev-parse HEAD` echo "lastRevision=${LASTREV}" > $FILE diff --git a/src/packages/deb/init.d/zookeeper b/src/packages/deb/init.d/zookeeper index 1c7b4f0472a..d0f7216deed 100644 --- a/src/packages/deb/init.d/zookeeper +++ b/src/packages/deb/init.d/zookeeper @@ -1,4 +1,4 @@ -#! /bin/sh +#! /usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -34,7 +34,17 @@ umask 022 . /lib/lsb/init-functions -ZOOPIDFILE=/var/lib/zookeeper/data/zookeeper_server.pid +ZOOPIDDIR=/var/lib/zookeeper/data +ZOOPIDFILE=${ZOOPIDDIR}/zookeeper_server.pid + +check_privsep_dir() { + # Create the PrivSep empty dir if necessary + if [ ! -d ${ZOOPIDDIR} ]; then + mkdir -p ${ZOOPIDDIR} + chown zookeeper:hadoop ${ZOOPIDDIR} + chmod 0775 ${ZOOPIDDIR} + fi +} # Are we running from init? run_by_init() { @@ -59,6 +69,7 @@ export PATH="${PATH:+$PATH:}/usr/sbin:/usr/bin" case "$1" in start) check_for_no_start + check_privsep_dir log_daemon_msg "Starting Apache ZooKeeper server" "zookeeper" if start-stop-daemon --start --quiet --oknodo --pidfile ${ZOOPIDFILE} -c zookeeper -x ${ZOOKEEPER_PREFIX}/sbin/zkServer.sh start; then log_end_msg 0 @@ -80,7 +91,7 @@ case "$1" in log_daemon_msg "Restarting Apache ZooKeeper server" "zookeeper" start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile ${ZOOPIDFILE} check_for_no_start log_end_msg - if start-stop-daemon --start --quiet --oknodo --pidfile ${ZOOPIDFILE} -c zookeeper -x ${ZOOKEEPER_PREFIX}/bin/zkServer.sh start; then + if start-stop-daemon --start --quiet --oknodo --pidfile ${ZOOPIDFILE} -c zookeeper -x ${ZOOKEEPER_PREFIX}/sbin/zkServer.sh start; then log_end_msg 0 else log_end_msg 1 @@ -98,7 +109,7 @@ case "$1" in 0) # old daemon stopped check_for_no_start log_end_msg - if start-stop-daemon --start --quiet --oknodo --pidfile ${ZOOPIDFILE} -c zookeeper -x ${ZOOKEEPER_PREFIX}/bin/zkServer.sh start; then + if start-stop-daemon --start --quiet --oknodo --pidfile ${ZOOPIDFILE} -c zookeeper -x ${ZOOKEEPER_PREFIX}/sbin/zkServer.sh start; then log_end_msg 0 else log_end_msg 1 diff --git a/src/packages/rpm/spec/zookeeper.spec b/src/packages/rpm/spec/zookeeper.spec index fa1333c3f3a..8e195c7165e 100644 --- a/src/packages/rpm/spec/zookeeper.spec +++ b/src/packages/rpm/spec/zookeeper.spec @@ -70,7 +70,6 @@ Prefix: %{_conf_dir} Prefix: %{_log_dir} Prefix: %{_pid_dir} Prefix: %{_var_dir} -Buildroot: %{_build_dir} Requires: sh-utils, textutils, /usr/sbin/useradd, /usr/sbin/usermod, /sbin/chkconfig, /sbin/service, jdk >= 1.6 AutoReqProv: no Provides: zookeeper diff --git a/src/pom.template b/src/pom.template new file mode 100644 index 00000000000..a02c0b3d235 --- /dev/null +++ b/src/pom.template @@ -0,0 +1,41 @@ +SKIP_LINE *************************************************************** +SKIP_LINE * Licensed to the Apache Software Foundation (ASF) under one +SKIP_LINE * or more contributor license agreements. See the NOTICE file +SKIP_LINE * distributed with this work for additional information +SKIP_LINE * regarding copyright ownership. The ASF licenses this file +SKIP_LINE * to you under the Apache License, Version 2.0 (the +SKIP_LINE * "License"); you may not use this file except in compliance +SKIP_LINE * with the License. You may obtain a copy of the License at +SKIP_LINE * +SKIP_LINE * http://www.apache.org/licenses/LICENSE-2.0 +SKIP_LINE * +SKIP_LINE * Unless required by applicable law or agreed to in writing, +SKIP_LINE * software distributed under the License is distributed on an +SKIP_LINE * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +SKIP_LINE * KIND, either express or implied. See the License for the +SKIP_LINE * specific language governing permissions and limitations +SKIP_LINE * under the License. +SKIP_LINE *************************************************************** + +${ivy.pom.license} +${ivy.pom.header} + + + 4.0.0 + ${ivy.pom.groupId} + ${ivy.pom.artifactId} + ${ivy.pom.packaging} + ${ivy.pom.version} + ${ivy.pom.name} + ${ivy.pom.description} + ${ivy.pom.url} + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + diff --git a/src/recipes/build-recipes.xml b/src/recipes/build-recipes.xml index bafaf63891f..470f593482f 100644 --- a/src/recipes/build-recipes.xml +++ b/src/recipes/build-recipes.xml @@ -111,7 +111,7 @@ @@ -122,11 +122,11 @@ - - + - + diff --git a/src/recipes/lock/build.xml b/src/recipes/lock/build.xml index fd187497360..841cc018145 100644 --- a/src/recipes/lock/build.xml +++ b/src/recipes/lock/build.xml @@ -112,14 +112,14 @@ - + - - + + - - + + diff --git a/src/recipes/queue/build.xml b/src/recipes/queue/build.xml index 0c2e8755f5a..0f3505ab9ad 100644 --- a/src/recipes/queue/build.xml +++ b/src/recipes/queue/build.xml @@ -112,14 +112,14 @@ - + - - + + - - + + diff --git a/src/zookeeper.jute b/src/zookeeper.jute index 6521e54cd1c..27a5a7ed44f 100644 --- a/src/zookeeper.jute +++ b/src/zookeeper.jute @@ -220,6 +220,11 @@ module org.apache.zookeeper.server.quorum { buffer data; // Only significant when type is request vector authinfo; } + class QuorumAuthPacket { + long magic; + int status; + buffer token; + } } module org.apache.zookeeper.server.persistence {