diff --git a/.gitignore b/.gitignore index 32b90a5dc95..0cbf40d712d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,64 @@ +# 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 @@ -13,3 +68,15 @@ 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 2f7418a9d8c..00000000000 --- a/CHANGES.txt +++ /dev/null @@ -1,2742 +0,0 @@ -Release 3.5.0 - 8/4/2014 - -NEW FEATURES: - ZOOKEEPER-1355. Add zk.updateServerList(newServerList) (Alex Shraer, Marshall McMullen via fpj) - - ZOOKEEPER-1572. Add an async (Java) interface for multi request (Sijie Guo via camille) - - ZOOKEEPER-107. Allow dynamic changes to server cluster membership (Alex Shraer via breed) - - ZOOKEEPER-1400. Allow logging via callback instead of raw FILE pointer - (Marshall McMullen via michim) - - ZOOKEEPER-1147. Add support for local sessions (Jay Shrauner, thawan via thawan) - - ZOOKEEPER-1691. Add a flag to disable standalone mode (Helen Hastings via michim) - - ZOOKEEPER-442. need a way to remove watches that are no longer of - interest (Rakesh R, Daniel Gómez Ferro via phunt) - - ZOOKEEPER-1830. Support command line shell for removing watches - (Rakesh R via michim) - - ZOOKEEPER-1887. C implementation of removeWatches (Raul Gutierrez Segales via - michim) - - ZOOKEEPER-1928. add configurable throttling to the number of snapshots - concurrently sent by a leader (Edward Carter via fpj) - - ZOOKEEPER-827. enable r/o mode in C client library (rgs via fpj) - - ZOOKEEPER-1346. Add Jetty HTTP server support for four letter words. - (Skye Wanderman-Milne, Bill Havanki via phunt) - -BUGFIXES: - - ZOOKEEPER-1992. Backward compatibility of the static configuration file (Hongchao Deng via Alex Shraer). - - ZOOKEEPER-1900. NullPointerException in truncate (Camille Fournier) - - ZOOKEEPER-786. Exception in ZooKeeper.toString - (Thomas Koch via phunt) - - ZOOKEEPER-1191. Synchronization issue - wait not in guarded block (Alex Shraer via breed) - - ZOOKEEPER-1192. Leader.waitForEpochAck() checks waitingForNewEpoch instead of checking electionFinished (Alex Shraer via breed) - - ZOOKEEPER-1203. Zookeeper systest is missing Junit Classes - (Prashant Gokhale via phunt) - - ZOOKEEPER-1174. FD leak when network unreachable (Ted Dunning via camille) - - ZOOKEEPER-1206. Sequential node creation does not use always use - digits in node name given certain Locales. (Mark Miller via phunt) - - ZOOKEEPER-1212. zkServer.sh stop action is not conformat with LSB - para 20.2 Init Script Actions (Roman Shaposhnik via phunt) - - ZOOKEEPER-1190. ant package is not including many of the bin scripts - in the package (zkServer.sh for example) (Eric Yang via phunt) - - ZOOKEEPER-1241. Typo in ZooKeeper Recipes and Solutions - documentation (Jingguo Yao via phunt) - - ZOOKEEPER-1220. ./zkCli.sh 'create' command is throwing - ArrayIndexOutOfBoundsException (kavita sharma via phunt) - - ZOOKEEPER-1256. ClientPortBindTest is failing on Mac OS X - (Daniel Gómez Ferro via phunt) - - ZOOKEEPER-1264. FollowerResyncConcurrencyTest failing intermittently. (phunt via camille) - - ZOOKEEPER-1246. Dead code in PrepRequestProcessor catch Exception block. (camille) - - ZOOKEEPER-1271. testEarlyLeaderAbandonment failing on solaris - - clients not retrying connection (mahadev via phunt) - - ZOOKEEPER-1264. FollowerResyncConcurrencyTest failing - intermittently. (breed, camille and Alex Shraer via camille) - - 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 (breed via camille) - - ZOOKEEPER-1291. AcceptedEpoch not updated at leader before it proposes the epoch to followers. (Alex Shraer via camille) - - ZOOKEEPER-1208. Ephemeral node not removed after the client session is long gone. (phunt via camille) - - ZOOKEEPER-1239. add logging/stats to identify fsync stalls. (phunt via camille) - - ZOOKEEPER-1311. ZooKeeper test jar is broken (Ivan Kelly via phunt) - - ZOOKEEPER-1305. zookeeper.c:prepend_string func can dereference null ptr. - (Daniel Lescohier via mahadev) - - ZOOKEEPER-1262. Documentation for Lock recipe has major flaw. - (Jordan Zimmerman via mahadev) - - ZOOKEEPER-1316. zookeeper_init leaks memory if chroot is just '/'. - (Akira Kitada via mahadev) - - ZOOKEEPER-1315. zookeeper_init always reports sessionPasswd=. - (Akira Kitada via mahadev) - - ZOOKEEPER-1317. Possible segfault in zookeeper_init. (Akira Kitada via mahadev) - - ZOOKEEPER-1319. Missing data after restarting+expanding a cluster. - (phunt and breed via mahadev) - - ZOOKEEPER-1269. Multi deserialization issues. (Camille Fournier via mahadev) - - ZOOKEEPER-1323. c client doesn't compile on freebsd - (michi mutsuzaki via phunt) - - ZOOKEEPER-1333. NPE in FileTxnSnapLog when restarting a cluster. - (Patrick Hunt via mahadev) - - ZOOKEEPER-1331. Typo in docs: acheive -> achieve (Andrew Ash via phunt) - - ZOOKEEPER-1089. zkServer.sh status does not work due to invalid - option of nc (Roman Shaposhnik via phunt) - - ZOOKEEPER-1343. getEpochToPropose should check if lastAcceptedEpoch is greater or equal than epoch (fpj via breed) - - ZOOKEEPER-1050. zooinspector shell scripts do not work - (Will Johnson via phunt) - - ZOOKEEPER-1294. One of the zookeeper server is not accepting any requests (Kavita Sharma via henryr) - - ZOOKEEPER-1358. In StaticHostProviderTest.java, testNextDoesNotSleepForZero tests that hostProvider.next(0) - doesn't sleep by checking that the latency of this call is less than 10sec (Alex Shraer via camille) - - ZOOKEEPER-1351. invalid test verification in MultiTransactionTest (phunt via camille) - - ZOOKEEPER-973. bind() could fail on Leader because it does not - setReuseAddress on its ServerSocket (Harsh J via phunt) - - ZOOKEEPER-1367. Data inconsistencies and unexpired ephemeral nodes after cluster restart. - (Benjamin Reed via mahadev) - - ZOOKEEPER-1353. C client test suite fails consistently. (Clint Byrum - via mahadev) - - ZOOKEEPER-1373. Hardcoded SASL login context name clashes with Hadoop security - configuration override. (Eugene Koontz and Thomas Weise via mahadev) - - ZOOKEEPER-1352. server.InvalidSnapshotTest is using connection timeouts - that are too short. (phunt via mahadev) - - ZOOKEEPER-1336. javadoc for multi is confusing, references functionality that doesn't seem - to exist. (phunt via mahadev) - - ZOOKEEPER-1327. there are still remnants of hadoop urls. (Harsh J via mahadev) - - ZOOKEEPER-1340. multi problem - typical user operations are generating ERROR level - messages in the server (phunt via mahadev) - - ZOOKEEPER-1374. C client multi-threaded test suite fails to compile - on ARM architectures. (James Page via mahadev) - - ZOOKEEPER-1337. multi's "Transaction" class is missing tests. (camille - and phunt via mahadev) - - ZOOKEEPER-1338. class cast exceptions may be thrown by multi ErrorResult - class (invalid equals) (phunt via mahadev) - - 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/" (Eugene Koontz via camille) - - ZOOKEEPER-1361. Leader.lead iterates over 'learners' set without proper synchronisation (henryr via camille) - - ZOOKEEPER-1277. servers stop serving when lower 32bits of zxid roll - over (phunt) - - ZOOKEEPER-1412. java client watches inconsistently triggered on - reconnect (phunt) - - ZOOKEEPER-1344. ZooKeeper client multi-update command is not - considering the Chroot request (Rakesh R via phunt) - - ZOOKEEPER-1307. zkCli.sh is exiting when an Invalid ACL exception is - thrown from setACL command through client (Kavita Sharma via phunt) - - ZOOKEEPER-1390. some expensive debug code not protected by a check - for debug (breed via camille) - - ZOOKEEPER-1406. dpkg init scripts don't restart - missing - check_priv_sep_dir (Chris Beauchamp via phunt) - - ZOOKEEPER-1403. zkCli.sh script quoting issue (James Page via phunt) - - ZOOKEEPER-1384. test-cppunit overrides LD_LIBRARY_PATH and fails if - gcc is in non-standard location (Jay Shrauner via phunt) - - ZOOKEEPER-1419. Leader election never settles for a 5-node cluster (flavio via camille) - - ZOOKEEPER-1433. improve ZxidRolloverTest (test seems flakey) (phunt via henryr) - - ZOOKEEPER-1395. node-watcher double-free redux (Mike Lundy via henryr) - - ZOOKEEPER-1439. c sdk: core in log_env for lack of checking the output - argument *pwp* of getpwuid_r (Yubing Yin via michim) - - ZOOKEEPER-1339. C client doesn't build with --enable-debug - (Eric Liang via michim) - - ZOOKEEPER-1048. addauth command does not work in cli_mt/cli_st - (allengao via michim) - - ZOOKEEPER-1318. In Python binding, get_children (and get and exists, and probably others) - with expired session doesn't raise exception properly (henryr via michim) - - ZOOKEEPER-642. "exceeded deadline by N ms" floods logs (Marc Celani via michim) - - ZOOKEEPER-1431. zkpython async calls leak memory (Kapil Thangavelu and Andre Cruz via henryr) - - ZOOKEEPER-1163. Memory leak in zk_hashtable.c:do_insert_watcher_object() - (Anupam Chanda via michim) - - ZOOKEEPER-1466. QuorumCnxManager.shutdown missing synchronization. (Patrick Hunt via mahadev) - - ZOOKEEPER-1490. If the configured log directory does not exist - zookeeper will not start. Better to create the directory and start - (suja s via phunt) - - ZOOKEEPER-1210. Can't build ZooKeeper RPM with RPM >= 4.6.0 (i.e. on - RHEL 6 and Fedora >= 10) (Tadeusz Andrzej Kadłubowski via phunt) - - ZOOKEEPER-1236. Security uses proprietary Sun APIs - (Adalberto Medeiros via phunt) - - ZOOKEEPER-1471. Jute generates invalid C++ code - (Michi Mutsuzaki via phunt) - - ZOOKEEPER-1465. Cluster availability following new leader election - takes a long time with large datasets - is correlated to dataset size - (fpj and Thawan Kooburat via camille) - - ZOOKEEPER-1427. Writing to local files is done non-atomically (phunt) - - ZOOKEEPER-1489. Data loss after truncate on transaction log (phunt) - - ZOOKEEPER-1521. LearnerHandler initLimit/syncLimit problems - specifying follower socket timeout limits (phunt) - - ZOOKEEPER-1493. C Client: zookeeper_process doesn't invoke - completion callback if zookeeper_close has been called - (Michi Mutsuzaki via phunt and mahadev) - - ZOOKEEPER-1522. intermittent failures in Zab test due to NPE in - recursiveDelete test function (phunt via flavio) - - ZOOKEEPER-1514. FastLeaderElection - leader ignores the round - information when joining a quorum (flavio via henryr) - - ZOOKEEPER-1533 Correct the documentation of the args for the JavaExample doc. - (Warren Turkal via michim) - - ZOOKEEPER-1536 c client : memory leak in winport.c (brooklin via michim) - - ZOOKEEPER-1481 allow the C cli to run exists with a watcher (phunt via michim) - - ZOOKEEPER-1328. Misplaced assertion for the test case 'FLELostMessageTest' - and not identifying misfunctions. (Rakesh R via mahadev) - - ZOOKEEPER-1380. zkperl: _zk_release_watch doesn't remove items properly from - the watch list. (Botond Hejj via mahadev) - - ZOOKEEPER-1538. Improve space handling in zkServer.sh and zkEnv.sh. (Andrew - Ferguson via mahadev) - - ZOOKEEPER-1501. Nagios plugin always returns OK when it cannot connect to - zookeeper. (Brian Sutherland via mahadev) - - ZOOKEEPER-1437. Client uses session before SASL authentication complete - (Eugene Koontz via mahadev) - - ZOOKEEPER-1494. C client: socket leak after receive timeout in - zookeeper_interest() (Michi Mutsuzaki via mahadev) - - ZOOKEEPER-1483. Fix leader election recipe documentation (Michi Mutsuzaki - via mahadev) - - ZOOKEEPER-1496. Ephemeral node not getting cleared even after client has - exited. (Rakesh R via mahadev) - - ZOOKEEPER-1550. ZooKeeperSaslClient does not finish anonymous login on - OpenJDK (Eugene Koontz via mahadev) - - ZOOKEEPER-1585. make dist for src/c broken in trunk (Raul Gutierrez Segales - via michim) - - ZOOKEEPER-1590. Patch to add zk.updateServerList(newServerList) - broke the build (fpj via phunt) - - ZOOKEEPER-1474. Cannot build Zookeeper with IBM Java: use of Sun - MXBean classes (Adalberto Medeiros via phunt) - - ZOOKEEPER-1591. Windows build is broken because inttypes.h doesn't exist - (Marshall McMullen via michim) - - ZOOKEEPER-1596. Zab1_0Test should ensure that the file is closed - (Enis Soztutar via phunt) - - ZOOKEEPER-1513. "Unreasonable length" exception while starting a - server (Skye W-M via phunt) - - ZOOKEEPER-1581. change copyright in notice to 2012 (breed via phunt) - - ZOOKEEPER-1553. Findbugs configuration is missing some dependencies - (Sean Busbey via phunt) - - ZOOKEEPER-1478. Small bug in QuorumTest.testFollowersStartAfterLeader( ) - (Alexander Shraer via fpj, breed, phunt) - - ZOOKEEPER-1387. Wrong epoch file created - (Benjamin Busjaeger via breed, phunt) - - ZOOKEEPER-1578. org.apache.zookeeper.server.quorum.Zab1_0Test failed due to - hard code with 33556 port (Li Ping via mahadev) - - ZOOKEEPER-1334. Zookeeper 3.4.x is not OSGi compliant - MANIFEST.MF - is flawed (Claus Ibsen via phunt) - - ZOOKEEPER-1603. StaticHostProviderTest testUpdateClientMigrateOrNot - hangs (Alexander Shraer via phunt) - - ZOOKEEPER-1597. Windows build failing (michim via phunt) - - ZOOKEEPER-1625. zkServer.sh is looking for clientPort in config file, but it - may no longer be there with ZK-1411 (Alexander Shraer via michim) - - ZOOKEEPER-1495. ZK client hangs when using a function not available - on the server. (Skye W-M via phunt) - - ZOOKEEPER-1620. NIOServerCnxnFactory (new code introduced in - ZK-1504) opens selectors but never closes them - (Thawan Kooburat via phunt) - - ZOOKEEPER-1628. Documented list of allowable characters in ZK doc - not in line with code (Gabriel Reid via phunt) - - ZOOKEEPER-1613. The documentation still points to 2008 in the - copyright notice (Edward Ribeiro via phunt) - - ZOOKEEPER-1562. Memory leaks in zoo_multi API - (Deepak Jagtap via phunt) - - ZOOKEEPER-1645. ZooKeeper OSGi package imports not complete - (Arnoud Glimmerveen via phunt) - - ZOOKEEPER-1641. Using slope=positive results in a jagged ganglia - graph of packets rcvd/sent (Ben Hartshorne via phunt) - - ZOOKEEPER-1648. Fix WatcherTest in JDK7 - (Thawan Kooburat via phunt) - - ZOOKEEPER-1606. intermittent failures in ZkDatabaseCorruptionTest on - jenkins (lixiaofeng via phunt) - - ZOOKEEPER-1647. OSGi package import/export changes not applied to - bin-jar (Arnoud Glimmerveen via phunt) - - ZOOKEEPER-1672. zookeeper client does not accept "-members" option - in reconfig command (Xiaoshuang Wang via phunt) - - ZOOKEEPER-1700. FLETest consistently failing - setLastSeenQuorumVerifier - seems to be hanging (phunt via fpj) - - ZOOKEEPER-1697. large snapshots can cause continuous quorum failure - (phunt via fpj) - - ZOOKEEPER-1706. Typo in Double Barriers example (Jingguo Yao via fpj) - - ZOOKEEPER-1324. Remove Duplicate NEWLEADER packets - from the Leader to the Follower. (Thawan, fpj via fpj) - - ZOOKEEPER-1642. Leader Loading Database Twice (fpj via camille) - - ZOOKEEPER-1663. scripts don't work when path contains spaces (Amichai Rothman via camille) - - ZOOKEEPER-1702. ZooKeeper client may write operation packets before - receiving successful response to connection request, can cause TCP - RST (Chris Nauroth via phunt) - - ZOOKEEPER-1629. testTransactionLogCorruption occasionally fails. (shralex via camille) - - ZOOKEEPER-1713. wrong time calculation in zkfuse.cc (german via fpj) - - ZOOKEEPER-1379. 'printwatches, redo, history and - connect '. client commands always print usage. This - is not necessary (edward via fpj) - - ZOOKEEPER-1670. zookeeper should set a default value - for SERVER_JVMFLAGS and CLIENT_JVMFLAGS so that memory - usage is controlled (Arpit Gupta via fpj) - - ZOOKEEPER-1448. Node+Quota creation in transaction log can crash leader startup (Botond Hejj via fpj) - - ZOOKEEPER-1664. Kerberos auth doesn't work with native platform GSS integration. (Boaz Kelmer via camille) - - ZOOKEEPER-1754. Read-only server allows to create znode (Rakesh R via fpj) - - ZOOKEEPER-1751. ClientCnxn#run could miss the second ping or connection get - dropped before a ping. (Jeffrey Zhong via mahadev) - - ZOOKEEPER-1657. Increased CPU usage by unnecessary SASL - checks (Philip K. Warren via fpj) - - ZOOKEEPER-1753. ClientCnxn is not properly releasing the resources, - which are used to ping RwServer (Rakesh R via fpj) - - ZOOKEEPER-1096. Leader communication should listen on - specified IP, not wildcard address (Jared Cantwell, - German Blanco via fpj) - - ZOOKEEPER-87. Follower does not shut itself down if its - too far behind the leader. (German Blanco via fpj) - - ZOOKEEPER-1696. Fail to run zookeeper client on Weblogic application server. - (Jeffrey Zhong via mahadev) - - ZOOKEEPER-1769. ZooInspector can't display node data/metadata/ACLs - (Benjamin Jaton via phunt) - - ZOOKEEPER-1718. Support JLine 2 (Manikumar Reddy via phunt) - - ZOOKEEPER-1655. Make jline dependency optional in maven pom - (Thomas Weise via phunt) - - ZOOKEEPER-1770. NullPointerException in SnapshotFormatter - (Germán Blanco via phunt) - - ZOOKEEPER-1733. FLETest#testLE is flaky on windows boxes - (Jeffrey Zhong via phunt) - - ZOOKEEPER-1773. incorrect reference to jline version/lib in docs - (Manikumar Reddy via phunt) - - ZOOKEEPER-732. Improper translation of error into Python exception - (Andrei Savu, Lei Zhang, fpj via fpj) - - ZOOKEEPER-1766. Consistent log severity level guards and statements - (Jackie Chang via michim) - - ZOOKEEPER-1778. Use static final Logger objects (Rakesh R via michim) - - ZOOKEEPER-1551. Observers ignore txns that come after snapshot and UPTODATE - (thawan, fpj via thawan) - - ZOOKEEPER-1781. ZooKeeper Server fails if snapCount is set to 1 - (Takashi Ohnishi via phunt, breed) - - ZOOKEEPER-1774. QuorumPeerMainTest fails consistently with - "complains about host" assertion failure (phunt) - - ZOOKEEPER-877. zkpython does not work with python3.1 - (Daniel Enman via phunt) - - ZOOKEEPER-1624. PrepRequestProcessor abort multi-operation incorrectly. (thawan via camille) - - ZOOKEEPER-1610. Some classes are using == or != to compare - Long/String objects instead of .equals() (Edward Ribeiro via phunt) - - ZOOKEEPER-1795. unable to build c client on ubuntu - (Raul Gutierrez Segales via phunt) - - ZOOKEEPER-1646. mt c client tests fail on Ubuntu Raring (phunt) - - ZOOKEEPER-1732. ZooKeeper server unable to join established - ensemble (German Blanco via fpj) - - ZOOKEEPER-1667. Watch event isn't handled correctly when - a client reestablish to a server (jacky007, fpj via fpj) - - ZOOKEEPER-1799. SaslAuthFailDesignatedClientTest.testAuth fails - frequently on SUSE (Jeffrey Zhong via phunt) - - ZOOKEEPER-1557. jenkins jdk7 test failure in - testBadSaslAuthNotifiesWatch (Eugene Koontz via phunt) - - ZOOKEEPER-1744. clientPortAddress breaks "zkServer.sh status" - (Nick Ohanian via phunt) - - ZOOKEEPER-1499. clientPort config changes not backwards-compatible - (Alexander Shraer via phunt, breed) - - ZOOKEEPER-1798. Fix race condition in testNormalObserverRun - (thawan, fpj via thawan) - - ZOOKEEPER-1783. Distinguish initial configuration from first established - configuration (shralex via breed) - - ZOOKEEPER-1812. ZooInspector reconnection always fails if first - connection fails (Benjamin Jaton via phunt) - - ZOOKEEPER-1815. Tolerate incorrectly set system hostname in tests - (some one via michim) - - ZOOKEEPER-1821. very ugly warning when compiling load_gen.c - (german blanco via fpj) - - ZOOKEEPER-1632. fix memory leaks in cli_st (fpj via michim) - - ZOOKEEPER-1459. Standalone ZooKeeperServer is not closing - the transaction log files on shutdown (Rakesh R via fpj) - - ZOOKEEPER-1019. zkfuse doesn't list dependency on boost in README - (Raul Gutierrez Segales via michim) - - ZOOKEEPER-1834. Catch IOException in FileTxnLog (fpj via michim) - - ZOOKEEPER-1382. Zookeeper server holds onto dead/expired session ids in the watch data structures - (Germán Blanco and Michael Morello via camille) - - ZOOKEEPER-1839. Deadlock in NettyServerCnxn (Rakesh R via michim) - - ZOOKEEPER-1622. session ids will be negative in the year 2022 - (Eric Newton via phunt) - - ZOOKEEPER-1756. zookeeper_interest() in C client can return a timeval of 0 - (Eric Lindvall via michim) - - ZOOKEEPER-1388. Client side 'PathValidation' is missing for the - multi-transaction api. (Rakesh R via marshallm, phunt) - - ZOOKEEPER-1849. Need to properly tear down tests in various cases - (Germán Blanco via fpj) - - ZOOKEEPER-1179. NettyServerCnxn does not properly close socket on - 4 letter word requests (Rakesh R, Germán Blanco via fpj) - - ZOOKEEPER-1852. ServerCnxnFactory instance is not properly - cleanedup (Rakesh R via fpj) - - ZOOKEEPER-1414. QuorumPeerMainTest.testQuorum, testBadPackets are failing - intermittently (Rakesh R via michim) - - ZOOKEEPER-1057. zookeeper c-client, connection to offline server fails to - successfully fallback to second zk host (Germán Blanco via michim) - - ZOOKEEPER-1857. PrepRequestProcessotTest doesn't shutdown ZooKeeper server - (Germán Blanco via michim) - - ZOOKEEPER-1860. Async versions of reconfig don't actually throw - KeeperException nor InterruptedException (Raul Gutierrez Segales via phunt) - - ZOOKEEPER-1837. Fix JMXEnv checks (potential race conditions) - (Germán Blanco via fpj) - - ZOOKEEPER-1858. JMX checks - potential race conditions while stopping - and starting server (Rakesh R via fpj) - - ZOOKEEPER-1867. Bug in ZkDatabaseCorruptionTest (fpj) - - ZOOKEEPER-1573. Unable to load database due to missing parent node - (Vinayakumar B via phunt, fpj) - - ZOOKEEPER-1811. The ZooKeeperSaslClient service name principal is - hardcoded to "zookeeper" (Harsh J via phunt) - - ZOOKEEPER-1874. Add proper teardown/cleanups in ReconfigTest to shutdown - quorumpeer (Rakesh R and Germán Blanco via michim) - - ZOOKEEPER-1873. Unnecessarily InstanceNotFoundException is coming when - unregister failed jmxbeans (Rakesh R via michim) - - ZOOKEEPER-1844. TruncateTest fails on windows (Rakesh R via fpj) - - ZOOKEEPER-1861. ConcurrentHashMap isn't used properly in QuorumCnxManager - (Ted Yu via camille) - - ZOOKEEPER-1755. Concurrent operations of four letter 'dump' ephemeral - command and killSession causing NPE (Rakesh R via camille) - - ZOOKEEPER-1779. ReconfigTest littering the source root with test files - (Abhiraj Butala via michim) - - ZOOKEEPER-1888. ZkCli.cmd commands fail with "'java' is not recognized as an - internal or external command" (Ivan Mitic via michim) - - ZOOKEEPER-1662. Fix to two small bugs in ReconfigTest.testPortChange() - (Alexander Shraer via michim) - - ZOOKEEPER-1883. C client unit test failures (Raul Gutierrez Segales via - michim) - - ZOOKEEPER-1878. Inconsistent behavior in autocreation of dataDir and - dataLogDir (Rakesh R via michim) - - ZOOKEEPER-1862. ServerCnxnTest.testServerCnxnExpiry is intermittently failing - (Rakesh R via michim) - - ZOOKEEPER-1901. [JDK8] Sort children for comparison in AsyncOps tests - (Andrew Purtell via michim) - - ZOOKEEPER-1894. ObserverTest.testObserver fails consistently. (michim via camille) - - ZOOKEEPER-1263. fix handling of min/max session timeout value initialization - (Rakesh R via michim) - - ZOOKEEPER-1904. WatcherTest#testWatchAutoResetWithPending is failing - (Rakesh R via michim) - - ZOOKEEPER-1725. Zookeeper Dynamic Conf writes out hostnames when IPs are - supplied (Alexander Shraer via michim) - - ZOOKEEPER-1906. zkpython: invalid data in GetData for empty node - (Nikita Vetoshkin via michim) - - ZOOKEEPER-1897. ZK Shell/Cli not processing commands (stack via michim) - - ZOOKEEPER-1357. Zab1_0Test uses hard-wired port numbers. Specifically, it uses - the same port for leader in two different tests. The second test periodically - fails complaining that the port is still in use. (Alexander Shraer via michim) - - ZOOKEEPER-1840. Server tries to connect to itself during dynamic reconfig - (Alexander Shraer via michim) - - ZOOKEEPER-1909. removeWatches doesn't return NOWATCHER when there is - no watch set (Raul Gutierrez Segales via rakeshr) - - ZOOKEEPER-1913. Invalid manifest files due to bogus revision property value - (Raul Gutierrez Segales via michim) - - ZOOKEEPER-1911. REST contrib module does not include all required files when - packaged (Sean Mackrory via michim) - - ZOOKEEPER-1819. DeserializationPerfTest calls method with wrong arguments - (Daniel Knightly via michim) - - ZOOKEEPER-1673. Zookeeper don't support cidr in expression in ACL with ip - scheme (Craig Condit via michim) - - ZOOKEEPER-1843. Oddity in ByteBufferInputStream skip (Bill Havanki via michim) - - ZOOKEEPER-1910. RemoveWatches wrongly removes the watcher if multiple watches - exists on a path (Rakesh R via camille) - - ZOOKEEPER-1695. Inconsistent error code and type for new errors introduced - by dynamic reconfiguration (Michi Mutsuzaki via rakeshr) - - ZOOKEEPER-1923. A typo in zookeeperStarted document (Chengwei Yang via michim) - - ZOOKEEPER-1926. Unit tests should only use build/test/data for data (Enis - Soztutar via michim) - - ZOOKEEPER-1891. StaticHostProviderTest.testUpdateLoadBalancing times out - (Michi Mutsuzaki via rakeshr) - - ZOOKEEPER-1836. addrvec_next() fails to set next parameter if - addrvec_hasnext() returns false (Dutch T. Meyer via michim) - - ZOOKEEPER-1062. Net-ZooKeeper: Net::ZooKeeper consumes 100% cpu on wait - (Botond Hejj via michim) - - ZOOKEEPER-1864. quorumVerifier is null when creating a QuorumPeerConfig - from parsing a Properties object (Michi Mutsuzaki via rakeshr) - - ZOOKEEPER-1797. PurgeTxnLog may delete data logs during roll (Rakesh R via - michim) - - ZOOKEEPER-1791. ZooKeeper package includes unnecessary jars that are part of - the package. (mahadev via michim) - - ZOOKEEPER-1214. QuorumPeer should unregister only its previsously registered - MBeans instead of use MBeanRegistry.unregisterAll() method. - (César Álvarez Núñez via michim) - - ZOOKEEPER-1699. Leader should timeout and give up leadership when losing - quorum of last proposed configuration (Alexander Shraer via michim) - - ZOOKEEPER-1870. flakey test in StandaloneDisabledTest.startSingleServerTest - (Helen Hastings via fpj) - - ZOOKEEPER-1945. deb - zkCli.sh, zkServer.sh and zkEnv.sh regression caused - by ZOOKEEPER-1663 (Mark Flickinger via fpj) - - ZOOKEEPER-1939. ReconfigRecoveryTest.testNextConfigUnreachable is - failing (Rakesh R via phunt) - - ZOOKEEPER-1835. dynamic configuration file renaming fails on Windows - (Bruno Freudensprung via rakeshr) - - ZOOKEEPER-1810. Add version to FLE notifications for trunk Germán Blanco via - michim) - - ZOOKEEPER-1222. getACL should only call DataTree.copyStat when passed in - stat is not null (Michi Mutsuzaki via rakeshr) - - ZOOKEEPER-1964. Fix Flaky Test in ReconfigTest.java (Hongchao Deng via fpj) - - ZOOKEEPER-1863. Race condition in commit processor leading to out of order - request completion, xid mismatch on client. (fpj and Dutch T Meyer via camille) - - ZOOKEEPER-1966. VS and line breaks (Orion Hodson via fpj) - - ZOOKEEPER-1683. ZooKeeper client NPE when updating server list on disconnected - client (shralex via michim) - - ZOOKEEPER-1969. Fix Port Already In Use for JettyAdminServerTest - (Hongchao Deng via phunt) - - ZOOKEEPER-1851. Follower and Observer Request Processors Do Not Forward - create2 Requests (Chris Chen via rakeshr) - - ZOOKEEPER-1807. Observers spam each other creating connections to the - election addr (Alex Shraer via fpj) - - ZOOKEEPER-1972. Fix invalid volatile long/int increment (++) - (Hongchao Deng via phunt) - - ZOOKEEPER-1973. Jetty Server changes broke ibm6 support - (Bill Havanki via phunt) - - ZOOKEEPER-1975. Turn off "internationalization warnings" in findbugs - exclude file (phunt) - - ZOOKEEPER-1979. Fix Performance Warnings found by Findbugs 2.0.3 - (Hongchao Deng via phunt) - - ZOOKEEPER-1981. Fix Dodgy Code Warnings identified by findbugs 2.0.3 - (Hongchao Deng via phunt) - - ZOOKEEPER-1984. testLeaderTimesoutOnNewQuorum is a flakey test - (Alex Shraer via phunt) - - ZOOKEEPER-1789. 3.4.x observer causes NPE on 3.5.0 (trunk) - participants (Alex Shraer via phunt) - - ZOOKEEPER-1982. Refactor (touch|add)Session in - SessionTrackerImpl.java (Hongchao Deng via phunt) - - ZOOKEEPER-1937. init script needs fixing for ZOOKEEPER-1719 - (Marshall McMullen via phunt) - - ZOOKEEPER-1877. Malformed ACL Id can crash server with skipACL=yes - (Chris Chen via phunt) - - ZOOKEEPER-1974. winvs2008 jenkins job failing with "unresolved - external symbol" (flavio via phunt) - - ZOOKEEPER-1933. Windows release build of zk client cannot connect to - zk server (Orion Hodson via fpj, phunt) - - ZOOKEEPER-1988. new test patch to verify dynamic reconfig backward - compatibility (Alexander Shraer via rakeshr) - -IMPROVEMENTS: - - ZOOKEEPER-1170. Fix compiler (eclipse) warnings: unused imports, - unused variables, missing generics (Thomas Koch via phunt) - - ZOOKEEPER-96. The jute parser should get generated from the jj files - instead of checking in the generated sources (Thomas Koch via phunt) - - ZOOKEEPER-1175. DataNode references parent node for no reason - (Thomas Koch via phunt) - - ZOOKEEPER-899. Update Netty version in trunk to 3.2.2 - (Thomas Koch via phunt) - - ZOOKEEPER-1182. Make findbugs usable in Eclipse (Thomas Koch via phunt) - - ZOOKEEPER-1184. jute generated files are not being cleaned up via "ant clean" - (Thomas Koch via phunt) - - ZOOKEEPER-1176. Remove dead code and basic cleanup in DataTree - (Thomas Koch via phunt) - - ZOOKEEPER-556. Startup messages should account for common error of - missing leading slash in config files (Thomas Koch via phunt) - - ZOOKEEPER-1155. Add windows automated builds (CI) for zookeeper c client bindings. (Dheeraj Agrawal via camille) - - ZOOKEEPER-1226. extract version check in separate method in PrepRequestProcessor (Thomas Koch via camille) - - ZOOKEEPER-1193. Remove upgrade code (Thomas Koch via phunt) - - ZOOKEEPER-1221. Provide accessors for Request.{hdr|txn} (Thomas Koch via phunt) - - ZOOKEEPER-1216. Fix more eclipse compiler warnings, also in Tests - (Thomas Koch via phunt) - - ZOOKEEPER-1254. test correct watch handling with multi ops - (Thomas Koch via phunt) - - ZOOKEEPER-1252. remove unused method o.a.z.test.AxyncTest.restart() - (Thomas Koch via phunt) - - ZOOKEEPER-1200. Remove obsolete DataTreeBuilder - (Thomas Koch via phunt) - - ZOOKEEPER-1247. dead code in PrepRequestProcessor.pRequest multi case - (Thomas Koch via phunt) - - ZOOKEEPER-1265. Normalize switch cases lists on request types - (Thomas Koch via phunt) - - ZOOKEEPER-1267. closeSession flag in finalRequestProcessor is superfluous - (Thomas Koch via phunt) - - ZOOKEEPER-1273. Copy'n'pasted unit test (Thomas Koch via phunt) - - ZOOKEEPER-1232. remove unused o.a.z.server.util.Profiler - (Thomas Koch via phunt) - - ZOOKEEPER-1253. return value of DataTree.createNode is never used - (Thomas Koch via phunt) - - ZOOKEEPER-756. some cleanup and improvements for zooinspector - (Colin Goodheart-Smithe & Thomas Koch via phunt) - - ZOOKEEPER-1292. FLETest is flaky (fpj via breed) - - ZOOKEEPER-1326. The CLI commands "delete" and "rmr" are confusing. - Can we have "rm" + "rmr" instead? (Harsh J via phunt) - - ZOOKEEPER-1342. quorum Listener & LearnerCnxAcceptor are missing - thread names (Rakesh R via phunt) - - ZOOKEEPER-1229. C client hashtable_remove redundantly calls hash function - (Harsh J via phunt) - - ZOOKEEPER-1345. Add a .gitignore file with general exclusions and - Eclipse project files excluded (Harsh J via phunt) - - ZOOKEEPER-1293. Remove unused readyToStart from Leader.java - (Alex Shraer via phunt) - - ZOOKEEPER-1322. Cleanup/fix logging in Quorum code. - (phunt via mahadev) - - ZOOKEEPER-1321. Add number of client connections metric in JMX and - srvr. (Neha Narkhede via camille) - - ZOOKEEPER-1389. it would be nice if start-foreground used exec $JAVA - in order to get rid of the intermediate shell process - (Roman Shaposhnik via phunt) - - ZOOKEEPER-1161. Provide an option for disabling auto-creation of the - data directory (phunt via henry) - - ZOOKEEPER-1397. Remove BookKeeper documentation links. (flavio via camille) - - ZOOKEEPER-1377. add support for dumping a snapshot file content (similar to LogFormatter). (phunt via camille) - - ZOOKEEPER-271. Better command line parsing in ZookeeperMain. - (Hartmut Lang via phunt) - - ZOOKEEPER-1435. cap space usage of default log4j rolling policy (phunt via henryr) - - ZOOKEEPER-1432. Add javadoc and debug logging for checkACL() method in - PrepRequestProcessor (Eugene Koontz via michim) - - ZOOKEEPER-1411. Consolidate membership management, distinguish between static and dynamic configuration parameters (Alex Shraer via breed) - - ZOOKEEPER-1451. C API improperly logs getaddrinfo failures on Linux when using glibc (Stephen Tyree via michim) - - ZOOKEEPER-1440. Spurious log error messages when QuorumCnxManager is shutting - down (Jordan Zimmerman via michim) - - ZOOKEEPER-1454. Document how to run autoreconf if cppunit is - installed in a non-standard directory (Michi Mutsuzaki via phunt) - - ZOOKEEPER-1503. remove redundant JAAS configuration code in SaslAuthTest and - SaslAuthFailTest (Eugene Koontz via phunt) - - ZOOKEEPER-1510. Should not log SASL errors for non-secure usage - (Todd Lipcon via phunt) - - ZOOKEEPER-1497. Allow server-side SASL login with JAAS configuration - to be programmatically set (rather than only by reading JAAS - configuration file) (Matteo Bertozzi via phunt) - - ZOOKEEPER-1238. Linger time should be -1 for Netty sockets. (Skye - W-M via henryr) - - ZOOKEEPER-1505. Multi-thread CommitProcessor (Jay Shrauner via phunt) - - ZOOKEEPER-1564. Allow JUnit test build with IBM Java - (Paulo Ricardo Paz Vital via phunt) - - ZOOKEEPER-1598. Ability to support more digits in the version string - (Raja Aluri via phunt) - - ZOOKEEPER-721. Minor cleanup related to the log4j version change - from 1.2.15 -> 1.2.16 (Sean Busbey via phunt) - - ZOOKEEPER-1583. Document maxClientCnxns in conf/zoo_sample.cfg - (Christopher Tubbs via phunt) - - ZOOKEEPER-1584. Adding mvn-install target for deploying the - zookeeper artifacts to .m2 repository (Ashish Singh via phunt) - - ZOOKEEPER-1602. a change to QuorumPeerConfig's API broke - compatibility with HBase (Alexander Shraer via phunt) - - ZOOKEEPER-1335. Add support for --config to zkEnv.sh to specify a config - directory different than what is expected (Arpit Gupta via mahadev) - - ZOOKEEPER-1504. Multi-thread NIOServerCnxn (Jay Shrauner via phunt) - - ZOOKEEPER-1297. Add stat information to create() call - (Lenni Kuff via phunt) - - ZOOKEEPER-1535. ZK Shell/Cli re-executes last command on exit (Edward Ribeiro via camille) - - ZOOKEEPER-1619. Allow spaces in URL (Edward Ribeiro via phunt) - - ZOOKEEPER-1615. minor typos in ZooKeeper Programmer's Guide web page - (Evan Zacks via phunt) - - ZOOKEEPER-1601. document changes for multi-threaded CommitProcessor - and NIOServerCnxn (Thawan Kooburat via phunt) - - ZOOKEEPER-1643. Windows: fetch_and_add not 64bit-compatible, may not be - correct (Erik Anderson via michim) - - ZOOKEEPER-1714 perl client segfaults if ZOO_READ_ACL_UNSAFE constant is used - (Botond Hejj via camille) - - ZOOKEEPER-1719. zkCli.sh, zkServer.sh and zkEnv.sh regression caused by ZOOKEEPER-1663 - (Marshall McMullen via camille) - - ZOOKEEPER-1413. Use on-disk transaction log for learner sync up (thawan) - - ZOOKEEPER-1400. Allow logging via callback instead of raw FILE pointer (michi via fpj) - - ZOOKEEPER-1679. c client: use -Wdeclaration-after-statement (michi via fpj) - - ZOOKEEPER-1750 Race condition producing NPE in NIOServerCnxn.toString - (Rakesh R via michim) - - ZOOKEEPER-1759. Adding ability to allow READ operations for authenticated users, - versus keeping ACLs wide open for READ. (Yuliya Feldman via camille) - - ZOOKEEPER-1552. Enable sync request processor in Observer (thawan, fpj) - - ZOOKEEPER-1758. Add documentation for zookeeper.observer.syncEnabled flag - (thawan, fpj via thawan) - - ZOOKEEPER-1771. ZooInspector authentication (Benjamin Jaton via phunt) - - ZOOKEEPER-1509. Please update documentation to reflect updated - FreeBSD support (George Neville-Neil via phunt) - - ZOOKEEPER-1627. Add org.apache.zookeeper.common to exported packages - in OSGi MANIFEST (Arnoud Glimmerveen via phunt) - - ZOOKEEPER-1715. Upgrade netty version (Sean Bridges via phunt) - - ZOOKEEPER-1666. Avoid Reverse DNS lookup if the hostname in - connection string is literal IP address. (George Cao via camille) - - ZOOKEEPER-1786. ZooKeeper data model documentation is incorrect - (Niraj Tolia via fpj) - - ZOOKEEPER-1430. add maven deploy support to the build - (Giridharan Kesavan via phunt) - - ZOOKEEPER-1440. Spurious log error messages when QuorumCnxManager is shutting - down (Jordan Zimmerman via michim) - - ZOOKEEPER-1638. Redundant zk.getZKDatabase().clear(); (neil bhakta via michim) - - ZOOKEEPER-1796. Move common code from {Follower, Observer}ZooKeeperServer into - LearnerZooKeeperServer (Raul Gutierrez Segales via michim) - - ZOOKEEPER-602. log all exceptions not caught by ZK threads (Rakesh R via - michim) - - ZOOKEEPER-297. centralize version numbering in the source/build (Diego de - Oliveira via michim) - - ZOOKEEPER-1408. CLI: sort output of ls command (Hartmut Lang via michim) - - ZOOKEEPER-1219. LeaderElectionSupport recipe is unnecessarily dispatching the - READY_START event even if the ELECTED node stopped/expired simultaneously. - (Rakesh R via michim) - - ZOOKEEPER-1728. Better error message when reconfig invoked in standalone mode - (Alexander Shraer via michim) - - ZOOKEEPER-1701. When new and old config have the same version, no need to - write new config to disk or create new connections (Alexander Shraer via - michim) - - ZOOKEEPER-1730. Make ZooKeeper easier to test - support simulating a session - expiration (Jordan Zimmerman via michim) - - ZOOKEEPER-1831. Document remove watches details to the guide (Rakesh R via - michim) - - ZOOKEEPER-1575. adding .gitattributes to prevent CRLF and LF mismatches for - source and text files (Raja Aluri via michim) - - ZOOKEEPER-657. Cut down the running time of ZKDatabase corruption - (Michi Mutsuzaki via rakeshr) - - ZOOKEEPER-716. dump server memory detail to the log during startup - (Michi Mutsuzaki via rakeshr) - - ZOOKEEPER-1930. A typo in zookeeper recipes.html (Chengwei Yang via michim) - - ZOOKEEPER-1659. Add JMX support for dynamic reconfiguration (Rakesh R via - michim) - - ZOOKEEPER-1928. add configurable throttling to the number of snapshots - concurrently sent by a leader (Edward Carter via fpj) - - ZOOKEEPER-1746. AsyncCallback.*Callback don't have any Javadoc - (Hongchao Deng via phunt) - - ZOOKEEPER-1938. bump version in the C library as we prepare for - 3.5.0 release (Raul Gutierrez Segales via phunt) - - ZOOKEEPER-1576. Zookeeper cluster - failed to connect to cluster if one of the - provided IPs causes java.net.UnknownHostException (Edward Ribeiro via camille) - - ZOOKEEPER-1918. Add 64 bit Windows as a supported development - platform (Michi Mutsuzaki via phunt) - - ZOOKEEPER-1946. Server logging should reflect dynamically - reconfigured address (Niko Vuokko via phunt and Alexander Shraer) - - ZOOKEEPER-1953. Add solution and project files to enable build with current - Visual Studio editions (VS 2012/2013)- 32-bit and 64-bit. - (Orion Hodson via fpj) - - ZOOKEEPER-1968. Make Jetty dependencies optional in ivy.xml - (Bill Havanki via phunt) - - ZOOKEEPER-927. there are currently 24 RAT warnings in the build -- - address directly or via exclusions (Michi Mutsuzaki via phunt) - - ZOOKEEPER-1986. refactor log trace on touchSession - (Hongchao Deng via phunt) - -headers - -Release 3.4.0 - - -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) - - ZOOKEEPER-1189. For an invalid snapshot file(less than 10bytes size) RandomAccessFile - stream is leaking. (Rakesh R via mahadev) - - ZOOKEEPER-1185. Send AuthFailed event to client if SASL authentication fails. - (Eugene Kuntz via mahadev) - - ZOOKEEPER-1181. Fix problems with Kerberos TGT renewal. - (Eugene Koontz via mahadev) - - ZOOKEEPER-1268. problems with read only mode, intermittent test failures - and ERRORs in the log (phunt via mahadev) - - ZOOKEEPER-1270. testEarlyLeaderAbandonment failing intermittently, - quorum formed, no serving. (Flavio, Camille and Alexander Shraer via mahadev) - - ZOOKEEPER-1299. Add winconfig.h file to ignore in release audit. (mahadev) - - ZOOKEEPER-1354. AuthTest.testBadAuthThenSendOtherCommands fails intermittently (phunt via camille) - - ZOOKEEPER-1417. investigate differences in client last zxid handling btw c and java clients (thawan via camille) - -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) - - ZOOKEEPER-1201. Clean SaslServerCallbackHandler.java. (Thomas Koch - 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) - - ZOOKEEPER-751. Recipe heading refers to 'recoverable' but should be - 'revocable' (Michi Mutsuzaki via rakeshr) - -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 5689ab64643..1ae33515a5f 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache ZooKeeper -Copyright 2009-2014 The Apache Software Foundation +Copyright 2009-2018 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 96% rename from README.txt rename to README.md index d056f5afb91..585ecf9d851 100644 --- a/README.txt +++ b/README.md @@ -33,4 +33,4 @@ The content of the legacy jar and the bin+sources jar are the same. As of version 3.3.0 bin/sources/javadoc jars contained in dist-maven directory are deployed to the Apache Maven repository after the release has been accepted by Apache: - http://people.apache.org/repo/m2-ibiblio-rsync-repository/ + https://repository.apache.org/ diff --git a/README_packaging.txt b/README_packaging.txt index 48b69183bea..9776f74ca90 100644 --- a/README_packaging.txt +++ b/README_packaging.txt @@ -3,6 +3,7 @@ README file for Packaging Notes Requirement ----------- +ant (recommended version 1.9.4 or later for concurrent JUnit test execution) gcc, cppunit and python-setuptools are required to build C and python bindings. @@ -19,47 +20,26 @@ apt-get --install python-setuptools Package build command --------------------- -Command to build Debian package: ant deb -Command to build RPM Package: ant rpm +The ZooKeeper project publishes releases as tarballs. For ZooKeeper packages +specific to your OS (such as rpm and deb), consider using Apache Bigtop: -rpm and deb packages are generated and placed in: +http://bigtop.apache.org/ -build/zookeeper*.[rpm|deb] -build/contrib/**.[rpm|deb] +Command to build tarball package: ant tar -Default package file structure layout +zookeeper-.tar.gz tarball file structure layout - /usr/bin - User executable - /usr/sbin - System executable - /usr/libexec - Configuration boot trap script - /usr/lib - Native libraries - /usr/share/doc/zookeeper - Documents - /usr/share/zookeeper - Project files - /usr/share/zookeeper/template/conf - Configuration template files - /etc/zookeeper - Configuration files - /etc/init.d/zookeeper - OS startup script + /bin - User executable + /sbin - System executable + /libexec - Configuration boot trap script + /lib - Library dependencies + /docs - Documents + /share/zookeeper - Project files -Source file structure layout ---------------------- - -src/packages/update-zookeeper-env.sh - - setup environment variables and symlink $PREFIX/etc/zookeeper to - /etc/zookeeper. - - This script is designed to run in post installation, and pre-remove - phase of ZooKeeper package. - - Run update-zookeeper-env.sh -h to get a list of supported parameters. - -src/packages/template - - Standard configuration template - -src/packages/deb - Meta data for creating Debian package - -src/packages/deb/init.d - Daemon start/stop script for Debian flavor of Linux +Command to build tarball package with native components: ant package-native tar -src/packages/rpm - Meta data for creating RPM package +zookeeper--lib.tar.gz tarball file structure layout -src/packages/rpm/init.d - Daemon start/stop script for Redhat flavor of Linux + /bin - User executable + /lib - Native libraries + /include/zookeeper - Native library headers diff --git a/bin/zkCleanup.sh b/bin/zkCleanup.sh index 38ee2e8be4a..331094b3ca2 100755 --- a/bin/zkCleanup.sh +++ b/bin/zkCleanup.sh @@ -39,13 +39,15 @@ fi ZOODATADIR="$(grep "^[[:space:]]*dataDir=" "$ZOOCFG" | sed -e 's/.*=//')" ZOODATALOGDIR="$(grep "^[[:space:]]*dataLogDir=" "$ZOOCFG" | sed -e 's/.*=//')" +ZOO_LOG_FILE=zookeeper-$USER-cleanup-$HOSTNAME.log + 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}" "-Dzookeeper.log.file=${ZOO_LOG_FILE}" \ -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}" "-Dzookeeper.log.file=${ZOO_LOG_FILE}" \ -cp "$CLASSPATH" $JVMFLAGS \ org.apache.zookeeper.server.PurgeTxnLog "$ZOODATALOGDIR" "$ZOODATADIR" $* fi diff --git a/bin/zkCli.cmd b/bin/zkCli.cmd index 0ffa0300e27..6faf7e6dead 100644 --- a/bin/zkCli.cmd +++ b/bin/zkCli.cmd @@ -17,8 +17,10 @@ REM limitations under the License. setlocal call "%~dp0zkEnv.cmd" +ZOO_LOG_FILE=zookeeper-%USERNAME%-cli-%COMPUTERNAME%.log + set ZOOMAIN=org.apache.zookeeper.ZooKeeperMain -call %JAVA% "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% %* +call %JAVA% "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" "-Dzookeeper.log.file=%ZOO_LOG_FILE%" -cp "%CLASSPATH%" %ZOOMAIN% %* endlocal diff --git a/bin/zkCli.sh b/bin/zkCli.sh index 992a91367d9..7e1b1c4ccfa 100755 --- a/bin/zkCli.sh +++ b/bin/zkCli.sh @@ -36,6 +36,8 @@ else . "$ZOOBINDIR"/zkEnv.sh fi -"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ +ZOO_LOG_FILE=zookeeper-$USER-cli-$HOSTNAME.log + +"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-Dzookeeper.log.file=${ZOO_LOG_FILE}" \ -cp "$CLASSPATH" $CLIENT_JVMFLAGS $JVMFLAGS \ org.apache.zookeeper.ZooKeeperMain "$@" diff --git a/bin/zkEnv.cmd b/bin/zkEnv.cmd index 33b6c4968f6..9497582bcfe 100644 --- a/bin/zkEnv.cmd +++ b/bin/zkEnv.cmd @@ -15,7 +15,7 @@ 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_LOG_DIR=%~dp0%..\logs set ZOO_LOG4J_PROP=INFO,CONSOLE REM for sanity sake assume Java 1.6 @@ -39,10 +39,15 @@ if not defined JAVA_HOME ( goto :eof ) -if not exist %JAVA_HOME%\bin\java.exe ( +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 +REM strip off trailing \ from JAVA_HOME or java does not start +if "%JAVA_HOME:~-1%" EQU "\" set "JAVA_HOME=%JAVA_HOME:~0,-1%" + +set JAVA="%JAVA_HOME%"\bin\java diff --git a/bin/zkEnv.sh b/bin/zkEnv.sh index 08519181b57..9806a4b5055 100755 --- a/bin/zkEnv.sh +++ b/bin/zkEnv.sh @@ -67,7 +67,7 @@ fi if [ "x${ZOO_LOG_DIR}" = "x" ] then - ZOO_LOG_DIR="." + ZOO_LOG_DIR="$ZOOKEEPER_PREFIX/logs" fi if [ "x${ZOO_LOG4J_PROP}" = "x" ] @@ -75,10 +75,13 @@ then ZOO_LOG4J_PROP="INFO,CONSOLE" fi -if [ "$JAVA_HOME" != "" ]; then - JAVA="$JAVA_HOME/bin/java" +if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then + JAVA="$JAVA_HOME/bin/java" +elif type -p java; then + JAVA=java else - JAVA=java + echo "Error: JAVA_HOME is not set and java could not be found in PATH." 1>&2 + exit 1 fi #add the zoocfg dir to classpath diff --git a/bin/zkServer.cmd b/bin/zkServer.cmd index 6b4cf026c12..b03a44386e7 100644 --- a/bin/zkServer.cmd +++ b/bin/zkServer.cmd @@ -18,7 +18,9 @@ setlocal call "%~dp0zkEnv.cmd" set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain +set ZOO_LOG_FILE=zookeeper-%USERNAME%-server-%COMPUTERNAME%.log + echo on -call %JAVA% "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %* +call %JAVA% "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" "-Dzookeeper.log.file=%ZOO_LOG_FILE%" "-XX:+HeapDumpOnOutOfMemoryError" "-XX:OnOutOfMemoryError=cmd /c taskkill /pid %%%%p /t /f" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %* endlocal diff --git a/bin/zkServer.sh b/bin/zkServer.sh index 74d9dc29a6d..e1709460b13 100755 --- a/bin/zkServer.sh +++ b/bin/zkServer.sh @@ -21,6 +21,18 @@ # 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,29 +42,39 @@ 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" != "x" ] then JVMFLAGS="$SERVER_JVMFLAGS $JVMFLAGS" @@ -80,8 +102,17 @@ fi echo "Using config: $ZOOCFG" >&2 -ZOO_DATADIR="$(grep "^[[:space:]]*dataDir" "$ZOOCFG" | sed -e 's/.*=//')" -ZOO_DATALOGDIR="$(grep "^[[:space:]]*dataLogDir" "$ZOOCFG" | sed -e 's/.*=//')" +case "$OSTYPE" in +*solaris*) + GREP=/usr/xpg4/bin/grep + ;; +*) + GREP=grep + ;; +esac +ZOO_DATADIR="$($GREP "^[[:space:]]*dataDir" "$ZOOCFG" | sed -e 's/.*=//')" +ZOO_DATADIR="$(echo -e "${ZOO_DATADIR}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" +ZOO_DATALOGDIR="$($GREP "^[[:space:]]*dataLogDir" "$ZOOCFG" | sed -e 's/.*=//')" # iff autocreate is turned off and the datadirs don't exist fail # immediately as we can't create the PID file, etc..., anyway. @@ -112,7 +143,8 @@ if [ ! -w "$ZOO_LOG_DIR" ] ; then mkdir -p "$ZOO_LOG_DIR" fi -_ZOO_DAEMON_OUT="$ZOO_LOG_DIR/zookeeper.out" +ZOO_LOG_FILE=zookeeper-$USER-server-$HOSTNAME.log +_ZOO_DAEMON_OUT="$ZOO_LOG_DIR/zookeeper-$USER-server-$HOSTNAME.out" case $1 in start) @@ -120,18 +152,33 @@ start) if [ -f "$ZOOPIDFILE" ]; then if kill -0 `cat "$ZOOPIDFILE"` > /dev/null 2>&1; then echo $command already running as process `cat "$ZOOPIDFILE"`. - exit 0 + exit 1 fi fi nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \ - "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ + "-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ + -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' \ -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 + pid=$(cat "${ZOOPIDFILE}") + if ps -p "${pid}" > /dev/null 2>&1; then + echo STARTED + else + echo FAILED TO START + exit 1 + fi else echo FAILED TO WRITE PID exit 1 @@ -147,11 +194,15 @@ start-foreground) ZOO_CMD=("$JAVA") fi "${ZOO_CMD[@]}" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \ - "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ + "-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ + -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' \ -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" ;; print-cmd) - echo "\"$JAVA\" $ZOO_DATADIR_AUTOCREATE -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\" $ZOO_DATADIR_AUTOCREATE -Dzookeeper.log.dir=\"${ZOO_LOG_DIR}\" \ + -Dzookeeper.log.file=\"${ZOO_LOG_FILE}\" -Dzookeeper.root.logger=\"${ZOO_LOG4J_PROP}\" \ + -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' \ + -cp \"$CLASSPATH\" $JVMFLAGS $ZOOMAIN \"$ZOOCFG\" > \"$_ZOO_DAEMON_OUT\" 2>&1 < /dev/null" ;; stop) echo -n "Stopping zookeeper ... " @@ -159,7 +210,7 @@ stop) then echo "no zookeeper to stop (could not find file $ZOOPIDFILE)" else - $KILL -9 $(cat "$ZOOPIDFILE") + $KILL $(cat "$ZOOPIDFILE") rm "$ZOOPIDFILE" echo STOPPED fi @@ -173,29 +224,44 @@ restart) ;; status) # -q is necessary on some versions of linux where nc returns too quickly, and no stat result is output - clientPortAddress=`grep "^[[:space:]]*clientPortAddress[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.*=//'` + clientPortAddress=`$GREP "^[[:space:]]*clientPortAddress[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.*=//'` if ! [ $clientPortAddress ] then clientPortAddress="localhost" fi - clientPort=`grep "^[[:space:]]*clientPort[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.*=//'` - if ! [ $clientPort ] + clientPort=`$GREP "^[[:space:]]*clientPort[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.*=//'` + if ! [[ "$clientPort" =~ ^[0-9]+$ ]] then - echo "Client port not found in static config file. Looking in dynamic config file." - dataDir=`grep "^[[:space:]]*dataDir" "$ZOOCFG" | sed -e 's/.*=//'` + dataDir=`$GREP "^[[:space:]]*dataDir" "$ZOOCFG" | sed -e 's/.*=//'` myid=`cat "$dataDir/myid"` - dynamicConfigFile=`grep "^[[:space:]]*dynamicConfigFile" "$ZOOCFG" | sed -e 's/.*=//'` - clientPort=`grep "^[[:space:]]*server.$myid" "$dynamicConfigFile" | sed -e 's/.*=//' | sed -e 's/.*;//' | sed -e 's/.*://'` - if ! [[ "$clientPort" =~ ^[0-9]+$ ]] ; then + if ! [[ "$myid" =~ ^[0-9]+$ ]] ; then + echo "clientPort not found and myid could not be determined. Terminating." + exit 1 + fi + clientPortAndAddress=`$GREP "^[[:space:]]*server.$myid=.*;.*" "$ZOOCFG" | sed -e 's/.*=//' | sed -e 's/.*;//'` + if [ ! "$clientPortAndAddress" ] ; then + echo "Client port not found in static config file. Looking in dynamic config file." + dynamicConfigFile=`$GREP "^[[:space:]]*dynamicConfigFile" "$ZOOCFG" | sed -e 's/.*=//'` + clientPortAndAddress=`$GREP "^[[:space:]]*server.$myid=.*;.*" "$dynamicConfigFile" | sed -e 's/.*=//' | sed -e 's/.*;//'` + fi + if [ ! "$clientPortAndAddress" ] ; then + echo "Client port not found. Terminating." + exit 1 + fi + if [[ "$clientPortAndAddress" =~ ^.*:[0-9]+ ]] ; then + clientPortAddress=`echo "$clientPortAndAddress" | sed -e 's/:.*//'` + fi + clientPort=`echo "$clientPortAndAddress" | sed -e 's/.*://'` + if [ ! "$clientPort" ] ; then echo "Client port not found. Terminating." exit 1 fi fi - echo "Client port found: $clientPort" - STAT=`"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ + echo "Client port found: $clientPort. Client address: $clientPortAddress." + STAT=`"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-Dzookeeper.log.file=${ZOO_LOG_FILE}" \ -cp "$CLASSPATH" $JVMFLAGS org.apache.zookeeper.client.FourLetterWordMain \ $clientPortAddress $clientPort srvr 2> /dev/null \ - | grep Mode` + | $GREP Mode` if [ "x$STAT" = "x" ] then echo "Error contacting service. It is probably not running." @@ -206,6 +272,6 @@ status) fi ;; *) - echo "Usage: $0 [--config ] {start|start-foreground|stop|restart|status|upgrade|print-cmd}" >&2 + echo "Usage: $0 [--config ] {start|start-foreground|stop|restart|status|print-cmd}" >&2 esac diff --git a/bin/zkTxnLogToolkit.cmd b/bin/zkTxnLogToolkit.cmd new file mode 100755 index 00000000000..362dc44b027 --- /dev/null +++ b/bin/zkTxnLogToolkit.cmd @@ -0,0 +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.persistence.TxnLogToolkit +call %JAVA% -cp "%CLASSPATH%" %ZOOMAIN% %* + +endlocal + diff --git a/src/packages/deb/zookeeper.control/preinst b/bin/zkTxnLogToolkit.sh old mode 100644 new mode 100755 similarity index 56% rename from src/packages/deb/zookeeper.control/preinst rename to bin/zkTxnLogToolkit.sh index d4ca7f7130f..8beed20ddd7 --- a/src/packages/deb/zookeeper.control/preinst +++ b/bin/zkTxnLogToolkit.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 @@ -15,6 +15,24 @@ # See the License for the specific language governing permissions and # limitations under the License. -getent group hadoop 2>/dev/null >/dev/null || /usr/sbin/groupadd -r hadoop +# +# If this scripted is run out of /usr/bin or some other system bin directory +# it should be linked to and not copied. Things like java jar files are found +# relative to the canonical path of this script. +# + +# use POSIX 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 + +"$JAVA" -cp "$CLASSPATH" $JVMFLAGS \ + org.apache.zookeeper.server.persistence.TxnLogToolkit "$@" + -/usr/sbin/useradd --comment "ZooKeeper" --shell /bin/bash -M -r --groups hadoop --home /usr/share/zookeeper zookeeper 2> /dev/null || : diff --git a/build.xml b/build.xml index e1ba3e6a99d..c109e2c851d 100644 --- a/build.xml +++ b/build.xml @@ -20,7 +20,8 @@ +xmlns:maven="antlib:org.apache.maven.artifact.ant" +xmlns:cs="antlib:com.puppycrawl.tools.checkstyle.ant"> @@ -31,16 +32,17 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - - + + - - + + + @@ -77,14 +79,18 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + + + + @@ -107,17 +113,20 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + + value="https://repo1.maven.org/maven2/org/apache/ivy/ivy" /> + + + - - + + + @@ -167,17 +177,6 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - - - - - - - - - - - @@ -196,6 +195,41 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -210,25 +244,29 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - - + - - + + - - + + - + + + + + + + - @@ -236,13 +274,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - - - - - - - + @@ -265,25 +297,24 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + - + + includes="org/apache/jute/**" debug="on" encoding="${build.encoding}" classpath="${ivy.lib}/audience-annotations-${audience-annotations.version}.jar"> @@ -323,10 +354,10 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + includes="org/apache/zookeeper/version/util/**" debug="on" encoding="${build.encoding}" /> - + @@ -341,7 +372,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + @@ -354,9 +385,9 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + + target="${javac.target}" source="${javac.source}" debug="on" encoding="${build.encoding}" classpath="${ivy.lib}/audience-annotations-${audience-annotations.version}.jar" /> @@ -398,12 +429,29 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> pattern="${ivy.jdiff.lib}/[artifact]-[revision].[ext]"/> + + + + + + pattern="${ivy.releaseaudit.lib}/[artifact]-[revision].[ext]"/> + + + + + + + + + + @@ -429,7 +477,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + target="${javac.target}" source="${javac.source}" debug="on" encoding="${build.encoding}"> @@ -439,11 +487,11 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + target="${javac.target}" source="${javac.source}" debug="on" encoding="${build.encoding}"> + target="${javac.target}" source="${javac.source}" debug="on" encoding="${build.encoding}"> @@ -478,7 +526,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - @@ -530,23 +578,23 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> 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" > - - - - - - - - - - + + + + - - - + + + + + + + @@ -581,7 +629,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + @@ -599,7 +647,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + @@ -622,7 +670,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + @@ -640,7 +688,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + @@ -759,7 +807,8 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + pomfile="${dist.maven.dir}/${final.name}.pom" + templatefile="${basedir}/src/pom.template" conf="default,test" > @@ -785,16 +834,19 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - - - + + + + + + @@ -824,12 +876,10 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - - @@ -864,7 +914,8 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + pomfile="${dist.maven.dir}/${name}.pom" + templatefile="${basedir}/src/pom.template"> @@ -900,27 +951,20 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - - - + + + + + + - - - - - - - - /tmp/zookeeper - ${VAR_DIR}/data - @@ -1016,140 +1060,6 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1237,7 +1147,6 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - @@ -1260,6 +1169,8 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + + @@ -1272,67 +1183,129 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + Running ${test.junit.threads} concurrent JUnit processes. - - - - - - - - - + + + - - - - + - - - + - + + + + + Tests failed! + + Running single JUnit process. Upgrade to Ant 1.9.4 or later to run multiple JUnit processes. + + + + + + + + + + + + + + + + + @@ -1427,11 +1400,11 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + - + - + @@ -1469,12 +1442,14 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - + + + - + @@ -1571,7 +1546,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - Tests failed! + @@ -1586,32 +1561,20 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - @@ -1621,9 +1584,33 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> classpathref="releaseaudit-classpath"/> + + + + + + + + + + + + + + + + + + + + @@ -1642,6 +1629,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + @@ -1730,6 +1718,27 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> + + + + + + + + + + + + + + + + + + + + + @@ -1797,7 +1806,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> - @@ -1818,7 +1827,7 @@ xmlns:maven="antlib:org.apache.maven.artifact.ant"> description="Create eclipse project files"> - + diff --git a/conf/log4j.properties b/conf/log4j.properties index 61cf5759cbc..4a2ede95503 100644 --- a/conf/log4j.properties +++ b/conf/log4j.properties @@ -27,7 +27,7 @@ zookeeper.log.threshold=INFO zookeeper.log.maxfilesize=256MB zookeeper.log.maxbackupindex=20 -zookeeper.tracelog.dir=. +zookeeper.tracelog.dir=${zookeeper.log.dir} zookeeper.tracelog.file=zookeeper_trace.log log4j.rootLogger=${zookeeper.root.logger} diff --git a/docs/index.html b/docs/index.html index aa8a571412d..8174a8632f8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -67,7 +67,7 @@ Wiki
  • -ZooKeeper 3.4 Documentation +ZooKeeper 3.5 Documentation
  • + + +
    + + + + + + + + + + + + +
    + +
    +
    + +
    + + +
    + +
    + +   +
    + + + + + +
    + +

    ZooKeeper Dynamic Reconfiguration

    + + + + + + +

    Overview

    +
    +

    Prior to the 3.5.0 release, the membership and all other configuration + parameters of Zookeeper were static - loaded during boot and immutable at + runtime. Operators resorted to ''rolling restarts'' - a manually intensive + and error-prone method of changing the configuration that has caused data + loss and inconsistency in production.

    +

    Starting with 3.5.0, “rolling restarts” are no longer needed! + ZooKeeper comes with full support for automated configuration changes: the + set of Zookeeper servers, their roles (participant / observer), all ports, + and even the quorum system can be changed dynamically, without service + interruption and while maintaining data consistency. Reconfigurations are + performed immediately, just like other operations in ZooKeeper. Multiple + changes can be done using a single reconfiguration command. The dynamic + reconfiguration functionality does not limit operation concurrency, does + not require client operations to be stopped during reconfigurations, has a + very simple interface for administrators and no added complexity to other + client operations.

    +

    New client-side features allow clients to find out about configuration + changes and to update the connection string (list of servers and their + client ports) stored in their ZooKeeper handle. A probabilistic algorithm + is used to rebalance clients across the new configuration servers while + keeping the extent of client migrations proportional to the change in + ensemble membership.

    +

    This document provides the administrator manual for reconfiguration. + For a detailed description of the reconfiguration algorithms, performance + measurements, and more, please see our paper:

    +
    + +
    +Shraer, A., Reed, B., Malkhi, D., Junqueira, F. Dynamic + Reconfiguration of Primary/Backup Clusters. In USENIX Annual + Technical Conference (ATC) (2012), 425-437 +
    +
    +

    Links: paper (pdf), slides (pdf), video, hadoop summit slides +

    +
    + +
    +

    +Note: Starting with 3.5.3, the dynamic reconfiguration + feature is disabled by default, and has to be explicitly turned on via + + reconfigEnabled configuration option. +

    +
    + + +

    Changes to Configuration Format

    +
    + +

    Specifying the client port

    +

    A client port of a server is the port on which the server accepts + client connection requests. Starting with 3.5.0 the + clientPort and clientPortAddress + configuration parameters should no longer be used. Instead, + this information is now part of the server keyword specification, which + becomes as follows:

    +

    +server.<positive id> = <address1>:<port1>:<port2>[:role];[<client port address>:]<client port> +

    +

    The client port specification is to the right of the semicolon. The + client port address is optional, and if not specified it defaults to + "0.0.0.0". As usual, role is also optional, it can be + participant or observer + (participant by default).

    +

    Examples of legal server statements:

    +
      + +
    • + +

      +server.5 = 125.23.63.23:1234:1235;1236 +

      + +
    • + +
    • + +

      +server.5 = 125.23.63.23:1234:1235:participant;1236 +

      + +
    • + +
    • + +

      +server.5 = 125.23.63.23:1234:1235:observer;1236 +

      + +
    • + +
    • + +

      +server.5 = 125.23.63.23:1234:1235;125.23.63.24:1236 +

      + +
    • + +
    • + +

      +server.5 = 125.23.63.23:1234:1235:participant;125.23.63.23:1236 +

      + +
    • + +
    + +

    The standaloneEnabled flag

    +

    Prior to 3.5.0, one could run ZooKeeper in Standalone mode or in a + Distributed mode. These are separate implementation stacks, and + switching between them during run time is not possible. By default (for + backward compatibility) standaloneEnabled is set to + true. The consequence of using this default is that + if started with a single server the ensemble will not be allowed to + grow, and if started with more than one server it will not be allowed to + shrink to contain fewer than two participants.

    +

    Setting the flag to false instructs the system + to run the Distributed software stack even if there is only a single + participant in the ensemble. To achieve this the (static) configuration + file should contain:

    +

    +standaloneEnabled=false +

    +

    With this setting it is possible to start a ZooKeeper ensemble + containing a single participant and to dynamically grow it by adding + more servers. Similarly, it is possible to shrink an ensemble so that + just a single participant remains, by removing servers.

    +

    Since running the Distributed mode allows more flexibility, we + recommend setting the flag to false. We expect that + the legacy Standalone mode will be deprecated in the future.

    + +

    The reconfigEnabled flag

    +

    Starting with 3.5.0 and prior to 3.5.3, there is no way to disable + dynamic reconfiguration feature. We would like to offer the option of + disabling reconfiguration feature because with reconfiguration enabled, + we have a security concern that a malicious actor can make arbitrary changes + to the configuration of a ZooKeeper ensemble, including adding a compromised + server to the ensemble. We prefer to leave to the discretion of the user to + decide whether to enable it or not and make sure that the appropriate security + measure are in place. So in 3.5.3 the + reconfigEnabled configuration option is introduced + such that the reconfiguration feature can be completely disabled and any attempts + to reconfigure a cluster through reconfig API with or without authentication + will fail by default, unless reconfigEnabled is set to + true. +

    +

    To set the option to true, the configuration file (zoo.cfg) should contain:

    +

    +reconfigEnabled=true +

    + +

    Dynamic configuration file

    +

    Starting with 3.5.0 we're distinguishing between dynamic + configuration parameters, which can be changed during runtime, and + static configuration parameters, which are read from a configuration + file when a server boots and don't change during its execution. For now, + the following configuration keywords are considered part of the dynamic + configuration: server, group + and weight.

    +

    Dynamic configuration parameters are stored in a separate file on + the server (which we call the dynamic configuration file). This file is + linked from the static config file using the new + dynamicConfigFile keyword.

    +

    +Example +

    +
    +
    zoo_replicated1.cfg
    +
    + +zoo_replicated1.cfg + +
    tickTime=2000
    +dataDir=/zookeeper/data/zookeeper1
    +initLimit=5
    +syncLimit=2
    +dynamicConfigFile=/zookeeper/conf/zoo_replicated1.cfg.dynamic
    + +
    +
    +
    +
    zoo_replicated1.cfg.dynamic
    +
    + +zoo_replicated1.cfg.dynamic + +
    server.1=125.23.63.23:2780:2783:participant;2791
    +server.2=125.23.63.24:2781:2784:participant;2792
    +server.3=125.23.63.25:2782:2785:participant;2793
    + +
    +
    +

    When the ensemble configuration changes, the static configuration + parameters remain the same. The dynamic parameters are pushed by + ZooKeeper and overwrite the dynamic configuration files on all servers. + Thus, the dynamic configuration files on the different servers are + usually identical (they can only differ momentarily when a + reconfiguration is in progress, or if a new configuration hasn't + propagated yet to some of the servers). Once created, the dynamic + configuration file should not be manually altered. Changed are only made + through the new reconfiguration commands outlined below. Note that + changing the config of an offline cluster could result in an + inconsistency with respect to configuration information stored in the + ZooKeeper log (and the special configuration znode, populated from the + log) and is therefore highly discouraged.

    +

    +Example 2 +

    +

    Users may prefer to initially specify a single configuration file. + The following is thus also legal:

    +
    +
    zoo_replicated1.cfg
    +
    + +zoo_replicated1.cfg + +
    tickTime=2000
    +dataDir=/zookeeper/data/zookeeper1
    +initLimit=5
    +syncLimit=2
    +clientPort=2791  // note that this line is now redundant and therefore not recommended
    +server.1=125.23.63.23:2780:2783:participant;2791
    +server.2=125.23.63.24:2781:2784:participant;2792
    +server.3=125.23.63.25:2782:2785:participant;2793
    + +
    +
    +

    The configuration files on each server will be automatically split + into dynamic and static files, if they are not already in this format. + So the configuration file above will be automatically transformed into + the two files in Example 1. Note that the clientPort and + clientPortAddress lines (if specified) will be automatically removed + during this process, if they are redundant (as in the example above). + The original static configuration file is backed up (in a .bak + file).

    + +

    Backward compatibility

    +

    We still support the old configuration format. For example, the + following configuration file is acceptable (but not recommended):

    +
    +
    zoo_replicated1.cfg
    +
    + +zoo_replicated1.cfg + +
    tickTime=2000
    +dataDir=/zookeeper/data/zookeeper1
    +initLimit=5
    +syncLimit=2
    +clientPort=2791
    +server.1=125.23.63.23:2780:2783:participant
    +server.2=125.23.63.24:2781:2784:participant
    +server.3=125.23.63.25:2782:2785:participant
    + +
    +
    +

    During boot, a dynamic configuration file is created and contains + the dynamic part of the configuration as explained earlier. In this + case, however, the line "clientPort=2791" will remain in the static + configuration file of server 1 since it is not redundant -- it was not + specified as part of the "server.1=..." using the format explained in + the section Changes to Configuration Format. If a reconfiguration + is invoked that sets the client port of server 1, we remove + "clientPort=2791" from the static configuration file (the dynamic file + now contain this information as part of the specification of server + 1).

    +
    + + +

    Upgrading to 3.5.0

    +
    +

    Upgrading a running ZooKeeper ensemble to 3.5.0 should be done only + after upgrading your ensemble to the 3.4.6 release. Note that this is only + necessary for rolling upgrades (if you're fine with shutting down the + system completely, you don't have to go through 3.4.6). If you attempt a + rolling upgrade without going through 3.4.6 (for example from 3.4.5), you + may get the following error:

    +
    2013-01-30 11:32:10,663 [myid:2] - INFO [localhost/127.0.0.1:2784:QuorumCnxManager$Listener@498] - Received connection request /127.0.0.1:60876
    +2013-01-30 11:32:10,663 [myid:2] - WARN [localhost/127.0.0.1:2784:QuorumCnxManager@349] - Invalid server id: -65536
    +

    During a rolling upgrade, each server is taken down in turn and + rebooted with the new 3.5.0 binaries. Before starting the server with + 3.5.0 binaries, we highly recommend updating the configuration file so + that all server statements "server.x=..." contain client ports (see the + section Specifying the client port). As explained earlier + you may leave the configuration in a single file, as well as leave the + clientPort/clientPortAddress statements (although if you specify client + ports in the new format, these statements are now redundant).

    +
    + + + +

    Dynamic Reconfiguration of the ZooKeeper Ensemble

    +
    +

    The ZooKeeper Java and C API were extended with getConfig and reconfig + commands that facilitate reconfiguration. Both commands have a synchronous + (blocking) variant and an asynchronous one. We demonstrate these commands + here using the Java CLI, but note that you can similarly use the C CLI or + invoke the commands directly from a program just like any other ZooKeeper + command.

    + +

    API

    +

    There are two sets of APIs for both Java and C client. +

    +
    + +
    + +Reconfiguration API + +
    +
    +

    Reconfiguration API is used to reconfigure the ZooKeeper cluster. + Starting with 3.5.3, reconfiguration Java APIs are moved into ZooKeeperAdmin class + from ZooKeeper class, and use of this API requires ACL setup and user + authentication (see Security for more information.). +

    +

    Note: for temporary backward compatibility, the reconfig() APIs will remain in ZooKeeper.java + where they were for a few alpha versions of 3.5.x. However, these APIs are deprecated and users + should move to the reconfigure() APIs in ZooKeeperAdmin.java. +

    +
    + + +
    + +Get Configuration API + +
    +
    +

    Get configuration APIs are used to retrieve ZooKeeper cluster configuration information + stored in /zookeeper/config znode. Use of this API does not require specific setup or authentication, + because /zookeeper/config is readable to any users.

    +
    + +
    + +

    Security

    +

    Prior to 3.5.3, there is no enforced security mechanism + over reconfig so any ZooKeeper clients that can connect to ZooKeeper server ensemble + will have the ability to change the state of a ZooKeeper cluster via reconfig. + It is thus possible for a malicious client to add compromised server to an ensemble, + e.g., add a compromised server, or remove legitimate servers. + Cases like these could be security vulnerabilities on a case by case basis. +

    +

    To address this security concern, we introduced access control over reconfig + starting from 3.5.3 such that only a specific set of users + can use reconfig commands or APIs, and these users need be configured explicitly. In addition, + the setup of ZooKeeper cluster must enable authentication so ZooKeeper clients can be authenticated. +

    +

    + We also provides an escape hatch for users who operate and interact with a ZooKeeper ensemble in a secured + environment (i.e. behind company firewall). For those users who want to use reconfiguration feature but + don't want the overhead of configuring an explicit list of authorized user for reconfig access checks, + they can set "skipACL" to "yes" which will + skip ACL check and allow any user to reconfigure cluster. +

    +

    + Overall, ZooKeeper provides flexible configuration options for the reconfigure feature + that allow a user to choose based on user's security requirement. + We leave to the discretion of the user to decide appropriate security measure are in place. +

    +
    + +
    + +Access Control + +
    +
    +

    The dynamic configuration is stored in a special znode + ZooDefs.CONFIG_NODE = /zookeeper/config. This node by default is read only + for all users, except super user and users that's explicitly configured for write + access. +

    +

    Clients that need to use reconfig commands or reconfig API should be configured as users + that have write access to CONFIG_NODE. By default, only the super user has full control including + write access to CONFIG_NODE. Additional users can be granted write access through superuser + by setting an ACL that has write permission associated with specified user. +

    +

    A few examples of how to setup ACLs and use reconfiguration API with authentication can be found in + ReconfigExceptionTest.java and TestReconfigServer.cc.

    +
    + + +
    + +Authentication + +
    +
    +

    Authentication of users is orthogonal to the access control and is delegated to + existing authentication mechanism supported by ZooKeeper's pluggable authentication schemes. + See ZooKeeper and SASL for more details on this topic. +

    +
    + + +
    + +Disable ACL check + +
    +
    +

    + ZooKeeper supports "skipACL" option such that ACL + check will be completely skipped, if skipACL is set to "yes". In such cases any unauthenticated + users can use reconfig API. +

    +
    + +
    + +

    Retrieving the current dynamic configuration

    +

    The dynamic configuration is stored in a special znode + ZooDefs.CONFIG_NODE = /zookeeper/config. The new + config CLI command reads this znode (currently it is + simply a wrapper to get /zookeeper/config). As with + normal reads, to retrieve the latest committed value you should do a + sync first.

    +
    [zk: 127.0.0.1:2791(CONNECTED) 3] config
    +server.1=localhost:2780:2783:participant;localhost:2791
    +server.2=localhost:2781:2784:participant;localhost:2792
    +server.3=localhost:2782:2785:participant;localhost:2793
    +version=400000003
    +
    +

    Notice the last line of the output. This is the configuration + version. The version equals to the zxid of the reconfiguration command + which created this configuration. The version of the first established + configuration equals to the zxid of the NEWLEADER message sent by the + first successfully established leader. When a configuration is written + to a dynamic configuration file, the version automatically becomes part + of the filename and the static configuration file is updated with the + path to the new dynamic configuration file. Configuration files + corresponding to earlier versions are retained for backup + purposes.

    +

    During boot time the version (if it exists) is extracted from the + filename. The version should never be altered manually by users or the + system administrator. It is used by the system to know which + configuration is most up-to-date. Manipulating it manually can result in + data loss and inconsistency.

    +

    Just like a get command, the + config CLI command accepts the -w + flag for setting a watch on the znode, and -s flag for + displaying the Stats of the znode. It additionally accepts a new flag + -c which outputs only the version and the client + connection string corresponding to the current configuration. For + example, for the configuration above we would get:

    +
    [zk: 127.0.0.1:2791(CONNECTED) 17] config -c
    +400000003 localhost:2791,localhost:2793,localhost:2792
    +

    Note that when using the API directly, this command is called + getConfig.

    +

    As any read command it returns the configuration known to the + follower to which your client is connected, which may be slightly + out-of-date. One can use the sync command for + stronger guarantees. For example using the Java API:

    +
    zk.sync(ZooDefs.CONFIG_NODE, void_callback, context);
    +zk.getConfig(watcher, callback, context);
    +

    Note: in 3.5.0 it doesn't really matter which path is passed to the + sync() command as all the server's state is brought + up to date with the leader (so one could use a different path instead of + ZooDefs.CONFIG_NODE). However, this may change in the future.

    + +

    Modifying the current dynamic configuration

    +

    Modifying the configuration is done through the + reconfig command. There are two modes of + reconfiguration: incremental and non-incremental (bulk). The + non-incremental simply specifies the new dynamic configuration of the + system. The incremental specifies changes to the current configuration. + The reconfig command returns the new + configuration.

    +

    A few examples are in: ReconfigTest.java, + ReconfigRecoveryTest.java and + TestReconfigServer.cc.

    + +

    General

    +

    +Removing servers: Any server can + be removed, including the leader (although removing the leader will + result in a short unavailability, see Figures 6 and 8 in the paper). The server will not be shut-down automatically. + Instead, it becomes a "non-voting follower". This is somewhat similar + to an observer in that its votes don't count towards the Quorum of + votes necessary to commit operations. However, unlike a non-voting + follower, an observer doesn't actually see any operation proposals and + does not ACK them. Thus a non-voting follower has a more significant + negative effect on system throughput compared to an observer. + Non-voting follower mode should only be used as a temporary mode, + before shutting the server down, or adding it as a follower or as an + observer to the ensemble. We do not shut the server down automatically + for two main reasons. The first reason is that we do not want all the + clients connected to this server to be immediately disconnected, + causing a flood of connection requests to other servers. Instead, it + is better if each client decides when to migrate independently. The + second reason is that removing a server may sometimes (rarely) be + necessary in order to change it from "observer" to "participant" (this + is explained in the section Additional comments).

    +

    Note that the new configuration should have some minimal number of + participants in order to be considered legal. If the proposed change + would leave the cluster with less than 2 participants and standalone + mode is enabled (standaloneEnabled=true, see the section The standaloneEnabled flag), the reconfig will not be + processed (BadArgumentsException). If standalone mode is disabled + (standaloneEnabled=false) then its legal to remain with 1 or more + participants.

    +

    +Adding servers: Before a + reconfiguration is invoked, the administrator must make sure that a + quorum (majority) of participants from the new configuration are + already connected and synced with the current leader. To achieve this + we need to connect a new joining server to the leader before it is + officially part of the ensemble. This is done by starting the joining + server using an initial list of servers which is technically not a + legal configuration of the system but (a) contains the joiner, and (b) + gives sufficient information to the joiner in order for it to find and + connect to the current leader. We list a few different options of + doing this safely.

    +
      + +
    1. + +

      Initial configuration of joiners is comprised of servers in + the last committed configuration and one or more joiners, where + joiners are listed as observers. + For example, if servers D and E are added at the same time to (A, + B, C) and server C is being removed, the initial configuration of + D could be (A, B, C, D) or (A, B, C, D, E), where D and E are + listed as observers. Similarly, the configuration of E could be + (A, B, C, E) or (A, B, C, D, E), where D and E are listed as + observers. Note that listing the joiners as + observers will not actually make them observers - it will only + prevent them from accidentally forming a quorum with other + joiners. Instead, they will contact the servers in the + current configuration and adopt the last committed configuration + (A, B, C), where the joiners are absent. Configuration files of + joiners are backed up and replaced automatically as this happens. + After connecting to the current leader, joiners become non-voting + followers until the system is reconfigured and they are added to + the ensemble (as participant or observer, as appropriate).

      + +
    2. + +
    3. + +

      Initial configuration of each joiner is comprised of servers + in the last committed configuration + the + joiner itself, listed as a participant. For example, to + add a new server D to a configuration consisting of servers (A, B, + C), the administrator can start D using an initial configuration + file consisting of servers (A, B, C, D). If both D and E are added + at the same time to (A, B, C), the initial configuration of D + could be (A, B, C, D) and the configuration of E could be (A, B, + C, E). Similarly, if D is added and C is removed at the same time, + the initial configuration of D could be (A, B, C, D). Never list + more than one joiner as participant in the initial configuration + (see warning below).

      + +
    4. + +
    5. + +

      Whether listing the joiner as an observer or as participant, + it is also fine not to list all the current configuration servers, + as long as the current leader is in the list. For example, when + adding D we could start D with a configuration file consisting of + just (A, D) if A is the current leader. however this is more + fragile since if A fails before D officially joins the ensemble, D + doesn’t know anyone else and therefore the administrator will have + to intervene and restart D with another server list.

      + +
    6. + +
    +
    +
    Warning
    +
    + +Warning + +

    Never specify more than one joining server in the same initial + configuration as participants. Currently, the joining servers don’t + know that they are joining an existing ensemble; if multiple joiners + are listed as participants they may form an independent quorum + creating a split-brain situation such as processing operations + independently from your main ensemble. It is OK to list multiple + joiners as observers in an initial config.

    + +
    +
    +

    If the configuration of existing servers changes or they become unavailable + before the joiner succeeds to connect and learn obout configuration changes, the + joiner may need to be restarted with an updated configuration file in order to be + able to connect.

    +

    Finally, note that once connected to the leader, a joiner adopts + the last committed configuration, in which it is absent (the initial + config of the joiner is backed up before being rewritten). If the + joiner restarts in this state, it will not be able to boot since it is + absent from its configuration file. In order to start it you’ll once + again have to specify an initial configuration.

    +

    +Modifying server parameters: One + can modify any of the ports of a server, or its role + (participant/observer) by adding it to the ensemble with different + parameters. This works in both the incremental and the bulk + reconfiguration modes. It is not necessary to remove the server and + then add it back; just specify the new parameters as if the server is + not yet in the system. The server will detect the configuration change + and perform the necessary adjustments. See an example in the section + Incremental mode and an exception to this + rule in the section Additional comments.

    +

    It is also possible to change the Quorum System used by the + ensemble (for example, change the Majority Quorum System to a + Hierarchical Quorum System on the fly). This, however, is only allowed + using the bulk (non-incremental) reconfiguration mode. In general, + incremental reconfiguration only works with the Majority Quorum + System. Bulk reconfiguration works with both Hierarchical and Majority + Quorum Systems.

    +

    +Performance Impact: There is + practically no performance impact when removing a follower, since it + is not being automatically shut down (the effect of removal is that + the server's votes are no longer being counted). When adding a server, + there is no leader change and no noticeable performance disruption. + For details and graphs please see Figures 6, 7 and 8 in the paper.

    +

    The most significant disruption will happen when a leader change + is caused, in one of the following cases:

    +
      + +
    1. + +

      Leader is removed from the ensemble.

      + +
    2. + +
    3. + +

      Leader's role is changed from participant to observer.

      + +
    4. + +
    5. + +

      The port used by the leader to send transactions to others + (quorum port) is modified.

      + +
    6. + +
    +

    In these cases we perform a leader hand-off where the old leader + nominates a new leader. The resulting unavailability is usually + shorter than when a leader crashes since detecting leader failure is + unnecessary and electing a new leader can usually be avoided during a + hand-off (see Figures 6 and 8 in the paper).

    +

    When the client port of a server is modified, it does not drop + existing client connections. New connections to the server will have + to use the new client port.

    +

    +Progress guarantees: Up to the + invocation of the reconfig operation, a quorum of the old + configuration is required to be available and connected for ZooKeeper + to be able to make progress. Once reconfig is invoked, a quorum of + both the old and of the new configurations must be available. The + final transition happens once (a) the new configuration is activated, + and (b) all operations scheduled before the new configuration is + activated by the leader are committed. Once (a) and (b) happen, only a + quorum of the new configuration is required. Note, however, that + neither (a) nor (b) are visible to a client. Specifically, when a + reconfiguration operation commits, it only means that an activation + message was sent out by the leader. It does not necessarily mean that + a quorum of the new configuration got this message (which is required + in order to activate it) or that (b) has happened. If one wants to + make sure that both (a) and (b) has already occurred (for example, in + order to know that it is safe to shut down old servers that were + removed), one can simply invoke an update + (set-data, or some other quorum operation, but not + a sync) and wait for it to commit. An alternative + way to achieve this was to introduce another round to the + reconfiguration protocol (which, for simplicity and compatibility with + Zab, we decided to avoid).

    + +

    Incremental mode

    +

    The incremental mode allows adding and removing servers to the + current configuration. Multiple changes are allowed. For + example:

    +

    +> reconfig -remove 3 -add + server.5=125.23.63.23:1234:1235;1236 +

    +

    Both the add and the remove options get a list of comma separated + arguments (no spaces):

    +

    +> reconfig -remove 3,4 -add + server.5=localhost:2111:2112;2113,6=localhost:2114:2115:observer;2116 +

    +

    The format of the server statement is exactly the same as + described in the section Specifying the client port and + includes the client port. Notice that here instead of "server.5=" you + can just say "5=". In the example above, if server 5 is already in the + system, but has different ports or is not an observer, it is updated + and once the configuration commits becomes an observer and starts + using these new ports. This is an easy way to turn participants into + observers and vise versa or change any of their ports, without + rebooting the server.

    +

    ZooKeeper supports two types of Quorum Systems – the simple + Majority system (where the leader commits operations after receiving + ACKs from a majority of voters) and a more complex Hierarchical + system, where votes of different servers have different weights and + servers are divided into voting groups. Currently, incremental + reconfiguration is allowed only if the last proposed configuration + known to the leader uses a Majority Quorum System + (BadArgumentsException is thrown otherwise).

    +

    Incremental mode - examples using the Java API:

    +
    List<String> leavingServers = new ArrayList<String>();
    +leavingServers.add("1");
    +leavingServers.add("2");
    +byte[] config = zk.reconfig(null, leavingServers, null, -1, new Stat());
    +
    List<String> leavingServers = new ArrayList<String>();
    +List<String> joiningServers = new ArrayList<String>();
    +leavingServers.add("1");
    +joiningServers.add("server.4=localhost:1234:1235;1236");
    +byte[] config = zk.reconfig(joiningServers, leavingServers, null, -1, new Stat());
    +
    +String configStr = new String(config);
    +System.out.println(configStr);
    +

    There is also an asynchronous API, and an API accepting comma + separated Strings instead of List<String>. See + src/java/main/org/apache/zookeeper/ZooKeeper.java.

    + +

    Non-incremental mode

    +

    The second mode of reconfiguration is non-incremental, whereby a + client gives a complete specification of the new dynamic system + configuration. The new configuration can either be given in place or + read from a file:

    +

    +> reconfig -file newconfig.cfg + //newconfig.cfg is a dynamic config file, see Dynamic configuration file +

    +

    +> reconfig -members + server.1=125.23.63.23:2780:2783:participant;2791,server.2=125.23.63.24:2781:2784:participant;2792,server.3=125.23.63.25:2782:2785:participant;2793 +

    +

    The new configuration may use a different Quorum System. For + example, you may specify a Hierarchical Quorum System even if the + current ensemble uses a Majority Quorum System.

    +

    Bulk mode - example using the Java API:

    +
    ArrayList<String> newMembers = new ArrayList<String>();
    +newMembers.add("server.1=1111:1234:1235;1236");
    +newMembers.add("server.2=1112:1237:1238;1239");
    +newMembers.add("server.3=1114:1240:1241:observer;1242");
    +
    +byte[] config = zk.reconfig(null, null, newMembers, -1, new Stat());
    +
    +String configStr = new String(config);
    +System.out.println(configStr);
    +

    There is also an asynchronous API, and an API accepting comma + separated String containing the new members instead of + List<String>. See + src/java/main/org/apache/zookeeper/ZooKeeper.java.

    + +

    Conditional reconfig

    +

    Sometimes (especially in non-incremental mode) a new proposed + configuration depends on what the client "believes" to be the current + configuration, and should be applied only to that configuration. + Specifically, the reconfig succeeds only if the + last configuration at the leader has the specified version.

    +

    +> reconfig -file <filename> -v <version> +

    +

    In the previously listed Java examples, instead of -1 one could + specify a configuration version to condition the + reconfiguration.

    + +

    Error conditions

    +

    In addition to normal ZooKeeper error conditions, a + reconfiguration may fail for the following reasons:

    +
      + +
    1. + +

      another reconfig is currently in progress + (ReconfigInProgress)

      + +
    2. + +
    3. + +

      the proposed change would leave the cluster with less than 2 + participants, in case standalone mode is enabled, or, if + standalone mode is disabled then its legal to remain with 1 or + more participants (BadArgumentsException)

      + +
    4. + +
    5. + +

      no quorum of the new configuration was connected and + up-to-date with the leader when the reconfiguration processing + began (NewConfigNoQuorum)

      + +
    6. + +
    7. + +

      +-v x was specified, but the version + y of the latest configuration is not + x (BadVersionException)

      + +
    8. + +
    9. + +

      an incremental reconfiguration was requested but the last + configuration at the leader uses a Quorum System which is + different from the Majority system (BadArgumentsException)

      + +
    10. + +
    11. + +

      syntax error (BadArgumentsException)

      + +
    12. + +
    13. + +

      I/O exception when reading the configuration from a file + (BadArgumentsException)

      + +
    14. + +
    +

    Most of these are illustrated by test-cases in + ReconfigFailureCases.java.

    + +

    Additional comments

    +

    +Liveness: To better understand + the difference between incremental and non-incremental + reconfiguration, suppose that client C1 adds server D to the system + while a different client C2 adds server E. With the non-incremental + mode, each client would first invoke config to find + out the current configuration, and then locally create a new list of + servers by adding its own suggested server. The new configuration can + then be submitted using the non-incremental + reconfig command. After both reconfigurations + complete, only one of E or D will be added (not both), depending on + which client's request arrives second to the leader, overwriting the + previous configuration. The other client can repeat the process until + its change takes effect. This method guarantees system-wide progress + (i.e., for one of the clients), but does not ensure that every client + succeeds. To have more control C2 may request to only execute the + reconfiguration in case the version of the current configuration + hasn't changed, as explained in the section Conditional reconfig. In this way it may avoid blindly + overwriting the configuration of C1 if C1's configuration reached the + leader first.

    +

    With incremental reconfiguration, both changes will take effect as + they are simply applied by the leader one after the other to the + current configuration, whatever that is (assuming that the second + reconfig request reaches the leader after it sends a commit message + for the first reconfig request -- currently the leader will refuse to + propose a reconfiguration if another one is already pending). Since + both clients are guaranteed to make progress, this method guarantees + stronger liveness. In practice, multiple concurrent reconfigurations + are probably rare. Non-incremental reconfiguration is currently the + only way to dynamically change the Quorum System. Incremental + configuration is currently only allowed with the Majority Quorum + System.

    +

    +Changing an observer into a + follower: Clearly, changing a server that participates in + voting into an observer may fail if error (2) occurs, i.e., if fewer + than the minimal allowed number of participants would remain. However, + converting an observer into a participant may sometimes fail for a + more subtle reason: Suppose, for example, that the current + configuration is (A, B, C, D), where A is the leader, B and C are + followers and D is an observer. In addition, suppose that B has + crashed. If a reconfiguration is submitted where D is said to become a + follower, it will fail with error (3) since in this configuration, a + majority of voters in the new configuration (any 3 voters), must be + connected and up-to-date with the leader. An observer cannot + acknowledge the history prefix sent during reconfiguration, and + therefore it does not count towards these 3 required servers and the + reconfiguration will be aborted. In case this happens, a client can + achieve the same task by two reconfig commands: first invoke a + reconfig to remove D from the configuration and then invoke a second + command to add it back as a participant (follower). During the + intermediate state D is a non-voting follower and can ACK the state + transfer performed during the second reconfig comand.

    +
    + + +

    Rebalancing Client Connections

    +
    +

    When a ZooKeeper cluster is started, if each client is given the same + connection string (list of servers), the client will randomly choose a + server in the list to connect to, which makes the expected number of + client connections per server the same for each of the servers. We + implemented a method that preserves this property when the set of servers + changes through reconfiguration. See Sections 4 and 5.1 in the paper.

    +

    In order for the method to work, all clients must subscribe to + configuration changes (by setting a watch on /zookeeper/config either + directly or through the getConfig API command). When + the watch is triggered, the client should read the new configuration by + invoking sync and getConfig and if + the configuration is indeed new invoke the + updateServerList API command. To avoid mass client + migration at the same time, it is better to have each client sleep a + random short period of time before invoking + updateServerList.

    +

    A few examples can be found in: + StaticHostProviderTest.java and + TestReconfig.cc +

    +

    Example (this is not a recipe, but a simplified example just to + explain the general idea):

    +
    +public void process(WatchedEvent event) {
    +    synchronized (this) {
    +        if (event.getType() == EventType.None) {
    +            connected = (event.getState() == KeeperState.SyncConnected);
    +            notifyAll();
    +        } else if (event.getPath()!=null &&  event.getPath().equals(ZooDefs.CONFIG_NODE)) {
    +            // in prod code never block the event thread!
    +            zk.sync(ZooDefs.CONFIG_NODE, this, null);
    +            zk.getConfig(this, this, null);
    +        }
    +    }
    +}
    +public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
    +    if (path!=null &&  path.equals(ZooDefs.CONFIG_NODE)) {
    +        String config[] = ConfigUtils.getClientConfigStr(new String(data)).split(" ");   // similar to config -c
    +        long version = Long.parseLong(config[0], 16);
    +        if (this.configVersion == null){
    +             this.configVersion = version;
    +        } else if (version > this.configVersion) {
    +            hostList = config[1];
    +            try {
    +                // the following command is not blocking but may cause the client to close the socket and
    +                // migrate to a different server. In practice its better to wait a short period of time, chosen
    +                // randomly, so that different clients migrate at different times
    +                zk.updateServerList(hostList);
    +            } catch (IOException e) {
    +                System.err.println("Error updating server list");
    +                e.printStackTrace();
    +            }
    +            this.configVersion = version;
    +} } }
    +
    + +

    + +

    +
    + +
     
    +
    + + + diff --git a/docs/zookeeperReconfig.pdf b/docs/zookeeperReconfig.pdf new file mode 100644 index 00000000000..9b292d40028 Binary files /dev/null and b/docs/zookeeperReconfig.pdf differ diff --git a/docs/zookeeperStarted.html b/docs/zookeeperStarted.html index 5485a8f6a65..19be58de0e5 100644 --- a/docs/zookeeperStarted.html +++ b/docs/zookeeperStarted.html @@ -67,7 +67,7 @@ Wiki
  • -ZooKeeper 3.4 Documentation +ZooKeeper 3.5 Documentation
  • - + + @@ -41,45 +42,98 @@ - - - + + + - - + + - - + + + + - + - - - - + - + + + + + + - - + rev="${apache-rat-tasks.version}" conf="releaseaudit->default"> + + + rev="${commons-lang.version}" conf="releaseaudit->default"/> + rev="${commons-collections.version}" 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..efa786f6378 --- /dev/null +++ b/src/LICENSE.txt @@ -0,0 +1,220 @@ + + 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 javacc, which is available under the +3-clause BSD License. For details, see a copy of the license in +lib/javacc.LICENSE.txt + +This distribution bundles jline 2.11, which is available under the +2-clause BSD License. For details, see a copy of the license in +lib/jline-2.11.LICENSE.txt + +This distribution bundles SLF4J 1.7.5, which is available under the MIT +License. For details, see a copy of the license in +lib/slf4j-1.7.5.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..4b29aa7a3d7 --- /dev/null +++ b/src/NOTICE.txt @@ -0,0 +1,101 @@ +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..00d639eac1d --- /dev/null +++ b/src/c/CMakeLists.txt @@ -0,0 +1,249 @@ +# 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.5) + +project(zookeeper VERSION 3.5.4) +set(email user@zookeeper.apache.org) +set(description "zookeeper C client") + +# general options +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) +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}) + +# SOCK_CLOEXEC +option(WANT_SOCK_CLOEXEC "Enables SOCK_CLOEXEC on sockets" OFF) +include(CheckSymbolExists) +check_symbol_exists(SOCK_CLOEXEC sys/socket.h HAVE_SOCK_CLOEXEC) +if(WANT_SOCK_CLOEXEC AND HAVE_SOCK_CLOEXEC) + set(SOCK_CLOEXEC_ENABLED 1) +endif() + + +# 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_CURRENT_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_CURRENT_BINARY_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_include_directories(hashtable PUBLIC include) +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 + src/addrvec.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_include_directories(zookeeper PUBLIC include ${CMAKE_CURRENT_BINARY_DIR}/include generated) +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/TestReconfig.cc + tests/TestReconfigServer.cc + tests/TestClientRetry.cc + tests/TestOperations.cc + tests/TestMulti.cc + tests/TestWatchers.cc + tests/TestClient.cc + tests/ZooKeeperQuorumServer.cc + tests/TestReadOnlyClient.cc) + +if(WANT_SYNCAPI) + list(APPEND test_sources tests/PthreadMocks.cc) +endif() + +if(WANT_CPPUNIT) + add_executable(zktest ${test_sources}) + target_include_directories(zktest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + + target_compile_definitions(zktest + PRIVATE -DZKSERVER_CMD="${CMAKE_CURRENT_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_CURRENT_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 39ed8a429d6..00000000000 --- a/src/c/Cli.vcproj +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/c/Cli.vcxproj b/src/c/Cli.vcxproj deleted file mode 100644 index 3104a4b41b0..00000000000 --- a/src/c/Cli.vcxproj +++ /dev/null @@ -1,160 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {F267C55D-E02C-4BAF-A246-0AA58E8FE4A6} - Win32Proj - cli - - - - Application - true - v120 - Unicode - - - Application - true - v120 - Unicode - - - Application - false - v120 - true - Unicode - - - Application - false - v120 - true - Unicode - - - - - - - - - - - - - - - - - - - true - - - true - - - false - - - false - - - - - - Level3 - Disabled - THREADED;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - include;generated - - - Console - true - ws2_32.lib - - - - - - - Level3 - Disabled - THREADED;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - include;generated - - - Console - true - ws2_32.lib - - - - - Level3 - - - MaxSpeed - true - true - THREADED;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - include;generated - - - Console - true - true - true - ws2_32.lib - - - - - Level3 - - - MaxSpeed - true - true - THREADED;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - include;generated - - - Console - true - true - true - ws2_32.lib - - - - - - - - {5754fb2b-5ea5-4988-851d-908ca533a626} - - - - - - \ No newline at end of file diff --git a/src/c/Cli.vcxproj.filters b/src/c/Cli.vcxproj.filters deleted file mode 100644 index db085edf741..00000000000 --- a/src/c/Cli.vcxproj.filters +++ /dev/null @@ -1,22 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - \ No newline at end of file diff --git a/src/c/Makefile.am b/src/c/Makefile.am index a7b26212f5a..b956b92e3a8 100644 --- a/src/c/Makefile.am +++ b/src/c/Makefile.am @@ -1,11 +1,16 @@ # need this for Doxygen integration include $(top_srcdir)/aminclude.am -AM_CPPFLAGS = -I${srcdir}/include -I${srcdir}/tests -I${srcdir}/generated +AUTOMAKE_OPTIONS = serial-tests +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 -Wdeclaration-after-statement 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 @@ -23,10 +28,10 @@ COMMON_SRC = src/zookeeper.c include/zookeeper.h include/zookeeper_version.h inc src/addrvec.h src/addrvec.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 +libzkst_la_LIBADD = -lm $(CLOCK_GETTIME_LIBS) lib_LTLIBRARIES = libzookeeper_st.la libzookeeper_st_la_SOURCES = @@ -38,7 +43,7 @@ if WANT_SYNCAPI noinst_LTLIBRARIES += libzkmt.la libzkmt_la_SOURCES =$(COMMON_SRC) src/mt_adaptor.c libzkmt_la_CFLAGS = -DTHREADED -libzkmt_la_LIBADD = -lm +libzkmt_la_LIBADD = -lm $(CLOCK_GETTIME_LIBS) lib_LTLIBRARIES += libzookeeper_mt.la libzookeeper_mt_la_SOURCES = @@ -97,23 +102,33 @@ TEST_SOURCES = \ tests/TestReadOnlyClient.cc \ $(NULL) -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 TESTS_ENVIRONMENT = ZKROOT=${srcdir}/../.. \ CLASSPATH=$$CLASSPATH:$$CLOVER_HOME/lib/clover.jar 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 = -shared $(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 = -shared $(SYMBOL_WRAPPERS_MT) $(SOLARIS_LIB_LDFLAGS) endif TESTS = $(check_PROGRAMS) diff --git a/src/c/README b/src/c/README index 045ef9ac43e..995c963a09f 100644 --- a/src/c/README +++ b/src/c/README @@ -71,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/cmake_config.h.in b/src/c/cmake_config.h.in new file mode 100644 index 00000000000..33bcc6cb0dd --- /dev/null +++ b/src/c/cmake_config.h.in @@ -0,0 +1,157 @@ +/** + * 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 + +/* Define to 1 if SOCK_CLOEXEC is available and wanted */ +#cmakedefine SOCK_CLOEXEC_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 350746f4f8b..7dce80e5804 100644 --- a/src/c/configure.ac +++ b/src/c/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ(2.59) -AC_INIT([zookeeper C client],3.5.0,[user@zookeeper.apache.org],[zookeeper]) +AC_INIT([zookeeper C client],3.5.4,[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 @@ -130,11 +130,11 @@ main() else exit(0); } -], AC_MSG_RESULT(yes) - ipv6=yes, - AC_MSG_RESULT(no) - ipv6=no, - AC_MSG_RESULT(no) +], AC_MSG_RESULT(yes) + ipv6=yes, + AC_MSG_RESULT(no) + ipv6=no, + AC_MSG_RESULT(no) ipv6=no) if test x"$ipv6" = xyes; then @@ -142,8 +142,66 @@ if test x"$ipv6" = xyes; then AC_SUBST(USEIPV6) fi +# use SOCK_CLOEXEC if available and wanted +AC_ARG_WITH([sock_cloexec], +[AS_HELP_STRING([--with-sock-cloexec],[build with SOCK_CLOEXEC flag set on the connections])], +[],[with_sock_cloexec=no]) + +AC_MSG_CHECKING([whether SOCK_CLOEXEC is available]) + +AC_TRY_RUN([ /* is SOCK_CLOEXEC available ? */ +#include +#include +#include +main() +{ +#ifdef SOCK_CLOEXEC + exit(0); +#else + exit(1); +#endif +} +], AC_MSG_RESULT(yes) + has_sock_cloexec=yes, + AC_MSG_RESULT(no) + has_sock_cloexec=no, + AC_MSG_RESULT(no) + has_sock_cloexec=no) + +if test "x$with_sock_cloexec" != xno && test "x$has_sock_cloexec" = xno; then + AC_MSG_WARN([cannot use SOCK_CLOEXEC -- SOCK_CLOEXEC undefined on this platform]) + with_sock_cloexec=no +fi + +if test "x$with_sock_cloexec" != xno; then +AC_MSG_NOTICE([building with SOCK_CLOEXEC]) +else +AC_MSG_NOTICE([building without SOCK_CLOEXEC]) +fi + +AS_IF([test x"$with_sock_cloexec" != xno], [AC_DEFINE([SOCK_CLOEXEC_ENABLED], [1], [Define to 1, if SOCK_CLOEXEC is defined and wanted])]) +AM_CONDITIONAL([SOCK_CLOEXEC_ENABLED],[test "x$with_sock_cloexec" != xno]) + +# Determine which libraries we need to use clock_gettime +saved_LIBS="$LIBS" +LIBS="" +AC_CHECK_LIB(rt, clock_gettime) +CLOCK_GETTIME_LIBS=$LIBS +AC_SUBST(CLOCK_GETTIME_LIBS) +LIBS="$saved_LIBS" + # Checks for library functions. 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 73ada1e1c53..876ad32d63e 100644 --- a/src/c/include/recordio.h +++ b/src/c/include/recordio.h @@ -19,6 +19,7 @@ #define __RECORDIO_H__ #include +#include /* for int64_t */ #ifdef WIN32 #include "winconfig.h" #endif @@ -70,7 +71,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 2c4c5dbe070..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 "user@zookeeper.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.5.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.5.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.5.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 18a203d75eb..af601f1547c 100644 --- a/src/c/include/zookeeper.h +++ b/src/c/include/zookeeper.h @@ -20,12 +20,18 @@ #define ZOOKEEPER_H_ #include + +/* we must not include config.h as a public header */ #ifndef WIN32 #include #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 @@ -124,7 +130,7 @@ enum ZOO_ERRORS { ZNOTREADONLY = -119, /*!< state-changing request is passed to read-only server */ ZEPHEMERALONLOCALSESSION = -120, /*!< Attempt to create ephemeral node on a local session */ ZNOWATCHER = -121, /*!< The watcher couldn't be found */ - ZRWSERVERFOUND = -122 /*!< r/w server found while in r/o mode */ + ZRECONFIGDISABLED = -123 /*!< Attempts to perform a reconfiguration operation when reconfiguration feature is disabled */ }; #ifdef __cplusplus @@ -591,8 +597,8 @@ ZOOAPI const char* zoo_get_current_server(zhandle_t* zh); * 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); @@ -646,12 +652,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, @@ -670,11 +676,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). */ @@ -1436,7 +1442,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); @@ -1504,6 +1510,41 @@ ZOOAPI void zoo_set_log_callback(zhandle_t *zh, log_callback_fn callback); */ ZOOAPI void zoo_deterministic_conn_order(int yesOrNo); +/** + * Type of watchers: used to select which type of watchers should be removed + */ +typedef enum { + ZWATCHERTYPE_CHILDREN = 1, + ZWATCHERTYPE_DATA = 2, + ZWATCHERTYPE_ANY = 3 +} ZooWatcherType; + +/** + * \brief removes the watchers for the given path and watcher type. + * + * \param zh the zookeeper handle obtained by a call to \ref zookeeper_init + * \param path the path for which watchers will be removed + * \param wtype the watcher type to be removed + * \param watcher the watcher to be removed, if null all watchers for that + * path (and watcher type) will be removed + * \param watcherCtx the contex associated with the watcher to be removed + * \param local whether the watchers will be removed locally even if there is + * no server connection + * \return the return code for the function call. + * ZOK - operation completed successfully + * ZNOWATCHER - the watcher couldn't be found. + * ZINVALIDSTATE - if !local, zhandle state is either ZOO_SESSION_EXPIRED_STATE + * or ZOO_AUTH_FAILED_STATE + * ZBADARGUMENTS - invalid input parameters + * ZMARSHALLINGERROR - failed to marshall a request; possibly, out of memory + * ZSYSTEMERROR - a system error occured + */ +ZOOAPI int zoo_aremove_watchers(zhandle_t *zh, const char *path, + ZooWatcherType wtype, watcher_fn watcher, void *watcherCtx, int local, + void_completion_t *completion, const void *data); + + +#ifdef THREADED /** * \brief create a node synchronously. * @@ -2001,15 +2042,6 @@ ZOOAPI int zoo_set_acl(zhandle_t *zh, const char *path, int version, */ ZOOAPI int zoo_multi(zhandle_t *zh, int count, const zoo_op_t *ops, zoo_op_result_t *results); -/** - * Type of watchers: used to select which type of watchers should be removed - */ -typedef enum { - ZWATCHERTYPE_CHILDREN = 1, - ZWATCHERTYPE_DATA = 2, - ZWATCHERTYPE_ANY = 3 -} ZooWatcherType; - /** * \brief removes the watchers for the given path and watcher type. * @@ -2032,31 +2064,7 @@ typedef enum { */ ZOOAPI int zoo_remove_watchers(zhandle_t *zh, const char *path, ZooWatcherType wtype, watcher_fn watcher, void *watcherCtx, int local); - -/** - * \brief removes the watchers for the given path and watcher type. - * - * \param zh the zookeeper handle obtained by a call to \ref zookeeper_init - * \param path the path for which watchers will be removed - * \param wtype the watcher type to be removed - * \param watcher the watcher to be removed, if null all watchers for that - * path (and watcher type) will be removed - * \param watcherCtx the contex associated with the watcher to be removed - * \param local whether the watchers will be removed locally even if there is - * no server connection - * \return the return code for the function call. - * ZOK - operation completed successfully - * ZNOWATCHER - the watcher couldn't be found. - * ZINVALIDSTATE - if !local, zhandle state is either ZOO_SESSION_EXPIRED_STATE - * or ZOO_AUTH_FAILED_STATE - * ZBADARGUMENTS - invalid input parameters - * ZMARSHALLINGERROR - failed to marshall a request; possibly, out of memory - * ZSYSTEMERROR - a system error occured - */ -ZOOAPI int zoo_aremove_watchers(zhandle_t *zh, const char *path, - ZooWatcherType wtype, watcher_fn watcher, void *watcherCtx, int local, - void_completion_t *completion, const void *data); - +#endif #ifdef __cplusplus } #endif diff --git a/src/c/include/zookeeper_version.h b/src/c/include/zookeeper_version.h index 74102463b7e..581690dadac 100644 --- a/src/c/include/zookeeper_version.h +++ b/src/c/include/zookeeper_version.h @@ -24,7 +24,7 @@ extern "C" { #define ZOO_MAJOR_VERSION 3 #define ZOO_MINOR_VERSION 5 -#define ZOO_PATCH_VERSION 0 +#define ZOO_PATCH_VERSION 4 #ifdef __cplusplus } diff --git a/src/c/src/addrvec.c b/src/c/src/addrvec.c index e641a46f226..fdfb68d34fd 100644 --- a/src/c/src/addrvec.c +++ b/src/c/src/addrvec.c @@ -21,7 +21,9 @@ #include #include #ifdef WIN32 -#include +#define random rand /* replace POSIX random with Windows rand */ +#include /* must always be included before ws2tcpip.h */ +#include /* for sockaddr_storage */ #include "winport.h" #endif diff --git a/src/c/src/addrvec.h b/src/c/src/addrvec.h index 3897095373b..a12642908bb 100644 --- a/src/c/src/addrvec.h +++ b/src/c/src/addrvec.h @@ -26,7 +26,7 @@ #include #else #include -#include "winstdint.h" +#include #endif /** diff --git a/src/c/src/cli.c b/src/c/src/cli.c index a9d11e173f6..6ca4a415f94 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 @@ -496,13 +504,8 @@ void processline(char *line) { } *ptr = '\0'; ptr++; - if (async) { - rc = zoo_aset(zh, line, ptr, strlen(ptr), -1, my_stat_completion, - strdup(line)); - } else { - struct Stat stat; - rc = zoo_set2(zh, line, ptr, strlen(ptr), -1, &stat); - } + rc = zoo_aset(zh, line, ptr, strlen(ptr), -1, my_stat_completion, + strdup(line)); if (rc) { fprintf(stderr, "Error %d for %s\n", rc, line); } @@ -571,11 +574,7 @@ void processline(char *line) { fprintf(stderr, "Path must start with /, found: %s\n", line); return; } - if (async) { - rc = zoo_adelete(zh, line, -1, my_void_completion, strdup(line)); - } else { - rc = zoo_delete(zh, line, -1); - } + rc = zoo_adelete(zh, line, -1, my_void_completion, strdup(line)); if (rc) { fprintf(stderr, "Error %d for %s\n", rc, line); } @@ -679,7 +678,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/load_gen.c b/src/c/src/load_gen.c index 0c513e81208..886fe1b3e3c 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 @@ -256,7 +252,7 @@ int main(int argc, char **argv) { deletedCounter=0; rc=recursiveDelete(argv[2]); if(rc==ZOK){ - LOG_INFO(LOGSTREAM, "Succesfully deleted a subtree starting at %s (%d nodes)", + LOG_INFO(LOGSTREAM, "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 23038fa7abf..38cced425ba 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) { @@ -375,8 +375,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; @@ -449,7 +448,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)) @@ -512,29 +511,41 @@ __attribute__((constructor)) int32_t get_xid() return fetch_and_add(&xid,1); } -void lock_reconfig(struct _zhandle *zh) +int lock_reconfig(struct _zhandle *zh) { struct adaptor_threads *adaptor = zh->adaptor_priv; - if(adaptor) - pthread_mutex_lock(&adaptor->reconfig_lock); + if (adaptor) { + return pthread_mutex_lock(&adaptor->reconfig_lock); + } else { + return 0; + } } -void unlock_reconfig(struct _zhandle *zh) +int unlock_reconfig(struct _zhandle *zh) { struct adaptor_threads *adaptor = zh->adaptor_priv; - if(adaptor) - pthread_mutex_unlock(&adaptor->reconfig_lock); + if (adaptor) { + return pthread_mutex_unlock(&adaptor->reconfig_lock); + } else { + return 0; + } } -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 968fdc4af83..77fae28785f 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; } @@ -302,9 +304,11 @@ static struct oarchive oa_default = { 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; @@ -319,9 +323,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 54c938257f0..5e9a4ff9965 100644 --- a/src/c/src/st_adaptor.c +++ b/src/c/src/st_adaptor.c @@ -24,42 +24,30 @@ #include #include -void zoo_lock_auth(zhandle_t *zh) -{ -} -void zoo_unlock_auth(zhandle_t *zh) -{ -} -void lock_buffer_list(buffer_head_t *l) -{ -} -void unlock_buffer_list(buffer_head_t *l) -{ -} -void lock_completion_list(completion_head_t *l) +int zoo_lock_auth(zhandle_t *zh) { + return 0; } -void unlock_completion_list(completion_head_t *l) +int zoo_unlock_auth(zhandle_t *zh) { + return 0; } -struct sync_completion *alloc_sync_completion(void) +int lock_buffer_list(buffer_head_t *l) { - return (struct sync_completion*)calloc(1, sizeof(struct sync_completion)); + return 0; } -int wait_sync_completion(struct sync_completion *sc) +int unlock_buffer_list(buffer_head_t *l) { return 0; } - -void free_sync_completion(struct sync_completion *sc) +int lock_completion_list(completion_head_t *l) { - free(sc); + return 0; } - -void notify_sync_completion(struct sync_completion *sc) +int unlock_completion_list(completion_head_t *l) { + return 0; } - int process_async(int outstanding_sync) { return outstanding_sync == 0; @@ -96,8 +84,22 @@ int32_t get_xid() return xid++; } -void lock_reconfig(struct _zhandle *zh){} -void unlock_reconfig(struct _zhandle *zh){} +int lock_reconfig(struct _zhandle *zh) +{ + return 0; +} + +int unlock_reconfig(struct _zhandle *zh) +{ + return 0; +} -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 46a5a2b73e1..d40614c9131 100644 --- a/src/c/src/winport.c +++ b/src/c/src/winport.c @@ -19,8 +19,9 @@ #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 @@ -256,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 2cfda47cbd7..d216f7fd30d 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 a4626e998d1..97995e36ace 100644 --- a/src/c/src/zk_adaptor.h +++ b/src/c/src/zk_adaptor.h @@ -77,10 +77,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; @@ -194,6 +194,7 @@ struct _zhandle { // Hostlist and list of addresses char *hostname; // hostname contains list of zookeeper servers to connect to struct sockaddr_storage addr_cur; // address of server we're currently connecting/connected to + struct sockaddr_storage addr_rw_server; // address of last known read/write server found. addrvec_t addrs; // current list of addresses we're connected to addrvec_t addrs_old; // old list of addresses that we are no longer connected to @@ -202,6 +203,11 @@ struct _zhandle { int reconfig; // Are we in the process of reconfiguring cluster's ensemble double pOld, pNew; // Probability for selecting between 'addrs_old' and 'addrs_new' int delay; + int disable_reconnection_attempt; // When set, client will not try reconnect to a different server in + // server list. This makes a sticky server for client, and is useful + // for testing if a sticky server is required, or if client wants to + // explicitly shuffle server by calling zoo_cycle_next_server. + // The default value is 0. watcher_fn watcher; // the registered watcher @@ -268,26 +274,28 @@ struct _zhandle { int adaptor_init(zhandle_t *zh); void adaptor_finish(zhandle_t *zh); void adaptor_destroy(zhandle_t *zh); +#if THREADED struct sync_completion *alloc_sync_completion(void); int wait_sync_completion(struct sync_completion *sc); void free_sync_completion(struct sync_completion *sc); void notify_sync_completion(struct sync_completion *sc); +#endif int adaptor_send_queue(zhandle_t *zh, int timeout); int process_async(int outstanding_sync); 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); // ensemble reconfigure access guards -void lock_reconfig(struct _zhandle *zh); -void unlock_reconfig(struct _zhandle *zh); +int lock_reconfig(struct _zhandle *zh); +int unlock_reconfig(struct _zhandle *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); diff --git a/src/c/src/zk_hashtable.c b/src/c/src/zk_hashtable.c index 8f87a96f7f0..da3fb835981 100644 --- a/src/c/src/zk_hashtable.c +++ b/src/c/src/zk_hashtable.c @@ -371,7 +371,7 @@ static void removeWatcherFromList(watcher_object_list_t *wl, watcher_fn watcher, while (e){ if (e->next && e->next->watcher == watcher && - e->context == watcherCtx) { + e->next->context == watcherCtx) { watcher_object_t *this = e->next; e->next = e->next->next; free(this); diff --git a/src/c/src/zk_log.c b/src/c/src/zk_log.c index 32584326201..436485e97c4 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 @@ -155,8 +158,9 @@ void log_message(log_callback_fn callback, ZooLogLevel curLevel, #ifndef THREADED + // pid_t is long on Solaris ofs = snprintf(buf, FORMAT_LOG_BUF_SIZE, - "%s:%d:%s@%s@%d: ", time, pid, + "%s:%ld:%s@%s@%d: ", time, (long)pid, dbgLevelStr[curLevel], funcName, line); #else @@ -167,7 +171,7 @@ void log_message(log_callback_fn callback, ZooLogLevel curLevel, #endif ofs = snprintf(buf, FORMAT_LOG_BUF_SIZE-1, - "%s:%d(0x%lx):%s@%s@%d: ", time, pid, tid, + "%s:%ld(0x%lx):%s@%s@%d: ", time, (long)pid, tid, dbgLevelStr[curLevel], funcName, line); #endif diff --git a/src/c/src/zookeeper.c b/src/c/src/zookeeper.c index ca81a1b6e54..47e2ee191a9 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 @@ -24,6 +24,7 @@ #define USE_IPV6 #endif +#include "config.h" #include #include #include @@ -41,18 +42,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" -#else -#include "winstdint.h" +#endif + +#ifdef HAVE_UNISTD_H +#include // needed for _POSIX_MONOTONIC_CLOCK #endif #ifdef HAVE_SYS_UTSNAME_H @@ -63,6 +79,19 @@ #include #endif +#ifdef __MACH__ // OS X +#include +#include +#endif + +#ifdef WIN32 +#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; @@ -222,36 +251,123 @@ static int handle_socket_error_msg(zhandle_t *zh, int line, int rc, static void cleanup_bufs(zhandle_t *zh,int callCompletion,int rc); static int disable_conn_permute=0; // permute enabled by default +static struct sockaddr_storage *addr_rw_server = 0; static __attribute__((unused)) void print_completion_queue(zhandle_t *zh); static void *SYNCHRONOUS_MARKER = (void*)&SYNCHRONOUS_MARKER; static int isValidPath(const char* path, const int flags); +#ifdef THREADED +static void process_sync_completion(zhandle_t *zh, + completion_list_t *cptr, + struct sync_completion *sc, + struct iarchive *ia); +#endif + #ifdef _WIN32 typedef SOCKET socket_t; typedef int sendsize_t; #define SEND_FLAGS 0 #else #ifdef __APPLE__ -#define MSG_NOSIGNAL SO_NOSIGPIPE +#define SEND_FLAGS SO_NOSIGPIPE +#endif +#ifdef __linux__ +#define SEND_FLAGS MSG_NOSIGNAL +#endif +#ifndef SEND_FLAGS +#define SEND_FLAGS 0 #endif typedef int socket_t; typedef ssize_t sendsize_t; -#define SEND_FLAGS MSG_NOSIGNAL #endif static void zookeeper_set_sock_nodelay(zhandle_t *, socket_t); static void zookeeper_set_sock_noblock(zhandle_t *, socket_t); static void zookeeper_set_sock_timeout(zhandle_t *, socket_t, int); -static int zookeeper_connect(zhandle_t *, struct sockaddr_storage *, socket_t); +static socket_t zookeeper_connect(zhandle_t *, struct sockaddr_storage *, socket_t); + +/* + * abort due to the use of a sync api in a singlethreaded environment + */ +static void abort_singlethreaded(zhandle_t *zh) +{ + LOG_ERROR(LOGCALLBACK(zh), "Sync completion used without threads"); + abort(); +} static sendsize_t zookeeper_send(socket_t s, const void* buf, size_t len) { return send(s, buf, len, SEND_FLAGS); } +/** + * Get the system time. + * + * If the monotonic clock is available, we use that. The monotonic clock does + * not change when the wall-clock time is adjusted by NTP or the system + * administrator. The monotonic clock returns a value which is monotonically + * increasing. + * + * If POSIX monotonic clocks are not available, we fall back on the wall-clock. + * + * @param tv (out param) The time. + */ +void get_system_time(struct timeval *tv) +{ + int ret; + +#ifdef __MACH__ // OS X + clock_serv_t cclock; + mach_timespec_t mts; + ret = host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + if (!ret) { + ret += clock_get_time(cclock, &mts); + ret += mach_port_deallocate(mach_task_self(), cclock); + if (!ret) { + tv->tv_sec = mts.tv_sec; + tv->tv_usec = mts.tv_nsec / 1000; + } + } + if (ret) { + // Default to gettimeofday in case of failure. + ret = gettimeofday(tv, NULL); + } +#elif CLOCK_MONOTONIC_RAW + // On Linux, CLOCK_MONOTONIC is affected by ntp slew but CLOCK_MONOTONIC_RAW + // is not. We want the non-slewed (constant rate) CLOCK_MONOTONIC_RAW if it + // is available. + struct timespec ts = { 0 }; + ret = clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; +#elif _POSIX_MONOTONIC_CLOCK + struct timespec ts = { 0 }; + ret = clock_gettime(CLOCK_MONOTONIC, &ts); + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; +#elif _WIN32 + LARGE_INTEGER counts, countsPerSecond, countsPerMicrosecond; + if (QueryPerformanceFrequency(&countsPerSecond) && + QueryPerformanceCounter(&counts)) { + countsPerMicrosecond.QuadPart = countsPerSecond.QuadPart / 1000000; + tv->tv_sec = (long)(counts.QuadPart / countsPerSecond.QuadPart); + tv->tv_usec = (long)((counts.QuadPart % countsPerSecond.QuadPart) / + countsPerMicrosecond.QuadPart); + ret = 0; + } else { + ret = gettimeofday(tv, NULL); + } +#else + ret = gettimeofday(tv, NULL); +#endif + if (ret) { + abort(); + } +} + const void *zoo_get_context(zhandle_t *zh) { return zh->context; @@ -458,8 +574,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); @@ -1088,6 +1218,7 @@ static zhandle_t *zookeeper_init_internal(const char *host, watcher_fn watcher, zh->active_node_watchers=create_zk_hashtable(); zh->active_exist_watchers=create_zk_hashtable(); zh->active_child_watchers=create_zk_hashtable(); + zh->disable_reconnection_attempt = 0; if (adaptor_init(zh) == -1) { goto abort; @@ -1525,27 +1656,27 @@ 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; - - 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 + 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) { +#ifdef THREADED + 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 + abort_singlethreaded(zh); +#endif + } else if (callCompletion) { // Fake the response buffer_list_t *bptr; h.xid = cptr->xid; @@ -1563,19 +1694,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); } @@ -1600,7 +1734,7 @@ static int is_connected(zhandle_t* zh) return (zh->state==ZOO_CONNECTED_STATE || zh->state==ZOO_READONLY_STATE); } -static void handle_error(zhandle_t *zh,int rc) +static void cleanup(zhandle_t *zh,int rc) { close(zh->fd); if (is_unrecoverable(zh)) { @@ -1616,12 +1750,6 @@ static void handle_error(zhandle_t *zh,int rc) LOG_DEBUG(LOGCALLBACK(zh), "Previous connection=[%s] delay=%d", zoo_get_current_server(zh), zh->delay); - // NOTE: If we're at the end of the list of addresses to connect to, then - // we want to delay the next connection attempt to avoid spinning. - // Then increment what host we'll connect to since we failed to connect to current - zh->delay = addrvec_atend(&zh->addrs); - addrvec_next(&zh->addrs, &zh->addr_cur); - if (!is_unrecoverable(zh)) { zh->state = 0; } @@ -1630,6 +1758,16 @@ static void handle_error(zhandle_t *zh,int rc) } } +static void handle_error(zhandle_t *zh,int rc) +{ + cleanup(zh, rc); + // NOTE: If we're at the end of the list of addresses to connect to, then + // we want to delay the next connection attempt to avoid spinning. + // Then increment what host we'll connect to since we failed to connect to current + zh->delay = addrvec_atend(&zh->addrs); + addrvec_next(&zh->addrs, &zh->addr_cur); +} + static int handle_socket_error_msg(zhandle_t *zh, int line, int rc, const char* format, ...) { @@ -1798,7 +1936,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); @@ -1806,7 +1944,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); @@ -1843,7 +1981,7 @@ static int deserialize_prime_response(struct prime_struct *resp, char* buffer) memcpy(&resp->sessionId, buffer + offset, sizeof(resp->sessionId)); offset = offset + sizeof(resp->sessionId); - resp->sessionId = htonll(resp->sessionId); + resp->sessionId = zoo_htonll(resp->sessionId); memcpy(&resp->passwd_len, buffer + offset, sizeof(resp->passwd_len)); offset = offset + sizeof(resp->passwd_len); @@ -1931,8 +2069,7 @@ 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); + get_system_time(&zh->last_ping); rc = rc < 0 ? rc : queue_buffer_bytes(&zh->to_send, get_buffer(oa), get_buffer_len(oa)); leave_critical(zh); @@ -1950,11 +2087,16 @@ static int ping_rw_server(zhandle_t* zh) socket_t sock; int rc; sendsize_t ssize; - struct sockaddr_storage addr; + int sock_flags; - addrvec_peek(&zh->addrs, &addr); + addrvec_peek(&zh->addrs, &zh->addr_rw_server); - sock = socket(addr.ss_family, SOCK_STREAM, 0); +#ifdef SOCK_CLOEXEC_ENABLED + sock_flags = SOCK_STREAM | SOCK_CLOEXEC; +#else + sock_flags = SOCK_STREAM; +#endif + sock = socket(zh->addr_rw_server.ss_family, sock_flags, 0); if (sock < 0) { return 0; } @@ -1962,7 +2104,7 @@ static int ping_rw_server(zhandle_t* zh) zookeeper_set_sock_nodelay(zh, sock); zookeeper_set_sock_timeout(zh, sock, 1); - rc = zookeeper_connect(zh, &addr, sock); + rc = zookeeper_connect(zh, &zh->addr_rw_server, sock); if (rc < 0) { return 0; } @@ -1984,13 +2126,16 @@ static int ping_rw_server(zhandle_t* zh) out: close(sock); + addr_rw_server = rc ? &zh->addr_rw_server : 0; return rc; } +#if !defined(WIN32) && !defined(min) static inline int min(int a, int b) { return a < b ? a : b; } +#endif static void zookeeper_set_sock_noblock(zhandle_t *zh, socket_t sock) { @@ -2054,7 +2199,26 @@ static socket_t zookeeper_connect(zhandle_t *zh, rc = connect(fd, (struct sockaddr *)addr, addr_len); #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 return rc; @@ -2063,13 +2227,21 @@ static socket_t zookeeper_connect(zhandle_t *zh, int zookeeper_interest(zhandle_t *zh, socket_t *fd, int *interest, struct timeval *tv) { + int sock_flags; int rc = 0; struct timeval now; + +#ifdef SOCK_CLOEXEC_ENABLED + sock_flags = SOCK_STREAM | SOCK_CLOEXEC; +#else + sock_flags = SOCK_STREAM; +#endif + if(zh==0 || fd==0 ||interest==0 || tv==0) return ZBADARGUMENTS; if (is_unrecoverable(zh)) return ZINVALIDSTATE; - gettimeofday(&now, 0); + get_system_time(&now); if(zh->next_deadline.tv_sec!=0 || zh->next_deadline.tv_usec!=0){ int time_left = calculate_interval(&zh->next_deadline, &now); int max_exceed = zh->recv_timeout / 10 > 200 ? 200 : @@ -2097,17 +2269,24 @@ int zookeeper_interest(zhandle_t *zh, socket_t *fd, int *interest, * * We always clear the delay setting. If we fail again, we'll set delay * again and on the next iteration we'll do the same. + * + * We will also delay if the disable_reconnection_attempt is set. */ - if (zh->delay == 1) { + if (zh->delay == 1 || zh->disable_reconnection_attempt == 1) { *tv = get_timeval(zh->recv_timeout/60); zh->delay = 0; LOG_WARN(LOGCALLBACK(zh), "Delaying connection after exhaustively trying all servers [%s]", zh->hostname); } else { - // No need to delay -- grab the next server and attempt connection - zoo_cycle_next_server(zh); - zh->fd = socket(zh->addr_cur.ss_family, SOCK_STREAM, 0); + if (addr_rw_server) { + zh->addr_cur = *addr_rw_server; + addr_rw_server = 0; + } else { + // No need to delay -- grab the next server and attempt connection + zoo_cycle_next_server(zh); + } + zh->fd = socket(zh->addr_cur.ss_family, sock_flags, 0); if (zh->fd < 0) { rc = handle_socket_error_msg(zh, __LINE__, @@ -2208,7 +2387,7 @@ int zookeeper_interest(zhandle_t *zh, socket_t *fd, int *interest, LOG_INFO(LOGCALLBACK(zh), "r/w server found at %s", format_endpoint_info(&addr)); - handle_error(zh, ZRWSERVERFOUND); + cleanup(zh, ZOK); } else { addrvec_next(&zh->addrs, NULL); } @@ -2279,7 +2458,7 @@ static int check_events(zhandle_t *zh, int events) "failed while receiving a server response"); } if (rc > 0) { - gettimeofday(&zh->last_recv, 0); + get_system_time(&zh->last_recv); if (zh->input_buffer != &zh->primer_buffer) { queue_buffer(&zh->to_process, zh->input_buffer, 0); } else { @@ -2420,124 +2599,6 @@ completion_list_t *dequeue_completion(completion_head_t *list) return cptr; } -static void process_sync_completion(zhandle_t *zh, - completion_list_t *cptr, - struct sync_completion *sc, - struct iarchive *ia) -{ - LOG_DEBUG(LOGCALLBACK(zh), "Processing sync_completion with type=%d xid=%#x rc=%d", - cptr->c.type, cptr->xid, sc->rc); - - switch(cptr->c.type) { - case COMPLETION_DATA: - if (sc->rc==0) { - struct GetDataResponse res; - int len; - deserialize_GetDataResponse(ia, "reply", &res); - if (res.data.len <= sc->u.data.buff_len) { - len = res.data.len; - } else { - len = sc->u.data.buff_len; - } - sc->u.data.buff_len = len; - // check if len is negative - // just of NULL which is -1 int - if (len == -1) { - sc->u.data.buffer = NULL; - } else { - memcpy(sc->u.data.buffer, res.data.buff, len); - } - sc->u.data.stat = res.stat; - deallocate_GetDataResponse(&res); - } - break; - case COMPLETION_STAT: - if (sc->rc==0) { - struct SetDataResponse res; - deserialize_SetDataResponse(ia, "reply", &res); - sc->u.stat = res.stat; - deallocate_SetDataResponse(&res); - } - break; - case COMPLETION_STRINGLIST: - if (sc->rc==0) { - struct GetChildrenResponse res; - deserialize_GetChildrenResponse(ia, "reply", &res); - sc->u.strs2 = res.children; - /* We don't deallocate since we are passing it back */ - // deallocate_GetChildrenResponse(&res); - } - break; - case COMPLETION_STRINGLIST_STAT: - if (sc->rc==0) { - struct GetChildren2Response res; - deserialize_GetChildren2Response(ia, "reply", &res); - sc->u.strs_stat.strs2 = res.children; - sc->u.strs_stat.stat2 = res.stat; - /* We don't deallocate since we are passing it back */ - // deallocate_GetChildren2Response(&res); - } - break; - case COMPLETION_STRING: - if (sc->rc==0) { - struct CreateResponse res; - int len; - const char * client_path; - deserialize_CreateResponse(ia, "reply", &res); - //ZOOKEEPER-1027 - client_path = sub_string(zh, res.path); - len = strlen(client_path) + 1;if (len > sc->u.str.str_len) { - len = sc->u.str.str_len; - } - if (len > 0) { - memcpy(sc->u.str.str, client_path, len - 1); - sc->u.str.str[len - 1] = '\0'; - } - free_duplicate_path(client_path, res.path); - deallocate_CreateResponse(&res); - } - break; - case COMPLETION_STRING_STAT: - if (sc->rc==0) { - struct Create2Response res; - int len; - const char * client_path; - deserialize_Create2Response(ia, "reply", &res); - client_path = sub_string(zh, res.path); - len = strlen(client_path) + 1; - if (len > sc->u.str.str_len) { - len = sc->u.str.str_len; - } - if (len > 0) { - memcpy(sc->u.str.str, client_path, len - 1); - sc->u.str.str[len - 1] = '\0'; - } - free_duplicate_path(client_path, res.path); - sc->u.stat = res.stat; - deallocate_Create2Response(&res); - } - break; - case COMPLETION_ACLLIST: - if (sc->rc==0) { - struct GetACLResponse res; - deserialize_GetACLResponse(ia, "reply", &res); - sc->u.acl.acl = res.acl; - sc->u.acl.stat = res.stat; - /* We don't deallocate since we are passing it back */ - //deallocate_GetACLResponse(&res); - } - break; - case COMPLETION_VOID: - break; - case COMPLETION_MULTI: - sc->rc = deserialize_multi(zh, cptr->xid, cptr, ia); - break; - default: - LOG_DEBUG(LOGCALLBACK(zh), "Unsupported completion type=%d", cptr->c.type); - break; - } -} - static int deserialize_multi(zhandle_t *zh, int xid, completion_list_t *cptr, struct iarchive *ia) { int rc = 0; @@ -2560,7 +2621,7 @@ static int deserialize_multi(zhandle_t *zh, int xid, completion_list_t *cptr, st deserialize_response(zh, 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 + //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); @@ -2628,6 +2689,7 @@ static void deserialize_response(zhandle_t *zh, int type, int xid, int failed, i 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); @@ -2660,12 +2722,8 @@ static void deserialize_response(zhandle_t *zh, int type, int xid, int failed, i case COMPLETION_VOID: LOG_DEBUG(LOGCALLBACK(zh), "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(LOGCALLBACK(zh), "Calling COMPLETION_MULTI for xid=%#x failed=%d rc=%d", @@ -2733,7 +2791,7 @@ static void isSocketReadable(zhandle_t* zh) } #endif else{ - gettimeofday(&zh->socket_readable,0); + get_system_time(&zh->socket_readable); } } @@ -2745,7 +2803,7 @@ static void checkResponseLatency(zhandle_t* zh) if(zh->socket_readable.tv_sec==0) return; - gettimeofday(&now,0); + get_system_time(&now); delay=calculate_interval(&zh->socket_readable, &now); if(delay>20) LOG_DEBUG(LOGCALLBACK(zh), "The following server response has spent at least %dms sitting in the client socket recv buffer",delay); @@ -2776,7 +2834,15 @@ int zookeeper_process(zhandle_t *zh, int events) bptr->buffer, bptr->curr_offset); deserialize_ReplyHeader(ia, "hdr", &hdr); - 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(LOGCALLBACK(zh), "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; @@ -2821,6 +2887,7 @@ int zookeeper_process(zhandle_t *zh, int events) if (zh->close_requested == 1 && cptr == NULL) { LOG_DEBUG(LOGCALLBACK(zh), "Completion queue has been cleared by zookeeper_close()"); close_buffer_iarchive(&ia); + free_buffer(bptr); return api_epilog(zh,ZINVALIDSTATE); } assert(cptr); @@ -2834,12 +2901,13 @@ 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)); } - if (hdr.xid != PING_XID && hdr.zxid > 0) { + if (hdr.zxid > 0) { // Update last_zxid only when it is a request response zh->last_zxid = hdr.zxid; } @@ -2847,23 +2915,11 @@ int zookeeper_process(zhandle_t *zh, int events) deactivateWatcher(zh, cptr->watcher_deregistration, 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(LOGCALLBACK(zh), "Got ping response in %d ms", elapsed); - - // Nothing to do with a ping response - free_buffer(bptr); - destroy_completion_entry(cptr); - } else { - LOG_DEBUG(LOGCALLBACK(zh), "Queueing asynchronous response"); - - cptr->buffer = bptr; - queue_completion(&zh->completions_to_process, cptr, 0); - } + LOG_DEBUG(LOGCALLBACK(zh), "Queueing asynchronous response"); + cptr->buffer = bptr; + queue_completion(&zh->completions_to_process, cptr, 0); } else { +#ifdef THREADED struct sync_completion *sc = (struct sync_completion*)cptr->data; sc->rc = rc; @@ -2874,6 +2930,9 @@ int zookeeper_process(zhandle_t *zh, int events) free_buffer(bptr); zh->outstanding_sync--; destroy_completion_entry(cptr); +#else + abort_singlethreaded(zh); +#endif } } @@ -3471,39 +3530,15 @@ static int CreateRequest_init(zhandle_t *zh, struct CreateRequest *req, return ZOK; } -static int Create2Request_init(zhandle_t *zh, struct Create2Request *req, - const char *path, const char *value, - int valuelen, const struct ACL_vector *acl_entries, int flags) -{ - int rc; - assert(req); - rc = Request_path_init(zh, flags, &req->path, path); - assert(req); - if (rc != ZOK) { - return rc; - } - req->flags = flags; - req->data.buff = (char*)value; - req->data.len = valuelen; - if (acl_entries == 0) { - req->acl.count = 0; - req->acl.data = 0; - } else { - req->acl = *acl_entries; - } - - return ZOK; -} - -int zoo_acreate(zhandle_t *zh, const char *path, const char *value, - int valuelen, const struct ACL_vector *acl_entries, int flags, - string_completion_t completion, const void *data) +int zoo_acreate(zhandle_t *zh, const char *path, const char *value, + int valuelen, const struct ACL_vector *acl_entries, int flags, + string_completion_t completion, const void *data) { struct oarchive *oa; struct RequestHeader h = {get_xid(), ZOO_CREATE_OP}; struct CreateRequest req; - int rc = CreateRequest_init(zh, &req, + int rc = CreateRequest_init(zh, &req, path, value, valuelen, acl_entries, flags); if (rc != ZOK) { return rc; @@ -3533,15 +3568,15 @@ int zoo_acreate2(zhandle_t *zh, const char *path, const char *value, { struct oarchive *oa; struct RequestHeader h = { get_xid(), ZOO_CREATE2_OP }; - struct Create2Request req; + struct CreateRequest req; - int rc = Create2Request_init(zh, &req, path, value, valuelen, acl_entries, flags); + int rc = CreateRequest_init(zh, &req, path, value, valuelen, acl_entries, flags); if (rc != ZOK) { return rc; } oa = create_buffer_oarchive(); rc = serialize_RequestHeader(oa, "header", &h); - rc = rc < 0 ? rc : serialize_Create2Request(oa, "req", &req); + rc = rc < 0 ? rc : serialize_CreateRequest(oa, "req", &req); enter_critical(zh); rc = rc < 0 ? rc : add_string_stat_completion(zh, h.xid, completion, data); rc = rc < 0 ? rc : queue_buffer_bytes(&zh->to_send, get_buffer(oa), @@ -3611,7 +3646,7 @@ int zoo_awexists(zhandle_t *zh, const char *path, struct oarchive *oa; struct RequestHeader h = {get_xid(), ZOO_EXISTS_OP}; struct ExistsRequest req; - int rc = Request_path_watch_init(zh, 0, &req.path, path, + int rc = Request_path_watch_init(zh, 0, &req.path, path, &req.watch, watcher != NULL); if (rc != ZOK) { return rc; @@ -3645,7 +3680,7 @@ static int zoo_awget_children_(zhandle_t *zh, const char *path, struct oarchive *oa; struct RequestHeader h = {get_xid(), ZOO_GETCHILDREN_OP}; struct GetChildrenRequest req ; - int rc = Request_path_watch_init(zh, 0, &req.path, path, + int rc = Request_path_watch_init(zh, 0, &req.path, path, &req.watch, watcher != NULL); if (rc != ZOK) { return rc; @@ -3693,7 +3728,7 @@ static int zoo_awget_children2_(zhandle_t *zh, const char *path, struct oarchive *oa; struct RequestHeader h = {get_xid(), ZOO_GETCHILDREN2_OP}; struct GetChildren2Request req ; - int rc = Request_path_watch_init(zh, 0, &req.path, path, + int rc = Request_path_watch_init(zh, 0, &req.path, path, &req.watch, watcher != NULL); if (rc != ZOK) { return rc; @@ -3983,6 +4018,76 @@ int zoo_amulti(zhandle_t *zh, int count, const zoo_op_t *ops, return (rc < 0) ? ZMARSHALLINGERROR : ZOK; } + +int zoo_aremove_watchers(zhandle_t *zh, const char *path, ZooWatcherType wtype, + watcher_fn watcher, void *watcherCtx, int local, + void_completion_t *completion, const void *data) +{ + char *server_path = prepend_string(zh, path); + int rc; + struct oarchive *oa; + struct RequestHeader h = { get_xid(), ZOO_REMOVE_WATCHES }; + struct RemoveWatchesRequest req = { (char*)server_path, wtype }; + watcher_deregistration_t *wdo; + + if (!zh || !isValidPath(server_path, 0)) { + rc = ZBADARGUMENTS; + goto done; + } + + if (!local && is_unrecoverable(zh)) { + rc = ZINVALIDSTATE; + goto done; + } + + if (!pathHasWatcher(zh, server_path, wtype, watcher, watcherCtx)) { + rc = ZNOWATCHER; + goto done; + } + + if (local) { + removeWatchers(zh, server_path, wtype, watcher, watcherCtx); +#ifdef THREADED + notify_sync_completion((struct sync_completion *)data); +#endif + rc = ZOK; + goto done; + } + + oa = create_buffer_oarchive(); + rc = serialize_RequestHeader(oa, "header", &h); + rc = rc < 0 ? rc : serialize_RemoveWatchesRequest(oa, "req", &req); + if (rc < 0) { + goto done; + } + + wdo = create_watcher_deregistration(server_path, watcher, watcherCtx, + wtype); + if (!wdo) { + rc = ZSYSTEMERROR; + goto done; + } + + enter_critical(zh); + rc = add_completion_deregistration(zh, h.xid, COMPLETION_VOID, + completion, data, 0, wdo, 0); + rc = rc < 0 ? rc : queue_buffer_bytes(&zh->to_send, get_buffer(oa), + get_buffer_len(oa)); + rc = rc < 0 ? ZMARSHALLINGERROR : ZOK; + leave_critical(zh); + + /* We queued the buffer, so don't free it */ + close_buffer_oarchive(&oa, 0); + + LOG_DEBUG(LOGCALLBACK(zh), "Sending request xid=%#x for path [%s] to %s", + h.xid, path, zoo_get_current_server(zh)); + + adaptor_send_queue(zh, 0); + +done: + free_duplicate_path(server_path, path); + return rc; +} void zoo_create_op_init(zoo_op_t *op, const char *path, const char *value, int valuelen, const struct ACL_vector *acl, int flags, char *path_buffer, int path_buffer_len) @@ -3999,7 +4104,7 @@ void zoo_create_op_init(zoo_op_t *op, const char *path, const char *value, } void zoo_create2_op_init(zoo_op_t *op, const char *path, const char *value, - int valuelen, const struct ACL_vector *acl, int flags, + int valuelen, const struct ACL_vector *acl, int flags, char *path_buffer, int path_buffer_len) { assert(op); @@ -4041,25 +4146,6 @@ void zoo_check_op_init(zoo_op_t *op, const char *path, int version) op->check_op.version = version; } -int zoo_multi(zhandle_t *zh, int count, const zoo_op_t *ops, zoo_op_result_t *results) -{ - int rc; - - struct sync_completion *sc = alloc_sync_completion(); - if (!sc) { - return ZSYSTEMERROR; - } - - rc = zoo_amulti(zh, count, ops, results, SYNCHRONOUS_MARKER, sc); - if (rc == ZOK) { - wait_sync_completion(sc); - rc = sc->rc; - } - free_sync_completion(sc); - - return rc; -} - /* specify timeout of 0 to make the function non-blocking */ /* timeout is in milliseconds */ int flush_send_queue(zhandle_t*zh, int timeout) @@ -4070,7 +4156,7 @@ int flush_send_queue(zhandle_t*zh, int timeout) fd_set pollSet; struct timeval wait; #endif - gettimeofday(&started,0); + get_system_time(&started); // we can't use dequeue_buffer() here because if (non-blocking) send_buffer() // returns EWOULDBLOCK we'd have to put the buffer back on the queue. // we use a recursive lock instead and only dequeue the buffer if a send was @@ -4083,7 +4169,7 @@ int flush_send_queue(zhandle_t*zh, int timeout) #endif int elapsed; struct timeval now; - gettimeofday(&now,0); + get_system_time(&now); elapsed=calculate_interval(&started,&now); if (elapsed>timeout) { rc = ZOPERATIONTIMEOUT; @@ -4122,7 +4208,7 @@ int flush_send_queue(zhandle_t*zh, int timeout) // if the buffer has been sent successfully, remove it from the queue if (rc > 0) remove_buffer(&zh->to_send); - gettimeofday(&zh->last_send, 0); + get_system_time(&zh->last_send); rc = ZOK; } unlock_buffer_list(&zh->to_send); @@ -4296,6 +4382,126 @@ void zoo_deterministic_conn_order(int yesOrNo) disable_conn_permute=yesOrNo; } +#ifdef THREADED + +static void process_sync_completion(zhandle_t *zh, + completion_list_t *cptr, + struct sync_completion *sc, + struct iarchive *ia) +{ + LOG_DEBUG(LOGCALLBACK(zh), "Processing sync_completion with type=%d xid=%#x rc=%d", + cptr->c.type, cptr->xid, sc->rc); + + switch(cptr->c.type) { + case COMPLETION_DATA: + if (sc->rc==0) { + struct GetDataResponse res; + int len; + deserialize_GetDataResponse(ia, "reply", &res); + if (res.data.len <= sc->u.data.buff_len) { + len = res.data.len; + } else { + len = sc->u.data.buff_len; + } + sc->u.data.buff_len = len; + // check if len is negative + // just of NULL which is -1 int + if (len == -1) { + sc->u.data.buffer = NULL; + } else { + memcpy(sc->u.data.buffer, res.data.buff, len); + } + sc->u.data.stat = res.stat; + deallocate_GetDataResponse(&res); + } + break; + case COMPLETION_STAT: + if (sc->rc==0) { + struct SetDataResponse res; + deserialize_SetDataResponse(ia, "reply", &res); + sc->u.stat = res.stat; + deallocate_SetDataResponse(&res); + } + break; + case COMPLETION_STRINGLIST: + if (sc->rc==0) { + struct GetChildrenResponse res; + deserialize_GetChildrenResponse(ia, "reply", &res); + sc->u.strs2 = res.children; + /* We don't deallocate since we are passing it back */ + // deallocate_GetChildrenResponse(&res); + } + break; + case COMPLETION_STRINGLIST_STAT: + if (sc->rc==0) { + struct GetChildren2Response res; + deserialize_GetChildren2Response(ia, "reply", &res); + sc->u.strs_stat.strs2 = res.children; + sc->u.strs_stat.stat2 = res.stat; + /* We don't deallocate since we are passing it back */ + // deallocate_GetChildren2Response(&res); + } + break; + case COMPLETION_STRING: + if (sc->rc==0) { + struct CreateResponse res; + int len; + const char * client_path; + deserialize_CreateResponse(ia, "reply", &res); + //ZOOKEEPER-1027 + client_path = sub_string(zh, res.path); + len = strlen(client_path) + 1;if (len > sc->u.str.str_len) { + len = sc->u.str.str_len; + } + if (len > 0) { + memcpy(sc->u.str.str, client_path, len - 1); + sc->u.str.str[len - 1] = '\0'; + } + free_duplicate_path(client_path, res.path); + deallocate_CreateResponse(&res); + } + break; + case COMPLETION_STRING_STAT: + if (sc->rc==0) { + struct Create2Response res; + int len; + const char * client_path; + deserialize_Create2Response(ia, "reply", &res); + client_path = sub_string(zh, res.path); + len = strlen(client_path) + 1; + if (len > sc->u.str.str_len) { + len = sc->u.str.str_len; + } + if (len > 0) { + memcpy(sc->u.str.str, client_path, len - 1); + sc->u.str.str[len - 1] = '\0'; + } + free_duplicate_path(client_path, res.path); + sc->u.stat = res.stat; + deallocate_Create2Response(&res); + } + break; + case COMPLETION_ACLLIST: + if (sc->rc==0) { + struct GetACLResponse res; + deserialize_GetACLResponse(ia, "reply", &res); + sc->u.acl.acl = res.acl; + sc->u.acl.stat = res.stat; + /* We don't deallocate since we are passing it back */ + //deallocate_GetACLResponse(&res); + } + break; + case COMPLETION_VOID: + break; + case COMPLETION_MULTI: + sc->rc = deserialize_multi(zh, cptr->xid, cptr, ia); + break; + default: + LOG_DEBUG(LOGCALLBACK(zh), "Unsupported completion type=%d", cptr->c.type); + break; + } +} + /*---------------------------------------------------------------------------* * SYNC API *---------------------------------------------------------------------------*/ @@ -4633,70 +4839,22 @@ int zoo_remove_watchers(zhandle_t *zh, const char *path, ZooWatcherType wtype, return rc; } -int zoo_aremove_watchers(zhandle_t *zh, const char *path, ZooWatcherType wtype, - watcher_fn watcher, void *watcherCtx, int local, - void_completion_t *completion, const void *data) +int zoo_multi(zhandle_t *zh, int count, const zoo_op_t *ops, zoo_op_result_t *results) { - char *server_path = prepend_string(zh, path); int rc; - struct oarchive *oa; - struct RequestHeader h = { get_xid(), ZOO_REMOVE_WATCHES }; - struct RemoveWatchesRequest req = { (char*)server_path, wtype }; - watcher_deregistration_t *wdo; - - if (!zh || !isValidPath(server_path, 0)) { - rc = ZBADARGUMENTS; - goto done; - } - - if (!local && is_unrecoverable(zh)) { - rc = ZINVALIDSTATE; - goto done; - } - - if (!pathHasWatcher(zh, server_path, wtype, watcher, watcherCtx)) { - rc = ZNOWATCHER; - goto done; - } - - if (local) { - removeWatchers(zh, server_path, wtype, watcher, watcherCtx); - notify_sync_completion((struct sync_completion *)data); - rc = ZOK; - goto done; - } - oa = create_buffer_oarchive(); - rc = serialize_RequestHeader(oa, "header", &h); - rc = rc < 0 ? rc : serialize_RemoveWatchesRequest(oa, "req", &req); - if (rc < 0) { - goto done; + struct sync_completion *sc = alloc_sync_completion(); + if (!sc) { + return ZSYSTEMERROR; } - wdo = create_watcher_deregistration(server_path, watcher, watcherCtx, - wtype); - if (!wdo) { - rc = ZSYSTEMERROR; - goto done; + rc = zoo_amulti(zh, count, ops, results, SYNCHRONOUS_MARKER, sc); + if (rc == ZOK) { + wait_sync_completion(sc); + rc = sc->rc; } + free_sync_completion(sc); - enter_critical(zh); - rc = add_completion_deregistration(zh, h.xid, COMPLETION_VOID, - completion, data, 0, wdo, 0); - rc = rc < 0 ? rc : queue_buffer_bytes(&zh->to_send, get_buffer(oa), - get_buffer_len(oa)); - rc = rc < 0 ? ZMARSHALLINGERROR : ZOK; - leave_critical(zh); - - /* We queued the buffer, so don't free it */ - close_buffer_oarchive(&oa, 0); - - LOG_DEBUG(LOGCALLBACK(zh), "Sending request xid=%#x for path [%s] to %s", - h.xid, path, zoo_get_current_server(zh)); - - adaptor_send_queue(zh, 0); - -done: - free_duplicate_path(server_path, path); return rc; } +#endif diff --git a/src/c/tests/LibCMocks.cc b/src/c/tests/LibCMocks.cc index 44ab0a9ad38..870a554ca3b 100644 --- a/src/c/tests/LibCMocks.cc +++ b/src/c/tests/LibCMocks.cc @@ -19,6 +19,7 @@ #include #include #include +#include // needed for _POSIX_MONOTONIC_CLOCK #include #include "Util.h" @@ -147,7 +148,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); @@ -331,3 +332,16 @@ int gettimeofday(struct timeval *tp, GETTIMEOFDAY_ARG2_TYPE tzp){ Mock_gettimeofday* Mock_gettimeofday::mock_=0; +// ***************************************************************************** +#ifdef _POSIX_MONOTONIC_CLOCK +// clock_gettime +int clock_gettime(clockid_t id, struct timespec *tp) { + if (!Mock_gettimeofday::mock_) + return LIBC_SYMBOLS.clock_gettime(id,tp); + struct timeval tv = { 0 }; + int res = Mock_gettimeofday::mock_->call(&tv, NULL); + tp->tv_sec = tv.tv_sec; + tp->tv_nsec = tv.tv_usec * 1000; + return res; +} +#endif diff --git a/src/c/tests/LibCSymTable.cc b/src/c/tests/LibCSymTable.cc index 53785796a93..23862f3c01d 100644 --- a/src/c/tests/LibCSymTable.cc +++ b/src/c/tests/LibCSymTable.cc @@ -17,6 +17,7 @@ */ #include "LibCSymTable.h" +#include // needed for _POSIX_MONOTONIC_CLOCK #define LOAD_SYM(sym) \ sym=(sym##_sig)dlsym(handle,#sym); \ @@ -51,6 +52,9 @@ LibCSymTable::LibCSymTable() LOAD_SYM(select); LOAD_SYM(poll); LOAD_SYM(gettimeofday); +#ifdef _POSIX_MONOTONIC_CLOCK + LOAD_SYM(clock_gettime); +#endif #ifdef THREADED LOAD_SYM(pthread_create); LOAD_SYM(pthread_detach); diff --git a/src/c/tests/LibCSymTable.h b/src/c/tests/LibCSymTable.h index 2f7e0c291f0..1b6f9db996d 100644 --- a/src/c/tests/LibCSymTable.h +++ b/src/c/tests/LibCSymTable.h @@ -26,6 +26,7 @@ #include #include #include +#include // needed for _POSIX_MONOTONIC_CLOCK #ifdef THREADED #include @@ -80,6 +81,9 @@ struct LibCSymTable DECLARE_SYM(int,select,(int,fd_set*,fd_set*,fd_set*,struct timeval*)); DECLARE_SYM(int,poll,(struct pollfd*,POLL_NFDS_TYPE,int)); DECLARE_SYM(int,gettimeofday,(struct timeval*,GETTIMEOFDAY_ARG2_TYPE)); +#ifdef _POSIX_MONOTONIC_CLOCK + DECLARE_SYM(int,clock_gettime,(clockid_t clk_id, struct timespec*)); +#endif #ifdef THREADED DECLARE_SYM(int,pthread_create,(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *)); diff --git a/src/c/tests/TestClient.cc b/src/c/tests/TestClient.cc index 3fbcb1b6080..92b169906b9 100644 --- a/src/c/tests/TestClient.cc +++ b/src/c/tests/TestClient.cc @@ -47,6 +47,10 @@ struct buff_struct_2 { char *buffer; }; +// TODO(br33d): the vast majority of this test is not usable with single threaded. +// it needs a overhaul to work properly with both threaded and single +// threaded (ZOOKEEPER-2640) +#ifdef THREADED // For testing LogMessage Callback functionality list logMessages; void logMessageHandler(const char* message) { @@ -1331,6 +1335,10 @@ class Zookeeper_simpleSystem : public CPPUNIT_NS::TestFixture &ZOO_OPEN_ACL_UNSAFE, 0, 0, 0); CPPUNIT_ASSERT_EQUAL((int)ZOK, rc); + rc = zoo_create(zk, "/something2", "", 0, + &ZOO_OPEN_ACL_UNSAFE, 0, 0, 0); + CPPUNIT_ASSERT_EQUAL((int)ZOK, rc); + char buf[1024]; int blen = sizeof(buf); rc = zoo_get(zk, "/something", 1, buf, &blen, NULL); @@ -1381,13 +1389,29 @@ class Zookeeper_simpleSystem : public CPPUNIT_NS::TestFixture zk = createClient(&ctx); /* add a watch, stop the server, and remove it locally */ - rc = zoo_wget(zk, "/something", watcher_remove_watchers, NULL, + void* ctx1=(void*)0x1; + void* ctx2=(void*)0x2; + + rc = zoo_wget(zk, "/something", watcher_remove_watchers, ctx1, buf, &blen, NULL); CPPUNIT_ASSERT_EQUAL((int)ZOK, rc); + + rc = zoo_wget(zk, "/something2", watcher_remove_watchers, ctx2, + buf, &blen, NULL); + CPPUNIT_ASSERT_EQUAL((int)ZOK, rc); + stopServer(); rc = zoo_remove_watchers(zk, "/something", ZWATCHERTYPE_DATA, - watcher_remove_watchers, NULL, 1); + watcher_remove_watchers, ctx1, 1); CPPUNIT_ASSERT_EQUAL((int)ZOK, rc); + + rc = zoo_remove_watchers(zk, "/something", ZWATCHERTYPE_DATA, + watcher_remove_watchers, ctx1, 1); + CPPUNIT_ASSERT_EQUAL((int)ZNOWATCHER, rc); + + rc = zoo_remove_watchers(zk, "/something2", ZWATCHERTYPE_DATA, + watcher_remove_watchers, ctx2, 1); + CPPUNIT_ASSERT_EQUAL((int)ZOK,rc); } }; @@ -1395,3 +1419,4 @@ volatile int Zookeeper_simpleSystem::count; zhandle_t *Zookeeper_simpleSystem::async_zk; const char Zookeeper_simpleSystem::hostPorts[] = "127.0.0.1:22181"; CPPUNIT_TEST_SUITE_REGISTRATION(Zookeeper_simpleSystem); +#endif 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 a54e047b3ba..0ee9566ffc0 100644 --- a/src/c/tests/TestMulti.cc +++ b/src/c/tests/TestMulti.cc @@ -160,12 +160,12 @@ typedef struct watchCtx { } } watchctx_t; +#ifdef THREADED class Zookeeper_multi : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(Zookeeper_multi); //FIXME: None of these tests pass in single-threaded mode. It seems to be a //flaw in the test suite setup. -#ifdef THREADED CPPUNIT_TEST(testCreate); CPPUNIT_TEST(testCreateDelete); CPPUNIT_TEST(testInvalidVersion); @@ -178,7 +178,6 @@ class Zookeeper_multi : public CPPUNIT_NS::TestFixture CPPUNIT_TEST(testCheck); CPPUNIT_TEST(testWatch); CPPUNIT_TEST(testSequentialNodeCreateInAsyncMulti); -#endif CPPUNIT_TEST_SUITE_END(); static void watcher(zhandle_t *, int type, int state, const char *path,void*v){ @@ -701,3 +700,4 @@ class Zookeeper_multi : public CPPUNIT_NS::TestFixture volatile int Zookeeper_multi::count; const char Zookeeper_multi::hostPorts[] = "127.0.0.1:22181"; CPPUNIT_TEST_SUITE_REGISTRATION(Zookeeper_multi); +#endif diff --git a/src/c/tests/TestOperations.cc b/src/c/tests/TestOperations.cc index 41d2d00846e..b8a4b3f8946 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/TestReadOnlyClient.cc b/src/c/tests/TestReadOnlyClient.cc index 37ab147a579..d73f1896cfe 100644 --- a/src/c/tests/TestReadOnlyClient.cc +++ b/src/c/tests/TestReadOnlyClient.cc @@ -27,11 +27,10 @@ #include "Util.h" #include "WatchUtil.h" +#ifdef THREADED class Zookeeper_readOnly : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(Zookeeper_readOnly); -#ifdef THREADED CPPUNIT_TEST(testReadOnly); -#endif CPPUNIT_TEST_SUITE_END(); static void watcher(zhandle_t* zh, int type, int state, @@ -108,3 +107,4 @@ class Zookeeper_readOnly : public CPPUNIT_NS::TestFixture { }; CPPUNIT_TEST_SUITE_REGISTRATION(Zookeeper_readOnly); +#endif diff --git a/src/c/tests/TestReconfig.cc b/src/c/tests/TestReconfig.cc index 056eb569628..ee030d5786a 100644 --- a/src/c/tests/TestReconfig.cc +++ b/src/c/tests/TestReconfig.cc @@ -55,6 +55,11 @@ class Client zh = zookeeper_init(hosts.c_str(),0,1000,0,0,0); CPPUNIT_ASSERT(zh); + // Set the flag to disable ZK from reconnecting to a different server. + // Our reconfig test case will do explicit server shuffling through + // zoo_cycle_next_server, and the reconnection attempts would interfere + // with the server states the tests cases assume. + zh->disable_reconnection_attempt = 1; reSeed(); cycleNextServer(); diff --git a/src/c/tests/TestReconfigServer.cc b/src/c/tests/TestReconfigServer.cc index 6a429ac82be..c15774e9e30 100644 --- a/src/c/tests/TestReconfigServer.cc +++ b/src/c/tests/TestReconfigServer.cc @@ -15,6 +15,9 @@ * the License. */ #include +#include +#include +#include #include #include #include "zookeeper.h" @@ -22,13 +25,14 @@ #include "Util.h" #include "ZooKeeperQuorumServer.h" +#ifdef THREADED class TestReconfigServer : public CPPUNIT_NS::TestFixture { CPPUNIT_TEST_SUITE(TestReconfigServer); -#ifdef THREADED CPPUNIT_TEST(testNonIncremental); CPPUNIT_TEST(testRemoveConnectedFollower); CPPUNIT_TEST(testRemoveFollower); -#endif + CPPUNIT_TEST(testReconfigFailureWithoutAuth); + CPPUNIT_TEST(testReconfigFailureWithoutServerSuperuserPasswordConfigured); CPPUNIT_TEST_SUITE_END(); public: @@ -39,7 +43,8 @@ class TestReconfigServer : public CPPUNIT_NS::TestFixture { void testNonIncremental(); void testRemoveConnectedFollower(); void testRemoveFollower(); - + void testReconfigFailureWithoutAuth(); + void testReconfigFailureWithoutServerSuperuserPasswordConfigured(); private: static const uint32_t NUM_SERVERS; FILE* logfile_; @@ -49,6 +54,7 @@ class TestReconfigServer : public CPPUNIT_NS::TestFixture { void parseConfig(char* buf, int len, std::vector& servers, std::string& version); bool waitForConnected(zhandle_t* zh, uint32_t timeout_sec); + zhandle_t* connectFollowers(std::vector &followers); }; const uint32_t TestReconfigServer::NUM_SERVERS = 3; @@ -70,7 +76,10 @@ TestReconfigServer:: void TestReconfigServer:: setUp() { - cluster_ = ZooKeeperQuorumServer::getCluster(NUM_SERVERS); + ZooKeeperQuorumServer::tConfigPairs configs; + configs.push_back(std::make_pair("reconfigEnabled", "true")); + cluster_ = ZooKeeperQuorumServer::getCluster(NUM_SERVERS, configs, + "SERVER_JVMFLAGS=-Dzookeeper.DigestAuthenticationProvider.superDigest=super:D/InIHSb7yEEbrWz8b9l71RjZJU="/* password is test */); } void TestReconfigServer:: @@ -151,7 +160,7 @@ testRemoveFollower() { zhandle_t* zk = zookeeper_init(host.c_str(), NULL, 10000, NULL, NULL, 0); CPPUNIT_ASSERT_EQUAL(true, waitForConnected(zk, 10)); CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_getconfig(zk, 0, buf, &len, &stat)); - + CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_add_auth(zk, "digest", "super:test", 10, NULL,(void*)ZOK)); // check if all the servers are listed in the config. parseConfig(buf, len, servers, version); // initially should be 1<<32, which is 0x100000000. This is the zxid @@ -219,6 +228,7 @@ testNonIncremental() { zhandle_t* zk = zookeeper_init(host.c_str(), NULL, 10000, NULL, NULL, 0); CPPUNIT_ASSERT_EQUAL(true, waitForConnected(zk, 10)); CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_getconfig(zk, 0, buf, &len, &stat)); + CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_add_auth(zk, "digest", "super:test", 10, NULL,(void*)ZOK)); // check if all the servers are listed in the config. parseConfig(buf, len, servers, version); @@ -274,37 +284,46 @@ testNonIncremental() { zookeeper_close(zk); } -/** - * 1. Connect to a follower. - * 2. Remove the follower the client is connected to. - */ -void TestReconfigServer:: -testRemoveConnectedFollower() { - std::vector servers; - std::string version; - struct Stat stat; - int len = 1024; - char buf[len]; - - // connect to a follower. +zhandle_t* TestReconfigServer:: +connectFollowers(std::vector &followers) { + std::stringstream ss; int32_t leader = getLeader(); - std::vector followers = getFollowers(); CPPUNIT_ASSERT(leader >= 0); CPPUNIT_ASSERT_EQUAL(NUM_SERVERS - 1, (uint32_t)(followers.size())); - std::stringstream ss; for (int i = 0; i < followers.size(); i++) { - ss << cluster_[followers[i]]->getHostPort() << ","; + ss << cluster_[followers[i]]->getHostPort() << ","; } ss << cluster_[leader]->getHostPort(); std::string hosts = ss.str().c_str(); zoo_deterministic_conn_order(true); zhandle_t* zk = zookeeper_init(hosts.c_str(), NULL, 10000, NULL, NULL, 0); CPPUNIT_ASSERT_EQUAL(true, waitForConnected(zk, 10)); + std::string connectedHost(zoo_get_current_server(zk)); std::string portString = connectedHost.substr(connectedHost.find(":") + 1); uint32_t port; std::istringstream (portString) >> port; CPPUNIT_ASSERT_EQUAL(cluster_[followers[0]]->getClientPort(), port); + return zk; +} + +/** + * 1. Connect to a follower. + * 2. Remove the follower the client is connected to. + */ +void TestReconfigServer:: +testRemoveConnectedFollower() { + std::vector servers; + std::string version; + struct Stat stat; + int len = 1024; + char buf[len]; + + // connect to a follower. + std::stringstream ss; + std::vector followers = getFollowers(); + zhandle_t* zk = connectFollowers(followers); + CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_add_auth(zk, "digest", "super:test", 10, NULL,(void*)ZOK)); // remove the follower. len = 1024; @@ -324,4 +343,78 @@ testRemoveConnectedFollower() { zookeeper_close(zk); } +/** + * ZOOKEEPER-2014: only admin or users who are explicitly granted permission can do reconfig. + */ +void TestReconfigServer:: +testReconfigFailureWithoutAuth() { + std::vector servers; + std::string version; + struct Stat stat; + int len = 1024; + char buf[len]; + + // connect to a follower. + std::stringstream ss; + std::vector followers = getFollowers(); + zhandle_t* zk = connectFollowers(followers); + + // remove the follower. + len = 1024; + ss.str(""); + ss << followers[0]; + // No auth, should fail. + CPPUNIT_ASSERT_EQUAL((int)ZNOAUTH, zoo_reconfig(zk, NULL, ss.str().c_str(), NULL, -1, buf, &len, &stat)); + // Wrong auth, should fail. + CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_add_auth(zk, "digest", "super:wrong", 11, NULL,(void*)ZOK)); + CPPUNIT_ASSERT_EQUAL((int)ZNOAUTH, zoo_reconfig(zk, NULL, ss.str().c_str(), NULL, -1, buf, &len, &stat)); + // Right auth, should pass. + CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_add_auth(zk, "digest", "super:test", 10, NULL,(void*)ZOK)); + CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_reconfig(zk, NULL, ss.str().c_str(), NULL, -1, buf, &len, &stat)); + CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_getconfig(zk, 0, buf, &len, &stat)); + parseConfig(buf, len, servers, version); + CPPUNIT_ASSERT_EQUAL(NUM_SERVERS - 1, (uint32_t)(servers.size())); + for (int i = 0; i < cluster_.size(); i++) { + if (i == followers[0]) { + continue; + } + CPPUNIT_ASSERT(std::find(servers.begin(), servers.end(), + cluster_[i]->getServerString()) != servers.end()); + } + zookeeper_close(zk); +} + +void TestReconfigServer:: +testReconfigFailureWithoutServerSuperuserPasswordConfigured() { + std::vector servers; + std::string version; + struct Stat stat; + int len = 1024; + char buf[len]; + + // Create a new quorum with the super user's password not configured. + tearDown(); + ZooKeeperQuorumServer::tConfigPairs configs; + configs.push_back(std::make_pair("reconfigEnabled", "true")); + cluster_ = ZooKeeperQuorumServer::getCluster(NUM_SERVERS, configs, ""); + + // connect to a follower. + std::stringstream ss; + std::vector followers = getFollowers(); + zhandle_t* zk = connectFollowers(followers); + + // remove the follower. + len = 1024; + ss.str(""); + ss << followers[0]; + // All cases should fail as server ensemble was not configured with the super user's password. + CPPUNIT_ASSERT_EQUAL((int)ZNOAUTH, zoo_reconfig(zk, NULL, ss.str().c_str(), NULL, -1, buf, &len, &stat)); + CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_add_auth(zk, "digest", "super:", 11, NULL,(void*)ZOK)); + CPPUNIT_ASSERT_EQUAL((int)ZNOAUTH, zoo_reconfig(zk, NULL, ss.str().c_str(), NULL, -1, buf, &len, &stat)); + CPPUNIT_ASSERT_EQUAL((int)ZOK, zoo_add_auth(zk, "digest", "super:test", 10, NULL,(void*)ZOK)); + CPPUNIT_ASSERT_EQUAL((int)ZNOAUTH, zoo_reconfig(zk, NULL, ss.str().c_str(), NULL, -1, buf, &len, &stat)); + zookeeper_close(zk); +} + CPPUNIT_TEST_SUITE_REGISTRATION(TestReconfigServer); +#endif diff --git a/src/c/tests/ZKMocks.cc b/src/c/tests/ZKMocks.cc index 263f3ce954f..1310ab9d4b8 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)); @@ -339,7 +339,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/ZooKeeperQuorumServer.cc b/src/c/tests/ZooKeeperQuorumServer.cc index 23392cdbe40..c38e385a15c 100644 --- a/src/c/tests/ZooKeeperQuorumServer.cc +++ b/src/c/tests/ZooKeeperQuorumServer.cc @@ -21,18 +21,21 @@ #include #include #include +#include +#include #include ZooKeeperQuorumServer:: -ZooKeeperQuorumServer(uint32_t id, uint32_t numServers) : +ZooKeeperQuorumServer(uint32_t id, uint32_t numServers, std::string config, std::string env) : id_(id), + env_(env), numServers_(numServers) { const char* root = getenv("ZKROOT"); if (root == NULL) { assert(!"Environment variable 'ZKROOT' is not set"); } root_ = root; - createConfigFile(); + createConfigFile(config); createDataDirectory(); start(); } @@ -58,6 +61,9 @@ void ZooKeeperQuorumServer:: start() { std::string command = root_ + "/bin/zkServer.sh start " + getConfigFileName(); + if (!env_.empty()) { + command = env_ + " " + command; + } assert(system(command.c_str()) == 0); } @@ -102,7 +108,7 @@ isFollower() { } void ZooKeeperQuorumServer:: -createConfigFile() { +createConfigFile(std::string config) { std::string command = "mkdir -p " + root_ + "/build/test/test-cppunit/conf"; assert(system(command.c_str()) == 0); std::ofstream confFile; @@ -118,6 +124,10 @@ createConfigFile() { for (int i = 0; i < numServers_; i++) { confFile << getServerString(i) << "\n"; } + // Append additional config, if any. + if (!config.empty()) { + confFile << config << std::endl; + } confFile.close(); } @@ -188,3 +198,33 @@ getCluster(uint32_t numServers) { } assert(!"The cluster didn't start for 10 seconds"); } + +std::vector ZooKeeperQuorumServer:: +getCluster(uint32_t numServers, ZooKeeperQuorumServer::tConfigPairs configs, std::string env) { + std::vector cluster; + std::string config; + for (ZooKeeperQuorumServer::tConfigPairs::const_iterator iter = configs.begin(); iter != configs.end(); ++iter) { + std::pair pair = *iter; + config += (pair.first + "=" + pair.second + "\n"); + } + for (int i = 0; i < numServers; i++) { + cluster.push_back(new ZooKeeperQuorumServer(i, numServers, config, env)); + } + + // Wait until all the servers start, and fail if they don't start within 10 + // seconds. + for (int i = 0; i < 10; i++) { + int j = 0; + for (; j < cluster.size(); j++) { + if (cluster[j]->getMode() == "") { + // The server hasn't started. + sleep(1); + break; + } + } + if (j == cluster.size()) { + return cluster; + } + } + assert(!"The cluster didn't start for 10 seconds"); +} diff --git a/src/c/tests/ZooKeeperQuorumServer.h b/src/c/tests/ZooKeeperQuorumServer.h index aa8b7ccf6f9..577072ebcc4 100644 --- a/src/c/tests/ZooKeeperQuorumServer.h +++ b/src/c/tests/ZooKeeperQuorumServer.h @@ -20,11 +20,16 @@ #include #include #include +#include class ZooKeeperQuorumServer { public: ~ZooKeeperQuorumServer(); + typedef std::vector > tConfigPairs; static std::vector getCluster(uint32_t numServers); + static std::vector getCluster(uint32_t numServers, + tConfigPairs configs, /* Additional config options as a list of key/value pairs. */ + std::string env /* Additional environment variables when starting zkServer.sh. */); std::string getHostPort(); uint32_t getClientPort(); void start(); @@ -35,10 +40,11 @@ class ZooKeeperQuorumServer { private: ZooKeeperQuorumServer(); - ZooKeeperQuorumServer(uint32_t id, uint32_t numServers); + ZooKeeperQuorumServer(uint32_t id, uint32_t numServers, std::string config = "", + std::string env = ""); ZooKeeperQuorumServer(const ZooKeeperQuorumServer& that); const ZooKeeperQuorumServer& operator=(const ZooKeeperQuorumServer& that); - void createConfigFile(); + void createConfigFile(std::string config = ""); std::string getConfigFileName(); void createDataDirectory(); std::string getDataDirectory(); @@ -52,6 +58,7 @@ class ZooKeeperQuorumServer { uint32_t numServers_; uint32_t id_; std::string root_; + std::string env_; }; #endif // ZOOKEEPER_QUORUM_SERVER_H diff --git a/src/c/tests/quorum.cfg b/src/c/tests/quorum.cfg index aa2dd8ce8c0..cb0aa81c856 100644 --- a/src/c/tests/quorum.cfg +++ b/src/c/tests/quorum.cfg @@ -1,7 +1,7 @@ tickTime=500 initLimit=10 syncLimit=5 -dataDir=/tmp/zkdata +dataDir=TMPDIR/zkdata clientPort=22181 server.1=localhost:22881:33881 server.2=localhost:22882:33882 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/tests/zkServer.sh b/src/c/tests/zkServer.sh index 33ab152b1d6..64e5a04919a 100755 --- a/src/c/tests/zkServer.sh +++ b/src/c/tests/zkServer.sh @@ -105,12 +105,12 @@ fi case $1 in start|startClean) if [ "x${base_dir}" == "x" ] - then + then mkdir -p /tmp/zkdata java -cp "$CLASSPATH" org.apache.zookeeper.server.ZooKeeperServerMain $ZOOPORT /tmp/zkdata 3000 $ZKMAXCNXNS &> /tmp/zk.log & pid=$! echo -n $! > /tmp/zk.pid - else + else mkdir -p "${base_dir}/build/tmp/zkdata" java -cp "$CLASSPATH" org.apache.zookeeper.server.ZooKeeperServerMain $ZOOPORT "${base_dir}/build/tmp/zkdata" 3000 $ZKMAXCNXNS &> "${base_dir}/build/tmp/zk.log" & pid=$! @@ -157,11 +157,14 @@ startReadOnly) echo "this target is for unit tests only" exit 2 else - mkdir -p /tmp/zkdata - rm -f /tmp/zkdata/myid && echo 1 > /tmp/zkdata/myid + tmpdir="${base_dir}/build/tmp" + mkdir -p "${tmpdir}/zkdata" + rm -f "${tmpdir}/zkdata/myid" && echo 1 > "${tmpdir}/zkdata/myid" + + sed "s#TMPDIR#${tmpdir}#g" ${base_dir}/src/c/tests/quorum.cfg > "${tmpdir}/quorum.cfg" # force read-only mode - java -cp "$CLASSPATH" -Dreadonlymode.enabled=true org.apache.zookeeper.server.quorum.QuorumPeerMain ${base_dir}/src/c/tests/quorum.cfg &> "${base_dir}/build/tmp/zk.log" & + java -cp "$CLASSPATH" -Dreadonlymode.enabled=true org.apache.zookeeper.server.quorum.QuorumPeerMain ${tmpdir}/quorum.cfg &> "${tmpdir}/zk.log" & pid=$! echo -n $pid > "${base_dir}/build/tmp/zk.pid" sleep 3 # wait until read-only server is up diff --git a/src/c/zookeeper-vs2013.sln b/src/c/zookeeper-vs2013.sln deleted file mode 100644 index 7d6499bed6c..00000000000 --- a/src/c/zookeeper-vs2013.sln +++ /dev/null @@ -1,37 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30110.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zookeeper", "zookeeper.vcxproj", "{5754FB2B-5EA5-4988-851D-908CA533A626}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cli", "cli.vcxproj", "{F267C55D-E02C-4BAF-A246-0AA58E8FE4A6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - 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}.Debug|x64.ActiveCfg = Debug|x64 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Debug|x64.Build.0 = Debug|x64 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Release|Win32.ActiveCfg = Release|Win32 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Release|Win32.Build.0 = Release|Win32 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Release|x64.ActiveCfg = Release|x64 - {5754FB2B-5EA5-4988-851D-908CA533A626}.Release|x64.Build.0 = Release|x64 - {F267C55D-E02C-4BAF-A246-0AA58E8FE4A6}.Debug|Win32.ActiveCfg = Debug|Win32 - {F267C55D-E02C-4BAF-A246-0AA58E8FE4A6}.Debug|Win32.Build.0 = Debug|Win32 - {F267C55D-E02C-4BAF-A246-0AA58E8FE4A6}.Debug|x64.ActiveCfg = Debug|x64 - {F267C55D-E02C-4BAF-A246-0AA58E8FE4A6}.Debug|x64.Build.0 = Debug|x64 - {F267C55D-E02C-4BAF-A246-0AA58E8FE4A6}.Release|Win32.ActiveCfg = Release|Win32 - {F267C55D-E02C-4BAF-A246-0AA58E8FE4A6}.Release|Win32.Build.0 = Release|Win32 - {F267C55D-E02C-4BAF-A246-0AA58E8FE4A6}.Release|x64.ActiveCfg = Release|x64 - {F267C55D-E02C-4BAF-A246-0AA58E8FE4A6}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/c/zookeeper.sln b/src/c/zookeeper.sln deleted file mode 100644 index 42f41c95264..00000000000 --- a/src/c/zookeeper.sln +++ /dev/null @@ -1,25 +0,0 @@ -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}" -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 6425fcd64ce..00000000000 --- a/src/c/zookeeper.vcproj +++ /dev/null @@ -1,308 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/c/zookeeper.vcxproj b/src/c/zookeeper.vcxproj deleted file mode 100644 index be4ad9aba39..00000000000 --- a/src/c/zookeeper.vcxproj +++ /dev/null @@ -1,196 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {5754FB2B-5EA5-4988-851D-908CA533A626} - zookeeper - Win32Proj - - - - DynamicLibrary - v120 - - - DynamicLibrary - v120 - - - DynamicLibrary - v120 - MultiByte - - - DynamicLibrary - v120 - MultiByte - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>11.0.61030.0 - - - $(ProjectName) - false - - - $(ProjectName) - false - - - true - - - $(ProjectName) - true - - - - Disabled - ./include;./generated;./hashtable;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;ZOOKEEPER_EXPORTS;DLL_EXPORT;THREADED;%(PreprocessorDefinitions) - true - EnableFastChecks - MultiThreadedDebugDLL - - Level3 - EditAndContinue - CompileAsC - - - ws2_32.lib;%(AdditionalDependencies) - true - Windows - false - - MachineX86 - - - - - Disabled - ./include;./generated;./hashtable;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;ZOOKEEPER_EXPORTS;DLL_EXPORT;THREADED;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebugDLL - - - Level3 - ProgramDatabase - CompileAsC - - - ws2_32.lib;%(AdditionalDependencies) - true - Windows - false - - - - - - - ./include;./generated;./hashtable;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;ZOOKEEPER_EXPORTS;DLL_EXPORT;THREADED;%(PreprocessorDefinitions) - MultiThreadedDLL - - Level3 - ProgramDatabase - CompileAsC - - - WS2_32.lib;%(AdditionalDependencies) - true - Windows - true - true - MachineX86 - $(OutDir)\$(TargetName).lib - - - - - ./include;./generated;./hashtable;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;ZOOKEEPER_EXPORTS;DLL_EXPORT;THREADED;%(PreprocessorDefinitions) - MultiThreadedDLL - - - Level3 - ProgramDatabase - CompileAsC - NoListing - - - WS2_32.lib;%(AdditionalDependencies) - true - Windows - true - true - $(OutDir)\$(TargetName).lib - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/c/zookeeper.vcxproj.filters b/src/c/zookeeper.vcxproj.filters deleted file mode 100644 index a373e802993..00000000000 --- a/src/c/zookeeper.vcxproj.filters +++ /dev/null @@ -1,96 +0,0 @@ - - - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Header Files - - - Source Files - - - \ No newline at end of file diff --git a/src/contrib/build-contrib.xml b/src/contrib/build-contrib.xml index 0e57d087af3..d0064483f20 100644 --- a/src/contrib/build-contrib.xml +++ b/src/contrib/build-contrib.xml @@ -39,11 +39,11 @@ - + - + + value="https://repo1.maven.org/maven2/org/apache/ivy/ivy" /> @@ -160,21 +160,6 @@
    - - - - - - - - - - - - - - - diff --git a/src/contrib/build.xml b/src/contrib/build.xml index 41fd432ef38..c1a716ced03 100644 --- a/src/contrib/build.xml +++ b/src/contrib/build.xml @@ -67,22 +67,4 @@
    - - - - - - - - - - - - - - - - - -
    diff --git a/src/contrib/loggraph/ivy.xml b/src/contrib/loggraph/ivy.xml index d6fa9d6d759..e3a1b486b7a 100644 --- a/src/contrib/loggraph/ivy.xml +++ b/src/contrib/loggraph/ivy.xml @@ -31,13 +31,13 @@ - - + + - - - + + + 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/monitoring/check_zookeeper.py b/src/contrib/monitoring/check_zookeeper.py index c00db8bc0b9..568d517cbf3 100755 --- a/src/contrib/monitoring/check_zookeeper.py +++ b/src/contrib/monitoring/check_zookeeper.py @@ -169,11 +169,16 @@ def __init__(self, host='localhost', port='2181', timeout=1): def get_stats(self): """ Get ZooKeeper server stats as a map """ data = self._send_cmd('mntr') + stat = self._parse_stat(self._send_cmd('stat')) if data: - return self._parse(data) + mntr = self._parse(data) + missing = ['zk_zxid', 'zk_zxid_counter', 'zk_zxid_epoch'] + for m in missing: + if m in stat: + mntr[m] = stat[m] + return mntr else: - data = self._send_cmd('stat') - return self._parse_stat(data) + return stat def _create_socket(self): return socket.socket() @@ -251,6 +256,13 @@ def _parse_stat(self, data): result['zk_znode_count'] = int(m.group(1)) continue + m = re.match('Zxid: (0x[0-9a-fA-F]+)', line) + if m is not None: + result['zk_zxid'] = m.group(1) + result['zk_zxid_counter'] = int(m.group(1), 16) & int('0xffffffff', 16) # lower 32 bits + result['zk_zxid_epoch'] = int(m.group(1), 16) >>32 # high 32 bits + continue + return result def _parse_line(self, line): diff --git a/src/contrib/rest/build.xml b/src/contrib/rest/build.xml index fb628bcd416..53c805763e7 100644 --- a/src/contrib/rest/build.xml +++ b/src/contrib/rest/build.xml @@ -81,8 +81,11 @@ + debug="on" encoding="${build.encoding}"> + + + @@ -129,6 +132,9 @@ + + + - - + + - + - + 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/rest/src/test/org/apache/zookeeper/server/jersey/Base.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/Base.java index 2d5f51af9ea..924f796792d 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/Base.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/Base.java @@ -20,17 +20,17 @@ import java.io.ByteArrayInputStream; -import junit.framework.TestCase; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.JUnit4ZKTestRunner; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.server.jersey.SetTest.MyWatcher; import org.apache.zookeeper.server.jersey.cfg.RestCfg; import org.junit.After; import org.junit.Before; +import org.junit.runner.RunWith; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.WebResource; @@ -39,7 +39,8 @@ * Test stand-alone server. * */ -public class Base extends TestCase { +@RunWith(JUnit4ZKTestRunner.class) +public class Base { protected static final Logger LOG = LoggerFactory.getLogger(Base.class); protected static final String CONTEXT_PATH = "/zk"; @@ -56,8 +57,6 @@ public class Base extends TestCase { @Before public void setUp() throws Exception { - super.setUp(); - RestCfg cfg = new RestCfg(new ByteArrayInputStream(String.format( "rest.port=%s\n" + "rest.endpoint.1=%s;%s\n", @@ -75,8 +74,6 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { - super.tearDown(); - client.destroy(); zk.close(); rest.stop(); diff --git a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/CreateTest.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/CreateTest.java index 49c33493d29..018c54b02a1 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/CreateTest.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/CreateTest.java @@ -30,6 +30,7 @@ import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.jersey.jaxb.ZPath; import org.junit.Test; +import org.junit.Assert; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -115,8 +116,6 @@ public CreateTest(String accept, String path, String name, String encoding, @Test public void testCreate() throws Exception { - LOG.info("STARTING " + getName()); - WebResource wr = znodesr.path(path).queryParam("dataformat", encoding) .queryParam("name", name); if (data == null) { @@ -134,7 +133,7 @@ public void testCreate() throws Exception { } else { cr = builder.post(ClientResponse.class, data); } - assertEquals(expectedStatus, cr.getClientResponseStatus()); + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); if (expectedPath == null) { return; @@ -142,11 +141,11 @@ public void testCreate() throws Exception { ZPath zpath = cr.getEntity(ZPath.class); if (sequence) { - assertTrue(zpath.path.startsWith(expectedPath.path)); - assertTrue(zpath.uri.startsWith(znodesr.path(path).toString())); + Assert.assertTrue(zpath.path.startsWith(expectedPath.path)); + Assert.assertTrue(zpath.uri.startsWith(znodesr.path(path).toString())); } else { - assertEquals(expectedPath, zpath); - assertEquals(znodesr.path(path).toString(), zpath.uri); + Assert.assertEquals(expectedPath, zpath); + Assert.assertEquals(znodesr.path(path).toString(), zpath.uri); } // use out-of-band method to verify @@ -154,9 +153,9 @@ public void testCreate() throws Exception { if (data == null && this.data == null) { return; } else if (data == null || this.data == null) { - assertEquals(data, this.data); + Assert.assertEquals(data, this.data); } else { - assertTrue(new String(data) + " == " + new String(this.data), + Assert.assertTrue(new String(data) + " == " + new String(this.data), Arrays.equals(data, this.data)); } } diff --git a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/DeleteTest.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/DeleteTest.java index 052239d0d2e..495f93b57a4 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/DeleteTest.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/DeleteTest.java @@ -30,6 +30,7 @@ import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.Stat; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -78,16 +79,15 @@ public void verify(String type) throws Exception { ClientResponse cr = znodesr.path(zpath).accept(type).type(type) .delete(ClientResponse.class); - assertEquals(expectedStatus, cr.getClientResponseStatus()); + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); // use out-of-band method to verify Stat stat = zk.exists(zpath, false); - assertNull(stat); + Assert.assertNull(stat); } @Test public void testDelete() throws Exception { - LOG.info("STARTING " + getName()); verify(MediaType.APPLICATION_OCTET_STREAM); verify(MediaType.APPLICATION_JSON); verify(MediaType.APPLICATION_XML); diff --git a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/ExistsTest.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/ExistsTest.java index 696ea959723..68b40f01886 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/ExistsTest.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/ExistsTest.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -63,16 +64,15 @@ private void verify(String type) { ClientResponse cr = znodesr.path(path).accept(type).type(type).head(); if (type.equals(MediaType.APPLICATION_OCTET_STREAM) && expectedStatus == ClientResponse.Status.OK) { - assertEquals(ClientResponse.Status.NO_CONTENT, + Assert.assertEquals(ClientResponse.Status.NO_CONTENT, cr.getClientResponseStatus()); } else { - assertEquals(expectedStatus, cr.getClientResponseStatus()); + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); } } @Test public void testExists() throws Exception { - LOG.info("STARTING " + getName()); verify(MediaType.APPLICATION_OCTET_STREAM); verify(MediaType.APPLICATION_JSON); verify(MediaType.APPLICATION_XML); diff --git a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/GetChildrenTest.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/GetChildrenTest.java index a046692037a..8f7fc981687 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/GetChildrenTest.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/GetChildrenTest.java @@ -31,6 +31,7 @@ import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.server.jersey.jaxb.ZChildren; import org.apache.zookeeper.server.jersey.jaxb.ZChildrenJSON; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -99,8 +100,6 @@ public GetChildrenTest(String accept, String path, ClientResponse.Status status, @Test public void testGetChildren() throws Exception { - LOG.info("STARTING " + getName()); - if (expectedChildren != null) { for(String child : expectedChildren) { zk.create(expectedPath + "/" + child, null, @@ -110,7 +109,7 @@ public void testGetChildren() throws Exception { ClientResponse cr = znodesr.path(path).queryParam("view", "children") .accept(accept).get(ClientResponse.class); - assertEquals(expectedStatus, cr.getClientResponseStatus()); + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); if (expectedChildren == null) { return; @@ -120,20 +119,20 @@ public void testGetChildren() throws Exception { ZChildrenJSON zchildren = cr.getEntity(ZChildrenJSON.class); Collections.sort(expectedChildren); Collections.sort(zchildren.children); - assertEquals(expectedChildren, zchildren.children); - assertEquals(znodesr.path(path).toString(), zchildren.uri); - assertEquals(znodesr.path(path).toString() + "/{child}", + Assert.assertEquals(expectedChildren, zchildren.children); + Assert.assertEquals(znodesr.path(path).toString(), zchildren.uri); + Assert.assertEquals(znodesr.path(path).toString() + "/{child}", zchildren.child_uri_template); } else if (accept.equals(MediaType.APPLICATION_XML)) { ZChildren zchildren = cr.getEntity(ZChildren.class); Collections.sort(expectedChildren); Collections.sort(zchildren.children); - assertEquals(expectedChildren, zchildren.children); - assertEquals(znodesr.path(path).toString(), zchildren.uri); - assertEquals(znodesr.path(path).toString() + "/{child}", + Assert.assertEquals(expectedChildren, zchildren.children); + Assert.assertEquals(znodesr.path(path).toString(), zchildren.uri); + Assert.assertEquals(znodesr.path(path).toString() + "/{child}", zchildren.child_uri_template); } else { - fail("unknown accept type"); + Assert.fail("unknown accept type"); } } } diff --git a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/GetTest.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/GetTest.java index f00946e244e..8ee1dc645de 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/GetTest.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/GetTest.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.server.jersey.jaxb.ZStat; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -97,8 +98,6 @@ public GetTest(String accept, String path, String encoding, @Test public void testGet() throws Exception { - LOG.info("STARTING " + getName()); - if (expectedStat != null) { if (expectedStat.data64 != null || expectedStat.dataUtf8 == null) { zk.setData(expectedStat.path, expectedStat.data64, -1); @@ -110,14 +109,14 @@ public void testGet() throws Exception { ClientResponse cr = znodesr.path(path).queryParam("dataformat", encoding) .accept(accept).get(ClientResponse.class); - assertEquals(expectedStatus, cr.getClientResponseStatus()); + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); if (expectedStat == null) { return; } ZStat zstat = cr.getEntity(ZStat.class); - assertEquals(expectedStat, zstat); - assertEquals(znodesr.path(path).toString(), zstat.uri); + Assert.assertEquals(expectedStat, zstat); + Assert.assertEquals(znodesr.path(path).toString(), zstat.uri); } } diff --git a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/RootTest.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/RootTest.java index a4f9b9edfb6..af8f9cf8357 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/RootTest.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/RootTest.java @@ -26,13 +26,13 @@ import org.slf4j.LoggerFactory; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.jersey.jaxb.ZPath; +import org.junit.Assert; import org.junit.Test; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResource.Builder; - /** * Test stand-alone server. * @@ -42,8 +42,6 @@ public class RootTest extends Base { @Test public void testCreate() throws Exception { - LOG.info("STARTING " + getName()); - String path = "/"; String name = "roottest-create"; byte[] data = "foo".getBytes(); @@ -54,15 +52,15 @@ public void testCreate() throws Exception { ClientResponse cr; cr = builder.post(ClientResponse.class, data); - assertEquals(ClientResponse.Status.CREATED, cr.getClientResponseStatus()); + Assert.assertEquals(ClientResponse.Status.CREATED, cr.getClientResponseStatus()); ZPath zpath = cr.getEntity(ZPath.class); - assertEquals(new ZPath(path + name), zpath); - assertEquals(znodesr.path(path).toString(), zpath.uri); + Assert.assertEquals(new ZPath(path + name), zpath); + Assert.assertEquals(znodesr.path(path).toString(), zpath.uri); // use out-of-band method to verify byte[] rdata = zk.getData(zpath.path, false, new Stat()); - assertTrue(new String(rdata) + " == " + new String(data), + Assert.assertTrue(new String(rdata) + " == " + new String(data), Arrays.equals(rdata, data)); } } diff --git a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/SessionTest.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/SessionTest.java index 3e165c06e5d..b8fc927ccbd 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/SessionTest.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/SessionTest.java @@ -29,6 +29,7 @@ import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.jersey.jaxb.ZSession; import org.codehaus.jettison.json.JSONException; +import org.junit.Assert; import org.junit.Test; import com.sun.jersey.api.client.Client; @@ -49,7 +50,7 @@ private ZSession createSession(String expire) { Builder b = wr.accept(MediaType.APPLICATION_JSON); ClientResponse cr = b.post(ClientResponse.class, null); - assertEquals(ClientResponse.Status.CREATED, cr + Assert.assertEquals(ClientResponse.Status.CREATED, cr .getClientResponseStatus()); return cr.getEntity(ZSession.class); @@ -58,10 +59,10 @@ private ZSession createSession(String expire) { @Test public void testCreateNewSession() throws JSONException { ZSession session = createSession(); - assertEquals(session.id.length(), 36); + Assert.assertEquals(session.id.length(), 36); // use out-of-band method to verify - assertTrue(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); + Assert.assertTrue(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); } @Test @@ -69,11 +70,11 @@ public void testSessionExpires() throws InterruptedException { ZSession session = createSession("1"); // use out-of-band method to verify - assertTrue(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); + Assert.assertTrue(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); // wait for the session to be closed Thread.sleep(1500); - assertFalse(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); + Assert.assertFalse(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); } @Test @@ -83,12 +84,12 @@ public void testDeleteSession() { WebResource wr = sessionsr.path(session.id); Builder b = wr.accept(MediaType.APPLICATION_JSON); - assertTrue(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); + Assert.assertTrue(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); ClientResponse cr = b.delete(ClientResponse.class, null); - assertEquals(ClientResponse.Status.NO_CONTENT, + Assert.assertEquals(ClientResponse.Status.NO_CONTENT, cr.getClientResponseStatus()); - assertFalse(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); + Assert.assertFalse(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); } @Test @@ -100,13 +101,13 @@ public void testSendHeartbeat() throws InterruptedException { Builder b = wr.accept(MediaType.APPLICATION_JSON); ClientResponse cr = b.put(ClientResponse.class, null); - assertEquals(ClientResponse.Status.OK, cr.getClientResponseStatus()); + Assert.assertEquals(ClientResponse.Status.OK, cr.getClientResponseStatus()); Thread.sleep(1500); - assertTrue(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); + Assert.assertTrue(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); Thread.sleep(1000); - assertFalse(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); + Assert.assertFalse(ZooKeeperService.isConnected(CONTEXT_PATH, session.id)); } @Test @@ -123,12 +124,12 @@ public void testCreateEphemeralZNode() Builder b = wr.accept(MediaType.APPLICATION_JSON); ClientResponse cr = b.post(ClientResponse.class); - assertEquals(ClientResponse.Status.CREATED, cr.getClientResponseStatus()); + Assert.assertEquals(ClientResponse.Status.CREATED, cr.getClientResponseStatus()); Stat stat = new Stat(); zk.getData("/ephemeral-test", false, stat); ZooKeeper sessionZK = ZooKeeperService.getClient(CONTEXT_PATH, session.id); - assertEquals(stat.getEphemeralOwner(), sessionZK.getSessionId()); + Assert.assertEquals(stat.getEphemeralOwner(), sessionZK.getSessionId()); } } diff --git a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/SetTest.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/SetTest.java index cbcfd21f6ac..a86ad462a54 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/SetTest.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/SetTest.java @@ -31,6 +31,7 @@ import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.jersey.jaxb.ZStat; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -106,8 +107,6 @@ public SetTest(String accept, String path, String encoding, @Test public void testSet() throws Exception { - LOG.info("STARTING " + getName()); - if (expectedStat != null) { zk.create(expectedStat.path, "initial".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); @@ -131,24 +130,24 @@ public void testSet() throws Exception { // TODO investigate cr = builder.put(ClientResponse.class, new String(data)); } - assertEquals(expectedStatus, cr.getClientResponseStatus()); + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); if (expectedStat == null) { return; } ZStat zstat = cr.getEntity(ZStat.class); - assertEquals(expectedStat, zstat); + Assert.assertEquals(expectedStat, zstat); // use out-of-band method to verify byte[] data = zk.getData(zstat.path, false, new Stat()); if (data == null && this.data == null) { return; } else if (data == null || this.data == null) { - fail((data == null ? null : new String(data)) + " == " + Assert.fail((data == null ? null : new String(data)) + " == " + (this.data == null ? null : new String(this.data))); } else { - assertTrue(new String(data) + " == " + new String(this.data), + Assert.assertTrue(new String(data) + " == " + new String(this.data), Arrays.equals(data, this.data)); } } diff --git a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/WadlTest.java b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/WadlTest.java index 4009e6d824e..c3b10c0edca 100644 --- a/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/WadlTest.java +++ b/src/contrib/rest/src/test/org/apache/zookeeper/server/jersey/WadlTest.java @@ -20,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.junit.Assert; import org.junit.Test; import com.sun.jersey.api.client.WebResource; @@ -38,7 +39,7 @@ public void testApplicationWadl() { WebResource r = client.resource(BASEURI); String serviceWadl = r.path("application.wadl"). accept(MediaTypes.WADL).get(String.class); - assertTrue("Something wrong. Returned wadl length not > 0.", + Assert.assertTrue("Something wrong. Returned wadl length not > 0.", serviceWadl.length() > 0); } } 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/zkperl/ZooKeeper.xs b/src/contrib/zkperl/ZooKeeper.xs index f65e076b2a3..4b6067b1024 100644 --- a/src/contrib/zkperl/ZooKeeper.xs +++ b/src/contrib/zkperl/ZooKeeper.xs @@ -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'; diff --git a/src/contrib/zkpython/build.xml b/src/contrib/zkpython/build.xml index d8254d14b0a..82ff902858f 100644 --- a/src/contrib/zkpython/build.xml +++ b/src/contrib/zkpython/build.xml @@ -25,10 +25,6 @@ - - - - @@ -111,88 +107,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/contrib/zkpython/ivy.xml b/src/contrib/zkpython/ivy.xml index 6931f4684bd..f8bad40139d 100644 --- a/src/contrib/zkpython/ivy.xml +++ b/src/contrib/zkpython/ivy.xml @@ -32,12 +32,4 @@ - - - - - - - - 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 0bf6c59c28e..4474661860a 100644 --- a/src/contrib/zkpython/src/c/zookeeper.c +++ b/src/contrib/zkpython/src/c/zookeeper.c @@ -721,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; @@ -1059,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) { diff --git a/src/contrib/zkpython/src/packages/deb/zkpython.control/control b/src/contrib/zkpython/src/packages/deb/zkpython.control/control deleted file mode 100644 index 6ec76d06957..00000000000 --- a/src/contrib/zkpython/src/packages/deb/zkpython.control/control +++ /dev/null @@ -1,23 +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: zkpython -Version: @version@ -Section: misc -Priority: optional -Architecture: all -Depends: python -Maintainer: Apache Software Foundation -Description: ZooKeeper python binding library -Distribution: development diff --git a/src/contrib/zkpython/src/packages/rpm/spec/zkpython.spec b/src/contrib/zkpython/src/packages/rpm/spec/zkpython.spec deleted file mode 100644 index d1f981431b6..00000000000 --- a/src/contrib/zkpython/src/packages/rpm/spec/zkpython.spec +++ /dev/null @@ -1,81 +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. - -# -# RPM Spec file for ZooKeeper version @version@ -# - -%define name zkpython -%define version @version@ -%define release @package.release@ - -# Installation Locations -%define _prefix @package.prefix@ - -# Build time settings -%define _build_dir @package.build.dir@ -%define _final_name @final.name@ -%define _python_lib @python.lib@ -%define debug_package %{nil} - -# Disable brp-java-repack-jars for aspect J -%define __os_install_post \ - /usr/lib/rpm/redhat/brp-compress \ - %{!?__debug_package:/usr/lib/rpm/redhat/brp-strip %{__strip}} \ - /usr/lib/rpm/redhat/brp-strip-static-archive %{__strip} \ - /usr/lib/rpm/redhat/brp-strip-comment-note %{__strip} %{__objdump} \ - /usr/lib/rpm/brp-python-bytecompile %{nil} - -# RPM searches perl files for dependancies and this breaks for non packaged perl lib -# like thrift so disable this -%define _use_internal_dependency_generator 0 - -Summary: ZooKeeper python binding library -Group: Development/Libraries -License: Apache License, Version 2.0 -URL: http://zookeeper.apache.org/ -Vendor: Apache Software Foundation -Name: %{name} -Version: %{version} -Release: %{release} -Source0: %{_python_lib} -Prefix: %{_prefix} -Requires: zookeeper-lib == %{version} -AutoReqProv: no -Provides: zkpython - -%description -ZooKeeper python binding library - -%prep -tar fxz %{_python_lib} -C %{_build_dir} - -%build - -######################### -#### INSTALL SECTION #### -######################### -%install - -%pre - -%post - -%preun - -%files -%defattr(-,root,root) -%{_prefix} - diff --git a/src/contrib/zkpython/src/python/zk.py b/src/contrib/zkpython/src/python/zk.py index 9c0f37482ed..24986e3aac8 100755 --- a/src/contrib/zkpython/src/python/zk.py +++ b/src/contrib/zkpython/src/python/zk.py @@ -52,7 +52,7 @@ def my_getc_watch( handle, type, state, path ): 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 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/build.xml b/src/contrib/zooinspector/build.xml index c48fa1940bd..52f3eb1848d 100644 --- a/src/contrib/zooinspector/build.xml +++ b/src/contrib/zooinspector/build.xml @@ -76,7 +76,7 @@ - + diff --git a/src/contrib/zooinspector/ivy.xml b/src/contrib/zooinspector/ivy.xml index 096f05c3f83..d841d18b2ce 100644 --- a/src/contrib/zooinspector/ivy.xml +++ b/src/contrib/zooinspector/ivy.xml @@ -32,17 +32,19 @@ - - + + + + - - - + + + rev="3.2.2" conf="releaseaudit->default"/> - \ No newline at end of file + 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 285b663c82b..3f8d0551f69 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 @@ -34,6 +34,8 @@ import javax.swing.JToolBar; import javax.swing.SwingWorker; +import org.apache.zookeeper.inspector.gui.actions.AddNodeAction; +import org.apache.zookeeper.inspector.gui.actions.DeleteNodeAction; import org.apache.zookeeper.inspector.gui.nodeviewer.ZooInspectorNodeViewer; import org.apache.zookeeper.inspector.logger.LoggerFactory; import org.apache.zookeeper.inspector.manager.ZooInspectorManager; @@ -102,74 +104,12 @@ public void actionPerformed(ActionEvent e) { treeViewer.refreshView(); } }); - toolbar.addActionListener(Toolbar.Button.addNode, 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."); - } - } - }); - toolbar.addActionListener(Toolbar.Button.deleteNode, 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; - } + toolbar.addActionListener(Toolbar.Button.addNode, + new AddNodeAction(this, treeViewer, zooInspectorManager)); + toolbar.addActionListener(Toolbar.Button.deleteNode, + new DeleteNodeAction(this, treeViewer, zooInspectorManager)); - @Override - protected void done() { - treeViewer.refreshView(); - } - }; - worker.execute(); - } - } - } - }); toolbar.addActionListener(Toolbar.Button.nodeViewers, new ActionListener() { public void actionPerformed(ActionEvent e) { 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 f6b405ab361..e08f2d3b753 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 @@ -21,6 +21,8 @@ import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; @@ -35,6 +37,7 @@ import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; +import javax.swing.JComponent; import javax.swing.JTree; import javax.swing.SwingWorker; import javax.swing.event.TreeSelectionListener; @@ -44,10 +47,13 @@ import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; +import org.apache.zookeeper.inspector.gui.actions.AddNodeAction; +import org.apache.zookeeper.inspector.gui.actions.DeleteNodeAction; import org.apache.zookeeper.inspector.manager.NodeListener; import org.apache.zookeeper.inspector.manager.ZooInspectorManager; import com.nitido.utils.toaster.Toaster; +import static javax.swing.KeyStroke.getKeyStroke; /** * A {@link JPanel} for showing the tree view of all the nodes in the zookeeper @@ -69,9 +75,29 @@ public class ZooInspectorTreeViewer extends JPanel implements NodeListener { public ZooInspectorTreeViewer( final ZooInspectorManager zooInspectorManager, TreeSelectionListener listener, IconResource iconResource) { + + this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_MASK), "deleteNode"); + + this.getActionMap().put("deleteNode", + new DeleteNodeAction(this, this, zooInspectorManager)); + + this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_MASK), "addNode"); + + this.getActionMap().put("addNode", + new AddNodeAction(this, this, zooInspectorManager)); + this.zooInspectorManager = zooInspectorManager; this.setLayout(new BorderLayout()); final JPopupMenu popupMenu = new JPopupMenu(); + + final JMenuItem addNode = new JMenuItem("Add Node"); + addNode.addActionListener(new AddNodeAction(this, this, zooInspectorManager)); + + final JMenuItem deleteNode = new JMenuItem("Delete Node"); + deleteNode.addActionListener(new DeleteNodeAction(this, this, zooInspectorManager)); + final JMenuItem addNotify = new JMenuItem("Add Change Notification"); this.toasterManager = new Toaster(); this.toasterManager.setBorderColor(Color.BLACK); @@ -105,6 +131,8 @@ public void mouseClicked(MouseEvent e) { // watched, and only show remove if a selected node is being // watched popupMenu.removeAll(); + popupMenu.add(addNode); + popupMenu.add(deleteNode); popupMenu.add(addNotify); popupMenu.add(removeNotify); popupMenu.show(ZooInspectorTreeViewer.this, e.getX(), e diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/actions/AddNodeAction.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/actions/AddNodeAction.java new file mode 100644 index 00000000000..30916112a2f --- /dev/null +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/actions/AddNodeAction.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.inspector.gui.actions; + +import org.apache.zookeeper.inspector.gui.ZooInspectorPanel; +import org.apache.zookeeper.inspector.gui.ZooInspectorTreeViewer; +import org.apache.zookeeper.inspector.manager.ZooInspectorManager; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import java.awt.event.KeyEvent; + +public class AddNodeAction extends AbstractAction { + + private JPanel panel; + private ZooInspectorTreeViewer treeViewer; + private ZooInspectorManager zooInspectorManager; + + public AddNodeAction(JPanel parentPanel, + ZooInspectorTreeViewer treeViewer, + ZooInspectorManager zooInspectorManager) { + this.panel = parentPanel; + this.treeViewer = treeViewer; + this.zooInspectorManager = zooInspectorManager; + } + + public void actionPerformed(ActionEvent e) { + final List selectedNodes = treeViewer + .getSelectedNodes(); + if (selectedNodes.size() == 1) { + final String nodeName = JOptionPane.showInputDialog( + panel, + "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 zooInspectorManager + .createNode(selectedNodes.get(0), + nodeName); + } + + @Override + protected void done() { + treeViewer.refreshView(); + } + }; + worker.execute(); + } + } else { + JOptionPane.showMessageDialog(panel, + "Please select 1 parent node for the new node."); + } + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/actions/DeleteNodeAction.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/actions/DeleteNodeAction.java new file mode 100644 index 00000000000..90016701e68 --- /dev/null +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/gui/actions/DeleteNodeAction.java @@ -0,0 +1,79 @@ +/** + * 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.actions; + +import org.apache.zookeeper.inspector.gui.ZooInspectorTreeViewer; +import org.apache.zookeeper.inspector.manager.ZooInspectorManager; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import java.awt.event.KeyEvent; + +public class DeleteNodeAction extends AbstractAction { + + private JPanel parentPanel; + private ZooInspectorTreeViewer treeViewer; + private ZooInspectorManager zooInspectorManager; + + public DeleteNodeAction(JPanel parentPanel, + ZooInspectorTreeViewer treeViewer, + ZooInspectorManager zooInspectorManager) { + this.parentPanel = parentPanel; + this.treeViewer = treeViewer; + this.zooInspectorManager = zooInspectorManager; + } + + + public void actionPerformed(ActionEvent e) { + final List selectedNodes = treeViewer + .getSelectedNodes(); + if (selectedNodes.size() == 0) { + JOptionPane.showMessageDialog(parentPanel, + "Please select at least 1 node to be deleted"); + } else { + int answer = JOptionPane.showConfirmDialog( + parentPanel, + "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) { + zooInspectorManager + .deleteNode(nodePath); + } + return true; + } + + @Override + protected void done() { + treeViewer.refreshView(); + } + }; + worker.execute(); + } + } + } +} diff --git a/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/NodesCache.java b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/NodesCache.java new file mode 100644 index 00000000000..45c5a275272 --- /dev/null +++ b/src/contrib/zooinspector/src/java/org/apache/zookeeper/inspector/manager/NodesCache.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.inspector.manager; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.inspector.logger.LoggerFactory; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class NodesCache { + + public static final int CACHE_SIZE = 40000; + + public static final int EXPIRATION_TIME = 100; + + private final LoadingCache> nodes; + + private ZooKeeper zooKeeper; + + public NodesCache(ZooKeeper zooKeeper) { + this.zooKeeper = zooKeeper; + this.nodes = CacheBuilder.newBuilder() + .maximumSize(CACHE_SIZE) + .expireAfterWrite(EXPIRATION_TIME, TimeUnit.MILLISECONDS) + .build( + new CacheLoader>() { + @Override + public List load(String nodePath) throws Exception { + return getChildren(nodePath); + } + } + ); + } + + public List getChildren(String nodePath) { + try { + Stat s = zooKeeper.exists(nodePath, false); + if (s != null) { + List children = this.zooKeeper.getChildren(nodePath, false); + Collections.sort(children); + return children; + } + } catch (Exception e) { + LoggerFactory.getLogger().error( + "Error occurred retrieving child of node: " + nodePath, e + ); + } + return null; + } + + public String getNodeChild(String nodePath, int index) { + List childNodes = null; + try { + childNodes = nodes.get(nodePath); + return childNodes.get(index); + } catch (ExecutionException e) { + LoggerFactory.getLogger().error( + "Error occurred retrieving child " + index + "of node: " + nodePath, e + ); + } + return null; + } + +} 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 02b8af448b8..57cdd95c0e0 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 @@ -109,6 +109,7 @@ public class ZooInspectorManagerImpl implements ZooInspectorManager { private String defaultHosts; private String defaultAuthScheme; private String defaultAuthValue; + private NodesCache nodesCache; /** * @throws IOException @@ -181,6 +182,8 @@ public void process(WatchedEvent event) { } if (!connected){ disconnect(); + } else { + this.nodesCache = new NodesCache(zooKeeper); } return connected; } @@ -216,14 +219,7 @@ public boolean disconnect() { */ 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 nodesCache.getChildren(nodePath); } return null; @@ -263,17 +259,7 @@ public String getData(String nodePath) { */ 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 this.nodesCache.getNodeChild(nodePath, childIndex); } return null; } @@ -296,7 +282,7 @@ public int getNodeIndex(String nodePath) { String parentPath = nodePath.substring(0, index); String child = nodePath.substring(index + 1); if (parentPath != null && parentPath.length() > 0) { - List children = this.getChildren(parentPath); + List children = this.nodesCache.getChildren(parentPath); if (children != null) { return children.indexOf(child); } @@ -626,7 +612,7 @@ public void addWatchers(Collection selectedNodes, zooKeeper)); } catch (Exception e) { LoggerFactory.getLogger().error( - "Error occured adding node watcher for node: " + "Error occurred adding node watcher for node: " + node, e); } } @@ -701,7 +687,7 @@ public void process(WatchedEvent event) { } } catch (Exception e) { LoggerFactory.getLogger().error( - "Error occured re-adding node watcherfor node " + "Error occurred re-adding node watcherfor node " + nodePath, e); } nodeListener.processEvent(event.getPath(), event.getType() @@ -860,7 +846,11 @@ public void setDefaultNodeViewerConfiguration( } public List getDefaultNodeViewerConfiguration() throws IOException { - return loadNodeViewersFile(defaultNodeViewersFile); + List defaultNodeViewers = loadNodeViewersFile(defaultNodeViewersFile); + if (defaultNodeViewers.isEmpty()) { + LoggerFactory.getLogger().warn("List of default node viewers is empty"); + } + return defaultNodeViewers; } /* 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 feb4301245f..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 @@ -217,12 +217,12 @@ public byte[] getData(String path, Watcher watcher, Stat stat) } @Override - public Stat setACL(String path, List acl, int version) + public Stat setACL(String path, List acl, int aclVersion) throws KeeperException, InterruptedException { int count = 0; do { try { - return super.setACL(path, acl, version); + return super.setACL(path, acl, aclVersion); } catch (KeeperException.ConnectionLossException e) { LoggerFactory.getLogger().warn( "ZooKeeper connection lost. Trying to reconnect."); diff --git a/src/contrib/zooinspector/zooInspector.cmd b/src/contrib/zooinspector/zooInspector.cmd index 0910798240e..0b9dcf85098 100644 --- a/src/contrib/zooinspector/zooInspector.cmd +++ b/src/contrib/zooinspector/zooInspector.cmd @@ -1,18 +1,21 @@ -#!/bin/sh +@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. -# 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\*;lib" org.apache.zookeeper.inspector.ZooInspector +set CLASSPATH=lib\*;lib +for /F %%f in ('dir /b "%~dp0%\*.jar" 2^>nul') do ( + set CLASSPATH=%%f;%CLASSPATH% +) +java -cp "%CLASSPATH%" org.apache.zookeeper.inspector.ZooInspector 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/index.xml b/src/docs/src/documentation/content/xdocs/index.xml index 1686fa65dfc..969e482cabe 100644 --- a/src/docs/src/documentation/content/xdocs/index.xml +++ b/src/docs/src/documentation/content/xdocs/index.xml @@ -65,6 +65,7 @@
  • JMX - how to enable JMX in ZooKeeper
  • Hierarchical quorums
  • Observers - non-voting ensemble members that easily improve ZooKeeper's scalability
  • +
  • Dynamic Reconfiguration - a guide on how to use dynamic reconfiguration in ZooKeeper
  • diff --git a/src/docs/src/documentation/content/xdocs/recipes.xml b/src/docs/src/documentation/content/xdocs/recipes.xml index f977a543be9..f53536fe20d 100644 --- a/src/docs/src/documentation/content/xdocs/recipes.xml +++ b/src/docs/src/documentation/content/xdocs/recipes.xml @@ -614,7 +614,7 @@ processes watching upon the current smallest znode, and checking if they are the new leader when the smallest znode goes away (note that the smallest znode will go away if the leader fails because the node is - ephemeral). But this causes a herd effect: upon of failure of the current + ephemeral). But this causes a herd effect: upon a failure of the current leader, all other processes receive a notification, and execute getChildren on "/election" to obtain the current list of children of "/election". If the number of clients is large, it causes a spike on the 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 41f079450f3..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 c4c43aa94ce..614fa6cc67b 100644 --- a/src/docs/src/documentation/content/xdocs/site.xml +++ b/src/docs/src/documentation/content/xdocs/site.xml @@ -51,6 +51,7 @@ 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 aef7e59b083..90bbf99bbcd 100644 --- a/src/docs/src/documentation/content/xdocs/tabs.xml +++ b/src/docs/src/documentation/content/xdocs/tabs.xml @@ -31,6 +31,6 @@ - + diff --git a/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml b/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml index 330b436fd93..62cd54bc7ff 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml @@ -74,39 +74,97 @@
    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 both server and client. + 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. - Win64 is supported as a development - platform only for both server and client. - - - 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.
    Required Software - ZooKeeper runs in Java, release 1.6 or greater (JDK 6 or - greater, FreeBSD support requires openjdk7). It runs as an + ZooKeeper runs in Java, release 1.8 or greater (JDK 8 or + greater, FreeBSD support requires openjdk8). It runs as an ensemble of ZooKeeper servers. Three ZooKeeper servers is the minimum recommended size for an ensemble, and we also recommend that they run on separate @@ -128,7 +186,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: @@ -195,14 +274,16 @@ server.3=zoo3:2888:3888 consists of a single line containing only the text of that machine's id. So myid of server 1 would contain the text "1" and nothing else. The id must be unique within the - ensemble and should have a value between 1 and 255. + ensemble and should have a value between 1 and 255. IMPORTANT: if you + enable extended features such as TTL Nodes (see below) the id must be + between 1 and 254 due to internal limitations. If your configuration file is set up, you can start a ZooKeeper server: - $ java -cp zookeeper.jar:lib/slf4j-api-1.7.5.jar:lib/slf4j-log4j12-1.7.5.jar:lib/log4j-1.2.16.jar:conf \ + $ java -cp zookeeper.jar:lib/slf4j-api-1.7.5.jar:lib/slf4j-log4j12-1.7.5.jar:lib/log4j-1.2.17.jar:conf \ org.apache.zookeeper.server.quorum.QuorumPeerMain zoo.cfg @@ -223,34 +304,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.7.5.jar:lib/slf4j-log4j12-1.7.5.jar:lib/log4j-1.2.16.jar:conf:src/java/lib/jline-2.11.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
    @@ -390,7 +447,7 @@ server.3=zoo3:2888:3888 Single Machine Requirements If ZooKeeper has to contend with other applications for - access to resourses like storage media, CPU, network, or + access to resources like storage media, CPU, network, or memory, its performance will suffer markedly. ZooKeeper has strong durability guarantees, which means it uses storage media to log changes before the operation responsible for the @@ -457,10 +514,14 @@ server.3=zoo3:2888:3888 of the znodes stored by a particular serving ensemble. These are the snapshot and transactional log files. As changes are made to the znodes these changes are appended to a - transaction log, occasionally, when a log grows large, a + transaction log. Occasionally, when a log grows large, a snapshot of the current state of all znodes will be written - to the filesystem. This snapshot supercedes all previous - logs. + to the filesystem and a new transaction log file is created + for future transactions. During snapshotting, ZooKeeper may + continue appending incoming transactions to the old log file. + Therefore, some transactions which are newer than a snapshot + may be found in the last transaction log preceding the + snapshot. A ZooKeeper server will not remove @@ -485,7 +546,7 @@ server.3=zoo3:2888:3888 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.7.5.jar:lib/slf4j-log4j12-1.7.5.jar:lib/log4j-1.2.16.jar:conf org.apache.zookeeper.server.PurgeTxnLog <dataDir> <snapDir> -n <count> + java -cp zookeeper.jar:lib/slf4j-api-1.7.5.jar:lib/slf4j-log4j12-1.7.5.jar:lib/log4j-1.2.17.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 @@ -533,6 +594,18 @@ server.3=zoo3:2888:3888 examples) managing your ZooKeeper server ensures that if the process does exit abnormally it will automatically be restarted and will quickly rejoin the cluster. + + It is also recommended to configure the ZooKeeper server process to + terminate and dump its heap if an + OutOfMemoryError occurs. This is achieved + by launching the JVM with the following arguments on Linux and Windows + respectively. The zkServer.sh and + zkServer.cmd scripts that ship with ZooKeeper set + these options. + + + -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' + "-XX:+HeapDumpOnOutOfMemoryError" "-XX:OnOutOfMemoryError=cmd /c taskkill /pid %%%%p /t /f"
    @@ -548,13 +621,24 @@ server.3=zoo3:2888:3888
    Logging - ZooKeeper uses log4j version 1.2 as - its logging infrastructure. The ZooKeeper default log4j.properties - file resides in the conf directory. Log4j requires that - log4j.properties either be in the working directory - (the directory from which ZooKeeper is run) or be accessible from the classpath. - - For more information, see + + ZooKeeper uses SLF4J + version 1.7.5 as its logging infrastructure. For backward compatibility it is bound to + LOG4J but you can use + LOGBack + or any other supported logging framework of your choice. + + + The ZooKeeper default log4j.properties + file resides in the conf directory. Log4j requires that + log4j.properties either be in the working directory + (the directory from which ZooKeeper is run) or be accessible from the classpath. + + + For more information about SLF4J, see + its manual. + + For more information about LOG4J, see Log4j Default Initialization Procedure of the log4j manual. @@ -590,6 +674,14 @@ server.3=zoo3:2888:3888 must be taken to ensure that the list of servers in all of the different configuration files match. + + In 3.5.0 and later, some of these parameters should be placed in + a dynamic configuration file. If they are placed in the static + configuration file, ZooKeeper will automatically move them over to the + dynamic configuration file. See + Dynamic Reconfiguration for more information. + +
    Minimum Configuration @@ -606,6 +698,22 @@ server.3=zoo3:2888:3888 + + secureClientPort + + + the port to listen on for secure client connections using SSL. + + clientPort specifies + the port for plaintext connections while + secureClientPort specifies the port for SSL + connections. Specifying both enables mixed-mode while omitting + either will disable that mode. + Note that SSL feature will be enabled when user plugs-in + zookeeper.serverCnxnFactory, zookeeper.clientCnxnSocket as Netty. + + + dataDir @@ -710,27 +818,15 @@ server.3=zoo3:2888:3888 (Java system property: zookeeper.snapCount) - ZooKeeper logs transactions to a transaction - log. After snapCount transactions are written to a log - file a snapshot is started and a new transaction log - file is created. The default snapCount is - 100,000. - - - - - 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.) + ZooKeeper records its transactions using snapshots and + a transaction log (think write-ahead log).The number of + transactions recorded in the transaction log before a snapshot + can be taken (and the transaction log rolled) is determined + by snapCount. In order to prevent all of the machines in the quorum + from taking a snapshot at the same time, each ZooKeeper server + will take a snapshot when the number of transactions in the transaction log + reaches a runtime generated random value in the [snapCount/2+1, snapCount] + range.The default snapCount is 100,000. @@ -791,7 +887,7 @@ server.3=zoo3:2888:3888 fsync.warningthresholdms (Java system property: fsync.warningthresholdms) + role="bold">zookeeper.fsync.warningthresholdms) New in 3.3.4: A warning message will be output to the log whenever an @@ -845,6 +941,40 @@ server.3=zoo3:2888:3888 feature. Default is "true" + + + zookeeper.extendedTypesEnabled + + + (Java system property only: zookeeper.extendedTypesEnabled) + + New in 3.5.4, 3.6.0: Define to "true" to enable + extended features such as the creation of TTL Nodes. + They are disabled by default. IMPORTANT: when enabled server IDs must + be less than 255 due to internal limitations. + + + + + + zookeeper.emulate353TTLNodes + + + (Java system property only: zookeeper.emulate353TTLNodes) + + New in 3.5.4, 3.6.0: Due to + ZOOKEEPER-2901 TTL nodes + created in version 3.5.3 are not supported in 3.5.4/3.6.0. However, a workaround is provided via the + zookeeper.emulate353TTLNodes system property. If you used TTL nodes in ZooKeeper 3.5.3 and need to maintain + compatibility set zookeeper.emulate353TTLNodes to "true" in addition to + zookeeper.extendedTypesEnabled. NOTE: due to the bug, server IDs + must be 127 or less. Additionally, the maximum support TTL value is 1099511627775 which is smaller + than what was allowed in 3.5.3 (1152921504606846975) + + +
    @@ -1022,22 +1152,115 @@ server.3=zoo3:2888:3888 + + + reconfigEnabled + + + (No Java system property) + + New in 3.5.3: + This controls the enabling or disabling of + + Dynamic Reconfiguration feature. When the feature + is enabled, users can perform reconfigure operations through + the ZooKeeper client API or through ZooKeeper command line tools + assuming users are authorized to perform such operations. + When the feature is disabled, no user, including the super user, + can perform a reconfiguration. Any attempt to reconfigure will return an error. + "reconfigEnabled" option can be set as + "reconfigEnabled=false" or + "reconfigEnabled=true" + to a server's config file, or using QuorumPeerConfig's + setReconfigEnabled method. The default value is false. + + If present, the value should be consistent across every server in + the entire ensemble. Setting the value as true on some servers and false + on other servers will cause inconsistent behavior depending on which server + is elected as leader. If the leader has a setting of + "reconfigEnabled=true", then the ensemble + will have reconfig feature enabled. If the leader has a setting of + "reconfigEnabled=false", then the ensemble + will have reconfig feature disabled. It is thus recommended to have a consistent + value for "reconfigEnabled" across servers + in the ensemble. + + + + + + 4lw.commands.whitelist + + + (Java system property: zookeeper.4lw.commands.whitelist) + + New in 3.5.3: + A list of comma separated Four Letter Words + commands that user wants to use. A valid Four Letter Words + command must be put in this list else ZooKeeper server will + not enable the command. + By default the whitelist only contains "srvr" command + which zkServer.sh uses. The rest of four letter word commands are disabled + by default. + + + 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 + + + If you really need enable all four letter word commands by default, you can use + the asterisk option so you 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.5.4: + 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. + + + +
    - Authentication & Authorization Options + Encryption, Authentication, Authorization Options The options in this section allow control over - authentication/authorization performed by the service. + encryption/authentication/authorization performed by the service. - zookeeper.DigestAuthenticationProvider.superDigest + DigestAuthenticationProvider.superDigest - (Java system property only: (Java system property: zookeeper.DigestAuthenticationProvider.superDigest) By default this feature is connection. + + + X509AuthenticationProvider.superUser + + (Java system property: zookeeper.X509AuthenticationProvider.superUser) + + The SSL-backed way to enable a ZooKeeper ensemble + administrator to access the znode hierarchy as a "super" user. + When this parameter is set to an X500 principal name, only an + authenticated client with that principal will be able to bypass + ACL checking and have full privileges to all znodes. + + + + + zookeeper.superUser + + (Java system property: zookeeper.superUser) + + Similar to zookeeper.X509AuthenticationProvider.superUser + but is generic for SASL based logins. It stores the name of + a user that can access the znode hierarchy as a "super" user. + + + + + + ssl.keyStore.location and ssl.keyStore.password + + (Java system properties: + zookeeper.ssl.keyStore.location and zookeeper.ssl.keyStore.password) + + Specifies the file path to a JKS containing the local + credentials to be used for SSL connections, and the + password to unlock the file. + + + + + ssl.trustStore.location and ssl.trustStore.password + + (Java system properties: + zookeeper.ssl.trustStore.location and zookeeper.ssl.trustStore.password) + + Specifies the file path to a JKS containing the remote + credentials to be used for SSL connections, and the + password to unlock the file. + + + + + ssl.authProvider + + (Java system property: zookeeper.ssl.authProvider) + + Specifies a subclass of + org.apache.zookeeper.auth.X509AuthenticationProvider + to use for secure client authentication. This is useful in + certificate key infrastructures that do not use JKS. It may be + necessary to extend javax.net.ssl.X509KeyManager + and javax.net.ssl.X509TrustManager + to get the desired behavior from the SSL stack. To configure the + ZooKeeper server to use the custom provider for authentication, + choose a scheme name for the custom AuthenticationProvider and + set the property zookeeper.authProvider.[scheme] + to the fully-qualified class name of the custom + implementation. This will load the provider into the ProviderRegistry. + Then set this property + zookeeper.ssl.authProvider=[scheme] and that provider + will be used for secure authentication. + +
    @@ -1281,14 +1581,37 @@ server.3=zoo3:2888:3888 + + znode.container.checkIntervalMs + + + (Java system property only) + + New in 3.5.1: The + time interval in milliseconds for each check of candidate container + and ttl nodes. Default is "60000". + + + + + znode.container.maxPerMinute + + + (Java system property only) + + New in 3.5.1: The + maximum number of container nodes that can be deleted per + minute. This prevents herding during container deletion. + Default is "10000". + +
    Communication using the Netty framework - New in - 3.4: Netty + Netty is an NIO based client/server communication framework, it simplifies (over NIO being used directly) many of the complexities of network level communication for java @@ -1297,17 +1620,14 @@ server.3=zoo3:2888:3888 (certificates). These are optional features and can be turned on or off individually. - Prior to version 3.4 ZooKeeper has always used NIO - directly, however in versions 3.4 and later Netty is - supported as an option to NIO (replaces). NIO continues to - be the default, however Netty based communication can be - used in place of NIO by setting the environment variable - "zookeeper.serverCnxnFactory" to - "org.apache.zookeeper.server.NettyServerCnxnFactory". You - have the option of setting this on either the client(s) or - server(s), typically you would want to set this on both, - however that is at your discretion. + In versions 3.5+, a ZooKeeper server can use Netty + instead of NIO (default option) by setting the environment + variable zookeeper.serverCnxnFactory + to org.apache.zookeeper.server.NettyServerCnxnFactory; + for the client, set zookeeper.clientCnxnSocket + to org.apache.zookeeper.ClientCnxnSocketNetty. + TBD - tuning options for netty - currently there are none that are netty specific but we should add some. Esp around max bound on the number of reader worker threads netty creates. @@ -1339,6 +1659,17 @@ server.3=zoo3:2888:3888 + + admin.serverAddress + + + (Java system property: zookeeper.admin.serverAddress) + + The address the embedded Jetty server listens on. Defaults to 0.0.0.0. + + + admin.serverPort @@ -1350,6 +1681,19 @@ server.3=zoo3:2888:3888 + + admin.idleTimeout + + + (Java system property: zookeeper.admin.idleTimeout) + + Set the maximum idle time in milliseconds that a connection can wait + before sending or receiving data. Defaults to 30000 ms. + + + + admin.commandURL @@ -1369,7 +1713,7 @@ server.3=zoo3:2888:3888
    ZooKeeper Commands -
    +
    The Four Letter Words ZooKeeper responds to a small set of commands. Each command is composed of four letters. You issue the commands to ZooKeeper via telnet @@ -1380,7 +1724,16 @@ server.3=zoo3:2888:3888 while "srvr" and "cons" give extended details on server and connections respectively. - + New in 3.5.3: + Four Letter Words need to be explicitly white listed before using. + Please refer 4lw.commands.whitelist + described in + cluster configuration section for details. + Moving forward, Four Letter Words will be deprecated, please use + AdminServer instead. + + + conf @@ -1493,6 +1846,16 @@ server.3=zoo3:2888:3888 + + dirs + + + New in 3.5.1: + Shows the total size of snapshot and log files in bytes + + + + wchp @@ -1544,6 +1907,113 @@ server.3=zoo3:2888:3888 key \t value + + + 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 + +
    +
    Here's an example of the ruok @@ -1641,8 +2111,11 @@ server.3=zoo3:2888:3888 The Log Directory contains the ZooKeeper transaction logs. Before any update takes place, ZooKeeper ensures that the transaction that represents the update is written to non-volatile storage. A new - log file is started each time a snapshot is begun. The log file's - suffix is the first zxid written to that log. + log file is started when the number of transactions written to the + current log file reaches a (variable) threshold. The threshold is + computed using the same parameter which influences the frequency of + snapshotting (see snapCount above). The log file's suffix is the first + zxid written to that log.
    @@ -1662,12 +2135,93 @@ server.3=zoo3:2888:3888 The ZooKeeper server creates snapshot and log files, but never deletes them. The retention policy of the data and log files is implemented outside of the ZooKeeper server. The - server itself only needs the latest complete fuzzy snapshot - and the log files from the start of that snapshot. See the + server itself only needs the latest complete fuzzy snapshot, all log + files following it, and the last log file preceding it. The latter + requirement is necessary to include updates which happened after this + snapshot was started but went into the existing log file at that time. + This is possible because snapshotting and rolling over of logs + proceed somewhat independently in ZooKeeper. See the maintenance section in 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. + +
    + +
    + Recovery - TxnLogToolkit + + TxnLogToolkit is a command line tool shipped with ZooKeeper which + is capable of recovering transaction log entries with broken CRC. + Running it without any command line parameters or with the "-h,--help" + argument, it outputs the following help page: + + + $ bin/zkTxnLogToolkit.sh + + usage: TxnLogToolkit [-dhrv] txn_log_file_name + -d,--dump Dump mode. Dump all entries of the log file. (this is the default) + -h,--help Print help message + -r,--recover Recovery mode. Re-calculate CRC for broken entries. + -v,--verbose Be verbose in recovery mode: print all entries, not just fixed ones. + -y,--yes Non-interactive mode: repair all CRC errors without asking + + + The default behaviour is safe: it dumps the entries of the given + transaction log file to the screen: (same as using '-d,--dump' parameter) + + + $ bin/zkTxnLogToolkit.sh log.100000001 + ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 + 4/5/18 2:15:58 PM CEST session 0x16295bafcc40000 cxid 0x0 zxid 0x100000001 createSession 30000 + CRC ERROR - 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null + 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null + 4/5/18 2:16:12 PM CEST session 0x26295bafcc90000 cxid 0x0 zxid 0x100000003 createSession 30000 + 4/5/18 2:17:34 PM CEST session 0x26295bafcc90000 cxid 0x0 zxid 0x200000001 closeSession null + 4/5/18 2:17:34 PM CEST session 0x16295bd23720000 cxid 0x0 zxid 0x200000002 createSession 30000 + 4/5/18 2:18:02 PM CEST session 0x16295bd23720000 cxid 0x2 zxid 0x200000003 create '/andor,#626262,v{s{31,s{'world,'anyone}}},F,1 + EOF reached after 6 txns. + + + There's a CRC error in the 2nd entry of the above transaction log file. In dump + mode, the toolkit only prints this information to the screen without touching the original file. In + recovery mode (-r,--recover flag) the original file still remains + untouched and all transactions will be copied over to a new txn log file with ".fixed" suffix. It recalculates + CRC values and copies the calculated value, if it doesn't match the original txn entry. + By default, the tool works interactively: it asks for confirmation whenever CRC error encountered. + + + $ bin/zkTxnLogToolkit.sh -r log.100000001 + ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 + CRC ERROR - 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null + Would you like to fix it (Yes/No/Abort) ? + + + Answering Yes means the newly calculated CRC value will be outputted + to the new file. No means that the original CRC value will be copied over. + Abort will abort the entire operation and exits. + (In this case the ".fixed" will not be deleted and left in a half-complete state: contains only entries which + have already been processed or only the header if the operation was aborted at the first entry.) + + + $ bin/zkTxnLogToolkit.sh -r log.100000001 + ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 + CRC ERROR - 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null + Would you like to fix it (Yes/No/Abort) ? y + EOF reached after 6 txns. + Recovery file log.100000001.fixed has been written with 1 fixed CRC error(s) + + + The default behaviour of recovery is to be silent: only entries with CRC error get printed to the screen. + One can turn on verbose mode with the -v,--verbose parameter to see all records. + Interactive mode can be turned off with the -y,--yes parameter. In this case all CRC errors will be fixed + in the new transaction file.
    @@ -1693,7 +2247,7 @@ server.3=zoo3:2888:3888 - incorrect placement of transasction log + incorrect placement of transaction log The most performance critical part of ZooKeeper is the @@ -1726,6 +2280,16 @@ server.3=zoo3:2888:3888 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/zookeeperInternals.xml b/src/docs/src/documentation/content/xdocs/zookeeperInternals.xml index 4954123387a..7815bc10fad 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperInternals.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperInternals.xml @@ -323,7 +323,7 @@ message when that proposal is committed.
    Summary -So there you go. Why does it work? Specifically, why does is set of proposals +So there you go. Why does it work? Specifically, why does a set of proposals believed by a new leader always contain any proposal that has actually been committed? First, all proposals have a unique zxid, so unlike other protocols, we never have to worry about two different values being proposed for the same zxid; followers diff --git a/src/docs/src/documentation/content/xdocs/zookeeperObservers.xml b/src/docs/src/documentation/content/xdocs/zookeeperObservers.xml index 99f80250438..fab676907c6 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 @@ -116,7 +116,7 @@ Example use cases Two example use cases for Observers are listed below. In fact, wherever - you wish to scale the numbe of clients of your ZooKeeper ensemble, or + you wish to scale the number of clients of your ZooKeeper ensemble, or where you wish to insulate the critical part of an ensemble from the load of dealing with client requests, Observers are a good architectural choice. diff --git a/src/docs/src/documentation/content/xdocs/zookeeperOver.xml b/src/docs/src/documentation/content/xdocs/zookeeperOver.xml index a2961d334d3..f972657489f 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperOver.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperOver.xml @@ -136,7 +136,7 @@
    Nodes and ephemeral nodes - Unlike is standard file systems, each node in a ZooKeeper + Unlike standard file systems, each node in a ZooKeeper namespace can have data associated with it as well as children. It is like having a file-system that allows a file to also be a directory. (ZooKeeper was designed to store coordination data: status information, @@ -166,9 +166,9 @@ Conditional updates and watches ZooKeeper supports the concept of watches. - Clients can set a watch on a znodes. A watch will be triggered and - removed when the znode changes. When a watch is triggered the client - receives a packet saying that the znode has changed. And if the + Clients can set a watch on a znode. A watch will be triggered and + removed when the znode changes. When a watch is triggered, the client + receives a packet saying that the znode has changed. If the connection between the client and one of the Zoo Keeper servers is broken, the client will receive a local notification. These can be used to [tbd]. @@ -293,7 +293,7 @@ of the ZooKeeper service. With the exception of the request processor, each of the servers that make up the ZooKeeper service replicates its own copy - of each of components. + of each of the components.
    ZooKeeper Components diff --git a/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml b/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml index 223cf8e5a85..35fd23e9a08 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml @@ -241,7 +241,40 @@ counter used to store the next sequence number is a signed int (4bytes) maintained by the parent node, the counter will overflow when incremented beyond 2147483647 (resulting in a - name "<path>-2147483647"). + name "<path>-2147483648"). +
    + +
    + Container Nodes + + Added in 3.5.3 + + ZooKeeper has the notion of container znodes. Container znodes are + special purpose znodes useful for recipes such as leader, lock, etc. + When the last child of a container is deleted, the container becomes + a candidate to be deleted by the server at some point in the future. + + Given this property, you should be prepared to get + KeeperException.NoNodeException when creating children inside of + container znodes. i.e. when creating child znodes inside of container znodes + always check for KeeperException.NoNodeException and recreate the container + znode when it occurs. +
    + +
    + TTL Nodes + + Added in 3.5.3 + + When creating PERSISTENT or PERSISTENT_SEQUENTIAL znodes, + you can optionally set a TTL in milliseconds for the znode. If the znode + is not modified within the TTL and has no children it will become a candidate + to be deleted by the server at some point in the future. + + Note: TTL Nodes must be enabled via System property as + they are disabled by default. See the Administrator's + Guide for details. If you attempt to create TTL Nodes without the proper System property set the server + will throw KeeperException.UnimplementedException.
    @@ -313,6 +346,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 @@ -797,11 +836,14 @@ recursive.
    ZooKeeper supports pluggable authentication schemes. Ids are - specified using the form scheme:id, - where scheme is a the authentication scheme - that the id corresponds to. For - example, ip:172.16.16.1 is an id for a - host with the address 172.16.16.1. + specified using the form scheme:expression, + where scheme is the authentication scheme + that the id corresponds to. The set of valid expressions are defined + by the scheme. For example, ip:172.16.16.1 is + an id for a host with the address 172.16.16.1 + using the ip scheme, whereas digest:bob:password + is an id for the user with the name of bob using + the digest scheme. When a client connects to ZooKeeper and authenticates itself, ZooKeeper associates all the ids that correspond to a @@ -865,9 +907,16 @@ single id, anyone, that represents anyone. - auth doesn't - use any id, represents any authenticated - user. + auth is a special + scheme which ignores any provided expression and instead uses the current user, + credentials, and scheme. Any expression (whether user like with SASL + authentication or user:password like with DIGEST authentication) provided is ignored + by the ZooKeeper server when persisting the ACL. However, the expression must still be + provided in the ACL because the ACL must match the form scheme:expression:perms. + This scheme is provided as a convenience as it is a common use-case for + a user to create a znode and then restrict access to that znode to only that user. + If there is no authenticated user, setting an ACL with the auth scheme will fail. + digest uses a username:password string to generate @@ -888,6 +937,12 @@ significant bits of the client host IP. + x509 uses the client + X500 Principal as an ACL ID identity. The ACL expression is the exact + X500 Principal name of a client. When using the secure port, clients + are automatically authenticated and their auth info for the x509 scheme + is set. +
    @@ -992,7 +1047,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 @@ -1344,6 +1399,114 @@ authProvider.2=com.f.MyAuth2 (SESSION_EXPIRED and AUTH_FAILED), the ZooKeeper object becomes invalid. On a close, the two threads shut down and any further access on zookeeper handle is undefined behavior and should be avoided. +
    + <emphasis role="bold">Client Configuration Parameters</emphasis> + + The following list contains configuration properties for the Java client. You can set any + of these properties using Java system properties. For server properties, please check the + following reference + Server configuration section. + + + + zookeeper.sasl.client + + Set the value to false to disable + SASL authentication. Default is true. + + + + zookeeper.sasl.clientconfig + + Specifies the context key in the JAAS login file. Default is "Client". + + + + zookeeper.sasl.client.username + + Traditionally, a principal is divided into three parts: the primary, the instance, and the realm. + The format of a typical Kerberos V5 principal is primary/instance@REALM. + zookeeper.sasl.client.username specifies the primary part of the server principal. Default + is "zookeeper". Instance part is derived from the server IP. Finally server's principal is + username/IP@realm, where username is the value of zookeeper.sasl.client.username, IP is + the server IP, and realm is the value of zookeeper.server.realm. + + + + zookeeper.server.realm + + Realm part of the server principal. By default it is the client principal realm. + + + + zookeeper.disableAutoWatchReset + + This switch controls whether automatic watch resetting is enabled. Clients automatically + reset watches during session reconnect by default, this option allows the client to turn off + this behavior by setting zookeeper.disableAutoWatchReset to true. + + + + + zookeeper.client.secure + + + If you want to connect to the server secure client port, you need to set this property to + true + on the client. This will connect to server using SSL with specified credentials. Note that + it requires the Netty client. + + + + + zookeeper.clientCnxnSocket + + + Specifies which ClientCnxnSocket to be used. Possible values are + org.apache.zookeeper.ClientCnxnSocketNIO + and + org.apache.zookeeper.ClientCnxnSocketNetty + . Default is + org.apache.zookeeper.ClientCnxnSocketNIO + . If you want to connect to server's secure client port, you need to set this property to + org.apache.zookeeper.ClientCnxnSocketNetty + on client. + + + + + zookeeper.ssl.keyStore.location and zookeeper.ssl.keyStore.password + + Specifies the file path to a JKS containing the local credentials to be used for SSL connections, + and the password to unlock the file. + + + + + zookeeper.ssl.trustStore.location and zookeeper.ssl.trustStore.password + + Specifies the file path to a JKS containing the remote credentials to be used for SSL connections, + and the password to unlock the file. + + + + + jute.maxbuffer + + It specifies the maximum size of the incoming data from the server. The default value is 4194304 + Bytes , or just 4 MB. This is really a sanity check. The ZooKeeper server is designed to store and send + data on the order of kilobytes. If incoming data length is more than this value, an IOException + is raised. + + + + zookeeper.kinit + + Specifies path to kinit binary. Default is "/usr/bin/kinit". + + + +
    @@ -1456,33 +1619,9 @@ 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): + Building Your Own C Client - $ 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 - "/". - - In order to be able to use the ZooKeeper API in your application + In order to be able to use the ZooKeeper C API in your application you have to remember to @@ -1502,10 +1641,9 @@ 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 +
    @@ -1580,7 +1718,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. diff --git a/src/docs/src/documentation/content/xdocs/zookeeperQuotas.xml b/src/docs/src/documentation/content/xdocs/zookeeperQuotas.xml index 6e5528bc10f..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.16.jar:src/java/lib/jline-2.11.jar:conf \ - 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/zookeeperReconfig.xml b/src/docs/src/documentation/content/xdocs/zookeeperReconfig.xml new file mode 100644 index 00000000000..a6b070156da --- /dev/null +++ b/src/docs/src/documentation/content/xdocs/zookeeperReconfig.xml @@ -0,0 +1,883 @@ + + + +
    + ZooKeeper Dynamic Reconfiguration + + + + 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 document contains information about Dynamic Reconfiguration in + ZooKeeper. + + +
    + Overview + Prior to the 3.5.0 release, the membership and all other configuration + parameters of Zookeeper were static - loaded during boot and immutable at + runtime. Operators resorted to ''rolling restarts'' - a manually intensive + and error-prone method of changing the configuration that has caused data + loss and inconsistency in production. + Starting with 3.5.0, “rolling restarts” are no longer needed! + ZooKeeper comes with full support for automated configuration changes: the + set of Zookeeper servers, their roles (participant / observer), all ports, + and even the quorum system can be changed dynamically, without service + interruption and while maintaining data consistency. Reconfigurations are + performed immediately, just like other operations in ZooKeeper. Multiple + changes can be done using a single reconfiguration command. The dynamic + reconfiguration functionality does not limit operation concurrency, does + not require client operations to be stopped during reconfigurations, has a + very simple interface for administrators and no added complexity to other + client operations. + New client-side features allow clients to find out about configuration + changes and to update the connection string (list of servers and their + client ports) stored in their ZooKeeper handle. A probabilistic algorithm + is used to rebalance clients across the new configuration servers while + keeping the extent of client migrations proportional to the change in + ensemble membership. + This document provides the administrator manual for reconfiguration. + For a detailed description of the reconfiguration algorithms, performance + measurements, and more, please see our paper: + + + Shraer, A., Reed, B., Malkhi, D., Junqueira, F. Dynamic + Reconfiguration of Primary/Backup Clusters. In USENIX Annual + Technical Conference (ATC) (2012), 425-437 + + Links: paper (pdf), slides (pdf), video, hadoop summit slides + + + + Note: Starting with 3.5.3, the dynamic reconfiguration + feature is disabled by default, and has to be explicitly turned on via + + reconfigEnabled configuration option. + +
    +
    + Changes to Configuration Format +
    + Specifying the client port + A client port of a server is the port on which the server accepts + client connection requests. Starting with 3.5.0 the + clientPort and clientPortAddress + configuration parameters should no longer be used. Instead, + this information is now part of the server keyword specification, which + becomes as follows: + = ::[:role];[:]]]> + The client port specification is to the right of the semicolon. The + client port address is optional, and if not specified it defaults to + "0.0.0.0". As usual, role is also optional, it can be + participant or observer + (participant by default). + Examples of legal server statements: + + + server.5 = 125.23.63.23:1234:1235;1236 + + + server.5 = 125.23.63.23:1234:1235:participant;1236 + + + server.5 = 125.23.63.23:1234:1235:observer;1236 + + + server.5 = 125.23.63.23:1234:1235;125.23.63.24:1236 + + + server.5 = 125.23.63.23:1234:1235:participant;125.23.63.23:1236 + + +
    +
    + The <emphasis>standaloneEnabled</emphasis> flag + Prior to 3.5.0, one could run ZooKeeper in Standalone mode or in a + Distributed mode. These are separate implementation stacks, and + switching between them during run time is not possible. By default (for + backward compatibility) standaloneEnabled is set to + true. The consequence of using this default is that + if started with a single server the ensemble will not be allowed to + grow, and if started with more than one server it will not be allowed to + shrink to contain fewer than two participants. + Setting the flag to false instructs the system + to run the Distributed software stack even if there is only a single + participant in the ensemble. To achieve this the (static) configuration + file should contain: + standaloneEnabled=false + With this setting it is possible to start a ZooKeeper ensemble + containing a single participant and to dynamically grow it by adding + more servers. Similarly, it is possible to shrink an ensemble so that + just a single participant remains, by removing servers. + Since running the Distributed mode allows more flexibility, we + recommend setting the flag to false. We expect that + the legacy Standalone mode will be deprecated in the future. +
    +
    + The <emphasis>reconfigEnabled</emphasis> flag + Starting with 3.5.0 and prior to 3.5.3, there is no way to disable + dynamic reconfiguration feature. We would like to offer the option of + disabling reconfiguration feature because with reconfiguration enabled, + we have a security concern that a malicious actor can make arbitrary changes + to the configuration of a ZooKeeper ensemble, including adding a compromised + server to the ensemble. We prefer to leave to the discretion of the user to + decide whether to enable it or not and make sure that the appropriate security + measure are in place. So in 3.5.3 the + reconfigEnabled configuration option is introduced + such that the reconfiguration feature can be completely disabled and any attempts + to reconfigure a cluster through reconfig API with or without authentication + will fail by default, unless reconfigEnabled is set to + true. + + To set the option to true, the configuration file (zoo.cfg) should contain: + reconfigEnabled=true +
    +
    + Dynamic configuration file + Starting with 3.5.0 we're distinguishing between dynamic + configuration parameters, which can be changed during runtime, and + static configuration parameters, which are read from a configuration + file when a server boots and don't change during its execution. For now, + the following configuration keywords are considered part of the dynamic + configuration: server, group + and weight. + Dynamic configuration parameters are stored in a separate file on + the server (which we call the dynamic configuration file). This file is + linked from the static config file using the new + dynamicConfigFile keyword. + Example + + zoo_replicated1.cfg + tickTime=2000 +dataDir=/zookeeper/data/zookeeper1 +initLimit=5 +syncLimit=2 +dynamicConfigFile=/zookeeper/conf/zoo_replicated1.cfg.dynamic + + + zoo_replicated1.cfg.dynamic + server.1=125.23.63.23:2780:2783:participant;2791 +server.2=125.23.63.24:2781:2784:participant;2792 +server.3=125.23.63.25:2782:2785:participant;2793 + + When the ensemble configuration changes, the static configuration + parameters remain the same. The dynamic parameters are pushed by + ZooKeeper and overwrite the dynamic configuration files on all servers. + Thus, the dynamic configuration files on the different servers are + usually identical (they can only differ momentarily when a + reconfiguration is in progress, or if a new configuration hasn't + propagated yet to some of the servers). Once created, the dynamic + configuration file should not be manually altered. Changed are only made + through the new reconfiguration commands outlined below. Note that + changing the config of an offline cluster could result in an + inconsistency with respect to configuration information stored in the + ZooKeeper log (and the special configuration znode, populated from the + log) and is therefore highly discouraged. + Example 2 + Users may prefer to initially specify a single configuration file. + The following is thus also legal: + + zoo_replicated1.cfg + tickTime=2000 +dataDir=/zookeeper/data/zookeeper1 +initLimit=5 +syncLimit=2 +clientPort=2791 // note that this line is now redundant and therefore not recommended +server.1=125.23.63.23:2780:2783:participant;2791 +server.2=125.23.63.24:2781:2784:participant;2792 +server.3=125.23.63.25:2782:2785:participant;2793 + + The configuration files on each server will be automatically split + into dynamic and static files, if they are not already in this format. + So the configuration file above will be automatically transformed into + the two files in Example 1. Note that the clientPort and + clientPortAddress lines (if specified) will be automatically removed + during this process, if they are redundant (as in the example above). + The original static configuration file is backed up (in a .bak + file). +
    +
    + Backward compatibility + We still support the old configuration format. For example, the + following configuration file is acceptable (but not recommended): + + zoo_replicated1.cfg + tickTime=2000 +dataDir=/zookeeper/data/zookeeper1 +initLimit=5 +syncLimit=2 +clientPort=2791 +server.1=125.23.63.23:2780:2783:participant +server.2=125.23.63.24:2781:2784:participant +server.3=125.23.63.25:2782:2785:participant + + During boot, a dynamic configuration file is created and contains + the dynamic part of the configuration as explained earlier. In this + case, however, the line "clientPort=2791" will remain in the static + configuration file of server 1 since it is not redundant -- it was not + specified as part of the "server.1=..." using the format explained in + the section . If a reconfiguration + is invoked that sets the client port of server 1, we remove + "clientPort=2791" from the static configuration file (the dynamic file + now contain this information as part of the specification of server + 1). +
    +
    +
    + Upgrading to 3.5.0 + Upgrading a running ZooKeeper ensemble to 3.5.0 should be done only + after upgrading your ensemble to the 3.4.6 release. Note that this is only + necessary for rolling upgrades (if you're fine with shutting down the + system completely, you don't have to go through 3.4.6). If you attempt a + rolling upgrade without going through 3.4.6 (for example from 3.4.5), you + may get the following error: + 2013-01-30 11:32:10,663 [myid:2] - INFO [localhost/127.0.0.1:2784:QuorumCnxManager$Listener@498] - Received connection request /127.0.0.1:60876 +2013-01-30 11:32:10,663 [myid:2] - WARN [localhost/127.0.0.1:2784:QuorumCnxManager@349] - Invalid server id: -65536 + During a rolling upgrade, each server is taken down in turn and + rebooted with the new 3.5.0 binaries. Before starting the server with + 3.5.0 binaries, we highly recommend updating the configuration file so + that all server statements "server.x=..." contain client ports (see the + section ). As explained earlier + you may leave the configuration in a single file, as well as leave the + clientPort/clientPortAddress statements (although if you specify client + ports in the new format, these statements are now redundant). +
    + +
    + Dynamic Reconfiguration of the ZooKeeper Ensemble + The ZooKeeper Java and C API were extended with getConfig and reconfig + commands that facilitate reconfiguration. Both commands have a synchronous + (blocking) variant and an asynchronous one. We demonstrate these commands + here using the Java CLI, but note that you can similarly use the C CLI or + invoke the commands directly from a program just like any other ZooKeeper + command. + +
    + API + There are two sets of APIs for both Java and C client. + + + + Reconfiguration API + + + Reconfiguration API is used to reconfigure the ZooKeeper cluster. + Starting with 3.5.3, reconfiguration Java APIs are moved into ZooKeeperAdmin class + from ZooKeeper class, and use of this API requires ACL setup and user + authentication (see for more information.). + + + Note: for temporary backward compatibility, the reconfig() APIs will remain in ZooKeeper.java + where they were for a few alpha versions of 3.5.x. However, these APIs are deprecated and users + should move to the reconfigure() APIs in ZooKeeperAdmin.java. + + + + + + Get Configuration API + + Get configuration APIs are used to retrieve ZooKeeper cluster configuration information + stored in /zookeeper/config znode. Use of this API does not require specific setup or authentication, + because /zookeeper/config is readable to any users. + + + +
    + +
    + Security + Prior to 3.5.3, there is no enforced security mechanism + over reconfig so any ZooKeeper clients that can connect to ZooKeeper server ensemble + will have the ability to change the state of a ZooKeeper cluster via reconfig. + It is thus possible for a malicious client to add compromised server to an ensemble, + e.g., add a compromised server, or remove legitimate servers. + Cases like these could be security vulnerabilities on a case by case basis. + + To address this security concern, we introduced access control over reconfig + starting from 3.5.3 such that only a specific set of users + can use reconfig commands or APIs, and these users need be configured explicitly. In addition, + the setup of ZooKeeper cluster must enable authentication so ZooKeeper clients can be authenticated. + + + We also provides an escape hatch for users who operate and interact with a ZooKeeper ensemble in a secured + environment (i.e. behind company firewall). For those users who want to use reconfiguration feature but + don't want the overhead of configuring an explicit list of authorized user for reconfig access checks, + they can set "skipACL" to "yes" which will + skip ACL check and allow any user to reconfigure cluster. + + + Overall, ZooKeeper provides flexible configuration options for the reconfigure feature + that allow a user to choose based on user's security requirement. + We leave to the discretion of the user to decide appropriate security measure are in place. + + + + Access Control + + + The dynamic configuration is stored in a special znode + ZooDefs.CONFIG_NODE = /zookeeper/config. This node by default is read only + for all users, except super user and users that's explicitly configured for write + access. + + + Clients that need to use reconfig commands or reconfig API should be configured as users + that have write access to CONFIG_NODE. By default, only the super user has full control including + write access to CONFIG_NODE. Additional users can be granted write access through superuser + by setting an ACL that has write permission associated with specified user. + + + A few examples of how to setup ACLs and use reconfiguration API with authentication can be found in + ReconfigExceptionTest.java and TestReconfigServer.cc. + + + + + Authentication + + + Authentication of users is orthogonal to the access control and is delegated to + existing authentication mechanism supported by ZooKeeper's pluggable authentication schemes. + See ZooKeeper and SASL for more details on this topic. + + + + + + Disable ACL check + + + ZooKeeper supports "skipACL" option such that ACL + check will be completely skipped, if skipACL is set to "yes". In such cases any unauthenticated + users can use reconfig API. + + + + +
    + +
    + Retrieving the current dynamic configuration + The dynamic configuration is stored in a special znode + ZooDefs.CONFIG_NODE = /zookeeper/config. The new + config CLI command reads this znode (currently it is + simply a wrapper to get /zookeeper/config). As with + normal reads, to retrieve the latest committed value you should do a + sync first. + [zk: 127.0.0.1:2791(CONNECTED) 3] config +server.1=localhost:2780:2783:participant;localhost:2791 +server.2=localhost:2781:2784:participant;localhost:2792 +server.3=localhost:2782:2785:participant;localhost:2793 +version=400000003 + Notice the last line of the output. This is the configuration + version. The version equals to the zxid of the reconfiguration command + which created this configuration. The version of the first established + configuration equals to the zxid of the NEWLEADER message sent by the + first successfully established leader. When a configuration is written + to a dynamic configuration file, the version automatically becomes part + of the filename and the static configuration file is updated with the + path to the new dynamic configuration file. Configuration files + corresponding to earlier versions are retained for backup + purposes. + During boot time the version (if it exists) is extracted from the + filename. The version should never be altered manually by users or the + system administrator. It is used by the system to know which + configuration is most up-to-date. Manipulating it manually can result in + data loss and inconsistency. + Just like a get command, the + config CLI command accepts the + flag for setting a watch on the znode, and flag for + displaying the Stats of the znode. It additionally accepts a new flag + which outputs only the version and the client + connection string corresponding to the current configuration. For + example, for the configuration above we would get: + [zk: 127.0.0.1:2791(CONNECTED) 17] config -c +400000003 localhost:2791,localhost:2793,localhost:2792 + Note that when using the API directly, this command is called + getConfig. + As any read command it returns the configuration known to the + follower to which your client is connected, which may be slightly + out-of-date. One can use the sync command for + stronger guarantees. For example using the Java API: + zk.sync(ZooDefs.CONFIG_NODE, void_callback, context); +zk.getConfig(watcher, callback, context); + Note: in 3.5.0 it doesn't really matter which path is passed to the + sync() command as all the server's state is brought + up to date with the leader (so one could use a different path instead of + ZooDefs.CONFIG_NODE). However, this may change in the future. +
    +
    + Modifying the current dynamic configuration + Modifying the configuration is done through the + reconfig command. There are two modes of + reconfiguration: incremental and non-incremental (bulk). The + non-incremental simply specifies the new dynamic configuration of the + system. The incremental specifies changes to the current configuration. + The reconfig command returns the new + configuration. + A few examples are in: ReconfigTest.java, + ReconfigRecoveryTest.java and + TestReconfigServer.cc. +
    + General + Removing servers: Any server can + be removed, including the leader (although removing the leader will + result in a short unavailability, see Figures 6 and 8 in the paper). The server will not be shut-down automatically. + Instead, it becomes a "non-voting follower". This is somewhat similar + to an observer in that its votes don't count towards the Quorum of + votes necessary to commit operations. However, unlike a non-voting + follower, an observer doesn't actually see any operation proposals and + does not ACK them. Thus a non-voting follower has a more significant + negative effect on system throughput compared to an observer. + Non-voting follower mode should only be used as a temporary mode, + before shutting the server down, or adding it as a follower or as an + observer to the ensemble. We do not shut the server down automatically + for two main reasons. The first reason is that we do not want all the + clients connected to this server to be immediately disconnected, + causing a flood of connection requests to other servers. Instead, it + is better if each client decides when to migrate independently. The + second reason is that removing a server may sometimes (rarely) be + necessary in order to change it from "observer" to "participant" (this + is explained in the section ). + Note that the new configuration should have some minimal number of + participants in order to be considered legal. If the proposed change + would leave the cluster with less than 2 participants and standalone + mode is enabled (standaloneEnabled=true, see the section ), the reconfig will not be + processed (BadArgumentsException). If standalone mode is disabled + (standaloneEnabled=false) then its legal to remain with 1 or more + participants. + Adding servers: Before a + reconfiguration is invoked, the administrator must make sure that a + quorum (majority) of participants from the new configuration are + already connected and synced with the current leader. To achieve this + we need to connect a new joining server to the leader before it is + officially part of the ensemble. This is done by starting the joining + server using an initial list of servers which is technically not a + legal configuration of the system but (a) contains the joiner, and (b) + gives sufficient information to the joiner in order for it to find and + connect to the current leader. We list a few different options of + doing this safely. + + + Initial configuration of joiners is comprised of servers in + the last committed configuration and one or more joiners, where + joiners are listed as observers. + For example, if servers D and E are added at the same time to (A, + B, C) and server C is being removed, the initial configuration of + D could be (A, B, C, D) or (A, B, C, D, E), where D and E are + listed as observers. Similarly, the configuration of E could be + (A, B, C, E) or (A, B, C, D, E), where D and E are listed as + observers. Note that listing the joiners as + observers will not actually make them observers - it will only + prevent them from accidentally forming a quorum with other + joiners. Instead, they will contact the servers in the + current configuration and adopt the last committed configuration + (A, B, C), where the joiners are absent. Configuration files of + joiners are backed up and replaced automatically as this happens. + After connecting to the current leader, joiners become non-voting + followers until the system is reconfigured and they are added to + the ensemble (as participant or observer, as appropriate). + + + Initial configuration of each joiner is comprised of servers + in the last committed configuration + the + joiner itself, listed as a participant. For example, to + add a new server D to a configuration consisting of servers (A, B, + C), the administrator can start D using an initial configuration + file consisting of servers (A, B, C, D). If both D and E are added + at the same time to (A, B, C), the initial configuration of D + could be (A, B, C, D) and the configuration of E could be (A, B, + C, E). Similarly, if D is added and C is removed at the same time, + the initial configuration of D could be (A, B, C, D). Never list + more than one joiner as participant in the initial configuration + (see warning below). + + + Whether listing the joiner as an observer or as participant, + it is also fine not to list all the current configuration servers, + as long as the current leader is in the list. For example, when + adding D we could start D with a configuration file consisting of + just (A, D) if A is the current leader. however this is more + fragile since if A fails before D officially joins the ensemble, D + doesn’t know anyone else and therefore the administrator will have + to intervene and restart D with another server list. + + + + Warning + Never specify more than one joining server in the same initial + configuration as participants. Currently, the joining servers don’t + know that they are joining an existing ensemble; if multiple joiners + are listed as participants they may form an independent quorum + creating a split-brain situation such as processing operations + independently from your main ensemble. It is OK to list multiple + joiners as observers in an initial config. + + If the configuration of existing servers changes or they become unavailable + before the joiner succeeds to connect and learn obout configuration changes, the + joiner may need to be restarted with an updated configuration file in order to be + able to connect. + Finally, note that once connected to the leader, a joiner adopts + the last committed configuration, in which it is absent (the initial + config of the joiner is backed up before being rewritten). If the + joiner restarts in this state, it will not be able to boot since it is + absent from its configuration file. In order to start it you’ll once + again have to specify an initial configuration. + Modifying server parameters: One + can modify any of the ports of a server, or its role + (participant/observer) by adding it to the ensemble with different + parameters. This works in both the incremental and the bulk + reconfiguration modes. It is not necessary to remove the server and + then add it back; just specify the new parameters as if the server is + not yet in the system. The server will detect the configuration change + and perform the necessary adjustments. See an example in the section + and an exception to this + rule in the section . + It is also possible to change the Quorum System used by the + ensemble (for example, change the Majority Quorum System to a + Hierarchical Quorum System on the fly). This, however, is only allowed + using the bulk (non-incremental) reconfiguration mode. In general, + incremental reconfiguration only works with the Majority Quorum + System. Bulk reconfiguration works with both Hierarchical and Majority + Quorum Systems. + Performance Impact: There is + practically no performance impact when removing a follower, since it + is not being automatically shut down (the effect of removal is that + the server's votes are no longer being counted). When adding a server, + there is no leader change and no noticeable performance disruption. + For details and graphs please see Figures 6, 7 and 8 in the paper. + The most significant disruption will happen when a leader change + is caused, in one of the following cases: + + + Leader is removed from the ensemble. + + + Leader's role is changed from participant to observer. + + + The port used by the leader to send transactions to others + (quorum port) is modified. + + + In these cases we perform a leader hand-off where the old leader + nominates a new leader. The resulting unavailability is usually + shorter than when a leader crashes since detecting leader failure is + unnecessary and electing a new leader can usually be avoided during a + hand-off (see Figures 6 and 8 in the paper). + When the client port of a server is modified, it does not drop + existing client connections. New connections to the server will have + to use the new client port. + Progress guarantees: Up to the + invocation of the reconfig operation, a quorum of the old + configuration is required to be available and connected for ZooKeeper + to be able to make progress. Once reconfig is invoked, a quorum of + both the old and of the new configurations must be available. The + final transition happens once (a) the new configuration is activated, + and (b) all operations scheduled before the new configuration is + activated by the leader are committed. Once (a) and (b) happen, only a + quorum of the new configuration is required. Note, however, that + neither (a) nor (b) are visible to a client. Specifically, when a + reconfiguration operation commits, it only means that an activation + message was sent out by the leader. It does not necessarily mean that + a quorum of the new configuration got this message (which is required + in order to activate it) or that (b) has happened. If one wants to + make sure that both (a) and (b) has already occurred (for example, in + order to know that it is safe to shut down old servers that were + removed), one can simply invoke an update + (set-data, or some other quorum operation, but not + a sync) and wait for it to commit. An alternative + way to achieve this was to introduce another round to the + reconfiguration protocol (which, for simplicity and compatibility with + Zab, we decided to avoid). +
    +
    + Incremental mode + The incremental mode allows adding and removing servers to the + current configuration. Multiple changes are allowed. For + example: + > reconfig -remove 3 -add + server.5=125.23.63.23:1234:1235;1236 + Both the add and the remove options get a list of comma separated + arguments (no spaces): + > reconfig -remove 3,4 -add + server.5=localhost:2111:2112;2113,6=localhost:2114:2115:observer;2116 + The format of the server statement is exactly the same as + described in the section and + includes the client port. Notice that here instead of "server.5=" you + can just say "5=". In the example above, if server 5 is already in the + system, but has different ports or is not an observer, it is updated + and once the configuration commits becomes an observer and starts + using these new ports. This is an easy way to turn participants into + observers and vise versa or change any of their ports, without + rebooting the server. + ZooKeeper supports two types of Quorum Systems – the simple + Majority system (where the leader commits operations after receiving + ACKs from a majority of voters) and a more complex Hierarchical + system, where votes of different servers have different weights and + servers are divided into voting groups. Currently, incremental + reconfiguration is allowed only if the last proposed configuration + known to the leader uses a Majority Quorum System + (BadArgumentsException is thrown otherwise). + Incremental mode - examples using the Java API: + leavingServers = new ArrayList(); +leavingServers.add("1"); +leavingServers.add("2"); +byte[] config = zk.reconfig(null, leavingServers, null, -1, new Stat());]]> + + leavingServers = new ArrayList(); +List joiningServers = new ArrayList(); +leavingServers.add("1"); +joiningServers.add("server.4=localhost:1234:1235;1236"); +byte[] config = zk.reconfig(joiningServers, leavingServers, null, -1, new Stat()); + +String configStr = new String(config); +System.out.println(configStr);]]> + There is also an asynchronous API, and an API accepting comma + separated Strings instead of List<String>. See + src/java/main/org/apache/zookeeper/ZooKeeper.java. +
    +
    + Non-incremental mode + The second mode of reconfiguration is non-incremental, whereby a + client gives a complete specification of the new dynamic system + configuration. The new configuration can either be given in place or + read from a file: + > reconfig -file newconfig.cfg + //newconfig.cfg is a dynamic config file, see + > reconfig -members + server.1=125.23.63.23:2780:2783:participant;2791,server.2=125.23.63.24:2781:2784:participant;2792,server.3=125.23.63.25:2782:2785:participant;2793 + The new configuration may use a different Quorum System. For + example, you may specify a Hierarchical Quorum System even if the + current ensemble uses a Majority Quorum System. + Bulk mode - example using the Java API: + newMembers = new ArrayList(); +newMembers.add("server.1=1111:1234:1235;1236"); +newMembers.add("server.2=1112:1237:1238;1239"); +newMembers.add("server.3=1114:1240:1241:observer;1242"); + +byte[] config = zk.reconfig(null, null, newMembers, -1, new Stat()); + +String configStr = new String(config); +System.out.println(configStr);]]> + There is also an asynchronous API, and an API accepting comma + separated String containing the new members instead of + List<String>. See + src/java/main/org/apache/zookeeper/ZooKeeper.java. +
    +
    + Conditional reconfig + Sometimes (especially in non-incremental mode) a new proposed + configuration depends on what the client "believes" to be the current + configuration, and should be applied only to that configuration. + Specifically, the reconfig succeeds only if the + last configuration at the leader has the specified version. + reconfig -file -v ]]> + In the previously listed Java examples, instead of -1 one could + specify a configuration version to condition the + reconfiguration. +
    +
    + Error conditions + In addition to normal ZooKeeper error conditions, a + reconfiguration may fail for the following reasons: + + + another reconfig is currently in progress + (ReconfigInProgress) + + + the proposed change would leave the cluster with less than 2 + participants, in case standalone mode is enabled, or, if + standalone mode is disabled then its legal to remain with 1 or + more participants (BadArgumentsException) + + + no quorum of the new configuration was connected and + up-to-date with the leader when the reconfiguration processing + began (NewConfigNoQuorum) + + + -v x was specified, but the version + y of the latest configuration is not + x (BadVersionException) + + + an incremental reconfiguration was requested but the last + configuration at the leader uses a Quorum System which is + different from the Majority system (BadArgumentsException) + + + syntax error (BadArgumentsException) + + + I/O exception when reading the configuration from a file + (BadArgumentsException) + + + Most of these are illustrated by test-cases in + ReconfigFailureCases.java. +
    +
    + Additional comments + Liveness: To better understand + the difference between incremental and non-incremental + reconfiguration, suppose that client C1 adds server D to the system + while a different client C2 adds server E. With the non-incremental + mode, each client would first invoke config to find + out the current configuration, and then locally create a new list of + servers by adding its own suggested server. The new configuration can + then be submitted using the non-incremental + reconfig command. After both reconfigurations + complete, only one of E or D will be added (not both), depending on + which client's request arrives second to the leader, overwriting the + previous configuration. The other client can repeat the process until + its change takes effect. This method guarantees system-wide progress + (i.e., for one of the clients), but does not ensure that every client + succeeds. To have more control C2 may request to only execute the + reconfiguration in case the version of the current configuration + hasn't changed, as explained in the section . In this way it may avoid blindly + overwriting the configuration of C1 if C1's configuration reached the + leader first. + With incremental reconfiguration, both changes will take effect as + they are simply applied by the leader one after the other to the + current configuration, whatever that is (assuming that the second + reconfig request reaches the leader after it sends a commit message + for the first reconfig request -- currently the leader will refuse to + propose a reconfiguration if another one is already pending). Since + both clients are guaranteed to make progress, this method guarantees + stronger liveness. In practice, multiple concurrent reconfigurations + are probably rare. Non-incremental reconfiguration is currently the + only way to dynamically change the Quorum System. Incremental + configuration is currently only allowed with the Majority Quorum + System. + Changing an observer into a + follower: Clearly, changing a server that participates in + voting into an observer may fail if error (2) occurs, i.e., if fewer + than the minimal allowed number of participants would remain. However, + converting an observer into a participant may sometimes fail for a + more subtle reason: Suppose, for example, that the current + configuration is (A, B, C, D), where A is the leader, B and C are + followers and D is an observer. In addition, suppose that B has + crashed. If a reconfiguration is submitted where D is said to become a + follower, it will fail with error (3) since in this configuration, a + majority of voters in the new configuration (any 3 voters), must be + connected and up-to-date with the leader. An observer cannot + acknowledge the history prefix sent during reconfiguration, and + therefore it does not count towards these 3 required servers and the + reconfiguration will be aborted. In case this happens, a client can + achieve the same task by two reconfig commands: first invoke a + reconfig to remove D from the configuration and then invoke a second + command to add it back as a participant (follower). During the + intermediate state D is a non-voting follower and can ACK the state + transfer performed during the second reconfig comand. +
    +
    +
    +
    + Rebalancing Client Connections + When a ZooKeeper cluster is started, if each client is given the same + connection string (list of servers), the client will randomly choose a + server in the list to connect to, which makes the expected number of + client connections per server the same for each of the servers. We + implemented a method that preserves this property when the set of servers + changes through reconfiguration. See Sections 4 and 5.1 in the paper. + In order for the method to work, all clients must subscribe to + configuration changes (by setting a watch on /zookeeper/config either + directly or through the getConfig API command). When + the watch is triggered, the client should read the new configuration by + invoking sync and getConfig and if + the configuration is indeed new invoke the + updateServerList API command. To avoid mass client + migration at the same time, it is better to have each client sleep a + random short period of time before invoking + updateServerList. + A few examples can be found in: + StaticHostProviderTest.java and + TestReconfig.cc + Example (this is not a recipe, but a simplified example just to + explain the general idea): + this.configVersion) { + hostList = config[1]; + try { + // the following command is not blocking but may cause the client to close the socket and + // migrate to a different server. In practice its better to wait a short period of time, chosen + // randomly, so that different clients migrate at different times + zk.updateServerList(hostList); + } catch (IOException e) { + System.err.println("Error updating server list"); + e.printStackTrace(); + } + this.configVersion = version; +} } }]]> +
    +
    diff --git a/src/docs/src/documentation/content/xdocs/zookeeperStarted.xml b/src/docs/src/documentation/content/xdocs/zookeeperStarted.xml index b24b17ad525..e5cd7775ec0 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperStarted.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperStarted.xml @@ -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: @@ -342,8 +313,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 @@ -396,6 +384,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 10b3606c842..e320ed6863d 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperTutorial.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperTutorial.xml @@ -196,7 +196,7 @@ a boolean flag that enables the process to set a watch. In the code the flag is Note that enter() throws both KeeperException and InterruptedException, so it is -the reponsability of the application to catch and handle such exceptions. +the responsibility of the application to catch and handle such exceptions. Once the computation is finished, a process calls leave() to leave the barrier. @@ -204,7 +204,7 @@ First it deletes its corresponding node, and then it gets the children of the ro node. If there is at least one child, then it waits for a notification (obs: note that the second parameter of the call to getChildren() is true, meaning that ZooKeeper has to set a watch on the the root node). Upon reception of a notification, -it checks once more whether the root node has any child. +it checks once more whether the root node has any children. /** @@ -233,7 +233,7 @@ it checks once more whether the root node has any child.
    Producer-Consumer Queues -A producer-consumer queue is a distributed data estructure thata group of processes +A producer-consumer queue is a distributed data structure that groups of processes use to generate and consume items. Producer processes create new elements and add them to the queue. Consumer processes remove elements from the list, and process them. In this implementation, the elements are simple integers. The queue is represented @@ -361,7 +361,41 @@ from each one.
    -
    Complete Source Listing + +
    +Complete example + +In the following section you can find a complete command line application to demonstrate the above mentioned +recipes. Use the following command to run it. + + +ZOOBINDIR="[path_to_distro]/bin" +. "$ZOOBINDIR"/zkEnv.sh +java SyncPrimitive [Test Type] [ZK server] [No of elements] [Client type] + + +
    +Queue test +Start a producer to create 100 elements + +java SyncPrimitive qTest localhost 100 p + + +Start a consumer to consume 100 elements + +java SyncPrimitive qTest localhost 100 c + +
    + +
    +Barrier test +Start a barrier with 2 participants (start as many times as many participants you'd like to enter) + +java SyncPrimitive bTest localhost 2 + +
    + +
    Source Listing SyncPrimitive.Java @@ -574,15 +608,19 @@ public class SyncPrimitive implements Watcher { mutex.wait(); } else { Integer min = new Integer(list.get(0).substring(7)); + String minNode = list.get(0); for(String s : list){ Integer tempValue = new Integer(s.substring(7)); //System.out.println("Temporary value: " + tempValue); - if(tempValue < min) min = tempValue; + if(tempValue < min) { + min = tempValue; + minNode = s; + } } - System.out.println("Temporary value: " + root + "/element" + min); - byte[] b = zk.getData(root + "/element" + min, - false, stat); - zk.delete(root + "/element" + min, 0); + System.out.println("Temporary value: " + root + "/" + minNode); + byte[] b = zk.getData(root + "/" + minNode, + false, stat); + zk.delete(root + "/" + minNode, 0); ByteBuffer buffer = ByteBuffer.wrap(b); retvalue = buffer.getInt(); @@ -669,7 +707,6 @@ public class SyncPrimitive implements Watcher { }
    +
    - - diff --git a/src/docs/src/documentation/skinconf.xml b/src/docs/src/documentation/skinconf.xml index 5745b85d283..9edf69eea66 100644 --- a/src/docs/src/documentation/skinconf.xml +++ b/src/docs/src/documentation/skinconf.xml @@ -84,7 +84,7 @@ which will be used to configure the chosen Forrest skin. images/favicon.ico - 2008-2013 + The Apache Software Foundation. http://www.apache.org/licenses/ diff --git a/src/java/lib/log4j-1.2.16.LICENSE.txt b/src/java/lib/log4j-1.2.17.LICENSE.txt similarity index 100% rename from src/java/lib/log4j-1.2.16.LICENSE.txt rename to src/java/lib/log4j-1.2.17.LICENSE.txt diff --git a/src/java/lib/slf4j-1.7.25.LICENSE.txt b/src/java/lib/slf4j-1.7.25.LICENSE.txt new file mode 100644 index 00000000000..a502dd93a86 --- /dev/null +++ b/src/java/lib/slf4j-1.7.25.LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2004-2017 QOS.ch +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/src/java/main/org/apache/jute/BinaryInputArchive.java b/src/java/main/org/apache/jute/BinaryInputArchive.java index 6b2cb46a854..7722bffc3ba 100644 --- a/src/java/main/org/apache/jute/BinaryInputArchive.java +++ b/src/java/main/org/apache/jute/BinaryInputArchive.java @@ -27,7 +27,7 @@ * */ public class BinaryInputArchive implements InputArchive { - + static public final String UNREASONBLE_LENGTH= "Unreasonable length = "; private DataInput in; static public BinaryInputArchive getArchive(InputStream strm) { @@ -78,6 +78,7 @@ public double readDouble(String tag) throws IOException { public String readString(String tag) throws IOException { int len = in.readInt(); if (len == -1) return null; + checkLength(len); byte b[] = new byte[len]; in.readFully(b); return new String(b, "UTF8"); @@ -88,12 +89,7 @@ public String readString(String tag) throws IOException { public byte[] readBuffer(String tag) throws IOException { int len = readInt(tag); if (len == -1) return null; - // Since this is a rough sanity check, add some padding to maxBuffer to - // make up for extra fields, etc. (otherwise e.g. clients may be able to - // write buffers larger than we can read from disk!) - if (len < 0 || len > maxBuffer + 1024) { - throw new IOException("Unreasonable length = " + len); - } + checkLength(len); byte[] arr = new byte[len]; in.readFully(arr); return arr; @@ -122,5 +118,13 @@ public Index startMap(String tag) throws IOException { } public void endMap(String tag) throws IOException {} - + + // Since this is a rough sanity check, add some padding to maxBuffer to + // make up for extra fields, etc. (otherwise e.g. clients may be able to + // write buffers larger than we can read from disk!) + private void checkLength(int len) throws IOException { + if (len < 0 || len > maxBuffer + 1024) { + throw new IOException(UNREASONBLE_LENGTH + len); + } + } } diff --git a/src/java/main/org/apache/jute/Record.java b/src/java/main/org/apache/jute/Record.java index bc7a350eeaf..d955280578d 100644 --- a/src/java/main/org/apache/jute/Record.java +++ b/src/java/main/org/apache/jute/Record.java @@ -18,12 +18,15 @@ package org.apache.jute; +import org.apache.yetus.audience.InterfaceAudience; + import java.io.IOException; /** * Interface that is implemented by generated classes. * */ +@InterfaceAudience.Public public interface Record { public void serialize(OutputArchive archive, String tag) throws IOException; diff --git a/src/java/main/org/apache/jute/Utils.java b/src/java/main/org/apache/jute/Utils.java index 243f2c8b0e7..1205fa2f33a 100644 --- a/src/java/main/org/apache/jute/Utils.java +++ b/src/java/main/org/apache/jute/Utils.java @@ -268,15 +268,15 @@ static byte[] fromCSVBuffer(String s) return stream.toByteArray(); } public static int compareBytes(byte b1[], int off1, int len1, byte b2[], int off2, int len2) { - int i; - for(i=0; i < len1 && i < len2; i++) { - if (b1[off1+i] != b2[off2+i]) { - return b1[off1+i] < b2[off2+1] ? -1 : 1; - } - } - if (len1 != len2) { - return len1 < len2 ? -1 : 1; - } - return 0; + int i; + for(i=0; i < len1 && i < len2; i++) { + if (b1[off1+i] != b2[off2+i]) { + return b1[off1+i] < b2[off2+i] ? -1 : 1; + } + } + if (len1 != len2) { + return len1 < len2 ? -1 : 1; + } + return 0; } } diff --git a/src/java/main/org/apache/jute/compiler/CGenerator.java b/src/java/main/org/apache/jute/compiler/CGenerator.java index 4bfdcadc9ae..af931c9a847 100644 --- a/src/java/main/org/apache/jute/compiler/CGenerator.java +++ b/src/java/main/org/apache/jute/compiler/CGenerator.java @@ -61,70 +61,69 @@ void genCode() throws IOException { + outputDirectory); } } - FileWriter c = new FileWriter(new File(outputDirectory, mName+".c")); - FileWriter h = new FileWriter(new File(outputDirectory, mName+".h")); - h.write("/**\n"); - h.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); - h.write("* or more contributor license agreements. See the NOTICE file\n"); - h.write("* distributed with this work for additional information\n"); - h.write("* regarding copyright ownership. The ASF licenses this file\n"); - h.write("* to you under the Apache License, Version 2.0 (the\n"); - h.write("* \"License\"); you may not use this file except in compliance\n"); - h.write("* with the License. You may obtain a copy of the License at\n"); - h.write("*\n"); - h.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); - h.write("*\n"); - h.write("* Unless required by applicable law or agreed to in writing, software\n"); - h.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); - h.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); - h.write("* See the License for the specific language governing permissions and\n"); - h.write("* limitations under the License.\n"); - h.write("*/\n"); - h.write("\n"); + try (FileWriter c = new FileWriter(new File(outputDirectory, mName + ".c")); + FileWriter h = new FileWriter(new File(outputDirectory, mName + ".h")); + ) { + h.write("/**\n"); + h.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); + h.write("* or more contributor license agreements. See the NOTICE file\n"); + h.write("* distributed with this work for additional information\n"); + h.write("* regarding copyright ownership. The ASF licenses this file\n"); + h.write("* to you under the Apache License, Version 2.0 (the\n"); + h.write("* \"License\"); you may not use this file except in compliance\n"); + h.write("* with the License. You may obtain a copy of the License at\n"); + h.write("*\n"); + h.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); + h.write("*\n"); + h.write("* Unless required by applicable law or agreed to in writing, software\n"); + h.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); + h.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); + h.write("* See the License for the specific language governing permissions and\n"); + h.write("* limitations under the License.\n"); + h.write("*/\n"); + h.write("\n"); - c.write("/**\n"); - c.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); - c.write("* or more contributor license agreements. See the NOTICE file\n"); - c.write("* distributed with this work for additional information\n"); - c.write("* regarding copyright ownership. The ASF licenses this file\n"); - c.write("* to you under the Apache License, Version 2.0 (the\n"); - c.write("* \"License\"); you may not use this file except in compliance\n"); - c.write("* with the License. You may obtain a copy of the License at\n"); - c.write("*\n"); - c.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); - c.write("*\n"); - c.write("* Unless required by applicable law or agreed to in writing, software\n"); - c.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); - c.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); - c.write("* See the License for the specific language governing permissions and\n"); - c.write("* limitations under the License.\n"); - c.write("*/\n"); - c.write("\n"); + c.write("/**\n"); + c.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); + c.write("* or more contributor license agreements. See the NOTICE file\n"); + c.write("* distributed with this work for additional information\n"); + c.write("* regarding copyright ownership. The ASF licenses this file\n"); + c.write("* to you under the Apache License, Version 2.0 (the\n"); + c.write("* \"License\"); you may not use this file except in compliance\n"); + c.write("* with the License. You may obtain a copy of the License at\n"); + c.write("*\n"); + c.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); + c.write("*\n"); + c.write("* Unless required by applicable law or agreed to in writing, software\n"); + c.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); + c.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); + c.write("* See the License for the specific language governing permissions and\n"); + c.write("* limitations under the License.\n"); + c.write("*/\n"); + c.write("\n"); - h.write("#ifndef __"+mName.toUpperCase().replace('.','_')+"__\n"); - h.write("#define __"+mName.toUpperCase().replace('.','_')+"__\n"); + h.write("#ifndef __" + mName.toUpperCase().replace('.', '_') + "__\n"); + h.write("#define __" + mName.toUpperCase().replace('.', '_') + "__\n"); - h.write("#include \"recordio.h\"\n"); - for (Iterator i = mInclFiles.iterator(); i.hasNext();) { - JFile f = i.next(); - h.write("#include \""+f.getName()+".h\"\n"); - } - // required for compilation from C++ - h.write("\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n"); - - c.write("#include \n"); // need it for calloc() & free() - c.write("#include \""+mName+".h\"\n\n"); + h.write("#include \"recordio.h\"\n"); + for (Iterator i = mInclFiles.iterator(); i.hasNext(); ) { + JFile f = i.next(); + h.write("#include \"" + f.getName() + ".h\"\n"); + } + // required for compilation from C++ + h.write("\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n"); - for (Iterator i = mRecList.iterator(); i.hasNext();) { - JRecord jr = i.next(); - jr.genCCode(h, c); - } + c.write("#include \n"); // need it for calloc() & free() + c.write("#include \"" + mName + ".h\"\n\n"); - h.write("\n#ifdef __cplusplus\n}\n#endif\n\n"); - h.write("#endif //"+mName.toUpperCase().replace('.','_')+"__\n"); + for (Iterator i = mRecList.iterator(); i.hasNext(); ) { + JRecord jr = i.next(); + jr.genCCode(h, c); + } - h.close(); - c.close(); + h.write("\n#ifdef __cplusplus\n}\n#endif\n\n"); + h.write("#endif //" + mName.toUpperCase().replace('.', '_') + "__\n"); + } } } diff --git a/src/java/main/org/apache/jute/compiler/CppGenerator.java b/src/java/main/org/apache/jute/compiler/CppGenerator.java index 16d44ce0e6c..9b1227871a3 100644 --- a/src/java/main/org/apache/jute/compiler/CppGenerator.java +++ b/src/java/main/org/apache/jute/compiler/CppGenerator.java @@ -61,65 +61,64 @@ void genCode() throws IOException { + outputDirectory); } } - FileWriter cc = new FileWriter(new File(outputDirectory, mName+".cc")); - FileWriter hh = new FileWriter(new File(outputDirectory, mName+".hh")); - hh.write("/**\n"); - hh.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); - hh.write("* or more contributor license agreements. See the NOTICE file\n"); - hh.write("* distributed with this work for additional information\n"); - hh.write("* regarding copyright ownership. The ASF licenses this file\n"); - hh.write("* to you under the Apache License, Version 2.0 (the\n"); - hh.write("* \"License\"); you may not use this file except in compliance\n"); - hh.write("* with the License. You may obtain a copy of the License at\n"); - hh.write("*\n"); - hh.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); - hh.write("*\n"); - hh.write("* Unless required by applicable law or agreed to in writing, software\n"); - hh.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); - hh.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); - hh.write("* See the License for the specific language governing permissions and\n"); - hh.write("* limitations under the License.\n"); - hh.write("*/\n"); - hh.write("\n"); + try (FileWriter cc = new FileWriter(new File(outputDirectory, mName + ".cc")); + FileWriter hh = new FileWriter(new File(outputDirectory, mName + ".hh")); + ) { + hh.write("/**\n"); + hh.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); + hh.write("* or more contributor license agreements. See the NOTICE file\n"); + hh.write("* distributed with this work for additional information\n"); + hh.write("* regarding copyright ownership. The ASF licenses this file\n"); + hh.write("* to you under the Apache License, Version 2.0 (the\n"); + hh.write("* \"License\"); you may not use this file except in compliance\n"); + hh.write("* with the License. You may obtain a copy of the License at\n"); + hh.write("*\n"); + hh.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); + hh.write("*\n"); + hh.write("* Unless required by applicable law or agreed to in writing, software\n"); + hh.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); + hh.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); + hh.write("* See the License for the specific language governing permissions and\n"); + hh.write("* limitations under the License.\n"); + hh.write("*/\n"); + hh.write("\n"); - cc.write("/**\n"); - cc.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); - cc.write("* or more contributor license agreements. See the NOTICE file\n"); - cc.write("* distributed with this work for additional information\n"); - cc.write("* regarding copyright ownership. The ASF licenses this file\n"); - cc.write("* to you under the Apache License, Version 2.0 (the\n"); - cc.write("* \"License\"); you may not use this file except in compliance\n"); - cc.write("* with the License. You may obtain a copy of the License at\n"); - cc.write("*\n"); - cc.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); - cc.write("*\n"); - cc.write("* Unless required by applicable law or agreed to in writing, software\n"); - cc.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); - cc.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); - cc.write("* See the License for the specific language governing permissions and\n"); - cc.write("* limitations under the License.\n"); - cc.write("*/\n"); - cc.write("\n"); + cc.write("/**\n"); + cc.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); + cc.write("* or more contributor license agreements. See the NOTICE file\n"); + cc.write("* distributed with this work for additional information\n"); + cc.write("* regarding copyright ownership. The ASF licenses this file\n"); + cc.write("* to you under the Apache License, Version 2.0 (the\n"); + cc.write("* \"License\"); you may not use this file except in compliance\n"); + cc.write("* with the License. You may obtain a copy of the License at\n"); + cc.write("*\n"); + cc.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); + cc.write("*\n"); + cc.write("* Unless required by applicable law or agreed to in writing, software\n"); + cc.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); + cc.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); + cc.write("* See the License for the specific language governing permissions and\n"); + cc.write("* limitations under the License.\n"); + cc.write("*/\n"); + cc.write("\n"); - hh.write("#ifndef __"+mName.toUpperCase().replace('.','_')+"__\n"); - hh.write("#define __"+mName.toUpperCase().replace('.','_')+"__\n"); + hh.write("#ifndef __" + mName.toUpperCase().replace('.', '_') + "__\n"); + hh.write("#define __" + mName.toUpperCase().replace('.', '_') + "__\n"); - hh.write("#include \"recordio.hh\"\n"); - for (Iterator i = mInclFiles.iterator(); i.hasNext();) { - JFile f = i.next(); - hh.write("#include \""+f.getName()+".hh\"\n"); - } - cc.write("#include \""+mName+".hh\"\n"); - - for (Iterator i = mRecList.iterator(); i.hasNext();) { - JRecord jr = i.next(); - jr.genCppCode(hh, cc); - } + hh.write("#include \"recordio.hh\"\n"); + for (Iterator i = mInclFiles.iterator(); i.hasNext(); ) { + JFile f = i.next(); + hh.write("#include \"" + f.getName() + ".hh\"\n"); + } + cc.write("#include \"" + mName + ".hh\"\n"); - hh.write("#endif //"+mName.toUpperCase().replace('.','_')+"__\n"); + for (Iterator i = mRecList.iterator(); i.hasNext(); ) { + JRecord jr = i.next(); + jr.genCppCode(hh, cc); + } - hh.close(); - cc.close(); + hh.write("#endif //" + mName.toUpperCase().replace('.', '_') + "__\n"); + } } } diff --git a/src/java/main/org/apache/jute/compiler/JRecord.java b/src/java/main/org/apache/jute/compiler/JRecord.java index 01ba2989afb..fece97d80e5 100644 --- a/src/java/main/org/apache/jute/compiler/JRecord.java +++ b/src/java/main/org/apache/jute/compiler/JRecord.java @@ -143,7 +143,7 @@ public String genCsharpWriteWrapper(String fname, String tag) { public void genCCode(FileWriter h, FileWriter c) throws IOException { for (JField f : mFields) { if (f.getType() instanceof JVector) { - JVector jv = (JVector)f.getType(); + JVector jv = (JVector) f.getType(); JType jvType = jv.getElementType(); String struct_name = JVector.extractVectorName(jvType); if (vectorStructs.get(struct_name) == null) { @@ -167,7 +167,7 @@ public void genCCode(FileWriter h, FileWriter c) throws IOException { c.write(" if (v->data) {\n"); c.write(" int32_t i;\n"); c.write(" for(i=0;icount; i++) {\n"); - c.write(" deallocate_"+JRecord.extractMethodSuffix(jvType)+"(&v->data[i]);\n"); + c.write(" deallocate_" + JRecord.extractMethodSuffix(jvType) + "(&v->data[i]);\n"); c.write(" }\n"); c.write(" free(v->data);\n"); c.write(" v->data = 0;\n"); @@ -215,7 +215,7 @@ public void genCCode(FileWriter h, FileWriter c) throws IOException { c.write("{\n"); c.write(" int rc;\n"); c.write(" rc = out->start_record(out, tag);\n"); - for(JField f : mFields) { + for (JField f : mFields) { genSerialize(c, f.getType(), f.getTag(), f.getName()); } c.write(" rc = rc ? rc : out->end_record(out, tag);\n"); @@ -225,7 +225,7 @@ public void genCCode(FileWriter h, FileWriter c) throws IOException { c.write("{\n"); c.write(" int rc;\n"); c.write(" rc = in->start_record(in, tag);\n"); - for(JField f : mFields) { + for (JField f : mFields) { genDeserialize(c, f.getType(), f.getTag(), f.getName()); } c.write(" rc = rc ? rc : in->end_record(in, tag);\n"); @@ -233,14 +233,14 @@ public void genCCode(FileWriter h, FileWriter c) throws IOException { c.write("}\n"); c.write("void deallocate_" + rec_name + "(struct " + rec_name + "*v)"); c.write("{\n"); - for(JField f : mFields) { + for (JField f : mFields) { if (f.getType() instanceof JRecord) { c.write(" deallocate_" + extractStructName(f.getType()) + "(&v->" + f.getName() + ");\n"); } else if (f.getType() instanceof JVector) { - JVector vt = (JVector)f.getType(); - c.write(" deallocate_" + JVector.extractVectorName(vt.getElementType())+ "(&v->"+f.getName()+");\n"); + JVector vt = (JVector) f.getType(); + c.write(" deallocate_" + JVector.extractVectorName(vt.getElementType()) + "(&v->" + f.getName() + ");\n"); } else if (f.getType() instanceof JCompType) { - c.write(" deallocate_" + extractMethodSuffix(f.getType()) + "(&v->"+f.getName()+");\n"); + c.write(" deallocate_" + extractMethodSuffix(f.getType()) + "(&v->" + f.getName() + ");\n"); } } c.write("}\n"); @@ -403,168 +403,167 @@ public void genJavaCode(File outputDirectory) throws IOException { } else if (!pkgdir.isDirectory()) { throw new IOException(pkgpath + " is not a directory."); } - File jfile = new File(pkgdir, getName()+".java"); - FileWriter jj = new FileWriter(jfile); - jj.write("// File generated by hadoop record compiler. Do not edit.\n"); - jj.write("/**\n"); - jj.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); - jj.write("* or more contributor license agreements. See the NOTICE file\n"); - jj.write("* distributed with this work for additional information\n"); - jj.write("* regarding copyright ownership. The ASF licenses this file\n"); - jj.write("* to you under the Apache License, Version 2.0 (the\n"); - jj.write("* \"License\"); you may not use this file except in compliance\n"); - jj.write("* with the License. You may obtain a copy of the License at\n"); - jj.write("*\n"); - jj.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); - jj.write("*\n"); - jj.write("* Unless required by applicable law or agreed to in writing, software\n"); - jj.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); - jj.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); - jj.write("* See the License for the specific language governing permissions and\n"); - jj.write("* limitations under the License.\n"); - jj.write("*/\n"); - jj.write("\n"); - jj.write("package "+getJavaPackage()+";\n\n"); - jj.write("import org.apache.jute.*;\n"); - jj.write("public class "+getName()+" implements Record {\n"); - for (Iterator i = mFields.iterator(); i.hasNext();) { - JField jf = i.next(); - jj.write(jf.genJavaDecl()); - } - jj.write(" public "+getName()+"() {\n"); - jj.write(" }\n"); + try (FileWriter jj = new FileWriter(new File(pkgdir, getName()+".java"))) { + jj.write("// File generated by hadoop record compiler. Do not edit.\n"); + jj.write("/**\n"); + jj.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); + jj.write("* or more contributor license agreements. See the NOTICE file\n"); + jj.write("* distributed with this work for additional information\n"); + jj.write("* regarding copyright ownership. The ASF licenses this file\n"); + jj.write("* to you under the Apache License, Version 2.0 (the\n"); + jj.write("* \"License\"); you may not use this file except in compliance\n"); + jj.write("* with the License. You may obtain a copy of the License at\n"); + jj.write("*\n"); + jj.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); + jj.write("*\n"); + jj.write("* Unless required by applicable law or agreed to in writing, software\n"); + jj.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); + jj.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); + jj.write("* See the License for the specific language governing permissions and\n"); + jj.write("* limitations under the License.\n"); + jj.write("*/\n"); + jj.write("\n"); + jj.write("package " + getJavaPackage() + ";\n\n"); + jj.write("import org.apache.jute.*;\n"); + jj.write("import org.apache.yetus.audience.InterfaceAudience;\n"); + jj.write("@InterfaceAudience.Public\n"); + jj.write("public class " + getName() + " implements Record {\n"); + for (Iterator i = mFields.iterator(); i.hasNext(); ) { + JField jf = i.next(); + jj.write(jf.genJavaDecl()); + } + jj.write(" public " + getName() + "() {\n"); + jj.write(" }\n"); - jj.write(" public "+getName()+"(\n"); - int fIdx = 0; - int fLen = mFields.size(); - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - jj.write(jf.genJavaConstructorParam(jf.getName())); - jj.write((fLen-1 == fIdx)?"":",\n"); - } - jj.write(") {\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - jj.write(jf.genJavaConstructorSet(jf.getName())); - } - jj.write(" }\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - jj.write(jf.genJavaGetSet(fIdx)); - } - jj.write(" public void serialize(OutputArchive a_, String tag) throws java.io.IOException {\n"); - jj.write(" a_.startRecord(this,tag);\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - jj.write(jf.genJavaWriteMethodName()); - } - jj.write(" a_.endRecord(this,tag);\n"); - jj.write(" }\n"); + jj.write(" public " + getName() + "(\n"); + int fIdx = 0; + int fLen = mFields.size(); + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + jj.write(jf.genJavaConstructorParam(jf.getName())); + jj.write((fLen - 1 == fIdx) ? "" : ",\n"); + } + jj.write(") {\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + jj.write(jf.genJavaConstructorSet(jf.getName())); + } + jj.write(" }\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + jj.write(jf.genJavaGetSet(fIdx)); + } + jj.write(" public void serialize(OutputArchive a_, String tag) throws java.io.IOException {\n"); + jj.write(" a_.startRecord(this,tag);\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + jj.write(jf.genJavaWriteMethodName()); + } + jj.write(" a_.endRecord(this,tag);\n"); + jj.write(" }\n"); - jj.write(" public void deserialize(InputArchive a_, String tag) throws java.io.IOException {\n"); - jj.write(" a_.startRecord(tag);\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - jj.write(jf.genJavaReadMethodName()); - } - jj.write(" a_.endRecord(tag);\n"); - jj.write("}\n"); - - jj.write(" public String toString() {\n"); - jj.write(" try {\n"); - jj.write(" java.io.ByteArrayOutputStream s =\n"); - jj.write(" new java.io.ByteArrayOutputStream();\n"); - jj.write(" CsvOutputArchive a_ = \n"); - jj.write(" new CsvOutputArchive(s);\n"); - jj.write(" a_.startRecord(this,\"\");\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - jj.write(jf.genJavaWriteMethodName()); - } - jj.write(" a_.endRecord(this,\"\");\n"); - jj.write(" return new String(s.toByteArray(), \"UTF-8\");\n"); - jj.write(" } catch (Throwable ex) {\n"); - jj.write(" ex.printStackTrace();\n"); - jj.write(" }\n"); - jj.write(" return \"ERROR\";\n"); - jj.write(" }\n"); - - jj.write(" public void write(java.io.DataOutput out) throws java.io.IOException {\n"); - jj.write(" BinaryOutputArchive archive = new BinaryOutputArchive(out);\n"); - jj.write(" serialize(archive, \"\");\n"); - jj.write(" }\n"); - - jj.write(" public void readFields(java.io.DataInput in) throws java.io.IOException {\n"); - jj.write(" BinaryInputArchive archive = new BinaryInputArchive(in);\n"); - jj.write(" deserialize(archive, \"\");\n"); - jj.write(" }\n"); - - jj.write(" public int compareTo (Object peer_) throws ClassCastException {\n"); - boolean unimplemented = false; - for (JField f : mFields) { - if ((f.getType() instanceof JMap) - || (f.getType() instanceof JVector)) - { - unimplemented = true; + jj.write(" public void deserialize(InputArchive a_, String tag) throws java.io.IOException {\n"); + jj.write(" a_.startRecord(tag);\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + jj.write(jf.genJavaReadMethodName()); } - } - if (unimplemented) { - jj.write(" throw new UnsupportedOperationException(\"comparing " - + getName() + " is unimplemented\");\n"); - } else { - jj.write(" if (!(peer_ instanceof "+getName()+")) {\n"); - jj.write(" throw new ClassCastException(\"Comparing different types of records.\");\n"); + jj.write(" a_.endRecord(tag);\n"); + jj.write("}\n"); + + jj.write(" public String toString() {\n"); + jj.write(" try {\n"); + jj.write(" java.io.ByteArrayOutputStream s =\n"); + jj.write(" new java.io.ByteArrayOutputStream();\n"); + jj.write(" CsvOutputArchive a_ = \n"); + jj.write(" new CsvOutputArchive(s);\n"); + jj.write(" a_.startRecord(this,\"\");\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + jj.write(jf.genJavaWriteMethodName()); + } + jj.write(" a_.endRecord(this,\"\");\n"); + jj.write(" return new String(s.toByteArray(), \"UTF-8\");\n"); + jj.write(" } catch (Throwable ex) {\n"); + jj.write(" ex.printStackTrace();\n"); jj.write(" }\n"); - jj.write(" "+getName()+" peer = ("+getName()+") peer_;\n"); - jj.write(" int ret = 0;\n"); + jj.write(" return \"ERROR\";\n"); + jj.write(" }\n"); + + jj.write(" public void write(java.io.DataOutput out) throws java.io.IOException {\n"); + jj.write(" BinaryOutputArchive archive = new BinaryOutputArchive(out);\n"); + jj.write(" serialize(archive, \"\");\n"); + jj.write(" }\n"); + + jj.write(" public void readFields(java.io.DataInput in) throws java.io.IOException {\n"); + jj.write(" BinaryInputArchive archive = new BinaryInputArchive(in);\n"); + jj.write(" deserialize(archive, \"\");\n"); + jj.write(" }\n"); + + jj.write(" public int compareTo (Object peer_) throws ClassCastException {\n"); + boolean unimplemented = false; + for (JField f : mFields) { + if ((f.getType() instanceof JMap) + || (f.getType() instanceof JVector)) { + unimplemented = true; + } + } + if (unimplemented) { + jj.write(" throw new UnsupportedOperationException(\"comparing " + + getName() + " is unimplemented\");\n"); + } else { + jj.write(" if (!(peer_ instanceof " + getName() + ")) {\n"); + jj.write(" throw new ClassCastException(\"Comparing different types of records.\");\n"); + jj.write(" }\n"); + jj.write(" " + getName() + " peer = (" + getName() + ") peer_;\n"); + jj.write(" int ret = 0;\n"); + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + jj.write(jf.genJavaCompareTo()); + jj.write(" if (ret != 0) return ret;\n"); + } + jj.write(" return ret;\n"); + } + jj.write(" }\n"); + + jj.write(" public boolean equals(Object peer_) {\n"); + jj.write(" if (!(peer_ instanceof " + getName() + ")) {\n"); + jj.write(" return false;\n"); + jj.write(" }\n"); + jj.write(" if (peer_ == this) {\n"); + jj.write(" return true;\n"); + jj.write(" }\n"); + jj.write(" " + getName() + " peer = (" + getName() + ") peer_;\n"); + jj.write(" boolean ret = false;\n"); for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { JField jf = i.next(); - jj.write(jf.genJavaCompareTo()); - jj.write(" if (ret != 0) return ret;\n"); + jj.write(jf.genJavaEquals()); + jj.write(" if (!ret) return ret;\n"); } jj.write(" return ret;\n"); - } - jj.write(" }\n"); - - jj.write(" public boolean equals(Object peer_) {\n"); - jj.write(" if (!(peer_ instanceof "+getName()+")) {\n"); - jj.write(" return false;\n"); - jj.write(" }\n"); - jj.write(" if (peer_ == this) {\n"); - jj.write(" return true;\n"); - jj.write(" }\n"); - jj.write(" "+getName()+" peer = ("+getName()+") peer_;\n"); - jj.write(" boolean ret = false;\n"); - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - jj.write(jf.genJavaEquals()); - jj.write(" if (!ret) return ret;\n"); - } - jj.write(" return ret;\n"); - jj.write(" }\n"); - - jj.write(" public int hashCode() {\n"); - jj.write(" int result = 17;\n"); - jj.write(" int ret;\n"); - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - jj.write(jf.genJavaHashCode()); - jj.write(" result = 37*result + ret;\n"); - } - jj.write(" return result;\n"); - jj.write(" }\n"); - jj.write(" public static String signature() {\n"); - jj.write(" return \""+getSignature()+"\";\n"); - jj.write(" }\n"); + jj.write(" }\n"); - jj.write("}\n"); + jj.write(" public int hashCode() {\n"); + jj.write(" int result = 17;\n"); + jj.write(" int ret;\n"); + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + jj.write(jf.genJavaHashCode()); + jj.write(" result = 37*result + ret;\n"); + } + jj.write(" return result;\n"); + jj.write(" }\n"); + jj.write(" public static String signature() {\n"); + jj.write(" return \"" + getSignature() + "\";\n"); + jj.write(" }\n"); - jj.close(); + jj.write("}\n"); + } } public void genCsharpCode(File outputDirectory) throws IOException { @@ -576,174 +575,174 @@ public void genCsharpCode(File outputDirectory) throws IOException { } else if (!outputDirectory.isDirectory()) { throw new IOException(outputDirectory + " is not a directory."); } - File csharpFile = new File(outputDirectory, getName()+".cs"); - FileWriter cs = new FileWriter(csharpFile); - cs.write("// File generated by hadoop record compiler. Do not edit.\n"); - cs.write("/**\n"); - cs.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); - cs.write("* or more contributor license agreements. See the NOTICE file\n"); - cs.write("* distributed with this work for additional information\n"); - cs.write("* regarding copyright ownership. The ASF licenses this file\n"); - cs.write("* to you under the Apache License, Version 2.0 (the\n"); - cs.write("* \"License\"); you may not use this file except in compliance\n"); - cs.write("* with the License. You may obtain a copy of the License at\n"); - cs.write("*\n"); - cs.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); - cs.write("*\n"); - cs.write("* Unless required by applicable law or agreed to in writing, software\n"); - cs.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); - cs.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); - cs.write("* See the License for the specific language governing permissions and\n"); - cs.write("* limitations under the License.\n"); - cs.write("*/\n"); - cs.write("\n"); - cs.write("using System;\n"); - cs.write("using Org.Apache.Jute;\n"); - cs.write("\n"); - cs.write("namespace "+getCsharpNameSpace()+"\n"); - cs.write("{\n"); - - String className = getCsharpName(); - cs.write("public class "+className+" : IRecord, IComparable \n"); - cs.write("{\n"); - cs.write(" public "+ className +"() {\n"); - cs.write(" }\n"); - - cs.write(" public "+className+"(\n"); - int fIdx = 0; - int fLen = mFields.size(); - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - cs.write(jf.genCsharpConstructorParam(jf.getCsharpName())); - cs.write((fLen-1 == fIdx)?"":",\n"); - } - cs.write(") {\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - cs.write(jf.genCsharpConstructorSet(jf.getCsharpName())); - } - cs.write(" }\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - cs.write(jf.genCsharpGetSet(fIdx)); + + try (FileWriter cs = new FileWriter(new File(outputDirectory, getName() + ".cs"));) { + cs.write("// File generated by hadoop record compiler. Do not edit.\n"); + cs.write("/**\n"); + cs.write("* Licensed to the Apache Software Foundation (ASF) under one\n"); + cs.write("* or more contributor license agreements. See the NOTICE file\n"); + cs.write("* distributed with this work for additional information\n"); + cs.write("* regarding copyright ownership. The ASF licenses this file\n"); + cs.write("* to you under the Apache License, Version 2.0 (the\n"); + cs.write("* \"License\"); you may not use this file except in compliance\n"); + cs.write("* with the License. You may obtain a copy of the License at\n"); + cs.write("*\n"); + cs.write("* http://www.apache.org/licenses/LICENSE-2.0\n"); + cs.write("*\n"); + cs.write("* Unless required by applicable law or agreed to in writing, software\n"); + cs.write("* distributed under the License is distributed on an \"AS IS\" BASIS,\n"); + cs.write("* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"); + cs.write("* See the License for the specific language governing permissions and\n"); + cs.write("* limitations under the License.\n"); + cs.write("*/\n"); cs.write("\n"); - } - cs.write(" public void Serialize(IOutputArchive a_, String tag) {\n"); - cs.write(" a_.StartRecord(this,tag);\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - cs.write(jf.genCsharpWriteMethodName()); - } - cs.write(" a_.EndRecord(this,tag);\n"); - cs.write(" }\n"); + cs.write("using System;\n"); + cs.write("using Org.Apache.Jute;\n"); + cs.write("\n"); + cs.write("namespace " + getCsharpNameSpace() + "\n"); + cs.write("{\n"); + + String className = getCsharpName(); + cs.write("public class " + className + " : IRecord, IComparable \n"); + cs.write("{\n"); + cs.write(" public " + className + "() {\n"); + cs.write(" }\n"); + + cs.write(" public " + className + "(\n"); + int fIdx = 0; + int fLen = mFields.size(); + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + cs.write(jf.genCsharpConstructorParam(jf.getCsharpName())); + cs.write((fLen - 1 == fIdx) ? "" : ",\n"); + } + cs.write(") {\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + cs.write(jf.genCsharpConstructorSet(jf.getCsharpName())); + } + cs.write(" }\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + cs.write(jf.genCsharpGetSet(fIdx)); + cs.write("\n"); + } + cs.write(" public void Serialize(IOutputArchive a_, String tag) {\n"); + cs.write(" a_.StartRecord(this,tag);\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + cs.write(jf.genCsharpWriteMethodName()); + } + cs.write(" a_.EndRecord(this,tag);\n"); + cs.write(" }\n"); - cs.write(" public void Deserialize(IInputArchive a_, String tag) {\n"); - cs.write(" a_.StartRecord(tag);\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - cs.write(jf.genCsharpReadMethodName()); - } - cs.write(" a_.EndRecord(tag);\n"); - cs.write("}\n"); - - cs.write(" public override String ToString() {\n"); - cs.write(" try {\n"); - cs.write(" System.IO.MemoryStream ms = new System.IO.MemoryStream();\n"); - cs.write(" MiscUtil.IO.EndianBinaryWriter writer =\n"); - cs.write(" new MiscUtil.IO.EndianBinaryWriter(MiscUtil.Conversion.EndianBitConverter.Big, ms, System.Text.Encoding.UTF8);\n"); - cs.write(" BinaryOutputArchive a_ = \n"); - cs.write(" new BinaryOutputArchive(writer);\n"); - cs.write(" a_.StartRecord(this,\"\");\n"); - fIdx = 0; - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - cs.write(jf.genCsharpWriteMethodName()); - } - cs.write(" a_.EndRecord(this,\"\");\n"); - cs.write(" ms.Position = 0;\n"); - cs.write(" return System.Text.Encoding.UTF8.GetString(ms.ToArray());\n"); - cs.write(" } catch (Exception ex) {\n"); - cs.write(" Console.WriteLine(ex.StackTrace);\n"); - cs.write(" }\n"); - cs.write(" return \"ERROR\";\n"); - cs.write(" }\n"); - - cs.write(" public void Write(MiscUtil.IO.EndianBinaryWriter writer) {\n"); - cs.write(" BinaryOutputArchive archive = new BinaryOutputArchive(writer);\n"); - cs.write(" Serialize(archive, \"\");\n"); - cs.write(" }\n"); - - cs.write(" public void ReadFields(MiscUtil.IO.EndianBinaryReader reader) {\n"); - cs.write(" BinaryInputArchive archive = new BinaryInputArchive(reader);\n"); - cs.write(" Deserialize(archive, \"\");\n"); - cs.write(" }\n"); - - cs.write(" public int CompareTo (object peer_) {\n"); - boolean unimplemented = false; - for (JField f : mFields) { - if ((f.getType() instanceof JMap) - || (f.getType() instanceof JVector)) - { - unimplemented = true; + cs.write(" public void Deserialize(IInputArchive a_, String tag) {\n"); + cs.write(" a_.StartRecord(tag);\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + cs.write(jf.genCsharpReadMethodName()); } - } - if (unimplemented) { - cs.write(" throw new InvalidOperationException(\"comparing " - + getCsharpName() + " is unimplemented\");\n"); - } else { - cs.write(" if (!(peer_ is "+getCsharpName()+")) {\n"); - cs.write(" throw new InvalidOperationException(\"Comparing different types of records.\");\n"); + cs.write(" a_.EndRecord(tag);\n"); + cs.write("}\n"); + + cs.write(" public override String ToString() {\n"); + cs.write(" try {\n"); + cs.write(" System.IO.MemoryStream ms = new System.IO.MemoryStream();\n"); + cs.write(" MiscUtil.IO.EndianBinaryWriter writer =\n"); + cs.write(" new MiscUtil.IO.EndianBinaryWriter(MiscUtil.Conversion.EndianBitConverter.Big, ms, System.Text.Encoding.UTF8);\n"); + cs.write(" BinaryOutputArchive a_ = \n"); + cs.write(" new BinaryOutputArchive(writer);\n"); + cs.write(" a_.StartRecord(this,\"\");\n"); + fIdx = 0; + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + cs.write(jf.genCsharpWriteMethodName()); + } + cs.write(" a_.EndRecord(this,\"\");\n"); + cs.write(" ms.Position = 0;\n"); + cs.write(" return System.Text.Encoding.UTF8.GetString(ms.ToArray());\n"); + cs.write(" } catch (Exception ex) {\n"); + cs.write(" Console.WriteLine(ex.StackTrace);\n"); + cs.write(" }\n"); + cs.write(" return \"ERROR\";\n"); + cs.write(" }\n"); + + cs.write(" public void Write(MiscUtil.IO.EndianBinaryWriter writer) {\n"); + cs.write(" BinaryOutputArchive archive = new BinaryOutputArchive(writer);\n"); + cs.write(" Serialize(archive, \"\");\n"); + cs.write(" }\n"); + + cs.write(" public void ReadFields(MiscUtil.IO.EndianBinaryReader reader) {\n"); + cs.write(" BinaryInputArchive archive = new BinaryInputArchive(reader);\n"); + cs.write(" Deserialize(archive, \"\");\n"); + cs.write(" }\n"); + + cs.write(" public int CompareTo (object peer_) {\n"); + boolean unimplemented = false; + for (JField f : mFields) { + if ((f.getType() instanceof JMap) + || (f.getType() instanceof JVector)) { + unimplemented = true; + } + } + if (unimplemented) { + cs.write(" throw new InvalidOperationException(\"comparing " + + getCsharpName() + " is unimplemented\");\n"); + } else { + cs.write(" if (!(peer_ is " + getCsharpName() + ")) {\n"); + cs.write(" throw new InvalidOperationException(\"Comparing different types of records.\");\n"); + cs.write(" }\n"); + cs.write(" " + getCsharpName() + " peer = (" + getCsharpName() + ") peer_;\n"); + cs.write(" int ret = 0;\n"); + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + cs.write(jf.genCsharpCompareTo()); + cs.write(" if (ret != 0) return ret;\n"); + } + cs.write(" return ret;\n"); + } + cs.write(" }\n"); + + cs.write(" public override bool Equals(object peer_) {\n"); + cs.write(" if (!(peer_ is " + getCsharpName() + ")) {\n"); + cs.write(" return false;\n"); cs.write(" }\n"); - cs.write(" "+getCsharpName()+" peer = ("+getCsharpName()+") peer_;\n"); - cs.write(" int ret = 0;\n"); + cs.write(" if (peer_ == this) {\n"); + cs.write(" return true;\n"); + cs.write(" }\n"); + cs.write(" bool ret = false;\n"); + cs.write(" " + getCsharpName() + " peer = (" + getCsharpName() + ")peer_;\n"); for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { JField jf = i.next(); - cs.write(jf.genCsharpCompareTo()); - cs.write(" if (ret != 0) return ret;\n"); + cs.write(jf.genCsharpEquals()); + cs.write(" if (!ret) return ret;\n"); } cs.write(" return ret;\n"); - } - cs.write(" }\n"); - - cs.write(" public override bool Equals(object peer_) {\n"); - cs.write(" if (!(peer_ is "+getCsharpName()+")) {\n"); - cs.write(" return false;\n"); - cs.write(" }\n"); - cs.write(" if (peer_ == this) {\n"); - cs.write(" return true;\n"); - cs.write(" }\n"); - cs.write(" bool ret = false;\n"); - cs.write(" " + getCsharpName() + " peer = (" + getCsharpName() + ")peer_;\n"); - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - cs.write(jf.genCsharpEquals()); - cs.write(" if (!ret) return ret;\n"); - } - cs.write(" return ret;\n"); - cs.write(" }\n"); + cs.write(" }\n"); - cs.write(" public override int GetHashCode() {\n"); - cs.write(" int result = 17;\n"); - cs.write(" int ret;\n"); - for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { - JField jf = i.next(); - cs.write(jf.genCsharpHashCode()); - cs.write(" result = 37*result + ret;\n"); - } - cs.write(" return result;\n"); - cs.write(" }\n"); - cs.write(" public static string Signature() {\n"); - cs.write(" return \""+getSignature()+"\";\n"); - cs.write(" }\n"); + cs.write(" public override int GetHashCode() {\n"); + cs.write(" int result = 17;\n"); + cs.write(" int ret;\n"); + for (Iterator i = mFields.iterator(); i.hasNext(); fIdx++) { + JField jf = i.next(); + cs.write(jf.genCsharpHashCode()); + cs.write(" result = 37*result + ret;\n"); + } + cs.write(" return result;\n"); + cs.write(" }\n"); + cs.write(" public static string Signature() {\n"); + cs.write(" return \"" + getSignature() + "\";\n"); + cs.write(" }\n"); - cs.write("}\n"); - cs.write("}\n"); + cs.write("}\n"); + cs.write("}\n"); - cs.close(); + cs.close(); + } } public static String getCsharpFQName(String name) { diff --git a/src/java/main/org/apache/zookeeper/AsyncCallback.java b/src/java/main/org/apache/zookeeper/AsyncCallback.java index 0bb0af4d1c9..c5529d7ccbd 100644 --- a/src/java/main/org/apache/zookeeper/AsyncCallback.java +++ b/src/java/main/org/apache/zookeeper/AsyncCallback.java @@ -19,6 +19,7 @@ import java.util.List; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; @@ -29,11 +30,13 @@ *

    * ZooKeeper provides asynchronous version as equivalent to synchronous APIs. */ +@InterfaceAudience.Public public interface AsyncCallback { /** * This callback is used to retrieve the stat of the node. */ + @InterfaceAudience.Public interface StatCallback extends AsyncCallback { /** * Process the result of the asynchronous call. @@ -68,6 +71,7 @@ interface StatCallback extends AsyncCallback { /** * This callback is used to retrieve the data and stat of the node. */ + @InterfaceAudience.Public interface DataCallback extends AsyncCallback { /** * Process the result of asynchronous calls. @@ -100,6 +104,7 @@ public void processResult(int rc, String path, Object ctx, byte data[], /** * This callback is used to retrieve the ACL and stat of the node. */ + @InterfaceAudience.Public interface ACLCallback extends AsyncCallback { /** * Process the result of the asynchronous call. @@ -132,6 +137,7 @@ public void processResult(int rc, String path, Object ctx, /** * This callback is used to retrieve the children of the node. */ + @InterfaceAudience.Public interface ChildrenCallback extends AsyncCallback { /** * Process the result of the asynchronous call. @@ -162,6 +168,7 @@ public void processResult(int rc, String path, Object ctx, /** * This callback is used to retrieve the children and stat of the node. */ + @InterfaceAudience.Public interface Children2Callback extends AsyncCallback { /** * Process the result of the asynchronous call. @@ -183,6 +190,7 @@ public void processResult(int rc, String path, Object ctx, /** * This callback is used to retrieve the name and stat of the node. */ + @InterfaceAudience.Public interface Create2Callback extends AsyncCallback { /** * Process the result of the asynchronous call. @@ -205,6 +213,7 @@ public void processResult(int rc, String path, Object ctx, /** * This callback is used to retrieve the name of the node. */ + @InterfaceAudience.Public interface StringCallback extends AsyncCallback { /** * Process the result of the asynchronous call. @@ -248,6 +257,7 @@ interface StringCallback extends AsyncCallback { * org.apache.zookeeper.ZooKeeper#sync(String, * org.apache.zookeeper.AsyncCallback.VoidCallback, Object)}. */ + @InterfaceAudience.Public interface VoidCallback extends AsyncCallback { /** * Process the result of the asynchronous call. @@ -289,6 +299,7 @@ interface VoidCallback extends AsyncCallback { * a single multi call. * See {@link org.apache.zookeeper.ZooKeeper#multi} for more information. */ + @InterfaceAudience.Public interface MultiCallback extends AsyncCallback { /** * Process the result of the asynchronous call. diff --git a/src/java/main/org/apache/zookeeper/ClientCnxn.java b/src/java/main/org/apache/zookeeper/ClientCnxn.java index b4ece07735d..1a7a7833736 100644 --- a/src/java/main/org/apache/zookeeper/ClientCnxn.java +++ b/src/java/main/org/apache/zookeeper/ClientCnxn.java @@ -22,13 +22,15 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.lang.Thread.UncaughtExceptionHandler; import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; +import java.net.SocketException; import java.nio.ByteBuffer; import java.util.HashSet; +import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -36,6 +38,7 @@ import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import javax.security.auth.login.LoginException; @@ -61,8 +64,10 @@ import org.apache.zookeeper.ZooDefs.OpCode; import org.apache.zookeeper.ZooKeeper.States; import org.apache.zookeeper.ZooKeeper.WatchRegistration; +import org.apache.zookeeper.client.ZKClientConfig; import org.apache.zookeeper.client.HostProvider; import org.apache.zookeeper.client.ZooKeeperSaslClient; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.proto.AuthPacket; import org.apache.zookeeper.proto.ConnectRequest; import org.apache.zookeeper.proto.Create2Response; @@ -84,6 +89,7 @@ import org.apache.zookeeper.server.ZooTrace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; /** * This class manages the socket i/o for the client. ClientCnxn maintains a list @@ -94,24 +100,15 @@ public class ClientCnxn { private static final Logger LOG = LoggerFactory.getLogger(ClientCnxn.class); - private static final String ZK_SASL_CLIENT_USERNAME = - "zookeeper.sasl.client.username"; - - /** This controls whether automatic watch resetting is enabled. - * Clients automatically reset watches during session reconnect, this - * option allows the client to turn off this behavior by setting - * the environment variable "zookeeper.disableAutoWatchReset" to "true" */ - private static boolean disableAutoWatchReset; - static { - // this var should not be public, but otw there is no easy way - // to test - disableAutoWatchReset = - Boolean.getBoolean("zookeeper.disableAutoWatchReset"); - if (LOG.isDebugEnabled()) { - LOG.debug("zookeeper.disableAutoWatchReset is " - + disableAutoWatchReset); - } - } + /* ZOOKEEPER-706: If a session has a large number of watches set then + * attempting to re-establish those watches after a connection loss may + * fail due to the SetWatches request exceeding the server's configured + * jute.maxBuffer value. To avoid this we instead split the watch + * re-establishement across multiple SetWatches calls. This constant + * controls the size of each call. It is set to 128kB to be conservative + * with respect to the server's 1MB default for jute.maxBuffer. + */ + private static final int SET_WATCHES_MAX_LENGTH = 128 * 1024; static class AuthData { AuthData(String scheme, byte data[]) { @@ -134,7 +131,7 @@ static class AuthData { /** * These are the packets that need to be sent. */ - private final LinkedList outgoingQueue = new LinkedList(); + private final LinkedBlockingDeque outgoingQueue = new LinkedBlockingDeque(); private int connectTimeout; @@ -203,6 +200,8 @@ static class AuthData { public ZooKeeperSaslClient zooKeeperSaslClient; + private final ZKClientConfig clientConfig; + public long getSessionId() { return sessionId; } @@ -395,23 +394,9 @@ public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeo sendThread = new SendThread(clientCnxnSocket); eventThread = new EventThread(); - + this.clientConfig=zooKeeper.getClientConfig(); } - /** - * tests use this to check on reset of watches - * @return if the auto reset of watches are disabled - */ - public static boolean getDisableAutoResetWatch() { - return disableAutoWatchReset; - } - /** - * tests use this to set the auto reset - * @param b the value to set disable watches to - */ - public static void setDisableAutoResetWatch(boolean b) { - disableAutoWatchReset = b; - } public void start() { sendThread.start(); eventThread.start(); @@ -526,7 +511,8 @@ public void run() { LOG.error("Event thread exiting due to interruption", e); } - LOG.info("EventThread shut down"); + LOG.info("EventThread shut down for session: 0x{}", + Long.toHexString(getSessionId())); } private void processEvent(Object event) { @@ -708,10 +694,8 @@ private void finishPacket(Packet p) { } } } catch (KeeperException.NoWatcherException nwe) { - LOG.error("Failed to find watcher!", nwe); p.replyHeader.setErr(nwe.code().intValue()); } catch (KeeperException ke) { - LOG.error("Exception when removing watcher", ke); p.replyHeader.setErr(ke.code().intValue()); } } @@ -803,9 +787,6 @@ public RWServerFoundException(String msg) { } } - public static final int packetLen = Integer.getInteger("jute.maxbuffer", - 4096 * 1024); - /** * This class services the outgoing request queue and generates the heart * beats. It also spawns the ReadThread. @@ -883,7 +864,7 @@ else if (serverPath.length() > chrootPath.length()) // If SASL authentication is currently in progress, construct and // send a response packet immediately, rather than queuing a // response as with other packets. - if (clientTunneledAuthenticationInProgress()) { + if (tunnelAuthInProgress()) { GetSASLRequest request = new GetSASLRequest(); request.deserialize(bbia,"token"); zooKeeperSaslClient.respondToServer(request.getToken(), @@ -959,46 +940,77 @@ ClientCnxnSocket getClientCnxnSocket() { return clientCnxnSocket; } + /** + * Setup session, previous watches, authentication. + */ void primeConnection() throws IOException { - LOG.info("Socket connection established to " - + clientCnxnSocket.getRemoteSocketAddress() - + ", initiating session"); + LOG.info("Socket connection established, initiating session, client: {}, server: {}", + clientCnxnSocket.getLocalSocketAddress(), + clientCnxnSocket.getRemoteSocketAddress()); isFirstConnect = false; long sessId = (seenRwServerBefore) ? sessionId : 0; ConnectRequest conReq = new ConnectRequest(0, lastZxid, sessionTimeout, sessId, sessionPasswd); - synchronized (outgoingQueue) { - // We add backwards since we are pushing into the front - // Only send if there's a pending watch - // TODO: here we have the only remaining use of zooKeeper in - // this class. It's to be eliminated! - if (!disableAutoWatchReset) { - List dataWatches = zooKeeper.getDataWatches(); - List existWatches = zooKeeper.getExistWatches(); - List childWatches = zooKeeper.getChildWatches(); - if (!dataWatches.isEmpty() - || !existWatches.isEmpty() || !childWatches.isEmpty()) { - SetWatches sw = new SetWatches(lastZxid, - prependChroot(dataWatches), - prependChroot(existWatches), - prependChroot(childWatches)); - RequestHeader h = new RequestHeader(); - h.setType(ZooDefs.OpCode.setWatches); - h.setXid(-8); - Packet packet = new Packet(h, new ReplyHeader(), sw, null, null); + // We add backwards since we are pushing into the front + // Only send if there's a pending watch + // TODO: here we have the only remaining use of zooKeeper in + // this class. It's to be eliminated! + if (!clientConfig.getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET)) { + List dataWatches = zooKeeper.getDataWatches(); + List existWatches = zooKeeper.getExistWatches(); + List childWatches = zooKeeper.getChildWatches(); + if (!dataWatches.isEmpty() + || !existWatches.isEmpty() || !childWatches.isEmpty()) { + Iterator dataWatchesIter = prependChroot(dataWatches).iterator(); + Iterator existWatchesIter = prependChroot(existWatches).iterator(); + Iterator childWatchesIter = prependChroot(childWatches).iterator(); + long setWatchesLastZxid = lastZxid; + + while (dataWatchesIter.hasNext() + || existWatchesIter.hasNext() || childWatchesIter.hasNext()) { + List dataWatchesBatch = new ArrayList(); + List existWatchesBatch = new ArrayList(); + List childWatchesBatch = new ArrayList(); + int batchLength = 0; + + // Note, we may exceed our max length by a bit when we add the last + // watch in the batch. This isn't ideal, but it makes the code simpler. + while (batchLength < SET_WATCHES_MAX_LENGTH) { + final String watch; + if (dataWatchesIter.hasNext()) { + watch = dataWatchesIter.next(); + dataWatchesBatch.add(watch); + } else if (existWatchesIter.hasNext()) { + watch = existWatchesIter.next(); + existWatchesBatch.add(watch); + } else if (childWatchesIter.hasNext()) { + watch = childWatchesIter.next(); + childWatchesBatch.add(watch); + } else { + break; + } + batchLength += watch.length(); + } + + SetWatches sw = new SetWatches(setWatchesLastZxid, + dataWatchesBatch, + existWatchesBatch, + childWatchesBatch); + RequestHeader header = new RequestHeader(-8, OpCode.setWatches); + Packet packet = new Packet(header, new ReplyHeader(), sw, null, null); outgoingQueue.addFirst(packet); } } + } - for (AuthData id : authInfo) { - outgoingQueue.addFirst(new Packet(new RequestHeader(-4, - OpCode.auth), null, new AuthPacket(0, id.scheme, - id.data), null, null)); - } - outgoingQueue.addFirst(new Packet(null, null, conReq, - null, null, readOnly)); + for (AuthData id : authInfo) { + outgoingQueue.addFirst(new Packet(new RequestHeader(-4, + OpCode.auth), null, new AuthPacket(0, id.scheme, + id.data), null, null)); } - clientCnxnSocket.enableReadWriteOnly(); + outgoingQueue.addFirst(new Packet(null, null, conReq, + null, null, readOnly)); + clientCnxnSocket.connectionPrimed(); if (LOG.isDebugEnabled()) { LOG.debug("Session establishment request sent on " + clientCnxnSocket.getRemoteSocketAddress()); @@ -1040,7 +1052,9 @@ private void sendPing() { // throws a LoginException: see startConnect() below. private boolean saslLoginFailed = false; - private void startConnect() throws IOException { + private void startConnect(InetSocketAddress addr) throws IOException { + // initializing it for new connection + saslLoginFailed = false; if(!isFirstConnect){ try { Thread.sleep(r.nextInt(1000)); @@ -1050,23 +1064,15 @@ private void startConnect() throws IOException { } state = States.CONNECTING; - InetSocketAddress addr; - if (rwServerAddress != null) { - addr = rwServerAddress; - rwServerAddress = null; - } else { - addr = hostProvider.next(1000); - } - - setName(getName().replaceAll("\\(.*\\)", - "(" + addr.getHostName() + ":" + addr.getPort() + ")")); - if (ZooKeeperSaslClient.isEnabled()) { + String hostPort = addr.getHostString() + ":" + addr.getPort(); + MDC.put("myid", hostPort); + setName(getName().replaceAll("\\(.*\\)", "(" + hostPort + ")")); + if (clientConfig.isSaslClientEnabled()) { try { - String principalUserName = System.getProperty( - ZK_SASL_CLIENT_USERNAME, "zookeeper"); - zooKeeperSaslClient = - new ZooKeeperSaslClient( - principalUserName+"/"+addr.getHostName()); + if (zooKeeperSaslClient != null) { + zooKeeperSaslClient.shutdown(); + } + zooKeeperSaslClient = new ZooKeeperSaslClient(getServerPrincipal(addr), clientConfig); } catch (LoginException e) { // An authentication error occurred when the SASL client tried to initialize: // for Kerberos this means that the client failed to authenticate with the KDC. @@ -1085,6 +1091,13 @@ private void startConnect() throws IOException { clientCnxnSocket.connect(addr); } + private String getServerPrincipal(InetSocketAddress addr) { + String principalUserName = clientConfig.getProperty(ZKClientConfig.ZK_SASL_CLIENT_USERNAME, + ZKClientConfig.ZK_SASL_CLIENT_USERNAME_DEFAULT); + String serverPrincipal = principalUserName + "/" + addr.getHostString(); + return serverPrincipal; + } + private void logStartConnect(InetSocketAddress addr) { String msg = "Opening socket connection to server " + addr; if (zooKeeperSaslClient != null) { @@ -1095,15 +1108,15 @@ private void logStartConnect(InetSocketAddress addr) { private static final String RETRY_CONN_MSG = ", closing socket connection and attempting reconnect"; - @Override public void run() { - clientCnxnSocket.introduce(this,sessionId); + clientCnxnSocket.introduce(this, sessionId, outgoingQueue); clientCnxnSocket.updateNow(); clientCnxnSocket.updateLastSendAndHeard(); int to; - long lastPingRwServer = System.currentTimeMillis(); + long lastPingRwServer = Time.currentElapsedTime(); final int MAX_SEND_PING_INTERVAL = 10000; //10 seconds + InetSocketAddress serverAddress = null; while (state.isAlive()) { try { if (!clientCnxnSocket.isConnected()) { @@ -1111,7 +1124,13 @@ public void run() { if (closing) { break; } - startConnect(); + if (rwServerAddress != null) { + serverAddress = rwServerAddress; + rwServerAddress = null; + } else { + serverAddress = hostProvider.next(1000); + } + startConnect(serverAddress); clientCnxnSocket.updateLastSendAndHeard(); } @@ -1153,11 +1172,14 @@ public void run() { } if (to <= 0) { - throw new SessionTimeoutException( - "Client session timed out, have not heard from server in " - + clientCnxnSocket.getIdleRecv() + "ms" - + " for sessionid 0x" - + Long.toHexString(sessionId)); + String warnInfo; + warnInfo = "Client session timed out, have not heard from server in " + + clientCnxnSocket.getIdleRecv() + + "ms" + + " for sessionid 0x" + + Long.toHexString(sessionId); + LOG.warn(warnInfo); + throw new SessionTimeoutException(warnInfo); } if (state.isConnected()) { //1000(1 second) is to prevent race condition missing to send the second ping @@ -1177,7 +1199,7 @@ public void run() { // If we are in read-only mode, seek for read/write server if (state == States.CONNECTEDREADONLY) { - long now = System.currentTimeMillis(); + long now = Time.currentElapsedTime(); int idlePingRwServer = (int) (now - lastPingRwServer); if (idlePingRwServer >= pingRwTimeout) { lastPingRwServer = now; @@ -1189,7 +1211,7 @@ public void run() { to = Math.min(to, pingRwTimeout - idlePingRwServer); } - clientCnxnSocket.doTransport(to, pendingQueue, outgoingQueue, ClientCnxn.this); + clientCnxnSocket.doTransport(to, pendingQueue, ClientCnxn.this); } catch (Throwable e) { if (closing) { if (LOG.isDebugEnabled()) { @@ -1209,15 +1231,17 @@ public void run() { LOG.info(e.getMessage() + RETRY_CONN_MSG); } else if (e instanceof RWServerFoundException) { LOG.info(e.getMessage()); + } else if (e instanceof SocketException) { + LOG.info("Socket error occurred: {}: {}", serverAddress, e.getMessage()); } else { - LOG.warn( - "Session 0x" - + Long.toHexString(getSessionId()) - + " for server " - + clientCnxnSocket.getRemoteSocketAddress() - + ", unexpected error" - + RETRY_CONN_MSG, e); + LOG.warn("Session 0x{} for server {}, unexpected error{}", + Long.toHexString(getSessionId()), + serverAddress, + RETRY_CONN_MSG, + e); } + // At this point, there might still be new packets appended to outgoingQueue. + // they will be handled in next connection or cleared up if closed. cleanup(); if (state.isAlive()) { eventThread.queueEvent(new WatchedEvent( @@ -1230,14 +1254,19 @@ public void run() { } } } - cleanup(); + synchronized (state) { + // When it comes to this point, it guarantees that later queued + // packet to outgoingQueue will be notified of death. + cleanup(); + } clientCnxnSocket.close(); if (state.isAlive()) { eventThread.queueEvent(new WatchedEvent(Event.EventType.None, Event.KeeperState.Disconnected, null)); } ZooTrace.logTraceMessage(LOG, ZooTrace.getTextTraceLevel(), - "SendThread exitedloop."); + "SendThread exited loop for session: 0x" + + Long.toHexString(getSessionId())); } private void pingRwServer() throws RWServerFoundException { @@ -1249,7 +1278,7 @@ private void pingRwServer() throws RWServerFoundException { Socket sock = null; BufferedReader br = null; try { - sock = new Socket(addr.getHostName(), addr.getPort()); + sock = new Socket(addr.getHostString(), addr.getPort()); sock.setSoLinger(false, -1); sock.setSoTimeout(1000); sock.setTcpNoDelay(true); @@ -1288,7 +1317,7 @@ private void pingRwServer() throws RWServerFoundException { // connection attempt rwServerAddress = addr; throw new RWServerFoundException("Majority server found at " - + addr.getHostName() + ":" + addr.getPort()); + + addr.getHostString() + ":" + addr.getPort()); } } @@ -1300,11 +1329,14 @@ private void cleanup() { } pendingQueue.clear(); } - synchronized (outgoingQueue) { - for (Packet p : outgoingQueue) { - conLossPacket(p); - } - outgoingQueue.clear(); + // We can't call outgoingQueue.clear() here because + // between iterating and clear up there might be new + // packets added in queuePacket(). + Iterator iter = outgoingQueue.iterator(); + while (iter.hasNext()) { + Packet p = iter.next(); + conLossPacket(p); + iter.remove(); } } @@ -1328,9 +1360,12 @@ void onConnected(int _negotiatedSessionTimeout, long _sessionId, Watcher.Event.EventType.None, Watcher.Event.KeeperState.Expired, null)); eventThread.queueEventOfDeath(); - throw new SessionExpiredException( - "Unable to reconnect to ZooKeeper service, session 0x" - + Long.toHexString(sessionId) + " has expired"); + + String warnInfo; + warnInfo = "Unable to reconnect to ZooKeeper service, session 0x" + + Long.toHexString(sessionId) + " has expired"; + LOG.warn(warnInfo); + throw new SessionExpiredException(warnInfo); } if (!readOnly && isRO) { LOG.error("Read/write client got connected to read-only server"); @@ -1357,16 +1392,16 @@ void onConnected(int _negotiatedSessionTimeout, long _sessionId, void close() { state = States.CLOSED; - clientCnxnSocket.wakeupCnxn(); + clientCnxnSocket.onClosing(); } void testableCloseSocket() throws IOException { clientCnxnSocket.testableCloseSocket(); } - public boolean clientTunneledAuthenticationInProgress() { + public boolean tunnelAuthInProgress() { // 1. SASL client is disabled. - if (!ZooKeeperSaslClient.isEnabled()) { + if (!clientConfig.isSaslClientEnabled()) { return false; } @@ -1404,6 +1439,9 @@ public void disconnect() { sendThread.close(); eventThread.queueEventOfDeath(); + if (zooKeeperSaslClient != null) { + zooKeeperSaslClient.shutdown(); + } } /** @@ -1464,8 +1502,8 @@ public ReplyHeader submitRequest(RequestHeader h, Record request, return r; } - public void enableWrite() { - sendThread.getClientCnxnSocket().enableWrite(); + public void saslCompleted() { + sendThread.getClientCnxnSocket().saslCompleted(); } public void sendPacket(Record request, Record response, AsyncCallback cb, int opCode) @@ -1485,14 +1523,14 @@ public void sendPacket(Record request, Record response, AsyncCallback cb, int op sendThread.sendPacket(p); } - Packet queuePacket(RequestHeader h, ReplyHeader r, Record request, + public Packet queuePacket(RequestHeader h, ReplyHeader r, Record request, Record response, AsyncCallback cb, String clientPath, String serverPath, Object ctx, WatchRegistration watchRegistration) { return queuePacket(h, r, request, response, cb, clientPath, serverPath, ctx, watchRegistration, null); } - Packet queuePacket(RequestHeader h, ReplyHeader r, Record request, + public Packet queuePacket(RequestHeader h, ReplyHeader r, Record request, Record response, AsyncCallback cb, String clientPath, String serverPath, Object ctx, WatchRegistration watchRegistration, WatchDeregistration watchDeregistration) { @@ -1501,13 +1539,17 @@ Packet queuePacket(RequestHeader h, ReplyHeader r, Record request, // Note that we do not generate the Xid for the packet yet. It is // generated later at send-time, by an implementation of ClientCnxnSocket::doIO(), // where the packet is actually sent. - synchronized (outgoingQueue) { - packet = new Packet(h, r, request, response, watchRegistration); - packet.cb = cb; - packet.ctx = ctx; - packet.clientPath = clientPath; - packet.serverPath = serverPath; - packet.watchDeregistration = watchDeregistration; + packet = new Packet(h, r, request, response, watchRegistration); + packet.cb = cb; + packet.ctx = ctx; + packet.clientPath = clientPath; + packet.serverPath = serverPath; + packet.watchDeregistration = watchDeregistration; + // The synchronized block here is for two purpose: + // 1. synchronize with the final cleanup() in SendThread.run() to avoid race + // 2. synchronized against each packet. So if a closeSession packet is added, + // later packet will be notified. + synchronized (state) { if (!state.isAlive() || closing) { conLossPacket(packet); } else { @@ -1519,7 +1561,7 @@ Packet queuePacket(RequestHeader h, ReplyHeader r, Record request, outgoingQueue.add(packet); } } - sendThread.getClientCnxnSocket().wakeupCnxn(); + sendThread.getClientCnxnSocket().packetAdded(); return packet; } diff --git a/src/java/main/org/apache/zookeeper/ClientCnxnSocket.java b/src/java/main/org/apache/zookeeper/ClientCnxnSocket.java index 5ca0ba77bcc..0e5316d4eed 100644 --- a/src/java/main/org/apache/zookeeper/ClientCnxnSocket.java +++ b/src/java/main/org/apache/zookeeper/ClientCnxnSocket.java @@ -22,11 +22,15 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; -import java.util.LinkedList; +import java.text.MessageFormat; import java.util.List; +import java.util.concurrent.LinkedBlockingDeque; import org.apache.jute.BinaryInputArchive; import org.apache.zookeeper.ClientCnxn.Packet; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.proto.ConnectResponse; import org.apache.zookeeper.server.ByteBufferInputStream; import org.slf4j.Logger; @@ -61,6 +65,9 @@ abstract class ClientCnxnSocket { protected long lastSend; protected long now; protected ClientCnxn.SendThread sendThread; + protected LinkedBlockingDeque outgoingQueue; + protected ZKClientConfig clientConfig; + private int packetLen = ZKClientConfig.CLIENT_MAX_PACKET_LENGTH_DEFAULT; /** * The sessionId is only available here for Log and Exception messages. @@ -68,13 +75,15 @@ abstract class ClientCnxnSocket { */ protected long sessionId; - void introduce(ClientCnxn.SendThread sendThread, long sessionId) { + void introduce(ClientCnxn.SendThread sendThread, long sessionId, + LinkedBlockingDeque outgoingQueue) { this.sendThread = sendThread; this.sessionId = sessionId; + this.outgoingQueue = outgoingQueue; } void updateNow() { - now = System.currentTimeMillis(); + now = Time.currentElapsedTime(); } int getIdleRecv() { @@ -108,7 +117,7 @@ void updateLastSendAndHeard() { protected void readLength() throws IOException { int len = incomingBuffer.getInt(); - if (len < 0 || len >= ClientCnxn.packetLen) { + if (len < 0 || len >= packetLen) { throw new IOException("Packet len" + len + " is out of range!"); } incomingBuffer = ByteBuffer.allocate(len); @@ -148,27 +157,92 @@ void readConnectResult() throws IOException { abstract void connect(InetSocketAddress addr) throws IOException; + /** + * Returns the address to which the socket is connected. + */ abstract SocketAddress getRemoteSocketAddress(); + /** + * Returns the address to which the socket is bound. + */ abstract SocketAddress getLocalSocketAddress(); + /** + * Clean up resources for a fresh new socket. + * It's called before reconnect or close. + */ abstract void cleanup(); - abstract void close(); - - abstract void wakeupCnxn(); + /** + * new packets are added to outgoingQueue. + */ + abstract void packetAdded(); - abstract void enableWrite(); + /** + * connState is marked CLOSED and notify ClientCnxnSocket to react. + */ + abstract void onClosing(); - abstract void disableWrite(); + /** + * Sasl completes. Allows non-priming packgets to be sent. + * Note that this method will only be called if Sasl starts and completes. + */ + abstract void saslCompleted(); - abstract void enableReadWriteOnly(); + /** + * being called after ClientCnxn finish PrimeConnection + */ + abstract void connectionPrimed(); + /** + * Do transportation work: + * - read packets into incomingBuffer. + * - write outgoing queue packets. + * - update relevant timestamp. + * + * @param waitTimeOut timeout in blocking wait. Unit in MilliSecond. + * @param pendingQueue These are the packets that have been sent and + * are waiting for a response. + * @param cnxn + * @throws IOException + * @throws InterruptedException + */ abstract void doTransport(int waitTimeOut, List pendingQueue, - LinkedList outgoingQueue, ClientCnxn cnxn) + ClientCnxn cnxn) throws IOException, InterruptedException; + /** + * Close the socket. + */ abstract void testableCloseSocket() throws IOException; + /** + * Close this client. + */ + abstract void close(); + + /** + * Send Sasl packets directly. + * The Sasl process will send the first (requestHeader == null) packet, + * and then block the doTransport write, + * finally unblock it when finished. + */ abstract void sendPacket(Packet p) throws IOException; + + protected void initProperties() throws IOException { + try { + packetLen = clientConfig.getInt(ZKConfig.JUTE_MAXBUFFER, + ZKClientConfig.CLIENT_MAX_PACKET_LENGTH_DEFAULT); + LOG.info("{} value is {} Bytes", ZKConfig.JUTE_MAXBUFFER, + packetLen); + } catch (NumberFormatException e) { + String msg = MessageFormat.format( + "Configured value {0} for property {1} can not be parsed to int", + clientConfig.getProperty(ZKConfig.JUTE_MAXBUFFER), + ZKConfig.JUTE_MAXBUFFER); + LOG.error(msg); + throw new IOException(msg); + } + } + } diff --git a/src/java/main/org/apache/zookeeper/ClientCnxnSocketNIO.java b/src/java/main/org/apache/zookeeper/ClientCnxnSocketNIO.java index adb27ee843c..f17a8197150 100644 --- a/src/java/main/org/apache/zookeeper/ClientCnxnSocketNIO.java +++ b/src/java/main/org/apache/zookeeper/ClientCnxnSocketNIO.java @@ -26,14 +26,15 @@ import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; -import java.util.LinkedList; +import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.Set; +import java.util.concurrent.LinkedBlockingDeque; import org.apache.zookeeper.ClientCnxn.EndOfStreamException; import org.apache.zookeeper.ClientCnxn.Packet; import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.client.ZKClientConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,8 +50,9 @@ public class ClientCnxnSocketNIO extends ClientCnxnSocket { private SocketAddress remoteSocketAddress; - ClientCnxnSocketNIO() throws IOException { - super(); + ClientCnxnSocketNIO(ZKClientConfig clientConfig) throws IOException { + this.clientConfig = clientConfig; + initProperties(); } @Override @@ -63,7 +65,7 @@ boolean isConnected() { * @throws InterruptedException * @throws IOException */ - void doIO(List pendingQueue, LinkedList outgoingQueue, ClientCnxn cnxn) + void doIO(List pendingQueue, ClientCnxn cnxn) throws InterruptedException, IOException { SocketChannel sock = (SocketChannel) sockKey.channel(); if (sock == null) { @@ -86,7 +88,7 @@ void doIO(List pendingQueue, LinkedList outgoingQueue, ClientCnx readConnectResult(); enableRead(); if (findSendablePacket(outgoingQueue, - cnxn.sendThread.clientTunneledAuthenticationInProgress()) != null) { + sendThread.tunnelAuthInProgress()) != null) { // Since SASL authentication has completed (if client is configured to do so), // outgoing packets waiting in the outgoingQueue can now be sent. enableWrite(); @@ -104,96 +106,87 @@ void doIO(List pendingQueue, LinkedList outgoingQueue, ClientCnx } } if (sockKey.isWritable()) { - synchronized(outgoingQueue) { - Packet p = findSendablePacket(outgoingQueue, - cnxn.sendThread.clientTunneledAuthenticationInProgress()); + Packet p = findSendablePacket(outgoingQueue, + sendThread.tunnelAuthInProgress()); - if (p != null) { - updateLastSend(); - // If we already started writing p, p.bb will already exist - if (p.bb == null) { - if ((p.requestHeader != null) && - (p.requestHeader.getType() != OpCode.ping) && - (p.requestHeader.getType() != OpCode.auth)) { - p.requestHeader.setXid(cnxn.getXid()); - } - p.createBB(); + if (p != null) { + updateLastSend(); + // If we already started writing p, p.bb will already exist + if (p.bb == null) { + if ((p.requestHeader != null) && + (p.requestHeader.getType() != OpCode.ping) && + (p.requestHeader.getType() != OpCode.auth)) { + p.requestHeader.setXid(cnxn.getXid()); } - sock.write(p.bb); - if (!p.bb.hasRemaining()) { - sentCount++; - outgoingQueue.removeFirstOccurrence(p); - if (p.requestHeader != null - && p.requestHeader.getType() != OpCode.ping - && p.requestHeader.getType() != OpCode.auth) { - synchronized (pendingQueue) { - pendingQueue.add(p); - } + p.createBB(); + } + sock.write(p.bb); + if (!p.bb.hasRemaining()) { + sentCount++; + outgoingQueue.removeFirstOccurrence(p); + if (p.requestHeader != null + && p.requestHeader.getType() != OpCode.ping + && p.requestHeader.getType() != OpCode.auth) { + synchronized (pendingQueue) { + pendingQueue.add(p); } } } - if (outgoingQueue.isEmpty()) { - // No more packets to send: turn off write interest flag. - // Will be turned on later by a later call to enableWrite(), - // from within ZooKeeperSaslClient (if client is configured - // to attempt SASL authentication), or in either doIO() or - // in doTransport() if not. - disableWrite(); - } else if (!initialized && p != null && !p.bb.hasRemaining()) { - // On initial connection, write the complete connect request - // packet, but then disable further writes until after - // receiving a successful connection response. If the - // session is expired, then the server sends the expiration - // response and immediately closes its end of the socket. If - // the client is simultaneously writing on its end, then the - // TCP stack may choose to abort with RST, in which case the - // client would never receive the session expired event. See - // http://docs.oracle.com/javase/6/docs/technotes/guides/net/articles/connection_release.html - disableWrite(); - } else { - // Just in case - enableWrite(); - } } - } - } - - private Packet findSendablePacket(LinkedList outgoingQueue, - boolean clientTunneledAuthenticationInProgress) { - synchronized (outgoingQueue) { if (outgoingQueue.isEmpty()) { - return null; - } - if (outgoingQueue.getFirst().bb != null // If we've already starting sending the first packet, we better finish - || !clientTunneledAuthenticationInProgress) { - return outgoingQueue.getFirst(); + // No more packets to send: turn off write interest flag. + // Will be turned on later by a later call to enableWrite(), + // from within ZooKeeperSaslClient (if client is configured + // to attempt SASL authentication), or in either doIO() or + // in doTransport() if not. + disableWrite(); + } else if (!initialized && p != null && !p.bb.hasRemaining()) { + // On initial connection, write the complete connect request + // packet, but then disable further writes until after + // receiving a successful connection response. If the + // session is expired, then the server sends the expiration + // response and immediately closes its end of the socket. If + // the client is simultaneously writing on its end, then the + // TCP stack may choose to abort with RST, in which case the + // client would never receive the session expired event. See + // http://docs.oracle.com/javase/6/docs/technotes/guides/net/articles/connection_release.html + disableWrite(); + } else { + // Just in case + enableWrite(); } + } + } - // Since client's authentication with server is in progress, - // send only the null-header packet queued by primeConnection(). - // This packet must be sent so that the SASL authentication process - // can proceed, but all other packets should wait until - // SASL authentication completes. - ListIterator iter = outgoingQueue.listIterator(); - while (iter.hasNext()) { - Packet p = iter.next(); - if (p.requestHeader == null) { - // We've found the priming-packet. Move it to the beginning of the queue. - iter.remove(); - outgoingQueue.add(0, p); - return p; - } else { - // Non-priming packet: defer it until later, leaving it in the queue - // until authentication completes. - if (LOG.isDebugEnabled()) { - LOG.debug("deferring non-priming packet: " + p + - "until SASL authentication completes."); - } - } - } - // no sendable packet found. + private Packet findSendablePacket(LinkedBlockingDeque outgoingQueue, + boolean tunneledAuthInProgres) { + if (outgoingQueue.isEmpty()) { return null; } + // If we've already starting sending the first packet, we better finish + if (outgoingQueue.getFirst().bb != null || !tunneledAuthInProgres) { + return outgoingQueue.getFirst(); + } + // Since client's authentication with server is in progress, + // send only the null-header packet queued by primeConnection(). + // This packet must be sent so that the SASL authentication process + // can proceed, but all other packets should wait until + // SASL authentication completes. + Iterator iter = outgoingQueue.iterator(); + while (iter.hasNext()) { + Packet p = iter.next(); + if (p.requestHeader == null) { + // We've found the priming-packet. Move it to the beginning of the queue. + iter.remove(); + outgoingQueue.addFirst(p); + return p; + } else { + // Non-priming packet: defer it until later, leaving it in the queue + // until authentication completes. + LOG.debug("deferring non-priming packet {} until SASL authentation completes.", p); + } + } + return null; } @Override @@ -333,13 +326,21 @@ private void updateSocketAddresses() { } @Override - synchronized void wakeupCnxn() { + void packetAdded() { + wakeupCnxn(); + } + + @Override + void onClosing() { + wakeupCnxn(); + } + + private synchronized void wakeupCnxn() { selector.wakeup(); } @Override - void doTransport(int waitTimeOut, List pendingQueue, LinkedList outgoingQueue, - ClientCnxn cnxn) + void doTransport(int waitTimeOut, List pendingQueue, ClientCnxn cnxn) throws IOException, InterruptedException { selector.select(waitTimeOut); Set selected; @@ -359,15 +360,13 @@ void doTransport(int waitTimeOut, List pendingQueue, LinkedList sendThread.primeConnection(); } } else if ((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) != 0) { - doIO(pendingQueue, outgoingQueue, cnxn); + doIO(pendingQueue, cnxn); } } if (sendThread.getZkState().isConnected()) { - synchronized(outgoingQueue) { - if (findSendablePacket(outgoingQueue, - cnxn.sendThread.clientTunneledAuthenticationInProgress()) != null) { - enableWrite(); - } + if (findSendablePacket(outgoingQueue, + sendThread.tunnelAuthInProgress()) != null) { + enableWrite(); } } selected.clear(); @@ -386,6 +385,10 @@ void testableCloseSocket() throws IOException { } @Override + void saslCompleted() { + enableWrite(); + } + synchronized void enableWrite() { int i = sockKey.interestOps(); if ((i & SelectionKey.OP_WRITE) == 0) { @@ -393,8 +396,7 @@ synchronized void enableWrite() { } } - @Override - public synchronized void disableWrite() { + private synchronized void disableWrite() { int i = sockKey.interestOps(); if ((i & SelectionKey.OP_WRITE) != 0) { sockKey.interestOps(i & (~SelectionKey.OP_WRITE)); @@ -409,7 +411,7 @@ synchronized private void enableRead() { } @Override - synchronized void enableReadWriteOnly() { + void connectionPrimed() { sockKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } @@ -427,6 +429,4 @@ void sendPacket(Packet p) throws IOException { ByteBuffer pbb = p.bb; sock.write(pbb); } - - } diff --git a/src/java/main/org/apache/zookeeper/ClientCnxnSocketNetty.java b/src/java/main/org/apache/zookeeper/ClientCnxnSocketNetty.java new file mode 100755 index 00000000000..ec789cb09b8 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/ClientCnxnSocketNetty.java @@ -0,0 +1,451 @@ +/** + * 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.ClientCnxn.EndOfStreamException; +import org.apache.zookeeper.ClientCnxn.Packet; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.common.X509Util; +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import org.jboss.netty.handler.ssl.SslHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static org.apache.zookeeper.common.X509Exception.SSLContextException; + +/** + * ClientCnxnSocketNetty implements ClientCnxnSocket abstract methods. + * It's responsible for connecting to server, reading/writing network traffic and + * being a layer between network data and higher level packets. + */ +public class ClientCnxnSocketNetty extends ClientCnxnSocket { + private static final Logger LOG = LoggerFactory.getLogger(ClientCnxnSocketNetty.class); + + ChannelFactory channelFactory = new NioClientSocketChannelFactory( + Executors.newCachedThreadPool(), Executors.newCachedThreadPool()); + Channel channel; + CountDownLatch firstConnect; + ChannelFuture connectFuture; + Lock connectLock = new ReentrantLock(); + AtomicBoolean disconnected = new AtomicBoolean(); + AtomicBoolean needSasl = new AtomicBoolean(); + Semaphore waitSasl = new Semaphore(0); + + ClientCnxnSocketNetty(ZKClientConfig clientConfig) throws IOException { + this.clientConfig = clientConfig; + initProperties(); + } + + /** + * lifecycles diagram: + *

    + * loop: + * - try: + * - - !isConnected() + * - - - connect() + * - - doTransport() + * - catch: + * - - cleanup() + * close() + *

    + * Other non-lifecycle methods are in jeopardy getting a null channel + * when calling in concurrency. We must handle it. + */ + + @Override + boolean isConnected() { + // Assuming that isConnected() is only used to initiate connection, + // not used by some other connection status judgement. + return channel != null; + } + + @Override + void connect(InetSocketAddress addr) throws IOException { + firstConnect = new CountDownLatch(1); + + ClientBootstrap bootstrap = new ClientBootstrap(channelFactory); + + bootstrap.setPipelineFactory(new ZKClientPipelineFactory(addr.getHostString(), addr.getPort())); + bootstrap.setOption("soLinger", -1); + bootstrap.setOption("tcpNoDelay", true); + + connectFuture = bootstrap.connect(addr); + connectFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture channelFuture) throws Exception { + // this lock guarantees that channel won't be assgined after cleanup(). + connectLock.lock(); + try { + if (!channelFuture.isSuccess() || connectFuture == null) { + LOG.info("future isn't success, cause: {}", channelFuture.getCause()); + return; + } + // setup channel, variables, connection, etc. + channel = channelFuture.getChannel(); + + disconnected.set(false); + initialized = false; + lenBuffer.clear(); + incomingBuffer = lenBuffer; + + sendThread.primeConnection(); + updateNow(); + updateLastSendAndHeard(); + + if (sendThread.tunnelAuthInProgress()) { + waitSasl.drainPermits(); + needSasl.set(true); + sendPrimePacket(); + } else { + needSasl.set(false); + } + + // we need to wake up on first connect to avoid timeout. + wakeupCnxn(); + firstConnect.countDown(); + LOG.info("channel is connected: {}", channelFuture.getChannel()); + } finally { + connectLock.unlock(); + } + } + }); + } + + @Override + void cleanup() { + connectLock.lock(); + try { + if (connectFuture != null) { + connectFuture.cancel(); + connectFuture = null; + } + if (channel != null) { + channel.close().awaitUninterruptibly(); + channel = null; + } + } finally { + connectLock.unlock(); + } + Iterator iter = outgoingQueue.iterator(); + while (iter.hasNext()) { + Packet p = iter.next(); + if (p == WakeupPacket.getInstance()) { + iter.remove(); + } + } + } + + @Override + void close() { + channelFactory.releaseExternalResources(); + } + + @Override + void saslCompleted() { + needSasl.set(false); + waitSasl.release(); + } + + @Override + void connectionPrimed() { + } + + @Override + void packetAdded() { + } + + @Override + void onClosing() { + firstConnect.countDown(); + wakeupCnxn(); + LOG.info("channel is told closing"); + } + + private void wakeupCnxn() { + if (needSasl.get()) { + waitSasl.release(); + } + outgoingQueue.add(WakeupPacket.getInstance()); + } + + @Override + void doTransport(int waitTimeOut, + List pendingQueue, + ClientCnxn cnxn) + throws IOException, InterruptedException { + try { + if (!firstConnect.await(waitTimeOut, TimeUnit.MILLISECONDS)) { + return; + } + Packet head = null; + if (needSasl.get()) { + if (!waitSasl.tryAcquire(waitTimeOut, TimeUnit.MILLISECONDS)) { + return; + } + } else { + if ((head = outgoingQueue.poll(waitTimeOut, TimeUnit.MILLISECONDS)) == null) { + return; + } + } + // check if being waken up on closing. + if (!sendThread.getZkState().isAlive()) { + // adding back the patck to notify of failure in conLossPacket(). + addBack(head); + return; + } + // channel disconnection happened + if (disconnected.get()) { + addBack(head); + throw new EndOfStreamException("channel for sessionid 0x" + + Long.toHexString(sessionId) + + " is lost"); + } + if (head != null) { + doWrite(pendingQueue, head, cnxn); + } + } finally { + updateNow(); + } + } + + private void addBack(Packet head) { + if (head != null && head != WakeupPacket.getInstance()) { + outgoingQueue.addFirst(head); + } + } + + private void sendPkt(Packet p) { + // Assuming the packet will be sent out successfully. Because if it fails, + // the channel will close and clean up queues. + p.createBB(); + updateLastSend(); + sentCount++; + channel.write(ChannelBuffers.wrappedBuffer(p.bb)); + } + + private void sendPrimePacket() { + // assuming the first packet is the priming packet. + sendPkt(outgoingQueue.remove()); + } + + /** + * doWrite handles writing the packets from outgoingQueue via network to server. + */ + private void doWrite(List pendingQueue, Packet p, ClientCnxn cnxn) { + updateNow(); + while (true) { + if (p != WakeupPacket.getInstance()) { + if ((p.requestHeader != null) && + (p.requestHeader.getType() != ZooDefs.OpCode.ping) && + (p.requestHeader.getType() != ZooDefs.OpCode.auth)) { + p.requestHeader.setXid(cnxn.getXid()); + synchronized (pendingQueue) { + pendingQueue.add(p); + } + } + sendPkt(p); + } + if (outgoingQueue.isEmpty()) { + break; + } + p = outgoingQueue.remove(); + } + } + + @Override + void sendPacket(ClientCnxn.Packet p) throws IOException { + if (channel == null) { + throw new IOException("channel has been closed"); + } + sendPkt(p); + } + + @Override + SocketAddress getRemoteSocketAddress() { + Channel copiedChanRef = channel; + return (copiedChanRef == null) ? null : copiedChanRef.getRemoteAddress(); + } + + @Override + SocketAddress getLocalSocketAddress() { + Channel copiedChanRef = channel; + return (copiedChanRef == null) ? null : copiedChanRef.getLocalAddress(); + } + + @Override + void testableCloseSocket() throws IOException { + Channel copiedChanRef = channel; + if (copiedChanRef != null) { + copiedChanRef.disconnect().awaitUninterruptibly(); + } + } + + + // *************** CientCnxnSocketNetty ****************** + private static class WakeupPacket { + private static final Packet instance = new Packet(null, null, null, null, null); + + protected WakeupPacket() { + // Exists only to defeat instantiation. + } + + public static Packet getInstance() { + return instance; + } + } + + /** + * ZKClientPipelineFactory is the netty pipeline factory for this netty + * connection implementation. + */ + private class ZKClientPipelineFactory implements ChannelPipelineFactory { + private SSLContext sslContext = null; + private SSLEngine sslEngine = null; + private String host; + private int port; + + public ZKClientPipelineFactory(String host, int port) { + this.host = host; + this.port = port; + } + + @Override + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + if (clientConfig.getBoolean(ZKClientConfig.SECURE_CLIENT)) { + initSSL(pipeline); + } + pipeline.addLast("handler", new ZKClientHandler()); + return pipeline; + } + + // The synchronized is to prevent the race on shared variable "sslEngine". + // Basically we only need to create it once. + private synchronized void initSSL(ChannelPipeline pipeline) throws SSLContextException { + if (sslContext == null || sslEngine == null) { + sslContext = X509Util.createSSLContext(clientConfig); + sslEngine = sslContext.createSSLEngine(host,port); + sslEngine.setUseClientMode(true); + } + pipeline.addLast("ssl", new SslHandler(sslEngine)); + LOG.info("SSL handler added for channel: {}", pipeline.getChannel()); + } + } + + /** + * ZKClientHandler is the netty handler that sits in netty upstream last + * place. It mainly handles read traffic and helps synchronize connection state. + */ + private class ZKClientHandler extends SimpleChannelUpstreamHandler { + AtomicBoolean channelClosed = new AtomicBoolean(false); + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + LOG.info("channel is disconnected: {}", ctx.getChannel()); + cleanup(); + } + + /** + * netty handler has encountered problems. We are cleaning it up and tell outside to close + * the channel/connection. + */ + private void cleanup() { + if (!channelClosed.compareAndSet(false, true)) { + return; + } + disconnected.set(true); + onClosing(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, + MessageEvent e) throws Exception { + updateNow(); + ChannelBuffer buf = (ChannelBuffer) e.getMessage(); + while (buf.readable()) { + if (incomingBuffer.remaining() > buf.readableBytes()) { + int newLimit = incomingBuffer.position() + + buf.readableBytes(); + incomingBuffer.limit(newLimit); + } + buf.readBytes(incomingBuffer); + incomingBuffer.limit(incomingBuffer.capacity()); + + if (!incomingBuffer.hasRemaining()) { + incomingBuffer.flip(); + if (incomingBuffer == lenBuffer) { + recvCount++; + readLength(); + } else if (!initialized) { + readConnectResult(); + lenBuffer.clear(); + incomingBuffer = lenBuffer; + initialized = true; + updateLastHeard(); + } else { + sendThread.readResponse(incomingBuffer); + lenBuffer.clear(); + incomingBuffer = lenBuffer; + updateLastHeard(); + } + } + } + wakeupCnxn(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + ExceptionEvent e) throws Exception { + LOG.warn("Exception caught: {}", e, e.getCause()); + cleanup(); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/CreateMode.java b/src/java/main/org/apache/zookeeper/CreateMode.java index d87f410c84a..587f7a12915 100644 --- a/src/java/main/org/apache/zookeeper/CreateMode.java +++ b/src/java/main/org/apache/zookeeper/CreateMode.java @@ -17,44 +17,73 @@ */ package org.apache.zookeeper; +import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.zookeeper.KeeperException; /*** * CreateMode value determines how the znode is created on ZooKeeper. */ +@InterfaceAudience.Public public enum CreateMode { /** * The znode will not be automatically deleted upon client's disconnect. */ - PERSISTENT (0, false, false), + PERSISTENT (0, false, false, false, false), /** * The znode will not be automatically deleted upon client's disconnect, * and its name will be appended with a monotonically increasing number. */ - PERSISTENT_SEQUENTIAL (2, false, true), + PERSISTENT_SEQUENTIAL (2, false, true, false, false), /** * The znode will be deleted upon the client's disconnect. */ - EPHEMERAL (1, true, false), + EPHEMERAL (1, true, false, false, false), /** * The znode will be deleted upon the client's disconnect, and its name * will be appended with a monotonically increasing number. */ - EPHEMERAL_SEQUENTIAL (3, true, true); + EPHEMERAL_SEQUENTIAL (3, true, true, false, false), + /** + * The znode will be a container node. Container + * nodes are special purpose nodes useful for recipes such as leader, lock, + * etc. When the last child of a container is deleted, the container becomes + * a candidate to be deleted by the server at some point in the future. + * Given this property, you should be prepared to get + * {@link org.apache.zookeeper.KeeperException.NoNodeException} + * when creating children inside of this container node. + */ + CONTAINER (4, false, false, true, false), + /** + * The znode will not be automatically deleted upon client's disconnect. + * However if the znode has not been modified within the given TTL, it + * will be deleted once it has no children. + */ + PERSISTENT_WITH_TTL(5, false, false, false, true), + /** + * The znode will not be automatically deleted upon client's disconnect, + * and its name will be appended with a monotonically increasing number. + * However if the znode has not been modified within the given TTL, it + * will be deleted once it has no children. + */ + PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true); private static final Logger LOG = LoggerFactory.getLogger(CreateMode.class); private boolean ephemeral; private boolean sequential; + private final boolean isContainer; private int flag; + private boolean isTTL; - CreateMode(int flag, boolean ephemeral, boolean sequential) { + CreateMode(int flag, boolean ephemeral, boolean sequential, + boolean isContainer, boolean isTTL) { this.flag = flag; this.ephemeral = ephemeral; this.sequential = sequential; + this.isContainer = isContainer; + this.isTTL = isTTL; } public boolean isEphemeral() { @@ -65,6 +94,14 @@ public boolean isSequential() { return sequential; } + public boolean isContainer() { + return isContainer; + } + + public boolean isTTL() { + return isTTL; + } + public int toFlag() { return flag; } @@ -82,6 +119,12 @@ static public CreateMode fromFlag(int flag) throws KeeperException { case 3: return CreateMode.EPHEMERAL_SEQUENTIAL ; + case 4: return CreateMode.CONTAINER; + + case 5: return CreateMode.PERSISTENT_WITH_TTL; + + case 6: return CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL; + default: String errMsg = "Received an invalid flag value: " + flag + " to convert to a CreateMode"; @@ -89,4 +132,35 @@ static public CreateMode fromFlag(int flag) throws KeeperException { throw new KeeperException.BadArgumentsException(errMsg); } } + + /** + * Map an integer value to a CreateMode value + */ + static public CreateMode fromFlag(int flag, CreateMode defaultMode) { + switch(flag) { + case 0: + return CreateMode.PERSISTENT; + + case 1: + return CreateMode.EPHEMERAL; + + case 2: + return CreateMode.PERSISTENT_SEQUENTIAL; + + case 3: + return CreateMode.EPHEMERAL_SEQUENTIAL; + + case 4: + return CreateMode.CONTAINER; + + case 5: + return CreateMode.PERSISTENT_WITH_TTL; + + case 6: + return CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL; + + default: + return defaultMode; + } + } } diff --git a/src/java/main/org/apache/zookeeper/KeeperException.java b/src/java/main/org/apache/zookeeper/KeeperException.java index a05f1abbb06..143fac5e88d 100644 --- a/src/java/main/org/apache/zookeeper/KeeperException.java +++ b/src/java/main/org/apache/zookeeper/KeeperException.java @@ -18,6 +18,8 @@ package org.apache.zookeeper; +import org.apache.yetus.audience.InterfaceAudience; + import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; @@ -25,6 +27,7 @@ import java.util.Map; @SuppressWarnings("serial") +@InterfaceAudience.Public public abstract class KeeperException extends Exception { /** * All multi-requests that result in an exception retain the results @@ -139,6 +142,8 @@ public static KeeperException create(Code code) { return new EphemeralOnLocalSessionException(); case NOWATCHER: return new NoWatcherException(); + case RECONFIGDISABLED: + return new ReconfigDisabledException(); case OK: default: throw new IllegalArgumentException("Invalid exception code"); @@ -170,6 +175,7 @@ public void setCode(int code) { * javadoc to include in the user API spec. */ @Deprecated + @InterfaceAudience.Public public interface CodeDeprecated { /** * @deprecated deprecated in 3.1.0, use {@link Code#OK} instead @@ -315,6 +321,7 @@ public interface CodeDeprecated { * constants. The old, deprecated, values are in "camel case" while the new * enum values are in all CAPS. */ + @InterfaceAudience.Public public static enum Code implements CodeDeprecated { /** Everything is OK */ OK (Ok), @@ -384,7 +391,9 @@ public static enum Code implements CodeDeprecated { /** Attempt to create ephemeral node on a local session */ EPHEMERALONLOCALSESSION (EphemeralOnLocalSession), /** Attempts to remove a non-existing watcher */ - NOWATCHER (-121); + NOWATCHER (-121), + /** Attempts to perform a reconfiguration operation when reconfiguration feature is disabled. */ + RECONFIGDISABLED(-123); private static final Map lookup = new HashMap(); @@ -469,6 +478,8 @@ static String getCodeMessage(Code code) { return "Ephemeral node on local session"; case NOWATCHER: return "No such watcher"; + case RECONFIGDISABLED: + return "Reconfig is disabled"; default: return "Unknown error " + code; } @@ -515,7 +526,7 @@ public String getPath() { @Override public String getMessage() { - if (path == null) { + if (path == null || path.isEmpty()) { return "KeeperErrorCode = " + getCodeMessage(code); } return "KeeperErrorCode = " + getCodeMessage(code) + " for " + path; @@ -540,6 +551,7 @@ public List getResults() { /** * @see Code#APIERROR */ + @InterfaceAudience.Public public static class APIErrorException extends KeeperException { public APIErrorException() { super(Code.APIERROR); @@ -549,6 +561,7 @@ public APIErrorException() { /** * @see Code#AUTHFAILED */ + @InterfaceAudience.Public public static class AuthFailedException extends KeeperException { public AuthFailedException() { super(Code.AUTHFAILED); @@ -558,6 +571,7 @@ public AuthFailedException() { /** * @see Code#BADARGUMENTS */ + @InterfaceAudience.Public public static class BadArgumentsException extends KeeperException { public BadArgumentsException() { super(Code.BADARGUMENTS); @@ -570,6 +584,7 @@ public BadArgumentsException(String path) { /** * @see Code#BADVERSION */ + @InterfaceAudience.Public public static class BadVersionException extends KeeperException { public BadVersionException() { super(Code.BADVERSION); @@ -582,6 +597,7 @@ public BadVersionException(String path) { /** * @see Code#CONNECTIONLOSS */ + @InterfaceAudience.Public public static class ConnectionLossException extends KeeperException { public ConnectionLossException() { super(Code.CONNECTIONLOSS); @@ -591,6 +607,7 @@ public ConnectionLossException() { /** * @see Code#DATAINCONSISTENCY */ + @InterfaceAudience.Public public static class DataInconsistencyException extends KeeperException { public DataInconsistencyException() { super(Code.DATAINCONSISTENCY); @@ -600,6 +617,7 @@ public DataInconsistencyException() { /** * @see Code#INVALIDACL */ + @InterfaceAudience.Public public static class InvalidACLException extends KeeperException { public InvalidACLException() { super(Code.INVALIDACL); @@ -612,6 +630,7 @@ public InvalidACLException(String path) { /** * @see Code#INVALIDCALLBACK */ + @InterfaceAudience.Public public static class InvalidCallbackException extends KeeperException { public InvalidCallbackException() { super(Code.INVALIDCALLBACK); @@ -621,6 +640,7 @@ public InvalidCallbackException() { /** * @see Code#MARSHALLINGERROR */ + @InterfaceAudience.Public public static class MarshallingErrorException extends KeeperException { public MarshallingErrorException() { super(Code.MARSHALLINGERROR); @@ -630,6 +650,7 @@ public MarshallingErrorException() { /** * @see Code#NOAUTH */ + @InterfaceAudience.Public public static class NoAuthException extends KeeperException { public NoAuthException() { super(Code.NOAUTH); @@ -639,6 +660,7 @@ public NoAuthException() { /** * @see Code#NEWCONFIGNOQUORUM */ + @InterfaceAudience.Public public static class NewConfigNoQuorum extends KeeperException { public NewConfigNoQuorum() { super(Code.NEWCONFIGNOQUORUM); @@ -648,6 +670,7 @@ public NewConfigNoQuorum() { /** * @see Code#RECONFIGINPROGRESS */ + @InterfaceAudience.Public public static class ReconfigInProgress extends KeeperException { public ReconfigInProgress() { super(Code.RECONFIGINPROGRESS); @@ -657,6 +680,7 @@ public ReconfigInProgress() { /** * @see Code#NOCHILDRENFOREPHEMERALS */ + @InterfaceAudience.Public public static class NoChildrenForEphemeralsException extends KeeperException { public NoChildrenForEphemeralsException() { super(Code.NOCHILDRENFOREPHEMERALS); @@ -669,6 +693,7 @@ public NoChildrenForEphemeralsException(String path) { /** * @see Code#NODEEXISTS */ + @InterfaceAudience.Public public static class NodeExistsException extends KeeperException { public NodeExistsException() { super(Code.NODEEXISTS); @@ -681,6 +706,7 @@ public NodeExistsException(String path) { /** * @see Code#NONODE */ + @InterfaceAudience.Public public static class NoNodeException extends KeeperException { public NoNodeException() { super(Code.NONODE); @@ -693,6 +719,7 @@ public NoNodeException(String path) { /** * @see Code#NOTEMPTY */ + @InterfaceAudience.Public public static class NotEmptyException extends KeeperException { public NotEmptyException() { super(Code.NOTEMPTY); @@ -705,6 +732,7 @@ public NotEmptyException(String path) { /** * @see Code#OPERATIONTIMEOUT */ + @InterfaceAudience.Public public static class OperationTimeoutException extends KeeperException { public OperationTimeoutException() { super(Code.OPERATIONTIMEOUT); @@ -714,6 +742,7 @@ public OperationTimeoutException() { /** * @see Code#RUNTIMEINCONSISTENCY */ + @InterfaceAudience.Public public static class RuntimeInconsistencyException extends KeeperException { public RuntimeInconsistencyException() { super(Code.RUNTIMEINCONSISTENCY); @@ -723,6 +752,7 @@ public RuntimeInconsistencyException() { /** * @see Code#SESSIONEXPIRED */ + @InterfaceAudience.Public public static class SessionExpiredException extends KeeperException { public SessionExpiredException() { super(Code.SESSIONEXPIRED); @@ -732,6 +762,7 @@ public SessionExpiredException() { /** * @see Code#UNKNOWNSESSION */ + @InterfaceAudience.Public public static class UnknownSessionException extends KeeperException { public UnknownSessionException() { super(Code.UNKNOWNSESSION); @@ -741,6 +772,7 @@ public UnknownSessionException() { /** * @see Code#SESSIONMOVED */ + @InterfaceAudience.Public public static class SessionMovedException extends KeeperException { public SessionMovedException() { super(Code.SESSIONMOVED); @@ -750,6 +782,7 @@ public SessionMovedException() { /** * @see Code#NOTREADONLY */ + @InterfaceAudience.Public public static class NotReadOnlyException extends KeeperException { public NotReadOnlyException() { super(Code.NOTREADONLY); @@ -759,6 +792,7 @@ public NotReadOnlyException() { /** * @see Code#EPHEMERALONLOCALSESSION */ + @InterfaceAudience.Public public static class EphemeralOnLocalSessionException extends KeeperException { public EphemeralOnLocalSessionException() { super(Code.EPHEMERALONLOCALSESSION); @@ -768,6 +802,7 @@ public EphemeralOnLocalSessionException() { /** * @see Code#SYSTEMERROR */ + @InterfaceAudience.Public public static class SystemErrorException extends KeeperException { public SystemErrorException() { super(Code.SYSTEMERROR); @@ -777,6 +812,7 @@ public SystemErrorException() { /** * @see Code#UNIMPLEMENTED */ + @InterfaceAudience.Public public static class UnimplementedException extends KeeperException { public UnimplementedException() { super(Code.UNIMPLEMENTED); @@ -786,6 +822,7 @@ public UnimplementedException() { /** * @see Code#NOWATCHER */ + @InterfaceAudience.Public public static class NoWatcherException extends KeeperException { public NoWatcherException() { super(Code.NOWATCHER); @@ -795,4 +832,15 @@ public NoWatcherException(String path) { super(Code.NOWATCHER, path); } } + + /** + * @see Code#RECONFIGDISABLED + */ + @InterfaceAudience.Public + public static class ReconfigDisabledException extends KeeperException { + public ReconfigDisabledException() { super(Code.RECONFIGDISABLED); } + public ReconfigDisabledException(String path) { + super(Code.RECONFIGDISABLED, path); + } + } } diff --git a/src/java/main/org/apache/zookeeper/Login.java b/src/java/main/org/apache/zookeeper/Login.java index 6d248ab37a0..d97d6c1fde4 100644 --- a/src/java/main/org/apache/zookeeper/Login.java +++ b/src/java/main/org/apache/zookeeper/Login.java @@ -32,16 +32,23 @@ import javax.security.auth.login.LoginException; import javax.security.auth.callback.CallbackHandler; -import org.apache.log4j.Logger; -import org.apache.zookeeper.client.ZooKeeperSaslClient; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.ZooKeeperSaslServer; +import org.apache.zookeeper.common.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.security.auth.kerberos.KerberosTicket; import javax.security.auth.Subject; + import java.util.Date; import java.util.Random; import java.util.Set; public class Login { - private static final Logger LOG = Logger.getLogger(Login.class); + private static final String KINIT_COMMAND_DEFAULT = "/usr/bin/kinit"; + private static final Logger LOG = LoggerFactory.getLogger(Login.class); public CallbackHandler callbackHandler; // LoginThread will sleep until 80% of time from last refresh to @@ -71,22 +78,29 @@ public class Login { private String loginContextName = null; private String principal = null; - private long lastLogin = 0; + // Initialize 'lastLogin' to do a login at first time + private long lastLogin = Time.currentElapsedTime() - MIN_TIME_BEFORE_RELOGIN; + private final ZKConfig zkConfig; /** - * LoginThread constructor. The constructor starts the thread used - * to periodically re-login to the Kerberos Ticket Granting Server. + * LoginThread constructor. The constructor starts the thread used to + * periodically re-login to the Kerberos Ticket Granting Server. + * * @param loginContextName - * name of section in JAAS file that will be use to login. - * Passed as first param to javax.security.auth.login.LoginContext(). + * name of section in JAAS file that will be use to login. Passed + * as first param to javax.security.auth.login.LoginContext(). * * @param callbackHandler - * Passed as second param to javax.security.auth.login.LoginContext(). + * Passed as second param to + * javax.security.auth.login.LoginContext(). + * @param zkConfig + * client or server configurations * @throws javax.security.auth.login.LoginException - * Thrown if authentication fails. + * Thrown if authentication fails. */ - public Login(final String loginContextName, CallbackHandler callbackHandler) + public Login(final String loginContextName, CallbackHandler callbackHandler, final ZKConfig zkConfig) throws LoginException { + this.zkConfig=zkConfig; this.callbackHandler = callbackHandler; login = login(loginContextName); this.loginContextName = loginContextName; @@ -121,25 +135,26 @@ public void run() { LOG.info("TGT refresh thread started."); while (true) { // renewal thread's main loop. if it exits from here, thread will exit. KerberosTicket tgt = getTGT(); - long now = System.currentTimeMillis(); + long now = Time.currentWallTime(); long nextRefresh; Date nextRefreshDate; if (tgt == null) { nextRefresh = now + MIN_TIME_BEFORE_RELOGIN; nextRefreshDate = new Date(nextRefresh); - LOG.warn("No TGT found: will try again at " + nextRefreshDate); + LOG.warn("No TGT found: will try again at {}", nextRefreshDate); } else { nextRefresh = getRefreshTime(tgt); long expiry = tgt.getEndTime().getTime(); Date expiryDate = new Date(expiry); if ((isUsingTicketCache) && (tgt.getEndTime().equals(tgt.getRenewTill()))) { - LOG.error("The TGT cannot be renewed beyond the next expiry date: " + expiryDate + "." + + Object[] logPayload = {expiryDate, principal, principal}; + LOG.error("The TGT cannot be renewed beyond the next expiry date: {}." + "This process will not be able to authenticate new SASL connections after that " + "time (for example, it will not be authenticate a new connection with a Zookeeper " + "Quorum member). Ask your system administrator to either increase the " + - "'renew until' time by doing : 'modprinc -maxrenewlife " + principal + "' within " + - "kadmin, or instead, to generate a keytab for " + principal + ". Because the TGT's " + - "expiry cannot be further extended by refreshing, exiting refresh thread now."); + "'renew until' time by doing : 'modprinc -maxrenewlife {}' within " + + "kadmin, or instead, to generate a keytab for {}. Because the TGT's " + + "expiry cannot be further extended by refreshing, exiting refresh thread now.", logPayload); return; } // determine how long to sleep from looking at ticket's expiry. @@ -149,30 +164,33 @@ public void run() { if ((nextRefresh > expiry) || ((now + MIN_TIME_BEFORE_RELOGIN) > expiry)) { // expiry is before next scheduled refresh). - LOG.info("refreshing now because expiry is before next scheduled refresh time."); nextRefresh = now; } else { if (nextRefresh < (now + MIN_TIME_BEFORE_RELOGIN)) { // next scheduled refresh is sooner than (now + MIN_TIME_BEFORE_LOGIN). Date until = new Date(nextRefresh); Date newuntil = new Date(now + MIN_TIME_BEFORE_RELOGIN); - LOG.warn("TGT refresh thread time adjusted from : " + until + " to : " + newuntil + " since " + Object[] logPayload = {until, newuntil, (MIN_TIME_BEFORE_RELOGIN / 1000)}; + LOG.warn("TGT refresh thread time adjusted from : {} to : {} since " + "the former is sooner than the minimum refresh interval (" - + MIN_TIME_BEFORE_RELOGIN / 1000 + " seconds) from now."); + + "{} seconds) from now.", logPayload); } nextRefresh = Math.max(nextRefresh, now + MIN_TIME_BEFORE_RELOGIN); } nextRefreshDate = new Date(nextRefresh); if (nextRefresh > expiry) { - LOG.error("next refresh: " + nextRefreshDate + " is later than expiry " + expiryDate - + ". This may indicate a clock skew problem. Check that this host and the KDC's " - + "hosts' clocks are in sync. Exiting refresh thread."); + Object[] logPayload = {nextRefreshDate, expiryDate}; + LOG.error("next refresh: {} is later than expiry {}." + + " This may indicate a clock skew problem. Check that this host and the KDC's " + + "hosts' clocks are in sync. Exiting refresh thread.", logPayload); return; } } - if (now < nextRefresh) { + if (now == nextRefresh) { + LOG.info("refreshing now because expiry is before next scheduled refresh time."); + } else if (now < nextRefresh) { Date until = new Date(nextRefresh); - LOG.info("TGT refresh sleeping until: " + until.toString()); + LOG.info("TGT refresh sleeping until: {}", until.toString()); try { Thread.sleep(nextRefresh - now); } catch (InterruptedException ie) { @@ -181,22 +199,19 @@ public void run() { } } else { - LOG.error("nextRefresh:" + nextRefreshDate + " is in the past: exiting refresh thread. Check" + LOG.error("nextRefresh:{} is in the past: exiting refresh thread. Check" + " clock sync between this host and KDC - (KDC's clock is likely ahead of this host)." + " Manual intervention will be required for this client to successfully authenticate." - + " Exiting refresh thread."); - return; + + " Exiting refresh thread.", nextRefreshDate); + break; } if (isUsingTicketCache) { - String cmd = "/usr/bin/kinit"; - if (System.getProperty("zookeeper.kinit") != null) { - cmd = System.getProperty("zookeeper.kinit"); - } + String cmd = zkConfig.getProperty(ZKConfig.KINIT_COMMAND, KINIT_COMMAND_DEFAULT); String kinitArgs = "-R"; int retry = 1; while (retry >= 0) { try { - LOG.debug("running ticket cache refresh command: " + cmd + " " + kinitArgs); + LOG.debug("running ticket cache refresh command: {} {}", cmd, kinitArgs); Shell.execCommand(cmd, kinitArgs); break; } catch (Exception e) { @@ -210,8 +225,9 @@ public void run() { return; } } else { - LOG.warn("Could not renew TGT due to problem running shell command: '" + cmd - + " " + kinitArgs + "'" + "; exception was:" + e + ". Exiting refresh thread.",e); + Object[] logPayload = {cmd, kinitArgs, e.toString(), e}; + LOG.warn("Could not renew TGT due to problem running shell command: '{}" + + " {}'; exception was:{}. Exiting refresh thread.", logPayload); return; } } @@ -234,7 +250,7 @@ public void run() { throw le; } } else { - LOG.error("Could not refresh TGT for principal: " + principal + ".", le); + LOG.error("Could not refresh TGT for principal: {}.", principal, le); } } } @@ -261,7 +277,7 @@ public void shutdown() { try { t.join(); } catch (InterruptedException e) { - LOG.warn("error while waiting for Login thread to shutdown: " + e); + LOG.warn("error while waiting for Login thread to shutdown: ", e); } } } @@ -279,26 +295,35 @@ private synchronized LoginContext login(final String loginContextName) throws Lo throw new LoginException("loginContext name (JAAS file section header) was null. " + "Please check your java.security.login.auth.config (=" + System.getProperty("java.security.login.auth.config") + - ") and your " + ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY + "(=" + - System.getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "Client") + ")"); + ") and your " + getLoginContextMessage()); } LoginContext loginContext = new LoginContext(loginContextName,callbackHandler); loginContext.login(); - LOG.info("successfully logged in."); + LOG.info("{} successfully logged in.", loginContextName); return loginContext; } + private String getLoginContextMessage() { + if (zkConfig instanceof ZKClientConfig) { + return ZKClientConfig.LOGIN_CONTEXT_NAME_KEY + "(=" + zkConfig.getProperty( + ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT) + ")"; + } else { + return ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY + "(=" + System.getProperty( + ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY, ZooKeeperSaslServer.DEFAULT_LOGIN_CONTEXT_NAME) + ")"; + } + } + // c.f. org.apache.hadoop.security.UserGroupInformation. private long getRefreshTime(KerberosTicket tgt) { long start = tgt.getStartTime().getTime(); long expires = tgt.getEndTime().getTime(); - LOG.info("TGT valid starting at: " + tgt.getStartTime().toString()); - LOG.info("TGT expires: " + tgt.getEndTime().toString()); + LOG.info("TGT valid starting at: {}", tgt.getStartTime().toString()); + LOG.info("TGT expires: {}", tgt.getEndTime().toString()); long proposedRefresh = start + (long) ((expires - start) * (TICKET_RENEW_WINDOW + (TICKET_RENEW_JITTER * rng.nextDouble()))); if (proposedRefresh > expires) { // proposedRefresh is too far in the future: it's after ticket expires: simply return now. - return System.currentTimeMillis(); + return Time.currentWallTime(); } else { return proposedRefresh; @@ -310,7 +335,8 @@ private synchronized KerberosTicket getTGT() { for(KerberosTicket ticket: tickets) { KerberosPrincipal server = ticket.getServer(); if (server.getName().equals("krbtgt/" + server.getRealm() + "@" + server.getRealm())) { - LOG.debug("Found tgt " + ticket + "."); + LOG.debug("Client principal is \"" + ticket.getClient().getName() + "\"."); + LOG.debug("Server principal is \"" + ticket.getServer().getName() + "\"."); return ticket; } } @@ -318,11 +344,11 @@ private synchronized KerberosTicket getTGT() { } private boolean hasSufficientTimeElapsed() { - long now = System.currentTimeMillis(); + long now = Time.currentElapsedTime(); if (now - getLastLogin() < MIN_TIME_BEFORE_RELOGIN ) { - LOG.warn("Not attempting to re-login since the last re-login was " + - "attempted less than " + (MIN_TIME_BEFORE_RELOGIN/1000) + " seconds"+ - " before."); + LOG.warn("Not attempting to re-login since the last re-login was " + + "attempted less than {} seconds before.", + (MIN_TIME_BEFORE_RELOGIN / 1000)); return false; } // register most recent relogin attempt @@ -379,7 +405,7 @@ private synchronized void reLogin() if (!hasSufficientTimeElapsed()) { return; } - LOG.info("Initiating logout for " + principal); + LOG.info("Initiating logout for {}", principal); synchronized (Login.class) { //clear up the kerberos state. But the tokens are not cleared! As per //the Java kerberos login module code, only the kerberos credentials @@ -388,7 +414,7 @@ private synchronized void reLogin() //login and also update the subject field of this instance to //have the new credentials (pass it to the LoginContext constructor) login = new LoginContext(loginContextName, getSubject()); - LOG.info("Initiating re-login for " + principal); + LOG.info("Initiating re-login for {}", principal); login.login(); setLogin(login); } diff --git a/src/java/main/org/apache/zookeeper/MultiTransactionRecord.java b/src/java/main/org/apache/zookeeper/MultiTransactionRecord.java index ea913b47572..336a677a61e 100644 --- a/src/java/main/org/apache/zookeeper/MultiTransactionRecord.java +++ b/src/java/main/org/apache/zookeeper/MultiTransactionRecord.java @@ -66,18 +66,12 @@ public void serialize(OutputArchive archive, String tag) throws IOException { MultiHeader h = new MultiHeader(op.getType(), false, -1); h.serialize(archive, tag); switch (op.getType()) { - case ZooDefs.OpCode.create: - op.toRequestRecord().serialize(archive, tag); - break; - case ZooDefs.OpCode.create2: - op.toRequestRecord().serialize(archive, tag); - break; + case ZooDefs.OpCode.create: + case ZooDefs.OpCode.create2: + case ZooDefs.OpCode.createTTL: + case ZooDefs.OpCode.createContainer: case ZooDefs.OpCode.delete: - op.toRequestRecord().serialize(archive, tag); - break; case ZooDefs.OpCode.setData: - op.toRequestRecord().serialize(archive, tag); - break; case ZooDefs.OpCode.check: op.toRequestRecord().serialize(archive, tag); break; @@ -97,16 +91,18 @@ public void deserialize(InputArchive archive, String tag) throws IOException { while (!h.getDone()) { switch (h.getType()) { - case ZooDefs.OpCode.create: + case ZooDefs.OpCode.create: + case ZooDefs.OpCode.create2: + case ZooDefs.OpCode.createContainer: CreateRequest cr = new CreateRequest(); cr.deserialize(archive, tag); add(Op.create(cr.getPath(), cr.getData(), cr.getAcl(), cr.getFlags())); break; - case ZooDefs.OpCode.create2: - Create2Request cr2 = new Create2Request(); - cr2.deserialize(archive, tag); - add(Op.create(cr2.getPath(), cr2.getData(), cr2.getAcl(), cr2.getFlags())); - break; + case ZooDefs.OpCode.createTTL: + CreateTTLRequest crTtl = new CreateTTLRequest(); + crTtl.deserialize(archive, tag); + add(Op.create(crTtl.getPath(), crTtl.getData(), crTtl.getAcl(), crTtl.getFlags(), crTtl.getTtl())); + break; case ZooDefs.OpCode.delete: DeleteRequest dr = new DeleteRequest(); dr.deserialize(archive, tag); diff --git a/src/java/main/org/apache/zookeeper/Op.java b/src/java/main/org/apache/zookeeper/Op.java index 97d3d7bc4e5..c73cc79cb76 100644 --- a/src/java/main/org/apache/zookeeper/Op.java +++ b/src/java/main/org/apache/zookeeper/Op.java @@ -22,8 +22,10 @@ import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.proto.CheckVersionRequest; import org.apache.zookeeper.proto.CreateRequest; +import org.apache.zookeeper.proto.CreateTTLRequest; import org.apache.zookeeper.proto.DeleteRequest; import org.apache.zookeeper.proto.SetDataRequest; +import org.apache.zookeeper.server.EphemeralType; import java.util.Arrays; import java.util.Iterator; @@ -70,6 +72,32 @@ public static Op create(String path, byte[] data, List acl, int flags) { return new Create(path, data, acl, flags); } + /** + * Constructs a create operation. Arguments are as for the ZooKeeper method of the same name + * but adding an optional ttl + * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode) + * @see CreateMode#fromFlag(int) + * + * @param path + * the path for the node + * @param data + * the initial data for the node + * @param acl + * the acl for the node + * @param flags + * specifying whether the node to be created is ephemeral + * and/or sequential but using the integer encoding. + * @param ttl + * optional ttl or 0 (flags must imply a TTL creation mode) + */ + public static Op create(String path, byte[] data, List acl, int flags, long ttl) { + CreateMode createMode = CreateMode.fromFlag(flags, CreateMode.PERSISTENT); + if (createMode.isTTL()) { + return new CreateTTL(path, data, acl, createMode, ttl); + } + return new Create(path, data, acl, flags); + } + /** * Constructs a create operation. Arguments are as for the ZooKeeper method of the same name. * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode) @@ -88,6 +116,30 @@ public static Op create(String path, byte[] data, List acl, CreateMode crea return new Create(path, data, acl, createMode); } + /** + * Constructs a create operation. Arguments are as for the ZooKeeper method of the same name + * but adding an optional ttl + * @see ZooKeeper#create(String, byte[], java.util.List, CreateMode) + * + * @param path + * the path for the node + * @param data + * the initial data for the node + * @param acl + * the acl for the node + * @param createMode + * specifying whether the node to be created is ephemeral + * and/or sequential + * @param ttl + * optional ttl or 0 (createMode must imply a TTL) + */ + public static Op create(String path, byte[] data, List acl, CreateMode createMode, long ttl) { + if (createMode.isTTL()) { + return new CreateTTL(path, data, acl, createMode, ttl); + } + return new Create(path, data, acl, createMode); + } + /** * Constructs a delete operation. Arguments are as for the ZooKeeper method of the same name. * @see ZooKeeper#delete(String, int) @@ -178,19 +230,26 @@ void validate() throws KeeperException { // these internal classes are public, but should not generally be referenced. // public static class Create extends Op { - private byte[] data; - private List acl; - private int flags; + protected byte[] data; + protected List acl; + protected int flags; private Create(String path, byte[] data, List acl, int flags) { - super(ZooDefs.OpCode.create, path); + super(getOpcode(CreateMode.fromFlag(flags, CreateMode.PERSISTENT)), path); this.data = data; this.acl = acl; this.flags = flags; } + private static int getOpcode(CreateMode createMode) { + if (createMode.isTTL()) { + return ZooDefs.OpCode.createTTL; + } + return createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create; + } + private Create(String path, byte[] data, List acl, CreateMode createMode) { - super(ZooDefs.OpCode.create, path); + super(getOpcode(createMode), path); this.data = data; this.acl = acl; this.flags = createMode.toFlag(); @@ -239,6 +298,48 @@ Op withChroot(String path) { void validate() throws KeeperException { CreateMode createMode = CreateMode.fromFlag(flags); PathUtils.validatePath(getPath(), createMode.isSequential()); + EphemeralType.validateTTL(createMode, -1); + } + } + + public static class CreateTTL extends Create { + private final long ttl; + + private CreateTTL(String path, byte[] data, List acl, int flags, long ttl) { + super(path, data, acl, flags); + this.ttl = ttl; + } + + private CreateTTL(String path, byte[] data, List acl, CreateMode createMode, long ttl) { + super(path, data, acl, createMode); + this.ttl = ttl; + } + + @Override + public boolean equals(Object o) { + return super.equals(o) && (o instanceof CreateTTL) && (ttl == ((CreateTTL)o).ttl); + } + + @Override + public int hashCode() { + return super.hashCode() + (int)(ttl ^ (ttl >>> 32)); + } + + @Override + public Record toRequestRecord() { + return new CreateTTLRequest(getPath(), data, acl, flags, ttl); + } + + @Override + Op withChroot(String path) { + return new CreateTTL(path, data, acl, flags, ttl); + } + + @Override + void validate() throws KeeperException { + CreateMode createMode = CreateMode.fromFlag(flags); + PathUtils.validatePath(getPath(), createMode.isSequential()); + EphemeralType.validateTTL(createMode, ttl); } } diff --git a/src/java/main/org/apache/zookeeper/SaslClientCallbackHandler.java b/src/java/main/org/apache/zookeeper/SaslClientCallbackHandler.java new file mode 100644 index 00000000000..d6f554955f0 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/SaslClientCallbackHandler.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; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.RealmCallback; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is used by the SASL mechanisms to get further information to complete + * the authentication. For example, a SASL mechanism might use this callback + * handler to do verification operation. The CallbackHandler interface here + * refers to javax.security.auth.callback.CallbackHandler. It should not be + * confused with ZooKeeper packet callbacks like + * org.apache.zookeeper.server.auth.SaslServerCallbackHandler. + */ +public class SaslClientCallbackHandler implements CallbackHandler { + private String password = null; + private static final Logger LOG = LoggerFactory.getLogger(SaslClientCallbackHandler.class); + private final String entity; + public SaslClientCallbackHandler(String password, String client) { + this.password = password; + this.entity = client; + } + + public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) { + NameCallback nc = (NameCallback) callback; + nc.setName(nc.getDefaultName()); + } + else { + if (callback instanceof PasswordCallback) { + PasswordCallback pc = (PasswordCallback)callback; + if (password != null) { + pc.setPassword(this.password.toCharArray()); + } else { + LOG.warn("Could not login: the {} is being asked for a password, but the ZooKeeper {}" + + " code does not currently support obtaining a password from the user." + + " Make sure that the {} is configured to use a ticket cache (using" + + " the JAAS configuration setting 'useTicketCache=true)' and restart the {}. If" + + " you still get this message after that, the TGT in the ticket cache has expired and must" + + " be manually refreshed. To do so, first determine if you are using a password or a" + + " keytab. If the former, run kinit in a Unix shell in the environment of the user who" + + " is running this Zookeeper {} using the command" + + " 'kinit ' (where is the name of the {}'s Kerberos principal)." + + " If the latter, do" + + " 'kinit -k -t ' (where is the name of the Kerberos principal, and" + + " is the location of the keytab file). After manually refreshing your cache," + + " restart this {}. If you continue to see this message after manually refreshing" + + " your cache, ensure that your KDC host's clock is in sync with this host's clock.", + new Object[]{entity, entity, entity, entity, entity, entity, entity}); + } + } + else { + if (callback instanceof RealmCallback) { + RealmCallback rc = (RealmCallback) callback; + rc.setText(rc.getDefaultText()); + } + else { + if (callback instanceof AuthorizeCallback) { + AuthorizeCallback ac = (AuthorizeCallback) callback; + String authid = ac.getAuthenticationID(); + String authzid = ac.getAuthorizationID(); + if (authid.equals(authzid)) { + ac.setAuthorized(true); + } else { + ac.setAuthorized(false); + } + if (ac.isAuthorized()) { + ac.setAuthorizedID(authzid); + } + } + else { + throw new UnsupportedCallbackException(callback, "Unrecognized SASL " + entity + "Callback"); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/java/main/org/apache/zookeeper/ServerAdminClient.java b/src/java/main/org/apache/zookeeper/ServerAdminClient.java index 17f5e6d9225..5efa53e9031 100644 --- a/src/java/main/org/apache/zookeeper/ServerAdminClient.java +++ b/src/java/main/org/apache/zookeeper/ServerAdminClient.java @@ -24,9 +24,12 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; + +import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@InterfaceAudience.Public public class ServerAdminClient { private static final Logger LOG = LoggerFactory.getLogger(ServerAdminClient.class); diff --git a/src/java/main/org/apache/zookeeper/Shell.java b/src/java/main/org/apache/zookeeper/Shell.java index 62169d797a7..1e6763f1905 100644 --- a/src/java/main/org/apache/zookeeper/Shell.java +++ b/src/java/main/org/apache/zookeeper/Shell.java @@ -38,18 +38,21 @@ import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.log4j.Logger; -/** +import org.apache.zookeeper.common.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** * A base class for running a Unix command. - * + * * Shell can be used to run unix commands like du or * df. It also offers facilities to gate commands by * time-intervals. */ abstract public class Shell { - private static final Logger LOG = Logger.getLogger(Shell.class); + private static final Logger LOG = LoggerFactory.getLogger(Shell.class); /** a Unix command to get the current user's name */ public final static String USER_NAME_COMMAND = "whoami"; @@ -146,7 +149,7 @@ protected void setWorkingDirectory(File dir) { /** check to see if a command needs to be executed and execute if needed */ protected void run() throws IOException { - if (lastTime + interval > System.currentTimeMillis()) + if (lastTime + interval > Time.currentElapsedTime()) return; exitCode = 0; // reset for next run runCommand(); @@ -245,7 +248,7 @@ public void run() { LOG.warn("Error while closing the error stream", ioe); } process.destroy(); - lastTime = System.currentTimeMillis(); + lastTime = Time.currentElapsedTime(); } } diff --git a/src/java/main/org/apache/zookeeper/Transaction.java b/src/java/main/org/apache/zookeeper/Transaction.java index d8f0a76fdbc..d8f0e01a63f 100644 --- a/src/java/main/org/apache/zookeeper/Transaction.java +++ b/src/java/main/org/apache/zookeeper/Transaction.java @@ -17,6 +17,7 @@ package org.apache.zookeeper; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.AsyncCallback.MultiCallback; import org.apache.zookeeper.data.ACL; import java.util.ArrayList; @@ -29,6 +30,7 @@ * @since 3.4.0 * */ +@InterfaceAudience.Public public class Transaction { private ZooKeeper zk; private List ops = new ArrayList(); diff --git a/src/java/main/org/apache/zookeeper/Version.java b/src/java/main/org/apache/zookeeper/Version.java index 46573717b0e..1f5cf1aa2df 100644 --- a/src/java/main/org/apache/zookeeper/Version.java +++ b/src/java/main/org/apache/zookeeper/Version.java @@ -20,10 +20,21 @@ public class Version implements org.apache.zookeeper.version.Info { + /* + * Since the SVN to Git port this field doesn't return the revision anymore + * TODO: remove this method and associated field declaration in VerGen + * @see {@link #getHashRevision()} + * @return the default value -1 + */ + @Deprecated public static int getRevision() { return REVISION; } + public static String getRevisionHash() { + return REVISION_HASH; + } + public static String getBuildDate() { return BUILD_DATE; } @@ -34,7 +45,7 @@ public static String getVersion() { } public static String getVersionRevision() { - return getVersion() + "-" + getRevision(); + return getVersion() + "-" + getRevisionHash(); } public static String getFullVersion() { diff --git a/src/java/main/org/apache/zookeeper/WatchedEvent.java b/src/java/main/org/apache/zookeeper/WatchedEvent.java index 63f29c3c631..851fc6c8567 100644 --- a/src/java/main/org/apache/zookeeper/WatchedEvent.java +++ b/src/java/main/org/apache/zookeeper/WatchedEvent.java @@ -17,6 +17,7 @@ */ package org.apache.zookeeper; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.proto.WatcherEvent; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.Event.KeeperState; @@ -27,6 +28,7 @@ * the current state of the ZooKeeper, and the path of the znode that * was involved in the event. */ +@InterfaceAudience.Public public class WatchedEvent { final private KeeperState keeperState; final private EventType eventType; diff --git a/src/java/main/org/apache/zookeeper/Watcher.java b/src/java/main/org/apache/zookeeper/Watcher.java index 7607bd29ed5..75dd3736757 100644 --- a/src/java/main/org/apache/zookeeper/Watcher.java +++ b/src/java/main/org/apache/zookeeper/Watcher.java @@ -18,23 +18,28 @@ package org.apache.zookeeper; +import org.apache.yetus.audience.InterfaceAudience; + /** * This interface specifies the public interface an event handler class must - * implement. A ZooKeeper client will get various events from the ZooKeepr + * implement. A ZooKeeper client will get various events from the ZooKeeper * server it connects to. An application using such a client handles these * events by registering a callback object with the client. The callback object * is expected to be an instance of a class that implements Watcher interface. * */ +@InterfaceAudience.Public public interface Watcher { /** * This interface defines the possible states an Event may represent */ + @InterfaceAudience.Public public interface Event { /** * Enumeration of states the ZooKeeper may be at the event */ + @InterfaceAudience.Public public enum KeeperState { /** Unused, this state is never generated by the server */ @Deprecated @@ -112,6 +117,7 @@ public static KeeperState fromInt(int intValue) { /** * Enumeration of types of events that may occur on the ZooKeeper */ + @InterfaceAudience.Public public enum EventType { None (-1), NodeCreated (1), @@ -152,6 +158,7 @@ public static EventType fromInt(int intValue) { /** * Enumeration of types of watchers */ + @InterfaceAudience.Public public enum WatcherType { Children(1), Data(2), Any(3); diff --git a/src/java/main/org/apache/zookeeper/ZKUtil.java b/src/java/main/org/apache/zookeeper/ZKUtil.java index 4713a08a934..a6abf2f42e1 100644 --- a/src/java/main/org/apache/zookeeper/ZKUtil.java +++ b/src/java/main/org/apache/zookeeper/ZKUtil.java @@ -18,34 +18,37 @@ package org.apache.zookeeper; import java.util.ArrayList; +import java.util.Collections; import java.util.Deque; import java.util.LinkedList; import java.util.List; +import org.apache.zookeeper.AsyncCallback.StringCallback; import org.apache.zookeeper.AsyncCallback.VoidCallback; +import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.common.PathUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - + public class ZKUtil { private static final Logger LOG = LoggerFactory.getLogger(ZKUtil.class); /** - * Recursively delete the node with the given path. + * Recursively delete the node with the given path. *

    * Important: All versions, of all nodes, under the given node are deleted. *

    - * If there is an error with deleting one of the sub-nodes in the tree, + * If there is an error with deleting one of the sub-nodes in the tree, * this operation would abort and would be the responsibility of the app to handle the same. - * + * * See {@link #delete(String, int)} for more details. - * + * * @throws IllegalArgumentException if an invalid path is specified */ public static void deleteRecursive(ZooKeeper zk, final String pathRoot) throws InterruptedException, KeeperException { PathUtils.validatePath(pathRoot); - + List tree = listSubTreeBFS(zk, pathRoot); LOG.debug("Deleting " + tree); LOG.debug("Deleting " + tree.size() + " subnodes "); @@ -54,15 +57,15 @@ public static void deleteRecursive(ZooKeeper zk, final String pathRoot) zk.delete(tree.get(i), -1); //Delete all versions of the node with -1. } } - + /** * Recursively delete the node with the given path. (async version). - * + * *

    * Important: All versions, of all nodes, under the given node are deleted. *

    - * If there is an error with deleting one of the sub-nodes in the tree, + * If there is an error with deleting one of the sub-nodes in the tree, * this operation would abort and would be the responsibility of the app to handle the same. *

    * @param zk the zookeeper handle @@ -76,7 +79,7 @@ public static void deleteRecursive(ZooKeeper zk, final String pathRoot, VoidCall throws InterruptedException, KeeperException { PathUtils.validatePath(pathRoot); - + List tree = listSubTreeBFS(zk, pathRoot); LOG.debug("Deleting " + tree); LOG.debug("Deleting " + tree.size() + " subnodes "); @@ -85,22 +88,22 @@ public static void deleteRecursive(ZooKeeper zk, final String pathRoot, VoidCall zk.delete(tree.get(i), -1, cb, ctx); //Delete all versions of the node with -1. } } - + /** - * BFS Traversal of the system under pathRoot, with the entries in the list, in the + * BFS Traversal of the system under pathRoot, with the entries in the list, in the * same order as that of the traversal. *

    * Important: This is not an atomic snapshot of the tree ever, but the * state as it exists across multiple RPCs from zkClient to the ensemble. - * For practical purposes, it is suggested to bring the clients to the ensemble - * down (i.e. prevent writes to pathRoot) to 'simulate' a snapshot behavior. - * + * For practical purposes, it is suggested to bring the clients to the ensemble + * down (i.e. prevent writes to pathRoot) to 'simulate' a snapshot behavior. + * * @param zk the zookeeper handle * @param pathRoot The znode path, for which the entire subtree needs to be listed. - * @throws InterruptedException - * @throws KeeperException + * @throws InterruptedException + * @throws KeeperException */ - public static List listSubTreeBFS(ZooKeeper zk, final String pathRoot) throws + public static List listSubTreeBFS(ZooKeeper zk, final String pathRoot) throws KeeperException, InterruptedException { Deque queue = new LinkedList(); List tree = new ArrayList(); @@ -120,5 +123,49 @@ public static List listSubTreeBFS(ZooKeeper zk, final String pathRoot) t } return tree; } - + + /** + * Visits the subtree with root as given path and calls the passed callback with each znode + * found during the search. It performs a depth-first, pre-order traversal of the tree. + *

    + * Important: This is not an atomic snapshot of the tree ever, but the + * state as it exists across multiple RPCs from zkClient to the ensemble. + * For practical purposes, it is suggested to bring the clients to the ensemble + * down (i.e. prevent writes to pathRoot) to 'simulate' a snapshot behavior. + */ + public static void visitSubTreeDFS(ZooKeeper zk, final String path, boolean watch, + StringCallback cb) throws KeeperException, InterruptedException { + PathUtils.validatePath(path); + + zk.getData(path, watch, null); + cb.processResult(Code.OK.intValue(), path, null, path); + visitSubTreeDFSHelper(zk, path, watch, cb); + } + + @SuppressWarnings("unchecked") + private static void visitSubTreeDFSHelper(ZooKeeper zk, final String path, + boolean watch, StringCallback cb) + throws KeeperException, InterruptedException { + // we've already validated, therefore if the path is of length 1 it's the root + final boolean isRoot = path.length() == 1; + try { + List children = zk.getChildren(path, watch, null); + Collections.sort(children); + + for (String child : children) { + String childPath = (isRoot ? path : path + "/") + child; + cb.processResult(Code.OK.intValue(), childPath, null, child); + } + + for (String child : children) { + String childPath = (isRoot ? path : path + "/") + child; + visitSubTreeDFSHelper(zk, childPath, watch, cb); + } + } + catch (KeeperException.NoNodeException e) { + // Handle race condition where a node is listed + // but gets deleted before it can be queried + return; // ignore + } + } } \ No newline at end of file diff --git a/src/java/main/org/apache/zookeeper/ZooDefs.java b/src/java/main/org/apache/zookeeper/ZooDefs.java index a4fc3310b5e..f685e32e2a7 100644 --- a/src/java/main/org/apache/zookeeper/ZooDefs.java +++ b/src/java/main/org/apache/zookeeper/ZooDefs.java @@ -21,13 +21,16 @@ import java.util.ArrayList; import java.util.Collections; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Id; +@InterfaceAudience.Public public class ZooDefs { final public static String CONFIG_NODE = "/zookeeper/config"; - + + @InterfaceAudience.Public public interface OpCode { public final int notification = 0; @@ -65,6 +68,12 @@ public interface OpCode { public final int removeWatches = 18; + public final int createContainer = 19; + + public final int deleteContainer = 20; + + public final int createTTL = 21; + public final int auth = 100; public final int setWatches = 101; @@ -78,6 +87,7 @@ public interface OpCode { public final int error = -1; } + @InterfaceAudience.Public public interface Perms { int READ = 1 << 0; @@ -92,6 +102,7 @@ public interface Perms { int ALL = READ | WRITE | CREATE | DELETE | ADMIN; } + @InterfaceAudience.Public public interface Ids { /** * This Id represents anyone. diff --git a/src/java/main/org/apache/zookeeper/ZooKeeper.java b/src/java/main/org/apache/zookeeper/ZooKeeper.java index dd13cc9ba50..6b3628bdaa3 100644 --- a/src/java/main/org/apache/zookeeper/ZooKeeper.java +++ b/src/java/main/org/apache/zookeeper/ZooKeeper.java @@ -18,18 +18,8 @@ package org.apache.zookeeper; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import org.apache.jute.Record; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.AsyncCallback.ACLCallback; import org.apache.zookeeper.AsyncCallback.Children2Callback; import org.apache.zookeeper.AsyncCallback.ChildrenCallback; @@ -45,17 +35,19 @@ import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.WatcherType; import org.apache.zookeeper.client.ConnectStringParser; +import org.apache.zookeeper.client.HostProvider; import org.apache.zookeeper.client.StaticHostProvider; +import org.apache.zookeeper.client.ZKClientConfig; import org.apache.zookeeper.client.ZooKeeperSaslClient; import org.apache.zookeeper.common.PathUtils; import org.apache.zookeeper.common.StringUtils; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.proto.CheckWatchesRequest; -import org.apache.zookeeper.proto.Create2Request; import org.apache.zookeeper.proto.Create2Response; import org.apache.zookeeper.proto.CreateRequest; import org.apache.zookeeper.proto.CreateResponse; +import org.apache.zookeeper.proto.CreateTTLRequest; import org.apache.zookeeper.proto.DeleteRequest; import org.apache.zookeeper.proto.ExistsRequest; import org.apache.zookeeper.proto.GetACLRequest; @@ -77,9 +69,22 @@ import org.apache.zookeeper.proto.SyncRequest; import org.apache.zookeeper.proto.SyncResponse; import org.apache.zookeeper.server.DataTree; +import org.apache.zookeeper.server.EphemeralType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * This is the main class of ZooKeeper client library. To use a ZooKeeper * service, an application must first instantiate an object of ZooKeeper class. @@ -120,16 +125,38 @@ * A client needs an object of a class implementing Watcher interface for * processing the events delivered to the client. * - * When a client drops current connection and re-connects to a server, all the + * When a client drops the current connection and re-connects to a server, all the * existing watches are considered as being triggered but the undelivered events * are lost. To emulate this, the client will generate a special event to tell - * the event handler a connection has been dropped. This special event has type - * EventNone and state sKeeperStateDisconnected. + * the event handler a connection has been dropped. This special event has + * EventType None and KeeperState Disconnected. * */ -public class ZooKeeper { +/* + * We suppress the "try" warning here because the close() method's signature + * allows it to throw InterruptedException which is strongly advised against + * by AutoCloseable (see: http://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html#close()). + * close() will never throw an InterruptedException but the exception remains in the + * signature for backwards compatibility purposes. +*/ +@SuppressWarnings("try") +@InterfaceAudience.Public +public class ZooKeeper implements AutoCloseable { + /** + * @deprecated Use {@link ZKClientConfig#ZOOKEEPER_CLIENT_CNXN_SOCKET} + * instead. + */ + @Deprecated public static final String ZOOKEEPER_CLIENT_CNXN_SOCKET = "zookeeper.clientCnxnSocket"; + // Setting this to "true" will enable encrypted client-server communication. + + /** + * @deprecated Use {@link ZKClientConfig#SECURE_CLIENT} + * instead. + */ + @Deprecated + public static final String SECURE_CLIENT = "zookeeper.client.secure"; protected final ClientCnxn cnxn; private static final Logger LOG; @@ -139,8 +166,8 @@ public class ZooKeeper { Environment.logEnv("Client environment:", LOG); } - private final StaticHostProvider hostProvider; - + protected final HostProvider hostProvider; + /** * This function allows a client to update the connection string by providing * a new comma separated list of host:port pairs, each corresponding to a @@ -198,21 +225,27 @@ public ZooKeeperSaslClient getSaslClient() { return cnxn.zooKeeperSaslClient; } - private final ZKWatchManager watchManager; + protected final ZKWatchManager watchManager; - List getDataWatches() { + private final ZKClientConfig clientConfig; + + public ZKClientConfig getClientConfig() { + return clientConfig; + } + + protected List getDataWatches() { synchronized(watchManager.dataWatches) { List rc = new ArrayList(watchManager.dataWatches.keySet()); return rc; } } - List getExistWatches() { + protected List getExistWatches() { synchronized(watchManager.existWatches) { List rc = new ArrayList(watchManager.existWatches.keySet()); return rc; } } - List getChildWatches() { + protected List getChildWatches() { synchronized(watchManager.childWatches) { List rc = new ArrayList(watchManager.childWatches.keySet()); return rc; @@ -233,8 +266,13 @@ static class ZKWatchManager implements ClientWatchManager { new HashMap>(); private final Map> childWatches = new HashMap>(); + private boolean disableAutoWatchReset; + + ZKWatchManager(boolean disableAutoWatchReset) { + this.disableAutoWatchReset = disableAutoWatchReset; + } - private volatile Watcher defaultWatcher; + protected volatile Watcher defaultWatcher; final private void addTo(Set from, Set to) { if (from != null) { @@ -428,9 +466,7 @@ public Set materialize(Watcher.Event.KeeperState state, switch (type) { case None: result.add(defaultWatcher); - boolean clear = ClientCnxn.getDisableAutoResetWatch() && - state != Watcher.Event.KeeperState.SyncConnected; - + boolean clear = disableAutoWatchReset && state != Watcher.Event.KeeperState.SyncConnected; synchronized(dataWatches) { for(Set ws: dataWatches.values()) { result.addAll(ws); @@ -481,7 +517,7 @@ public Set materialize(Watcher.Event.KeeperState state, synchronized (existWatches) { Set list = existWatches.remove(clientPath); if (list != null) { - addTo(existWatches.remove(clientPath), result); + addTo(list, result); LOG.warn("We are triggering an exists watch for delete! Shouldn't happen!"); } } @@ -503,7 +539,7 @@ public Set materialize(Watcher.Event.KeeperState state, /** * Register a watcher for a particular path. */ - abstract class WatchRegistration { + public abstract class WatchRegistration { private Watcher watcher; private String clientPath; public WatchRegistration(Watcher watcher, String clientPath) @@ -584,6 +620,7 @@ protected Map> getWatches(int rc) { } } + @InterfaceAudience.Public public enum States { CONNECTING, ASSOCIATING, CONNECTED, CONNECTEDREADONLY, CLOSED, AUTH_FAILED, NOT_CONNECTED; @@ -688,6 +725,60 @@ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) * @param watcher * a watcher object which will be notified of state changes, may * also be notified for node events + * @param conf + * (added in 3.5.2) passing this conf object gives each client the flexibility of + * configuring properties differently compared to other instances + * @throws IOException + * in cases of network failure + * @throws IllegalArgumentException + * if an invalid chroot path is specified + */ + public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, + ZKClientConfig conf) throws IOException { + this(connectString, sessionTimeout, watcher, false, conf); + } + + /** + * To create a ZooKeeper client object, the application needs to pass a + * connection string containing a comma separated list of host:port pairs, + * each corresponding to a ZooKeeper server. + *

    + * Session establishment is asynchronous. This constructor will initiate + * connection to the server and return immediately - potentially (usually) + * before the session is fully established. The watcher argument specifies + * the watcher that will be notified of any changes in state. This + * notification can come at any point before or after the constructor call + * has returned. + *

    + * The instantiated ZooKeeper client object will pick an arbitrary server + * from the connectString and attempt to connect to it. If establishment of + * the connection fails, another server in the connect string will be tried + * (the order is non-deterministic, as we random shuffle the list), until a + * connection is established. The client will continue attempts until the + * session is explicitly closed. + *

    + * Added in 3.2.0: An optional "chroot" suffix may also be appended to the + * connection string. This will run the client commands while interpreting + * all paths relative to this root (similar to the unix chroot command). + *

    + * For backward compatibility, there is another version + * {@link #ZooKeeper(String, int, Watcher, boolean)} which uses + * default {@link StaticHostProvider} + * + * @param connectString + * comma separated host:port pairs, each corresponding to a zk + * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If + * the optional chroot suffix is used the example would look + * like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" + * where the client would be rooted at "/app/a" and all paths + * would be relative to this root - ie getting/setting/etc... + * "/foo/bar" would result in operations being run on + * "/app/a/foo/bar" (from the server perspective). + * @param sessionTimeout + * session timeout in milliseconds + * @param watcher + * a watcher object which will be notified of state changes, may + * also be notified for node events * @param canBeReadOnly * (added in 3.4) whether the created client is allowed to go to * read-only mode in case of partitioning. Read-only mode @@ -695,7 +786,9 @@ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) * servers but there's partitioned server it could reach, it * connects to one in read-only mode, i.e. read requests are * allowed while write requests are not. It continues seeking for - * majority in the background. + * majority in the background. + * @param aHostProvider + * use this as HostProvider to enable custom behaviour. * * @throws IOException * in cases of network failure @@ -703,26 +796,212 @@ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) * if an invalid chroot path is specified */ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, - boolean canBeReadOnly) - throws IOException - { + boolean canBeReadOnly, HostProvider aHostProvider) + throws IOException { + this(connectString, sessionTimeout, watcher, canBeReadOnly, + aHostProvider, null); + } + + + /** + * To create a ZooKeeper client object, the application needs to pass a + * connection string containing a comma separated list of host:port pairs, + * each corresponding to a ZooKeeper server. + *

    + * Session establishment is asynchronous. This constructor will initiate + * connection to the server and return immediately - potentially (usually) + * before the session is fully established. The watcher argument specifies + * the watcher that will be notified of any changes in state. This + * notification can come at any point before or after the constructor call + * has returned. + *

    + * The instantiated ZooKeeper client object will pick an arbitrary server + * from the connectString and attempt to connect to it. If establishment of + * the connection fails, another server in the connect string will be tried + * (the order is non-deterministic, as we random shuffle the list), until a + * connection is established. The client will continue attempts until the + * session is explicitly closed. + *

    + * Added in 3.2.0: An optional "chroot" suffix may also be appended to the + * connection string. This will run the client commands while interpreting + * all paths relative to this root (similar to the unix chroot command). + *

    + * For backward compatibility, there is another version + * {@link #ZooKeeper(String, int, Watcher, boolean)} which uses default + * {@link StaticHostProvider} + * + * @param connectString + * comma separated host:port pairs, each corresponding to a zk + * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If + * the optional chroot suffix is used the example would look + * like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" + * where the client would be rooted at "/app/a" and all paths + * would be relative to this root - ie getting/setting/etc... + * "/foo/bar" would result in operations being run on + * "/app/a/foo/bar" (from the server perspective). + * @param sessionTimeout + * session timeout in milliseconds + * @param watcher + * a watcher object which will be notified of state changes, may + * also be notified for node events + * @param canBeReadOnly + * (added in 3.4) whether the created client is allowed to go to + * read-only mode in case of partitioning. Read-only mode + * basically means that if the client can't find any majority + * servers but there's partitioned server it could reach, it + * connects to one in read-only mode, i.e. read requests are + * allowed while write requests are not. It continues seeking for + * majority in the background. + * @param aHostProvider + * use this as HostProvider to enable custom behaviour. + * @param clientConfig + * (added in 3.5.2) passing this conf object gives each client the flexibility of + * configuring properties differently compared to other instances + * @throws IOException + * in cases of network failure + * @throws IllegalArgumentException + * if an invalid chroot path is specified + */ + public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, + boolean canBeReadOnly, HostProvider aHostProvider, + ZKClientConfig clientConfig) throws IOException { LOG.info("Initiating client connection, connectString=" + connectString + " sessionTimeout=" + sessionTimeout + " watcher=" + watcher); + if (clientConfig == null) { + clientConfig = new ZKClientConfig(); + } + this.clientConfig = clientConfig; watchManager = defaultWatchManager(); watchManager.defaultWatcher = watcher; - ConnectStringParser connectStringParser = new ConnectStringParser( connectString); - - hostProvider = new StaticHostProvider( - connectStringParser.getServerAddresses()); + hostProvider = aHostProvider; + cnxn = new ClientCnxn(connectStringParser.getChrootPath(), hostProvider, sessionTimeout, this, watchManager, getClientCnxnSocket(), canBeReadOnly); cnxn.start(); } + /** + * To create a ZooKeeper client object, the application needs to pass a + * connection string containing a comma separated list of host:port pairs, + * each corresponding to a ZooKeeper server. + *

    + * Session establishment is asynchronous. This constructor will initiate + * connection to the server and return immediately - potentially (usually) + * before the session is fully established. The watcher argument specifies + * the watcher that will be notified of any changes in state. This + * notification can come at any point before or after the constructor call + * has returned. + *

    + * The instantiated ZooKeeper client object will pick an arbitrary server + * from the connectString and attempt to connect to it. If establishment of + * the connection fails, another server in the connect string will be tried + * (the order is non-deterministic, as we random shuffle the list), until a + * connection is established. The client will continue attempts until the + * session is explicitly closed. + *

    + * Added in 3.2.0: An optional "chroot" suffix may also be appended to the + * connection string. This will run the client commands while interpreting + * all paths relative to this root (similar to the unix chroot command). + *

    + * + * @param connectString + * comma separated host:port pairs, each corresponding to a zk + * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If + * the optional chroot suffix is used the example would look + * like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" + * where the client would be rooted at "/app/a" and all paths + * would be relative to this root - ie getting/setting/etc... + * "/foo/bar" would result in operations being run on + * "/app/a/foo/bar" (from the server perspective). + * @param sessionTimeout + * session timeout in milliseconds + * @param watcher + * a watcher object which will be notified of state changes, may + * also be notified for node events + * @param canBeReadOnly + * (added in 3.4) whether the created client is allowed to go to + * read-only mode in case of partitioning. Read-only mode + * basically means that if the client can't find any majority + * servers but there's partitioned server it could reach, it + * connects to one in read-only mode, i.e. read requests are + * allowed while write requests are not. It continues seeking for + * majority in the background. + * + * @throws IOException + * in cases of network failure + * @throws IllegalArgumentException + * if an invalid chroot path is specified + */ + public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, + boolean canBeReadOnly) throws IOException { + this(connectString, sessionTimeout, watcher, canBeReadOnly, + createDefaultHostProvider(connectString)); + } + + /** + * To create a ZooKeeper client object, the application needs to pass a + * connection string containing a comma separated list of host:port pairs, + * each corresponding to a ZooKeeper server. + *

    + * Session establishment is asynchronous. This constructor will initiate + * connection to the server and return immediately - potentially (usually) + * before the session is fully established. The watcher argument specifies + * the watcher that will be notified of any changes in state. This + * notification can come at any point before or after the constructor call + * has returned. + *

    + * The instantiated ZooKeeper client object will pick an arbitrary server + * from the connectString and attempt to connect to it. If establishment of + * the connection fails, another server in the connect string will be tried + * (the order is non-deterministic, as we random shuffle the list), until a + * connection is established. The client will continue attempts until the + * session is explicitly closed. + *

    + * Added in 3.2.0: An optional "chroot" suffix may also be appended to the + * connection string. This will run the client commands while interpreting + * all paths relative to this root (similar to the unix chroot command). + *

    + * + * @param connectString + * comma separated host:port pairs, each corresponding to a zk + * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If + * the optional chroot suffix is used the example would look + * like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" + * where the client would be rooted at "/app/a" and all paths + * would be relative to this root - ie getting/setting/etc... + * "/foo/bar" would result in operations being run on + * "/app/a/foo/bar" (from the server perspective). + * @param sessionTimeout + * session timeout in milliseconds + * @param watcher + * a watcher object which will be notified of state changes, may + * also be notified for node events + * @param canBeReadOnly + * (added in 3.4) whether the created client is allowed to go to + * read-only mode in case of partitioning. Read-only mode + * basically means that if the client can't find any majority + * servers but there's partitioned server it could reach, it + * connects to one in read-only mode, i.e. read requests are + * allowed while write requests are not. It continues seeking for + * majority in the background. + * @param conf + * (added in 3.5.2) passing this conf object gives each client the flexibility of + * configuring properties differently compared to other instances + * @throws IOException + * in cases of network failure + * @throws IllegalArgumentException + * if an invalid chroot path is specified + */ + public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, + boolean canBeReadOnly, ZKClientConfig conf) throws IOException { + this(connectString, sessionTimeout, watcher, canBeReadOnly, + createDefaultHostProvider(connectString), conf); + } + /** * To create a ZooKeeper client object, the application needs to pass a * connection string containing a comma separated list of host:port pairs, @@ -810,6 +1089,10 @@ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, * sessionPasswd respectively if reconnecting. Otherwise, if not * reconnecting, use the other constructor which does not require these * parameters. + *

    + * For backward compatibility, there is another version + * {@link #ZooKeeper(String, int, Watcher, long, byte[], boolean)} which uses + * default {@link StaticHostProvider} * * @param connectString * comma separated host:port pairs, each corresponding to a zk @@ -837,14 +1120,14 @@ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, * connects to one in read-only mode, i.e. read requests are * allowed while write requests are not. It continues seeking for * majority in the background. - * + * @param aHostProvider + * use this as HostProvider to enable custom behaviour. * @throws IOException in cases of network failure * @throws IllegalArgumentException if an invalid chroot path is specified */ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, - long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) - throws IOException - { + long sessionId, byte[] sessionPasswd, boolean canBeReadOnly, + HostProvider aHostProvider) throws IOException { LOG.info("Initiating client connection, connectString=" + connectString + " sessionTimeout=" + sessionTimeout + " watcher=" + watcher @@ -852,13 +1135,14 @@ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, + " sessionPasswd=" + (sessionPasswd == null ? "" : "")); + this.clientConfig = new ZKClientConfig(); watchManager = defaultWatchManager(); watchManager.defaultWatcher = watcher; ConnectStringParser connectStringParser = new ConnectStringParser( connectString); - hostProvider = new StaticHostProvider( - connectStringParser.getServerAddresses()); + hostProvider = aHostProvider; + cnxn = new ClientCnxn(connectStringParser.getChrootPath(), hostProvider, sessionTimeout, this, watchManager, getClientCnxnSocket(), sessionId, sessionPasswd, canBeReadOnly); @@ -866,6 +1150,80 @@ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, cnxn.start(); } + /** + * To create a ZooKeeper client object, the application needs to pass a + * connection string containing a comma separated list of host:port pairs, + * each corresponding to a ZooKeeper server. + *

    + * Session establishment is asynchronous. This constructor will initiate + * connection to the server and return immediately - potentially (usually) + * before the session is fully established. The watcher argument specifies + * the watcher that will be notified of any changes in state. This + * notification can come at any point before or after the constructor call + * has returned. + *

    + * The instantiated ZooKeeper client object will pick an arbitrary server + * from the connectString and attempt to connect to it. If establishment of + * the connection fails, another server in the connect string will be tried + * (the order is non-deterministic, as we random shuffle the list), until a + * connection is established. The client will continue attempts until the + * session is explicitly closed (or the session is expired by the server). + *

    + * Added in 3.2.0: An optional "chroot" suffix may also be appended to the + * connection string. This will run the client commands while interpreting + * all paths relative to this root (similar to the unix chroot command). + *

    + * Use {@link #getSessionId} and {@link #getSessionPasswd} on an established + * client connection, these values must be passed as sessionId and + * sessionPasswd respectively if reconnecting. Otherwise, if not + * reconnecting, use the other constructor which does not require these + * parameters. + *

    + * This constructor uses a StaticHostProvider; there is another one + * to enable custom behaviour. + * + * @param connectString + * comma separated host:port pairs, each corresponding to a zk + * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" + * If the optional chroot suffix is used the example would look + * like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" + * where the client would be rooted at "/app/a" and all paths + * would be relative to this root - ie getting/setting/etc... + * "/foo/bar" would result in operations being run on + * "/app/a/foo/bar" (from the server perspective). + * @param sessionTimeout + * session timeout in milliseconds + * @param watcher + * a watcher object which will be notified of state changes, may + * also be notified for node events + * @param sessionId + * specific session id to use if reconnecting + * @param sessionPasswd + * password for this session + * @param canBeReadOnly + * (added in 3.4) whether the created client is allowed to go to + * read-only mode in case of partitioning. Read-only mode + * basically means that if the client can't find any majority + * servers but there's partitioned server it could reach, it + * connects to one in read-only mode, i.e. read requests are + * allowed while write requests are not. It continues seeking for + * majority in the background. + * @throws IOException in cases of network failure + * @throws IllegalArgumentException if an invalid chroot path is specified + */ + public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, + long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) + throws IOException { + this(connectString, sessionTimeout, watcher, sessionId, sessionPasswd, + canBeReadOnly, createDefaultHostProvider(connectString)); + } + + // default hostprovider + private static HostProvider createDefaultHostProvider(String connectString) { + return new StaticHostProvider( + new ConnectStringParser(connectString).getServerAddresses()); + } + // VisibleForTesting public Testable getTestable() { return new ZooKeeperTestable(this, cnxn); @@ -873,7 +1231,7 @@ public Testable getTestable() { /* Useful for testing watch handling behavior */ protected ZKWatchManager defaultWatchManager() { - return new ZKWatchManager(); + return new ZKWatchManager(getClientConfig().getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET)); } /** @@ -942,6 +1300,14 @@ public synchronized void register(Watcher watcher) { * invalid. All the ephemeral nodes in the ZooKeeper server associated with * the session will be removed. The watches left on those nodes (and on * their parents) will be triggered. + *

    + * Added in 3.5.3: try-with-resources + * may be used instead of calling close directly. + *

    + *

    + * This method does not wait for all internal threads to exit. + * Use the {@link #close(int) } method to wait for all resources to be released + *

    * * @throws InterruptedException */ @@ -968,6 +1334,22 @@ public synchronized void close() throws InterruptedException { LOG.info("Session: 0x" + Long.toHexString(getSessionId()) + " closed"); } + /** + * Close this client object as the {@link #close() } method. + * This method will wait for internal resources to be released. + * + * @param waitForShutdownTimeoutMs timeout (in milliseconds) to wait for resources to be released. + * Use zero or a negative value to skip the wait + * @throws InterruptedException + * @return true if waitForShutdownTimeout is greater than zero and all of the resources have been released + * + * @since 3.5.4 + */ + public boolean close(int waitForShutdownTimeoutMs) throws InterruptedException { + close(); + return testableWaitForShutdown(waitForShutdownTimeoutMs); + } + /** * Prepend the chroot to the client path (if present). The expectation of * this function is that the client path has been validated before this @@ -1048,11 +1430,12 @@ public String create(final String path, byte data[], List acl, { final String clientPath = path; PathUtils.validatePath(clientPath, createMode.isSequential()); + EphemeralType.validateTTL(createMode, -1); final String serverPath = prependChroot(clientPath); RequestHeader h = new RequestHeader(); - h.setType(ZooDefs.OpCode.create); + h.setType(createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create); CreateRequest request = new CreateRequest(); CreateResponse response = new CreateResponse(); request.setData(data); @@ -1134,23 +1517,34 @@ public String create(final String path, byte data[], List acl, public String create(final String path, byte data[], List acl, CreateMode createMode, Stat stat) throws KeeperException, InterruptedException { + return create(path, data, acl, createMode, stat, -1); + } + + /** + * same as {@link #create(String, byte[], List, CreateMode, Stat)} but + * allows for specifying a TTL when mode is {@link CreateMode#PERSISTENT_WITH_TTL} + * or {@link CreateMode#PERSISTENT_SEQUENTIAL_WITH_TTL}. If the znode has not been modified + * within the given TTL, it will be deleted once it has no children. The TTL unit is + * milliseconds and must be greater than 0 and less than or equal to + * {@link EphemeralType#maxValue()} for {@link EphemeralType#TTL}. + */ + public String create(final String path, byte data[], List acl, + CreateMode createMode, Stat stat, long ttl) + throws KeeperException, InterruptedException { final String clientPath = path; PathUtils.validatePath(clientPath, createMode.isSequential()); + EphemeralType.validateTTL(createMode, ttl); final String serverPath = prependChroot(clientPath); RequestHeader h = new RequestHeader(); - h.setType(ZooDefs.OpCode.create2); - Create2Request request = new Create2Request(); + setCreateHeader(createMode, h); Create2Response response = new Create2Response(); - request.setData(data); - request.setFlags(createMode.toFlag()); - request.setPath(serverPath); if (acl != null && acl.size() == 0) { throw new KeeperException.InvalidACLException(); } - request.setAcl(acl); - ReplyHeader r = cnxn.submitRequest(h, request, response, null); + Record record = makeCreateRecord(createMode, serverPath, data, acl, ttl); + ReplyHeader r = cnxn.submitRequest(h, record, response, null); if (r.getErr() != 0) { throw KeeperException.create(KeeperException.Code.get(r.getErr()), clientPath); @@ -1165,6 +1559,35 @@ public String create(final String path, byte data[], List acl, } } + private void setCreateHeader(CreateMode createMode, RequestHeader h) { + if (createMode.isTTL()) { + h.setType(ZooDefs.OpCode.createTTL); + } else { + h.setType(createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create2); + } + } + + private Record makeCreateRecord(CreateMode createMode, String serverPath, byte[] data, List acl, long ttl) { + Record record; + if (createMode.isTTL()) { + CreateTTLRequest request = new CreateTTLRequest(); + request.setData(data); + request.setFlags(createMode.toFlag()); + request.setPath(serverPath); + request.setAcl(acl); + request.setTtl(ttl); + record = request; + } else { + CreateRequest request = new CreateRequest(); + request.setData(data); + request.setFlags(createMode.toFlag()); + request.setPath(serverPath); + request.setAcl(acl); + record = request; + } + return record; + } + /** * The asynchronous version of create. * @@ -1175,11 +1598,12 @@ public void create(final String path, byte data[], List acl, { final String clientPath = path; PathUtils.validatePath(clientPath, createMode.isSequential()); + EphemeralType.validateTTL(createMode, -1); final String serverPath = prependChroot(clientPath); RequestHeader h = new RequestHeader(); - h.setType(ZooDefs.OpCode.create); + h.setType(createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create); CreateRequest request = new CreateRequest(); CreateResponse response = new CreateResponse(); ReplyHeader r = new ReplyHeader(); @@ -1198,22 +1622,30 @@ public void create(final String path, byte data[], List acl, */ public void create(final String path, byte data[], List acl, CreateMode createMode, Create2Callback cb, Object ctx) + { + create(path, data, acl, createMode, cb, ctx, -1); + } + + /** + * The asynchronous version of create with ttl. + * + * @see #create(String, byte[], List, CreateMode, Stat, long) + */ + public void create(final String path, byte data[], List acl, + CreateMode createMode, Create2Callback cb, Object ctx, long ttl) { final String clientPath = path; PathUtils.validatePath(clientPath, createMode.isSequential()); + EphemeralType.validateTTL(createMode, ttl); final String serverPath = prependChroot(clientPath); RequestHeader h = new RequestHeader(); - h.setType(ZooDefs.OpCode.create2); - Create2Request request = new Create2Request(); - Create2Response response = new Create2Response(); + setCreateHeader(createMode, h); ReplyHeader r = new ReplyHeader(); - request.setData(data); - request.setFlags(createMode.toFlag()); - request.setPath(serverPath); - request.setAcl(acl); - cnxn.queuePacket(h, r, request, response, cb, clientPath, + Create2Response response = new Create2Response(); + Record record = makeCreateRecord(createMode, serverPath, data, acl, ttl); + cnxn.queuePacket(h, r, record, response, cb, clientPath, serverPath, ctx, null); } @@ -1780,86 +2212,42 @@ public byte[] getConfig(boolean watch, Stat stat) public void getConfig(boolean watch, DataCallback cb, Object ctx) { getConfig(watch ? watchManager.defaultWatcher : null, cb, ctx); } - + /** - * Reconfigure - add/remove servers. Return the new configuration. - * @param joiningServers - * a comma separated list of servers being added (incremental reconfiguration) - * @param leavingServers - * a comma separated list of servers being removed (incremental reconfiguration) - * @param newMembers - * a comma separated list of new membership (non-incremental reconfiguration) - * @param fromConfig - * version of the current configuration (optional - causes reconfiguration to throw an exception if configuration is no longer current) - * @param stat the stat of /zookeeper/config znode will be copied to this - * parameter if not null. - * @return new configuration - * @throws InterruptedException If the server transaction is interrupted. - * @throws KeeperException If the server signals an error with a non-zero error code. + * @deprecated instead use the reconfigure() methods instead in {@link org.apache.zookeeper.admin.ZooKeeperAdmin} */ - public byte[] reconfig(String joiningServers, String leavingServers, String newMembers, long fromConfig, Stat stat) throws KeeperException, InterruptedException - { - RequestHeader h = new RequestHeader(); - h.setType(ZooDefs.OpCode.reconfig); - ReconfigRequest request = new ReconfigRequest(joiningServers, leavingServers, newMembers, fromConfig); - GetDataResponse response = new GetDataResponse(); - ReplyHeader r = cnxn.submitRequest(h, request, response, null); - if (r.getErr() != 0) { - throw KeeperException.create(KeeperException.Code.get(r.getErr()), ""); - } - if (stat != null) { - DataTree.copyStat(response.getStat(), stat); - } - return response.getData(); + @Deprecated + public byte[] reconfig(String joiningServers, String leavingServers, String newMembers, long fromConfig, Stat stat) throws KeeperException, InterruptedException { + return internalReconfig(joiningServers, leavingServers, newMembers, fromConfig, stat); } /** - * Convenience wrapper around reconfig that takes Lists of strings instead of comma-separated servers. - * - * @see #reconfig - * + * @deprecated instead use the reconfigure() methods instead in {@link org.apache.zookeeper.admin.ZooKeeperAdmin} */ - public byte[] reconfig(List joiningServers, List leavingServers, List newMembers, long fromConfig, Stat stat) throws KeeperException, InterruptedException - { - return reconfig(StringUtils.joinStrings(joiningServers, ","), - StringUtils.joinStrings(leavingServers, ","), - StringUtils.joinStrings(newMembers, ","), - fromConfig, stat); + @Deprecated + public byte[] reconfig(List joiningServers, List leavingServers, List newMembers, long fromConfig, Stat stat) throws KeeperException, InterruptedException { + return internalReconfig(joiningServers, leavingServers, newMembers, fromConfig, stat); } /** - * The Asynchronous version of reconfig. - * - * @see #reconfig - * - **/ + * @deprecated instead use the reconfigure() methods instead in {@link org.apache.zookeeper.admin.ZooKeeperAdmin} + */ + @Deprecated public void reconfig(String joiningServers, String leavingServers, - String newMembers, long fromConfig, DataCallback cb, Object ctx) - { - RequestHeader h = new RequestHeader(); - h.setType(ZooDefs.OpCode.reconfig); - ReconfigRequest request = new ReconfigRequest(joiningServers, leavingServers, newMembers, fromConfig); - GetDataResponse response = new GetDataResponse(); - cnxn.queuePacket(h, new ReplyHeader(), request, response, cb, - ZooDefs.CONFIG_NODE, ZooDefs.CONFIG_NODE, ctx, null); + String newMembers, long fromConfig, DataCallback cb, Object ctx) { + internalReconfig(joiningServers, leavingServers, newMembers, fromConfig, cb, ctx); } - + /** - * Convenience wrapper around asynchronous reconfig that takes Lists of strings instead of comma-separated servers. - * - * @see #reconfig - * + * @deprecated instead use the reconfigure() methods instead in {@link org.apache.zookeeper.admin.ZooKeeperAdmin} */ + @Deprecated public void reconfig(List joiningServers, - List leavingServers, List newMembers, long fromConfig, - DataCallback cb, Object ctx) - { - reconfig(StringUtils.joinStrings(joiningServers, ","), - StringUtils.joinStrings(leavingServers, ","), - StringUtils.joinStrings(newMembers, ","), - fromConfig, cb, ctx); + List leavingServers, List newMembers, long fromConfig, + DataCallback cb, Object ctx) { + internalReconfig(joiningServers, leavingServers, newMembers, fromConfig, cb, ctx); } - + /** * Set the data for the node of the given path if such a node exists and the * given version matches the version of the node (if the given version is @@ -1999,25 +2387,25 @@ public void getACL(final String path, Stat stat, ACLCallback cb, /** * Set the ACL for the node of the given path if such a node exists and the - * given version matches the version of the node. Return the stat of the + * given aclVersion matches the acl version of the node. Return the stat of the * node. *

    * A KeeperException with error code KeeperException.NoNode will be thrown * if no node with the given path exists. *

    * A KeeperException with error code KeeperException.BadVersion will be - * thrown if the given version does not match the node's version. + * thrown if the given aclVersion does not match the node's aclVersion. * - * @param path - * @param acl - * @param version + * @param path the given path for the node + * @param acl the given acl for the node + * @param aclVersion the given acl version of the node * @return the stat of the node. * @throws InterruptedException If the server transaction is interrupted. * @throws KeeperException If the server signals an error with a non-zero error code. * @throws org.apache.zookeeper.KeeperException.InvalidACLException If the acl is invalide. * @throws IllegalArgumentException if an invalid path is specified */ - public Stat setACL(final String path, List acl, int version) + public Stat setACL(final String path, List acl, int aclVersion) throws KeeperException, InterruptedException { final String clientPath = path; @@ -2033,7 +2421,7 @@ public Stat setACL(final String path, List acl, int version) throw new KeeperException.InvalidACLException(clientPath); } request.setAcl(acl); - request.setVersion(version); + request.setVersion(aclVersion); SetACLResponse response = new SetACLResponse(); ReplyHeader r = cnxn.submitRequest(h, request, response, null); if (r.getErr() != 0) { @@ -2353,7 +2741,7 @@ public void sync(final String path, VoidCallback cb, Object ctx){ * server connection * @throws InterruptedException * if the server transaction is interrupted. - * @throws KeeperException.NoWatcher + * @throws KeeperException.NoWatcherException * if no watcher exists that match the specified parameters * @throws KeeperException * if the server signals an error with a non-zero error code. @@ -2404,7 +2792,7 @@ public void removeWatches(String path, Watcher watcher, * server connection * @throws InterruptedException * if the server transaction is interrupted. - * @throws KeeperException.NoWatcher + * @throws KeeperException.NoWatcherException * if no watcher exists that match the specified parameters * @throws KeeperException * if the server signals an error with a non-zero error code. @@ -2577,15 +2965,16 @@ protected SocketAddress testableLocalSocketAddress() { return cnxn.sendThread.getClientCnxnSocket().getLocalSocketAddress(); } - private static ClientCnxnSocket getClientCnxnSocket() throws IOException { - String clientCnxnSocketName = System - .getProperty(ZOOKEEPER_CLIENT_CNXN_SOCKET); + private ClientCnxnSocket getClientCnxnSocket() throws IOException { + String clientCnxnSocketName = getClientConfig().getProperty( + ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET); if (clientCnxnSocketName == null) { clientCnxnSocketName = ClientCnxnSocketNIO.class.getName(); } try { - return (ClientCnxnSocket) Class.forName(clientCnxnSocketName) - .newInstance(); + Constructor clientCxnConstructor = Class.forName(clientCnxnSocketName).getDeclaredConstructor(ZKClientConfig.class); + ClientCnxnSocket clientCxnSocket = (ClientCnxnSocket) clientCxnConstructor.newInstance(getClientConfig()); + return clientCxnSocket; } catch (Exception e) { IOException ioe = new IOException("Couldn't instantiate " + clientCnxnSocketName); @@ -2593,4 +2982,45 @@ private static ClientCnxnSocket getClientCnxnSocket() throws IOException { throw ioe; } } + + protected byte[] internalReconfig(String joiningServers, String leavingServers, String newMembers, long fromConfig, Stat stat) throws KeeperException, InterruptedException { + RequestHeader h = new RequestHeader(); + h.setType(ZooDefs.OpCode.reconfig); + ReconfigRequest request = new ReconfigRequest(joiningServers, leavingServers, newMembers, fromConfig); + GetDataResponse response = new GetDataResponse(); + ReplyHeader r = cnxn.submitRequest(h, request, response, null); + if (r.getErr() != 0) { + throw KeeperException.create(KeeperException.Code.get(r.getErr()), ""); + } + if (stat != null) { + DataTree.copyStat(response.getStat(), stat); + } + return response.getData(); + } + + protected byte[] internalReconfig(List joiningServers, List leavingServers, List newMembers, long fromConfig, Stat stat) throws KeeperException, InterruptedException { + return internalReconfig(StringUtils.joinStrings(joiningServers, ","), + StringUtils.joinStrings(leavingServers, ","), + StringUtils.joinStrings(newMembers, ","), + fromConfig, stat); + } + + protected void internalReconfig(String joiningServers, String leavingServers, + String newMembers, long fromConfig, DataCallback cb, Object ctx) { + RequestHeader h = new RequestHeader(); + h.setType(ZooDefs.OpCode.reconfig); + ReconfigRequest request = new ReconfigRequest(joiningServers, leavingServers, newMembers, fromConfig); + GetDataResponse response = new GetDataResponse(); + cnxn.queuePacket(h, new ReplyHeader(), request, response, cb, + ZooDefs.CONFIG_NODE, ZooDefs.CONFIG_NODE, ctx, null); + } + + protected void internalReconfig(List joiningServers, + List leavingServers, List newMembers, long fromConfig, + DataCallback cb, Object ctx) { + internalReconfig(StringUtils.joinStrings(joiningServers, ","), + StringUtils.joinStrings(leavingServers, ","), + StringUtils.joinStrings(newMembers, ","), + fromConfig, cb, ctx); + } } diff --git a/src/java/main/org/apache/zookeeper/ZooKeeperMain.java b/src/java/main/org/apache/zookeeper/ZooKeeperMain.java index 496e88748cf..ccfb92c6392 100644 --- a/src/java/main/org/apache/zookeeper/ZooKeeperMain.java +++ b/src/java/main/org/apache/zookeeper/ZooKeeperMain.java @@ -34,12 +34,18 @@ import java.util.Map.Entry; import java.util.NoSuchElementException; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.zookeeper.cli.CliException; +import org.apache.zookeeper.cli.CommandNotFoundException; +import org.apache.zookeeper.cli.MalformedCommandException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.Stat; -import java.util.StringTokenizer; -import org.apache.commons.cli.ParseException; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.apache.zookeeper.cli.AddAuthCommand; import org.apache.zookeeper.cli.CliCommand; import org.apache.zookeeper.cli.CloseCommand; @@ -60,21 +66,25 @@ import org.apache.zookeeper.cli.SetQuotaCommand; import org.apache.zookeeper.cli.StatCommand; import org.apache.zookeeper.cli.SyncCommand; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.admin.ZooKeeperAdmin; /** * The command line client to ZooKeeper. * */ +@InterfaceAudience.Public public class ZooKeeperMain { private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperMain.class); - protected static final Map commandMap = new HashMap( ); - protected static final Map commandMapCli = + static final Map commandMap = new HashMap( ); + static final Map commandMapCli = new HashMap( ); protected MyCommandOptions cl = new MyCommandOptions(); protected HashMap history = new HashMap( ); protected int commandCount = 0; protected boolean printWatches = true; + protected int exitCode = 0; protected ZooKeeper zk; protected String host = ""; @@ -145,6 +155,8 @@ static class MyCommandOptions { private Map options = new HashMap(); private List cmdArgs = null; private String command = null; + public static final Pattern ARGS_PATTERN = Pattern.compile("\\s*([^\"\']\\S*|\"[^\"]*\"|'[^']*')\\s*"); + public static final Pattern QUOTED_PATTERN = Pattern.compile("^([\'\"])(.*)(\\1)$"); public MyCommandOptions() { options.put("server", "localhost:2181"); @@ -216,18 +228,22 @@ public boolean parseOptions(String[] args) { * @return true if parsing succeeded. */ public boolean parseCommand( String cmdstring ) { - StringTokenizer cmdTokens = new StringTokenizer(cmdstring, " "); - String[] args = new String[cmdTokens.countTokens()]; - int tokenIndex = 0; - while (cmdTokens.hasMoreTokens()) { - args[tokenIndex] = cmdTokens.nextToken(); - tokenIndex++; + Matcher matcher = ARGS_PATTERN.matcher(cmdstring); + + List args = new LinkedList(); + while (matcher.find()) { + String value = matcher.group(1); + if (QUOTED_PATTERN.matcher(value).matches()) { + // Strip off the surrounding quotes + value = value.substring(1, value.length() - 1); + } + args.add(value); } - if (args.length == 0){ + if (args.isEmpty()){ return false; } - command = args[0]; - cmdArgs = Arrays.asList(args); + command = args.get(0); + cmdArgs = args; return true; } } @@ -262,15 +278,17 @@ protected void connectToZK(String newHost) throws InterruptedException, IOExcept if (zk != null && zk.getState().isAlive()) { zk.close(); } + host = newHost; boolean readOnly = cl.getOption("readonly") != null; - zk = new ZooKeeper(host, - Integer.parseInt(cl.getOption("timeout")), - new MyWatcher(), readOnly); + if (cl.getOption("secure") != null) { + System.setProperty(ZKClientConfig.SECURE_CLIENT, "true"); + System.out.println("Secure connection is enabled"); + } + zk = new ZooKeeperAdmin(host, Integer.parseInt(cl.getOption("timeout")), new MyWatcher(), readOnly); } - public static void main(String args[]) - throws KeeperException, IOException, InterruptedException + public static void main(String args[]) throws CliException, IOException, InterruptedException { ZooKeeperMain main = new ZooKeeperMain(args); main.run(); @@ -280,15 +298,13 @@ public ZooKeeperMain(String args[]) throws IOException, InterruptedException { cl.parseOptions(args); System.out.println("Connecting to " + cl.getOption("server")); connectToZK(cl.getOption("server")); - //zk = new ZooKeeper(cl.getOption("server"), -// Integer.parseInt(cl.getOption("timeout")), new MyWatcher()); } public ZooKeeperMain(ZooKeeper zk) { this.zk = zk; } - void run() throws KeeperException, IOException, InterruptedException { + void run() throws CliException, IOException, InterruptedException { if (cl.getCommand() == null) { System.out.println("Welcome to ZooKeeper!"); @@ -346,10 +362,10 @@ void run() throws KeeperException, IOException, InterruptedException { // Command line args non-null. Run what was passed. processCmd(cl); } + System.exit(exitCode); } - public void executeLine(String line) - throws InterruptedException, IOException, KeeperException { + public void executeLine(String line) throws CliException, InterruptedException, IOException { if (!line.equals("")) { cl.parseCommand(line); addToHistory(commandCount,line); @@ -566,80 +582,51 @@ public static boolean createQuota(ZooKeeper zk, String path, return true; } - protected boolean processCmd(MyCommandOptions co) - throws KeeperException, IOException, InterruptedException - { + protected boolean processCmd(MyCommandOptions co) throws CliException, IOException, InterruptedException { + boolean watch = false; try { - return processZKCmd(co); - } catch (IllegalArgumentException e) { - System.err.println("Command failed: " + e); - } catch (KeeperException.NoNodeException e) { - System.err.println("Node does not exist: " + e.getPath()); - } catch (KeeperException.NoChildrenForEphemeralsException e) { - System.err.println("Ephemerals cannot have children: " - + e.getPath()); - } catch (KeeperException.NodeExistsException e) { - System.err.println("Node already exists: " + e.getPath()); - } catch (KeeperException.NotEmptyException e) { - System.err.println("Node not empty: " + e.getPath()); - } catch (KeeperException.NotReadOnlyException e) { - System.err.println("Not a read-only call: " + e.getPath()); - }catch (KeeperException.InvalidACLException e) { - System.err.println("Acl is not valid : "+e.getPath()); - }catch (KeeperException.NoAuthException e) { - System.err.println("Authentication is not valid : "+e.getPath()); - }catch (KeeperException.BadArgumentsException e) { - System.err.println("Arguments are not valid : "+e.getPath()); - }catch (KeeperException.BadVersionException e) { - System.err.println("version No is not valid : "+e.getPath()); - }catch (KeeperException.ReconfigInProgress e) { - System.err.println("Another reconfiguration is in progress -- concurrent " + - "reconfigs not supported (yet)"); - }catch (KeeperException.NewConfigNoQuorum e) { - System.err.println("No quorum of new config is connected and " + - "up-to-date with the leader of last commmitted config - try invoking reconfiguration after " + - "new servers are connected and synced"); + watch = processZKCmd(co); + exitCode = 0; + } catch (CliException ex) { + exitCode = ex.getExitCode(); + System.err.println(ex.getMessage()); } - return false; + return watch; } - protected boolean processZKCmd(MyCommandOptions co) - throws KeeperException, IOException, InterruptedException - { + protected boolean processZKCmd(MyCommandOptions co) throws CliException, IOException, InterruptedException { String[] args = co.getArgArray(); String cmd = co.getCommand(); if (args.length < 1) { usage(); - return false; + throw new MalformedCommandException("No command entered"); } if (!commandMap.containsKey(cmd)) { usage(); - return false; + throw new CommandNotFoundException("Command not found " + cmd); } boolean watch = false; LOG.debug("Processing " + cmd); - - try { + + if (cmd.equals("quit")) { zk.close(); - System.exit(0); + System.exit(exitCode); } else if (cmd.equals("redo") && args.length >= 2) { Integer i = Integer.decode(args[1]); - if (commandCount <= i){ // don't allow redoing this redo - System.out.println("Command index out of range"); - return false; + if (commandCount <= i || i < 0) { // don't allow redoing this redo + throw new MalformedCommandException("Command index out of range"); } cl.parseCommand(history.get(i)); - if (cl.getCommand().equals( "redo" )){ - System.out.println("No redoing redos"); - return false; + if (cl.getCommand().equals("redo")) { + throw new MalformedCommandException("No redoing redos"); } history.put(commandCount, history.get(i)); - processCmd( cl); + processCmd(cl); } else if (cmd.equals("history")) { - for (int i=commandCount - 10;i<=commandCount;++i) { + for (int i = commandCount - 10; i <= commandCount; ++i) { if (i < 0) continue; System.out.println(i + " - " + history.get(i)); } @@ -650,12 +637,12 @@ protected boolean processZKCmd(MyCommandOptions co) printWatches = args[1].equals("on"); } } else if (cmd.equals("connect")) { - if (args.length >=2) { + if (args.length >= 2) { connectToZK(args[1]); } else { - connectToZK(host); + connectToZK(host); } - } + } // Below commands all need a live connection if (zk == null || !zk.getState().isAlive()) { @@ -671,12 +658,6 @@ protected boolean processZKCmd(MyCommandOptions co) } else if (!commandMap.containsKey(cmd)) { usage(); } - - } catch (ParseException ex) { - System.err.println(ex.getMessage()); - usage(); - return false; - } return watch; } } diff --git a/src/java/main/org/apache/zookeeper/ZooKeeperTestable.java b/src/java/main/org/apache/zookeeper/ZooKeeperTestable.java index 775d1a26d88..873615b3a38 100644 --- a/src/java/main/org/apache/zookeeper/ZooKeeperTestable.java +++ b/src/java/main/org/apache/zookeeper/ZooKeeperTestable.java @@ -41,7 +41,7 @@ public void injectSessionExpiration() { Watcher.Event.EventType.None, Watcher.Event.KeeperState.Expired, null)); clientCnxn.eventThread.queueEventOfDeath(); - clientCnxn.sendThread.getClientCnxnSocket().wakeupCnxn(); clientCnxn.state = ZooKeeper.States.CLOSED; + clientCnxn.sendThread.getClientCnxnSocket().onClosing(); } } diff --git a/src/java/main/org/apache/zookeeper/admin/ZooKeeperAdmin.java b/src/java/main/org/apache/zookeeper/admin/ZooKeeperAdmin.java new file mode 100644 index 00000000000..f12053ae213 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/admin/ZooKeeperAdmin.java @@ -0,0 +1,225 @@ +/** + * 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.admin; + +import java.io.IOException; +import java.util.List; + +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.AsyncCallback.DataCallback; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.data.Stat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is the main class for ZooKeeperAdmin client library. + * This library is used to perform cluster administration tasks, + * such as reconfigure cluster membership. The ZooKeeperAdmin class + * inherits ZooKeeper and has similar usage pattern as ZooKeeper class. + * Please check {@link ZooKeeper} class document for more details. + * + * @since 3.5.3 + */ +// See ZooKeeper.java for an explanation of why we need @SuppressWarnings("try") +@SuppressWarnings("try") +@InterfaceAudience.Public +public class ZooKeeperAdmin extends ZooKeeper { + private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperAdmin.class); + + /** + * Create a ZooKeeperAdmin object which is used to perform dynamic reconfiguration + * operations. + * + * @param connectString + * comma separated host:port pairs, each corresponding to a zk + * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If + * the optional chroot suffix is used the example would look + * like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" + * where the client would be rooted at "/app/a" and all paths + * would be relative to this root - ie getting/setting/etc... + * "/foo/bar" would result in operations being run on + * "/app/a/foo/bar" (from the server perspective). + * @param sessionTimeout + * session timeout in milliseconds + * @param watcher + * a watcher object which will be notified of state changes, may + * also be notified for node events + * + * @throws IOException + * in cases of network failure + * @throws IllegalArgumentException + * if an invalid chroot path is specified + * + * @see ZooKeeper#ZooKeeper(String, int, Watcher) + * + */ + public ZooKeeperAdmin(String connectString, int sessionTimeout, Watcher watcher) + throws IOException { + super(connectString, sessionTimeout, watcher); + } + + /** + * Create a ZooKeeperAdmin object which is used to perform dynamic reconfiguration + * operations. + * + * @param connectString + * comma separated host:port pairs, each corresponding to a zk + * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If + * the optional chroot suffix is used the example would look + * like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" + * where the client would be rooted at "/app/a" and all paths + * would be relative to this root - ie getting/setting/etc... + * "/foo/bar" would result in operations being run on + * "/app/a/foo/bar" (from the server perspective). + * @param sessionTimeout + * session timeout in milliseconds + * @param watcher + * a watcher object which will be notified of state changes, may + * also be notified for node events + * @param conf + * passing this conf object gives each client the flexibility of + * configuring properties differently compared to other instances + * + * @throws IOException + * in cases of network failure + * @throws IllegalArgumentException + * if an invalid chroot path is specified + * + * @see ZooKeeper#ZooKeeper(String, int, Watcher, ZKClientConfig) + */ + public ZooKeeperAdmin(String connectString, int sessionTimeout, Watcher watcher, + ZKClientConfig conf) throws IOException { + super(connectString, sessionTimeout, watcher, conf); + } + + /** + * Create a ZooKeeperAdmin object which is used to perform dynamic reconfiguration + * operations. + * + * @param connectString + * comma separated host:port pairs, each corresponding to a zk + * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" If + * the optional chroot suffix is used the example would look + * like: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" + * where the client would be rooted at "/app/a" and all paths + * would be relative to this root - ie getting/setting/etc... + * "/foo/bar" would result in operations being run on + * "/app/a/foo/bar" (from the server perspective). + * @param sessionTimeout + * session timeout in milliseconds + * @param watcher + * a watcher object which will be notified of state changes, may + * also be notified for node events + * @param canBeReadOnly + * whether the created client is allowed to go to + * read-only mode in case of partitioning. Read-only mode + * basically means that if the client can't find any majority + * servers but there's partitioned server it could reach, it + * connects to one in read-only mode, i.e. read requests are + * allowed while write requests are not. It continues seeking for + * majority in the background. + * + * @throws IOException + * in cases of network failure + * @throws IllegalArgumentException + * if an invalid chroot path is specified + * + * @see ZooKeeper#ZooKeeper(String, int, Watcher, boolean) + */ + public ZooKeeperAdmin(String connectString, int sessionTimeout, Watcher watcher, + boolean canBeReadOnly) throws IOException { + super(connectString, sessionTimeout, watcher, canBeReadOnly); + } + + /** + * Reconfigure - add/remove servers. Return the new configuration. + * @param joiningServers + * a comma separated list of servers being added (incremental reconfiguration) + * @param leavingServers + * a comma separated list of servers being removed (incremental reconfiguration) + * @param newMembers + * a comma separated list of new membership (non-incremental reconfiguration) + * @param fromConfig + * version of the current configuration + * (optional - causes reconfiguration to throw an exception if configuration is no longer current) + * @param stat the stat of /zookeeper/config znode will be copied to this + * parameter if not null. + * @return new configuration + * @throws InterruptedException If the server transaction is interrupted. + * @throws KeeperException If the server signals an error with a non-zero error code. + */ + public byte[] reconfigure(String joiningServers, String leavingServers, + String newMembers, long fromConfig, Stat stat) throws KeeperException, InterruptedException { + return internalReconfig(joiningServers, leavingServers, newMembers, fromConfig, stat); + } + + /** + * Convenience wrapper around reconfig that takes Lists of strings instead of comma-separated servers. + * + * @see #reconfigure + * + */ + public byte[] reconfigure(List joiningServers, List leavingServers, + List newMembers, long fromConfig, + Stat stat) throws KeeperException, InterruptedException { + return internalReconfig(joiningServers, leavingServers, newMembers, fromConfig, stat); + } + + /** + * The Asynchronous version of reconfig. + * + * @see #reconfigure + * + **/ + public void reconfigure(String joiningServers, String leavingServers, + String newMembers, long fromConfig, DataCallback cb, Object ctx) { + internalReconfig(joiningServers, leavingServers, newMembers, fromConfig, cb, ctx); + } + + /** + * Convenience wrapper around asynchronous reconfig that takes Lists of strings instead of comma-separated servers. + * + * @see #reconfigure + * + */ + public void reconfigure(List joiningServers, + List leavingServers, List newMembers, long fromConfig, + DataCallback cb, Object ctx) { + internalReconfig(joiningServers, leavingServers, newMembers, fromConfig, cb, ctx); + } + + /** + * String representation of this ZooKeeperAdmin client. Suitable for things + * like logging. + * + * Do NOT count on the format of this string, it may change without + * warning. + * + * @since 3.5.3 + */ + @Override + public String toString() { + return super.toString(); + } +} diff --git a/src/java/main/org/apache/zookeeper/cli/AddAuthCommand.java b/src/java/main/org/apache/zookeeper/cli/AddAuthCommand.java index e4887450c00..e2a333ab439 100644 --- a/src/java/main/org/apache/zookeeper/cli/AddAuthCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/AddAuthCommand.java @@ -38,20 +38,25 @@ public AddAuthCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - CommandLine cl = parser.parse(options, cmdArgs); + CommandLine cl; + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } + args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, IOException, - InterruptedException { + public boolean exec() throws CliException { byte[] b = null; if (args.length >= 3) { b = args[2].getBytes(); diff --git a/src/java/main/org/apache/zookeeper/cli/CliCommand.java b/src/java/main/org/apache/zookeeper/cli/CliCommand.java index 527a19edaf8..41e3d859443 100644 --- a/src/java/main/org/apache/zookeeper/cli/CliCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/CliCommand.java @@ -17,18 +17,14 @@ */ package org.apache.zookeeper.cli; -import java.io.IOException; import java.io.PrintStream; import java.util.Map; -import org.apache.commons.cli.ParseException; -import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; /** * base class for all CLI commands */ abstract public class CliCommand { - protected ZooKeeper zk; protected PrintStream out; protected PrintStream err; @@ -66,7 +62,7 @@ public void setErr(PrintStream err) { /** * set the zookeper instance - * @param zk the zookeper instance + * @param zk the ZooKeeper instance. */ public void setZk(ZooKeeper zk) { this.zk = zk; @@ -108,17 +104,14 @@ public void addToMap(Map cmdMap) { * parse the command arguments * @param cmdArgs * @return this CliCommand - * @throws ParseException + * @throws CliParseException */ - abstract public CliCommand parse(String cmdArgs[]) throws ParseException; + abstract public CliCommand parse(String cmdArgs[]) throws CliParseException; /** * * @return - * @throws KeeperException - * @throws IOException - * @throws InterruptedException + * @throws CliException */ - abstract public boolean exec() throws KeeperException, - IOException, InterruptedException; + abstract public boolean exec() throws CliException; } diff --git a/src/java/main/org/apache/zookeeper/cli/CliException.java b/src/java/main/org/apache/zookeeper/cli/CliException.java new file mode 100644 index 00000000000..6e9c6d6ec28 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/cli/CliException.java @@ -0,0 +1,57 @@ +/** + * 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.cli; + +@SuppressWarnings("serial") +public class CliException extends Exception { + + protected int exitCode; + + protected static final int DEFAULT_EXCEPTION_EXIT_CODE = 1; + + public CliException(String message) { + this(message, DEFAULT_EXCEPTION_EXIT_CODE); + } + + public CliException(String message, int exitCode) { + super(message); + this.exitCode = exitCode; + } + + public CliException(Throwable cause) { + this(cause, DEFAULT_EXCEPTION_EXIT_CODE); + } + + public CliException(Throwable cause, int exitCode) { + super(cause); + this.exitCode = exitCode; + } + + public CliException(String message, Throwable cause) { + this(message, cause, DEFAULT_EXCEPTION_EXIT_CODE); + } + + public CliException(String message, Throwable cause, int exitCode) { + super(message, cause); + this.exitCode = exitCode; + } + + public int getExitCode() { + return exitCode; + } +} diff --git a/src/java/main/org/apache/zookeeper/cli/CliParseException.java b/src/java/main/org/apache/zookeeper/cli/CliParseException.java new file mode 100644 index 00000000000..a6326dc8d2c --- /dev/null +++ b/src/java/main/org/apache/zookeeper/cli/CliParseException.java @@ -0,0 +1,31 @@ +/** + * 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.cli; + +import org.apache.commons.cli.ParseException; + +@SuppressWarnings("serial") +public class CliParseException extends CliException { + public CliParseException(ParseException parseException) { + super(parseException); + } + + public CliParseException(String message) { + super(message); + } +} diff --git a/src/java/main/org/apache/zookeeper/cli/CliWrapperException.java b/src/java/main/org/apache/zookeeper/cli/CliWrapperException.java new file mode 100644 index 00000000000..c0fafda19d3 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/cli/CliWrapperException.java @@ -0,0 +1,60 @@ +/** + * 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.cli; + +import org.apache.zookeeper.KeeperException; + +@SuppressWarnings("serial") +public class CliWrapperException extends CliException { + public CliWrapperException(Throwable cause) { + super(getMessage(cause), cause); + } + + private static String getMessage(Throwable cause) { + if (cause instanceof KeeperException) { + KeeperException keeperException = (KeeperException) cause; + if (keeperException instanceof KeeperException.NoNodeException) { + return "Node does not exist: " + keeperException.getPath(); + } else if (keeperException instanceof KeeperException.NoChildrenForEphemeralsException) { + return "Ephemerals cannot have children: " + keeperException.getPath(); + } else if (keeperException instanceof KeeperException.NodeExistsException) { + return "Node already exists: " + keeperException.getPath(); + } else if (keeperException instanceof KeeperException.NotEmptyException) { + return "Node not empty: " + keeperException.getPath(); + } else if (keeperException instanceof KeeperException.NotReadOnlyException) { + return "Not a read-only call: " + keeperException.getPath(); + } else if (keeperException instanceof KeeperException.InvalidACLException) { + return "Acl is not valid : " + keeperException.getPath(); + } else if (keeperException instanceof KeeperException.NoAuthException) { + return "Authentication is not valid : " + keeperException.getPath(); + } else if (keeperException instanceof KeeperException.BadArgumentsException) { + return "Arguments are not valid : " + keeperException.getPath(); + } else if (keeperException instanceof KeeperException.BadVersionException) { + return "version No is not valid : " + keeperException.getPath(); + } else if (keeperException instanceof KeeperException.ReconfigInProgress) { + return "Another reconfiguration is in progress -- concurrent " + + "reconfigs not supported (yet)"; + } else if (keeperException instanceof KeeperException.NewConfigNoQuorum) { + return "No quorum of new config is connected and " + + "up-to-date with the leader of last commmitted config - try invoking reconfiguration after " + + "new servers are connected and synced"; + } + } + return cause.getMessage(); + } +} diff --git a/src/java/main/org/apache/zookeeper/cli/CloseCommand.java b/src/java/main/org/apache/zookeeper/cli/CloseCommand.java index 1d96639cde4..d1c83d9c1a1 100644 --- a/src/java/main/org/apache/zookeeper/cli/CloseCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/CloseCommand.java @@ -17,10 +17,6 @@ */ package org.apache.zookeeper.cli; -import java.io.IOException; -import org.apache.commons.cli.ParseException; -import org.apache.zookeeper.KeeperException; - /** * close command for cli */ @@ -32,13 +28,17 @@ public CloseCommand() { @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { return this; } @Override - public boolean exec() throws KeeperException, IOException, InterruptedException { - zk.close(); + public boolean exec() throws CliException { + try { + zk.close(); + } catch (Exception ex) { + throw new CliWrapperException(ex); + } return false; } diff --git a/src/java/main/org/apache/zookeeper/cli/CommandNotFoundException.java b/src/java/main/org/apache/zookeeper/cli/CommandNotFoundException.java new file mode 100644 index 00000000000..7090ef0692f --- /dev/null +++ b/src/java/main/org/apache/zookeeper/cli/CommandNotFoundException.java @@ -0,0 +1,26 @@ +/** + * 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.cli; + +@SuppressWarnings("serial") +public class CommandNotFoundException extends CliException { + + public CommandNotFoundException(String command) { + super("Command not found: " + command, 127); + } +} diff --git a/src/java/main/org/apache/zookeeper/cli/CreateCommand.java b/src/java/main/org/apache/zookeeper/cli/CreateCommand.java index c6de7c6ebfc..04d49097c1b 100644 --- a/src/java/main/org/apache/zookeeper/cli/CreateCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/CreateCommand.java @@ -23,6 +23,8 @@ import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.EphemeralType; /** * create command for cli @@ -33,38 +35,77 @@ public class CreateCommand extends CliCommand { private String[] args; private CommandLine cl; - { + static { options.addOption(new Option("e", false, "ephemeral")); options.addOption(new Option("s", false, "sequential")); + options.addOption(new Option("c", false, "container")); + options.addOption(new Option("t", true, "ttl")); } public CreateCommand() { - super("create", "[-s] [-e] path [data] [acl]"); + super("create", "[-s] [-e] [-c] [-t ttl] path [data] [acl]"); } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if(args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, InterruptedException { - CreateMode flags = CreateMode.PERSISTENT; - if(cl.hasOption("e") && cl.hasOption("s")) { + public boolean exec() throws CliException { + boolean hasE = cl.hasOption("e"); + boolean hasS = cl.hasOption("s"); + boolean hasC = cl.hasOption("c"); + boolean hasT = cl.hasOption("t"); + if (hasC && (hasE || hasS)) { + throw new MalformedCommandException("-c cannot be combined with -s or -e. Containers cannot be ephemeral or sequential."); + } + long ttl; + try { + ttl = hasT ? Long.parseLong(cl.getOptionValue("t")) : 0; + } catch (NumberFormatException e) { + throw new MalformedCommandException("-t argument must be a long value"); + } + + if ( hasT && hasE ) { + throw new MalformedCommandException("TTLs cannot be used with Ephemeral znodes"); + } + if ( hasT && hasC ) { + throw new MalformedCommandException("TTLs cannot be used with Container znodes"); + } + + CreateMode flags; + if(hasE && hasS) { flags = CreateMode.EPHEMERAL_SEQUENTIAL; - } else if (cl.hasOption("e")) { + } else if (hasE) { flags = CreateMode.EPHEMERAL; - } else if (cl.hasOption("s")) { - flags = CreateMode.PERSISTENT_SEQUENTIAL; + } else if (hasS) { + flags = hasT ? CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL : CreateMode.PERSISTENT_SEQUENTIAL; + } else if (hasC) { + flags = CreateMode.CONTAINER; + } else { + flags = hasT ? CreateMode.PERSISTENT_WITH_TTL : CreateMode.PERSISTENT; } + if ( hasT ) { + try { + EphemeralType.TTL.toEphemeralOwner(ttl); + } catch (IllegalArgumentException e) { + throw new MalformedCommandException(e.getMessage()); + } + } + String path = args[1]; byte[] data = null; if (args.length > 2) { @@ -75,14 +116,18 @@ public boolean exec() throws KeeperException, InterruptedException { acl = AclParser.parse(args[3]); } try { - String newPath = zk.create(path, data, acl, flags); + String newPath = hasT ? zk.create(path, data, acl, flags, new Stat(), ttl) : zk.create(path, data, acl, flags); err.println("Created " + newPath); + } catch(IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); } catch(KeeperException.EphemeralOnLocalSessionException e) { err.println("Unable to create ephemeral node on a local session"); - return false; + throw new CliWrapperException(e); } catch (KeeperException.InvalidACLException ex) { err.println(ex.getMessage()); - return false; + throw new CliWrapperException(ex); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); } return true; } diff --git a/src/java/main/org/apache/zookeeper/cli/DelQuotaCommand.java b/src/java/main/org/apache/zookeeper/cli/DelQuotaCommand.java index 2043f4752de..80054067516 100644 --- a/src/java/main/org/apache/zookeeper/cli/DelQuotaCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/DelQuotaCommand.java @@ -45,31 +45,38 @@ public DelQuotaCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, IOException, - InterruptedException { + public boolean exec() throws CliException { //if neither option -n or -b is specified, we delete // the quota node for thsi node. String path = args[1]; - if (cl.hasOption("b")) { - delQuota(zk, path, true, false); - } else if (cl.hasOption("n")) { - delQuota(zk, path, false, true); - } else if (args.length == 2) { - // we dont have an option specified. - // just delete whole quota node - delQuota(zk, path, true, true); + try { + if (cl.hasOption("b")) { + delQuota(zk, path, true, false); + } else if (cl.hasOption("n")) { + delQuota(zk, path, false, true); + } else if (args.length == 2) { + // we dont have an option specified. + // just delete whole quota node + delQuota(zk, path, true, true); + } + } catch (KeeperException|InterruptedException|IOException ex) { + throw new CliWrapperException(ex); } return false; } @@ -88,7 +95,7 @@ public boolean exec() throws KeeperException, IOException, */ public static boolean delQuota(ZooKeeper zk, String path, boolean bytes, boolean numNodes) - throws KeeperException, IOException, InterruptedException { + throws KeeperException, IOException, InterruptedException, MalformedPathException { String parentPath = Quotas.quotaZookeeper + path; String quotaPath = Quotas.quotaZookeeper + path + "/" + Quotas.limitNode; @@ -99,6 +106,8 @@ public static boolean delQuota(ZooKeeper zk, String path, byte[] data = null; try { data = zk.getData(quotaPath, false, new Stat()); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); } catch (KeeperException.NoNodeException ne) { System.err.println("quota does not exist for " + path); return true; diff --git a/src/java/main/org/apache/zookeeper/cli/DeleteAllCommand.java b/src/java/main/org/apache/zookeeper/cli/DeleteAllCommand.java index badf5b73cee..a3f785307e8 100644 --- a/src/java/main/org/apache/zookeeper/cli/DeleteAllCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/DeleteAllCommand.java @@ -42,24 +42,34 @@ public DeleteAllCommand(String cmdStr) { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - CommandLine cl = parser.parse(options, cmdArgs); + CommandLine cl; + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, - InterruptedException { + public boolean exec() throws CliException { printDeprecatedWarning(); String path = args[1]; - ZKUtil.deleteRecursive(zk, path); + try { + ZKUtil.deleteRecursive(zk, path); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); + } return false; } diff --git a/src/java/main/org/apache/zookeeper/cli/DeleteCommand.java b/src/java/main/org/apache/zookeeper/cli/DeleteCommand.java index 4714be5c4db..d3c67b6fe54 100644 --- a/src/java/main/org/apache/zookeeper/cli/DeleteCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/DeleteCommand.java @@ -38,12 +38,16 @@ public DeleteCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } retainCompatibility(cmdArgs); @@ -51,25 +55,22 @@ public CliCommand parse(String[] cmdArgs) throws ParseException { return this; } - private void retainCompatibility(String[] cmdArgs) throws ParseException { - // delete path [version] + private void retainCompatibility(String[] cmdArgs) throws CliParseException { if (args.length > 2) { - // rewrite to option - String [] newCmd = new String[4]; - newCmd[0] = cmdArgs[0]; - newCmd[1] = "-v"; - newCmd[2] = cmdArgs[2]; // version - newCmd[3] = cmdArgs[1]; // path err.println("'delete path [version]' has been deprecated. " + "Please use 'delete [-v version] path' instead."); Parser parser = new PosixParser(); - cl = parser.parse(options, newCmd); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); } } @Override - public boolean exec() throws KeeperException, InterruptedException { + public boolean exec() throws CliException { String path = args[1]; int version; if (cl.hasOption("v")) { @@ -79,9 +80,11 @@ public boolean exec() throws KeeperException, InterruptedException { } try { - zk.delete(path, version); - } catch(KeeperException.BadVersionException ex) { - err.println(ex.getMessage()); + zk.delete(path, version); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch(KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); } return false; } diff --git a/src/java/main/org/apache/zookeeper/cli/GetAclCommand.java b/src/java/main/org/apache/zookeeper/cli/GetAclCommand.java index e646a04d36f..b5feb60794d 100644 --- a/src/java/main/org/apache/zookeeper/cli/GetAclCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/GetAclCommand.java @@ -45,22 +45,34 @@ public GetAclCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, InterruptedException { + public boolean exec() throws CliException { String path = args[1]; Stat stat = new Stat(); - List acl = zk.getACL(path, stat); + List acl; + try { + acl = zk.getACL(path, stat); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); + } + for (ACL a : acl) { out.println(a.getId() + ": " + getPermString(a.getPerms())); diff --git a/src/java/main/org/apache/zookeeper/cli/GetCommand.java b/src/java/main/org/apache/zookeeper/cli/GetCommand.java index 0ec827086ff..6e58a5ec3a1 100644 --- a/src/java/main/org/apache/zookeeper/cli/GetCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/GetCommand.java @@ -40,13 +40,17 @@ public GetCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } retainCompatibility(cmdArgs); @@ -54,7 +58,7 @@ public CliCommand parse(String[] cmdArgs) throws ParseException { return this; } - private void retainCompatibility(String[] cmdArgs) throws ParseException { + private void retainCompatibility(String[] cmdArgs) throws CliParseException { // get path [watch] if (args.length > 2) { // rewrite to option @@ -62,17 +66,28 @@ private void retainCompatibility(String[] cmdArgs) throws ParseException { err.println("'get path [watch]' has been deprecated. " + "Please use 'get [-s] [-w] path' instead."); Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); } } @Override - public boolean exec() throws KeeperException, InterruptedException { + public boolean exec() throws CliException { boolean watch = cl.hasOption("w"); String path = args[1]; Stat stat = new Stat(); - byte data[] = zk.getData(path, watch, stat); + byte data[]; + try { + data = zk.getData(path, watch, stat); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliException(ex); + } data = (data == null) ? "null".getBytes() : data; out.println(new String(data)); if (cl.hasOption("s")) { diff --git a/src/java/main/org/apache/zookeeper/cli/GetConfigCommand.java b/src/java/main/org/apache/zookeeper/cli/GetConfigCommand.java index 8c6a4f5fe3a..dda62814f45 100644 --- a/src/java/main/org/apache/zookeeper/cli/GetConfigCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/GetConfigCommand.java @@ -42,23 +42,32 @@ public GetConfigCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 1) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, InterruptedException { + public boolean exec() throws CliException { boolean watch = cl.hasOption("w"); Stat stat = new Stat(); - byte data[] = zk.getConfig(watch, stat); + byte data[]; + try { + data = zk.getConfig(watch, stat); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); + } data = (data == null) ? "null".getBytes() : data; if (cl.hasOption("c")) { out.println(ConfigUtils.getClientConfigStr(new String(data))); diff --git a/src/java/main/org/apache/zookeeper/cli/ListQuotaCommand.java b/src/java/main/org/apache/zookeeper/cli/ListQuotaCommand.java index 907c4663968..8c51c26996d 100644 --- a/src/java/main/org/apache/zookeeper/cli/ListQuotaCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/ListQuotaCommand.java @@ -36,20 +36,24 @@ public ListQuotaCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - CommandLine cl = parser.parse(options, cmdArgs); + CommandLine cl; + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if(args.length < 2) { - throw new ParseException(getUsageStr()); - } + throw new CliParseException(getUsageStr()); + } return this; } @Override - public boolean exec() throws KeeperException, - InterruptedException { + public boolean exec() throws CliException { String path = args[1]; String absolutePath = Quotas.quotaZookeeper + path + "/" + Quotas.limitNode; @@ -65,8 +69,12 @@ public boolean exec() throws KeeperException, + Quotas.statNode, false, stat); out.println("Output stat for " + path + " " + new StatsTrack(new String(data)).toString()); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); } catch (KeeperException.NoNodeException ne) { err.println("quota for " + path + " does not exist."); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); } return false; diff --git a/src/java/main/org/apache/zookeeper/cli/Ls2Command.java b/src/java/main/org/apache/zookeeper/cli/Ls2Command.java index 2b025830f2f..aed1b0e6f92 100644 --- a/src/java/main/org/apache/zookeeper/cli/Ls2Command.java +++ b/src/java/main/org/apache/zookeeper/cli/Ls2Command.java @@ -34,25 +34,37 @@ public Ls2Command() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - CommandLine cl = parser.parse(options, cmdArgs); + CommandLine cl; + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); - } + throw new CliParseException(getUsageStr()); + } return this; } @Override - public boolean exec() throws KeeperException, InterruptedException { + public boolean exec() throws CliException { err.println("'ls2' has been deprecated. " + "Please use 'ls [-s] path' instead."); String path = args[1]; boolean watch = args.length > 2; Stat stat = new Stat(); - List children = zk.getChildren(path, watch, stat); + List children; + try { + children = zk.getChildren(path, watch, stat); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); + } out.println(children); new StatPrinter(out).print(stat); return watch; diff --git a/src/java/main/org/apache/zookeeper/cli/LsCommand.java b/src/java/main/org/apache/zookeeper/cli/LsCommand.java index 43099b88daf..9e53d5d468e 100644 --- a/src/java/main/org/apache/zookeeper/cli/LsCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/LsCommand.java @@ -19,7 +19,9 @@ import java.util.Collections; import java.util.List; import org.apache.commons.cli.*; +import org.apache.zookeeper.AsyncCallback.StringCallback; import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZKUtil; import org.apache.zookeeper.data.Stat; /** @@ -35,10 +37,11 @@ public class LsCommand extends CliCommand { options.addOption("?", false, "help"); options.addOption("s", false, "stat"); options.addOption("w", false, "watch"); + options.addOption("R", false, "recurse"); } public LsCommand() { - super("ls", "[-s] [-w] path"); + super("ls", "[-s] [-w] [-R] path"); } private void printHelp() { @@ -47,24 +50,25 @@ private void printHelp() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } + args = cl.getArgs(); if (cl.hasOption("?")) { printHelp(); } - if (args.length < 2) { - throw new ParseException(getUsageStr()); - } - retainCompatibility(cmdArgs); - + return this; } - private void retainCompatibility(String[] cmdArgs) throws ParseException { + private void retainCompatibility(String[] cmdArgs) throws CliParseException { // get path [watch] if (args.length > 2) { // rewrite to option @@ -72,51 +76,62 @@ private void retainCompatibility(String[] cmdArgs) throws ParseException { err.println("'ls path [watch]' has been deprecated. " + "Please use 'ls [-w] path' instead."); Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); } } @Override - public boolean exec() throws KeeperException, InterruptedException { + public boolean exec() throws CliException { + if (args.length < 2) { + throw new MalformedCommandException(getUsageStr()); + } + String path = args[1]; boolean watch = cl.hasOption("w"); boolean withStat = cl.hasOption("s"); + boolean recursive = cl.hasOption("R"); try { - Stat stat = new Stat(); - List children; - if (withStat) { - // with stat - children = zk.getChildren(path, watch, stat); + if (recursive) { + ZKUtil.visitSubTreeDFS(zk, path, watch, new StringCallback() { + @Override + public void processResult(int rc, String path, Object ctx, String name) { + out.println(path); + } + }); } else { - // without stat - children = zk.getChildren(path, watch); - } - out.println(printChildren(children)); - if (withStat) { - new StatPrinter(out).print(stat); + Stat stat = withStat ? new Stat() : null; + List children = zk.getChildren(path, watch, stat); + printChildren(children, stat); } - } catch (KeeperException.NoAuthException ex) { - err.println(ex.getMessage()); - watch = false; + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); } return watch; } - private String printChildren(List children) { + private void printChildren(List children, Stat stat) { Collections.sort(children); - StringBuilder sb = new StringBuilder(); - sb.append("["); + out.append("["); boolean first = true; for (String child : children) { if (!first) { - sb.append(", "); + out.append(", "); } else { first = false; } - sb.append(child); + out.append(child); + } + out.append("]"); + if (stat != null) { + new StatPrinter(out).print(stat); } - sb.append("]"); - return sb.toString(); + out.append("\n"); } } diff --git a/src/java/main/org/apache/zookeeper/cli/MalformedCommandException.java b/src/java/main/org/apache/zookeeper/cli/MalformedCommandException.java new file mode 100644 index 00000000000..72b19efd399 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/cli/MalformedCommandException.java @@ -0,0 +1,25 @@ +/** + * 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.cli; + +@SuppressWarnings("serial") +public class MalformedCommandException extends CliException { + public MalformedCommandException(String message) { + super(message); + } +} diff --git a/src/java/main/org/apache/zookeeper/cli/MalformedPathException.java b/src/java/main/org/apache/zookeeper/cli/MalformedPathException.java new file mode 100644 index 00000000000..e65765b3edf --- /dev/null +++ b/src/java/main/org/apache/zookeeper/cli/MalformedPathException.java @@ -0,0 +1,25 @@ +/** + * 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.cli; + +@SuppressWarnings("serial") +public class MalformedPathException extends CliException { + public MalformedPathException(String message) { + super(message); + } +} diff --git a/src/java/main/org/apache/zookeeper/cli/ReconfigCommand.java b/src/java/main/org/apache/zookeeper/cli/ReconfigCommand.java index 282af5548dc..342f5d2e3c5 100644 --- a/src/java/main/org/apache/zookeeper/cli/ReconfigCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/ReconfigCommand.java @@ -18,12 +18,11 @@ package org.apache.zookeeper.cli; import java.io.FileInputStream; -import java.util.ArrayList; -import java.util.List; import java.util.Properties; import org.apache.commons.cli.*; import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.admin.ZooKeeperAdmin; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.quorum.QuorumPeerConfig; @@ -79,20 +78,24 @@ public ReconfigCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { joining = null; leaving = null; members = null; Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } if (!(cl.hasOption("file") || cl.hasOption("members")) && !cl.hasOption("add") && !cl.hasOption("remove")) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } if (cl.hasOption("v")) { try{ version = Long.parseLong(cl.getOptionValue("v"), 16); } catch (NumberFormatException e){ - throw new ParseException("-v must be followed by a long (configuration version)"); + throw new CliParseException("-v must be followed by a long (configuration version)"); } } else { version = -1; @@ -100,12 +103,12 @@ public CliCommand parse(String[] cmdArgs) throws ParseException { // Simple error checking for conflicting modes if ((cl.hasOption("file") || cl.hasOption("members")) && (cl.hasOption("add") || cl.hasOption("remove"))) { - throw new ParseException("Can't use -file or -members together with -add or -remove (mixing incremental" + + throw new CliParseException("Can't use -file or -members together with -add or -remove (mixing incremental" + " and non-incremental modes is not allowed)"); } if (cl.hasOption("file") && cl.hasOption("members")) { - throw new ParseException("Can't use -file and -members together (conflicting non-incremental modes)"); + throw new CliParseException("Can't use -file and -members together (conflicting non-incremental modes)"); } // Set the joining/leaving/members values based on the mode we're in @@ -132,25 +135,34 @@ public CliCommand parse(String[] cmdArgs) throws ParseException { //client doesn't know what leader election alg is used members = QuorumPeerConfig.parseDynamicConfig(dynamicCfg, 0, true, false).toString(); } catch (Exception e) { - throw new ParseException("Error processing " + cl.getOptionValue("file") + e.getMessage()); + throw new CliParseException("Error processing " + cl.getOptionValue("file") + e.getMessage()); } } return this; } @Override - public boolean exec() throws KeeperException, InterruptedException { + public boolean exec() throws CliException { try { Stat stat = new Stat(); - byte[] curConfig = zk.reconfig(joining, + if (!(zk instanceof ZooKeeperAdmin)) { + // This should never happen when executing reconfig command line, + // because it is guaranteed that we have a ZooKeeperAdmin instance ready + // to use in CliCommand stack. + // The only exception would be in test code where clients can directly set + // ZooKeeper object to ZooKeeperMain. + return false; + } + + byte[] curConfig = ((ZooKeeperAdmin)zk).reconfigure(joining, leaving, members, version, stat); out.println("Committed new configuration:\n" + new String(curConfig)); if (cl.hasOption("s")) { new StatPrinter(out).print(stat); } - } catch (KeeperException ex) { - err.println(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); } return false; } diff --git a/src/java/main/org/apache/zookeeper/cli/RemoveWatchesCommand.java b/src/java/main/org/apache/zookeeper/cli/RemoveWatchesCommand.java index f63cf0f501a..28634435e18 100644 --- a/src/java/main/org/apache/zookeeper/cli/RemoveWatchesCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/RemoveWatchesCommand.java @@ -47,18 +47,22 @@ public RemoveWatchesCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, InterruptedException { + public boolean exec() throws CliWrapperException, MalformedPathException { String path = args[1]; WatcherType wtype = WatcherType.Any; // if no matching option -c or -d or -a is specified, we remove @@ -75,9 +79,10 @@ public boolean exec() throws KeeperException, InterruptedException { try { zk.removeAllWatches(path, wtype, local); - } catch (KeeperException.NoWatcherException ex) { - err.println(ex.getMessage()); - return false; + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); } return true; } diff --git a/src/java/main/org/apache/zookeeper/cli/SetAclCommand.java b/src/java/main/org/apache/zookeeper/cli/SetAclCommand.java index f27446c4ee9..d2cfc0d3526 100644 --- a/src/java/main/org/apache/zookeeper/cli/SetAclCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/SetAclCommand.java @@ -42,20 +42,23 @@ public SetAclCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 3) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, - InterruptedException { + public boolean exec() throws CliException { String path = args[1]; String aclStr = args[2]; List acl = AclParser.parse(aclStr); @@ -70,10 +73,10 @@ public boolean exec() throws KeeperException, if (cl.hasOption("s")) { new StatPrinter(out).print(stat); } - } catch (KeeperException.InvalidACLException ex) { - err.println(ex.getMessage()); - } catch (KeeperException.NoAuthException ex) { - err.println(ex.getMessage()); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); } return false; diff --git a/src/java/main/org/apache/zookeeper/cli/SetCommand.java b/src/java/main/org/apache/zookeeper/cli/SetCommand.java index 1c393774f7f..43ca2e1c364 100644 --- a/src/java/main/org/apache/zookeeper/cli/SetCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/SetCommand.java @@ -40,19 +40,23 @@ public SetCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 3) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, InterruptedException { + public boolean exec() throws CliException { String path = args[1]; byte[] data = args[2].getBytes(); int version; @@ -67,8 +71,10 @@ public boolean exec() throws KeeperException, InterruptedException { if (cl.hasOption("s")) { new StatPrinter(out).print(stat); } - } catch (KeeperException.BadVersionException ex) { - err.println(ex.getMessage()); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); } return false; } diff --git a/src/java/main/org/apache/zookeeper/cli/SetQuotaCommand.java b/src/java/main/org/apache/zookeeper/cli/SetQuotaCommand.java index 873c6d2a962..7df56672cf1 100644 --- a/src/java/main/org/apache/zookeeper/cli/SetQuotaCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/SetQuotaCommand.java @@ -46,33 +46,44 @@ public SetQuotaCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, IOException, - InterruptedException { + public boolean exec() throws CliException { // get the args String path = args[1]; if (cl.hasOption("b")) { // we are setting the bytes quota long bytes = Long.parseLong(cl.getOptionValue("b")); - createQuota(zk, path, bytes, -1); + try { + createQuota(zk, path, bytes, -1); + } catch (KeeperException|IOException|InterruptedException ex) { + throw new CliWrapperException(ex); + } } else if (cl.hasOption("n")) { // we are setting the num quota int numNodes = Integer.parseInt(cl.getOptionValue("n")); - createQuota(zk, path, -1L, numNodes); + try { + createQuota(zk, path, -1L, numNodes); + } catch (KeeperException|IOException|InterruptedException ex) { + throw new CliWrapperException(ex); + } } else { - err.println(getUsageStr()); + throw new MalformedCommandException(getUsageStr()); } return false; @@ -80,11 +91,16 @@ public boolean exec() throws KeeperException, IOException, public static boolean createQuota(ZooKeeper zk, String path, long bytes, int numNodes) - throws KeeperException, IOException, InterruptedException { + throws KeeperException, IOException, InterruptedException, MalformedPathException { // check if the path exists. We cannot create // quota for a path that already exists in zookeeper // for now. - Stat initStat = zk.exists(path, false); + Stat initStat; + try { + initStat = zk.exists(path, false); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } if (initStat == null) { throw new IllegalArgumentException(path + " does not exist."); } diff --git a/src/java/main/org/apache/zookeeper/cli/StatCommand.java b/src/java/main/org/apache/zookeeper/cli/StatCommand.java index e5cfe54b882..33d8e874a1a 100644 --- a/src/java/main/org/apache/zookeeper/cli/StatCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/StatCommand.java @@ -40,12 +40,16 @@ public StatCommand() { @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if(args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } retainCompatibility(cmdArgs); @@ -53,7 +57,7 @@ public CliCommand parse(String[] cmdArgs) throws ParseException { return this; } - private void retainCompatibility(String[] cmdArgs) throws ParseException { + private void retainCompatibility(String[] cmdArgs) throws CliParseException { // stat path [watch] if (args.length > 2) { // rewrite to option @@ -61,19 +65,29 @@ private void retainCompatibility(String[] cmdArgs) throws ParseException { err.println("'stat path [watch]' has been deprecated. " + "Please use 'stat [-w] path' instead."); Parser parser = new PosixParser(); - cl = parser.parse(options, cmdArgs); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); } } @Override - public boolean exec() throws KeeperException, - InterruptedException { + public boolean exec() throws CliException { String path = args[1]; boolean watch = cl.hasOption("w"); - Stat stat = zk.exists(path, watch); + Stat stat; + try { + stat = zk.exists(path, watch); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); + } if (stat == null) { - throw new KeeperException.NoNodeException(path); + throw new CliWrapperException(new KeeperException.NoNodeException(path)); } new StatPrinter(out).print(stat); return watch; diff --git a/src/java/main/org/apache/zookeeper/cli/SyncCommand.java b/src/java/main/org/apache/zookeeper/cli/SyncCommand.java index c12d35d350d..74affd27bac 100644 --- a/src/java/main/org/apache/zookeeper/cli/SyncCommand.java +++ b/src/java/main/org/apache/zookeeper/cli/SyncCommand.java @@ -38,26 +38,36 @@ public SyncCommand() { } @Override - public CliCommand parse(String[] cmdArgs) throws ParseException { + public CliCommand parse(String[] cmdArgs) throws CliParseException { Parser parser = new PosixParser(); - CommandLine cl = parser.parse(options, cmdArgs); + CommandLine cl; + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } args = cl.getArgs(); if (args.length < 2) { - throw new ParseException(getUsageStr()); + throw new CliParseException(getUsageStr()); } return this; } @Override - public boolean exec() throws KeeperException, IOException, InterruptedException { + public boolean exec() throws CliException { String path = args[1]; - zk.sync(path, new AsyncCallback.VoidCallback() { + try { + zk.sync(path, new AsyncCallback.VoidCallback() { + + public void processResult(int rc, String path, Object ctx) { + out.println("Sync returned " + rc); + } + }, null); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } - public void processResult(int rc, String path, Object ctx) { - out.println("Sync returned " + rc); - } - }, null); return false; } diff --git a/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java b/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java index e41465ab93a..b396c15c3e0 100644 --- a/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java +++ b/src/java/main/org/apache/zookeeper/client/FourLetterWordMain.java @@ -18,17 +18,30 @@ package org.apache.zookeeper.client; -import org.apache.log4j.Logger; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.Socket; +import java.net.SocketTimeoutException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.zookeeper.common.X509Exception.SSLContextException; +import org.apache.zookeeper.common.X509Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@InterfaceAudience.Public public class FourLetterWordMain { - protected static final Logger LOG = Logger.getLogger(FourLetterWordMain.class); - + //in milliseconds, socket should connect/read within this period otherwise SocketTimeoutException + private static final int DEFAULT_SOCKET_TIMEOUT = 5000; + protected static final Logger LOG = LoggerFactory.getLogger(FourLetterWordMain.class); /** * Send the 4letterword * @param host the destination host @@ -36,19 +49,69 @@ public class FourLetterWordMain { * @param cmd the 4letterword * @return server response * @throws java.io.IOException + * @throws SSLContextException */ public static String send4LetterWord(String host, int port, String cmd) - throws IOException - { - LOG.info("connecting to " + host + " " + port); - Socket sock = new Socket(host, port); + throws IOException, SSLContextException { + return send4LetterWord(host, port, cmd, false, DEFAULT_SOCKET_TIMEOUT); + } + + /** + * Send the 4letterword + * @param host the destination host + * @param port the destination port + * @param cmd the 4letterword + * @param secure whether to use SSL + * @return server response + * @throws java.io.IOException + * @throws SSLContextException + */ + public static String send4LetterWord(String host, int port, String cmd, boolean secure) + throws IOException, SSLContextException { + return send4LetterWord(host, port, cmd, secure, DEFAULT_SOCKET_TIMEOUT); + } + + /** + * Send the 4letterword + * @param host the destination host + * @param port the destination port + * @param cmd the 4letterword + * @param secure whether to use SSL + * @param timeout in milliseconds, maximum time to wait while connecting/reading data + * @return server response + * @throws java.io.IOException + * @throws SSLContextException + */ + public static String send4LetterWord(String host, int port, String cmd, boolean secure, int timeout) + throws IOException, SSLContextException { + LOG.info("connecting to {} {}", host, port); + Socket sock; + InetSocketAddress hostaddress= host != null ? new InetSocketAddress(host, port) : + new InetSocketAddress(InetAddress.getByName(null), port); + if (secure) { + LOG.info("using secure socket"); + SSLContext sslContext = X509Util.createSSLContext(); + SSLSocketFactory socketFactory = sslContext.getSocketFactory(); + SSLSocket sslSock = (SSLSocket) socketFactory.createSocket(); + sslSock.connect(hostaddress, timeout); + sslSock.startHandshake(); + sock = sslSock; + } else { + sock = new Socket(); + sock.connect(hostaddress, timeout); + } + sock.setSoTimeout(timeout); 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(); + if (!secure) { + // SSL prohibits unilateral half-close + sock.shutdownOutput(); + } reader = new BufferedReader( @@ -59,6 +122,8 @@ public static String send4LetterWord(String host, int port, String cmd) sb.append(line + "\n"); } return sb.toString(); + } catch (SocketTimeoutException e) { + throw new IOException("Exception while executing four letter word: " + cmd, e); } finally { sock.close(); if (reader != null) { @@ -68,12 +133,14 @@ public static String send4LetterWord(String host, int port, String cmd) } public static void main(String[] args) - throws IOException + throws IOException, SSLContextException { - if (args.length != 3) { - System.out.println("Usage: FourLetterWordMain "); - } else { + if (args.length == 3) { System.out.println(send4LetterWord(args[0], Integer.parseInt(args[1]), args[2])); + } else if (args.length == 4) { + System.out.println(send4LetterWord(args[0], Integer.parseInt(args[1]), args[2], Boolean.parseBoolean(args[3]))); + } else { + System.out.println("Usage: FourLetterWordMain "); } } } diff --git a/src/java/main/org/apache/zookeeper/client/HostProvider.java b/src/java/main/org/apache/zookeeper/client/HostProvider.java index c47fd82a63e..ec3ca97f167 100644 --- a/src/java/main/org/apache/zookeeper/client/HostProvider.java +++ b/src/java/main/org/apache/zookeeper/client/HostProvider.java @@ -18,6 +18,8 @@ package org.apache.zookeeper.client; +import org.apache.yetus.audience.InterfaceAudience; + import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Collection; @@ -41,6 +43,7 @@ * * A HostProvider that re-resolves the InetSocketAddress after a timeout. * * A HostProvider that prefers nearby hosts. */ +@InterfaceAudience.Public public interface HostProvider { public int size(); diff --git a/src/java/main/org/apache/zookeeper/client/StaticHostProvider.java b/src/java/main/org/apache/zookeeper/client/StaticHostProvider.java index 4e0301848c9..cb539361fbc 100644 --- a/src/java/main/org/apache/zookeeper/client/StaticHostProvider.java +++ b/src/java/main/org/apache/zookeeper/client/StaticHostProvider.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.Random; -import org.apache.zookeeper.common.HostNameUtils; +import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +35,7 @@ * Most simple HostProvider, resolves only on instantiation. * */ +@InterfaceAudience.Public public final class StaticHostProvider implements HostProvider { private static final Logger LOG = LoggerFactory .getLogger(StaticHostProvider.class); @@ -111,14 +112,11 @@ private List resolveAndShuffle(Collection for (InetSocketAddress address : serverAddresses) { try { InetAddress ia = address.getAddress(); - String addr = (ia != null) ? ia.getHostAddress() : - address.getHostName(); + String addr = (ia != null) ? ia.getHostAddress() : address.getHostString(); InetAddress resolvedAddresses[] = InetAddress.getAllByName(addr); for (InetAddress resolvedAddress : resolvedAddresses) { - tmpList.add(new InetSocketAddress(InetAddress.getByAddress( - HostNameUtils.getHostString(address), - resolvedAddress.getAddress()), - address.getPort())); + InetAddress taddr = InetAddress.getByAddress(address.getHostString(), resolvedAddress.getAddress()); + tmpList.add(new InetSocketAddress(taddr, address.getPort())); } } catch (UnknownHostException ex) { LOG.warn("No IP address found for server: {}", address, ex); @@ -142,9 +140,11 @@ private List resolveAndShuffle(Collection * If true is returned, the function sets pOld and pNew that correspond to the probability to migrate to ones of the * new servers in serverAddresses or one of the old servers (migrating to one of the old servers is done only * if our client's currentHost is not in serverAddresses). See nextHostInReconfigMode for the selection logic. - * - * See {@link https://issues.apache.org/jira/browse/ZOOKEEPER-1355} for the protocol and its evaluation, and - * StaticHostProviderTest for the tests that illustrate how load balancing works with this policy. + * + * See ZOOKEEPER-1355 + * for the protocol and its evaluation, and StaticHostProviderTest for the tests that illustrate how load balancing + * works with this policy. + * * @param serverAddresses new host list * @param currentHost the host to which this client is currently connected * @return true if changing connections is necessary for load-balancing, false otherwise @@ -188,7 +188,7 @@ public synchronized boolean updateServerList( && ((addr.getAddress() != null && myServer.getAddress() != null && addr .getAddress().equals(myServer.getAddress())) || addr - .getHostName().equals(myServer.getHostName()))) { + .getHostString().equals(myServer.getHostString()))) { myServerInNewConfig = true; break; } diff --git a/src/java/main/org/apache/zookeeper/client/ZKClientConfig.java b/src/java/main/org/apache/zookeeper/client/ZKClientConfig.java new file mode 100644 index 00000000000..3c106277108 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/client/ZKClientConfig.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.client; + +import java.io.File; + +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; + +/** + * Handles client specific properties + * @since 3.5.2 + */ +@InterfaceAudience.Public +public class ZKClientConfig extends ZKConfig { + public static final String ZK_SASL_CLIENT_USERNAME = "zookeeper.sasl.client.username"; + public static final String ZK_SASL_CLIENT_USERNAME_DEFAULT = "zookeeper"; + @SuppressWarnings("deprecation") + public static final String LOGIN_CONTEXT_NAME_KEY = ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY;; + public static final String LOGIN_CONTEXT_NAME_KEY_DEFAULT = "Client"; + @SuppressWarnings("deprecation") + public static final String ENABLE_CLIENT_SASL_KEY = ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY; + @SuppressWarnings("deprecation") + public static final String ENABLE_CLIENT_SASL_DEFAULT = ZooKeeperSaslClient.ENABLE_CLIENT_SASL_DEFAULT; + public static final String ZOOKEEPER_SERVER_REALM = "zookeeper.server.realm"; + /** + * This controls whether automatic watch resetting is enabled. Clients + * automatically reset watches during session reconnect, this option allows + * the client to turn off this behavior by setting the property + * "zookeeper.disableAutoWatchReset" to "true" + */ + public static final String DISABLE_AUTO_WATCH_RESET = "zookeeper.disableAutoWatchReset"; + @SuppressWarnings("deprecation") + public static final String ZOOKEEPER_CLIENT_CNXN_SOCKET = ZooKeeper.ZOOKEEPER_CLIENT_CNXN_SOCKET; + /** + * Setting this to "true" will enable encrypted client-server communication. + */ + @SuppressWarnings("deprecation") + public static final String SECURE_CLIENT = ZooKeeper.SECURE_CLIENT; + public static final int CLIENT_MAX_PACKET_LENGTH_DEFAULT = 4096 * 1024; /* 4 MB */ + + public ZKClientConfig() { + super(); + } + + public ZKClientConfig(File configFile) throws ConfigException { + super(configFile); + } + + public ZKClientConfig(String configPath) throws ConfigException { + super(configPath); + } + + @Override + protected void handleBackwardCompatibility() { + /** + * backward compatibility for properties which are common to both client + * and server + */ + super.handleBackwardCompatibility(); + + /** + * backward compatibility for client specific properties + */ + setProperty(ZK_SASL_CLIENT_USERNAME, System.getProperty(ZK_SASL_CLIENT_USERNAME)); + setProperty(LOGIN_CONTEXT_NAME_KEY, System.getProperty(LOGIN_CONTEXT_NAME_KEY)); + setProperty(ENABLE_CLIENT_SASL_KEY, System.getProperty(ENABLE_CLIENT_SASL_KEY)); + setProperty(ZOOKEEPER_SERVER_REALM, System.getProperty(ZOOKEEPER_SERVER_REALM)); + setProperty(DISABLE_AUTO_WATCH_RESET, System.getProperty(DISABLE_AUTO_WATCH_RESET)); + setProperty(ZOOKEEPER_CLIENT_CNXN_SOCKET, System.getProperty(ZOOKEEPER_CLIENT_CNXN_SOCKET)); + setProperty(SECURE_CLIENT, System.getProperty(SECURE_CLIENT)); + } + + /** + * Returns true if the SASL client is enabled. By default, the client is + * enabled but can be disabled by setting the system property + * zookeeper.sasl.client to false. See + * ZOOKEEPER-1657 for more information. + * + * @return true if the SASL client is enabled. + */ + public boolean isSaslClientEnabled() { + return Boolean.valueOf(getProperty(ENABLE_CLIENT_SASL_KEY, ENABLE_CLIENT_SASL_DEFAULT)); + } +} diff --git a/src/java/main/org/apache/zookeeper/client/ZooKeeperSaslClient.java b/src/java/main/org/apache/zookeeper/client/ZooKeeperSaslClient.java index dbc10801949..85506262987 100644 --- a/src/java/main/org/apache/zookeeper/client/ZooKeeperSaslClient.java +++ b/src/java/main/org/apache/zookeeper/client/ZooKeeperSaslClient.java @@ -19,51 +19,54 @@ package org.apache.zookeeper.client; import java.io.IOException; -import java.security.Principal; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginException; -import javax.security.sasl.AuthorizeCallback; -import javax.security.sasl.RealmCallback; -import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslException; import org.apache.zookeeper.AsyncCallback; import org.apache.zookeeper.ClientCnxn; -import org.apache.zookeeper.Environment; import org.apache.zookeeper.Login; +import org.apache.zookeeper.SaslClientCallbackHandler; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.proto.GetSASLRequest; import org.apache.zookeeper.proto.SetSASLResponse; -import org.apache.zookeeper.server.auth.KerberosName; -import org.ietf.jgss.GSSContext; -import org.ietf.jgss.GSSCredential; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.Oid; + +import org.apache.zookeeper.util.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class manages SASL authentication for the client. It - * allows ClientCnxn to authenticate using SASL with a Zookeeper server. + * allows ClientCnxn to authenticate using SASL with a ZooKeeper server. */ public class ZooKeeperSaslClient { + /** + * @deprecated Use {@link ZKClientConfig#LOGIN_CONTEXT_NAME_KEY} + * instead. + */ + @Deprecated public static final String LOGIN_CONTEXT_NAME_KEY = "zookeeper.sasl.clientconfig"; + /** + * @deprecated Use {@link ZKClientConfig#ENABLE_CLIENT_SASL_KEY} + * instead. + */ + @Deprecated public static final String ENABLE_CLIENT_SASL_KEY = "zookeeper.sasl.client"; + /** + * @deprecated Use {@link ZKClientConfig#ENABLE_CLIENT_SASL_DEFAULT} + * instead. + */ + @Deprecated public static final String ENABLE_CLIENT_SASL_DEFAULT = "true"; + private volatile boolean initializedLogin = false; /** * Returns true if the SASL client is enabled. By default, the client @@ -71,16 +74,21 @@ public class ZooKeeperSaslClient { * zookeeper.sasl.client to false. See * ZOOKEEPER-1657 for more information. * - * @return If the SASL client is enabled. + * @return true if the SASL client is enabled. + * @deprecated Use {@link ZKClientConfig#isSaslClientEnabled} instead */ + @Deprecated public static boolean isEnabled() { - return Boolean.valueOf(System.getProperty(ENABLE_CLIENT_SASL_KEY, ENABLE_CLIENT_SASL_DEFAULT)); + return Boolean.valueOf(System.getProperty( + ZKClientConfig.ENABLE_CLIENT_SASL_KEY, + ZKClientConfig.ENABLE_CLIENT_SASL_DEFAULT)); } private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperSaslClient.class); - private static Login login = null; + private Login login = null; private SaslClient saslClient; private boolean isSASLConfigured = true; + private final ZKClientConfig clientConfig; private byte[] saslToken = new byte[0]; @@ -104,19 +112,22 @@ public String getLoginContext() { return null; } - public ZooKeeperSaslClient(final String serverPrincipal) - throws LoginException { + public ZooKeeperSaslClient(final String serverPrincipal, ZKClientConfig clientConfig) throws LoginException { /** * ZOOKEEPER-1373: allow system property to specify the JAAS * configuration section that the zookeeper client should use. * Default to "Client". */ - String clientSection = System.getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "Client"); + String clientSection = clientConfig.getProperty( + ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, + ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT); + this.clientConfig = clientConfig; // Note that 'Configuration' here refers to javax.security.auth.login.Configuration. AppConfigurationEntry entries[] = null; RuntimeException runtimeException = null; try { - entries = Configuration.getConfiguration().getAppConfigurationEntry(clientSection); + entries = Configuration.getConfiguration() + .getAppConfigurationEntry(clientSection); } catch (SecurityException e) { // handle below: might be harmless if the user doesn't intend to use JAAS authentication. runtimeException = e; @@ -133,15 +144,18 @@ public ZooKeeperSaslClient(final String serverPrincipal) // Handle situation of clientSection's being null: it might simply because the client does not intend to // use SASL, so not necessarily an error. saslState = SaslState.FAILED; - String explicitClientSection = System.getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY); + String explicitClientSection = clientConfig + .getProperty(ZKClientConfig.LOGIN_CONTEXT_NAME_KEY); if (explicitClientSection != null) { // If the user explicitly overrides the default Login Context, they probably expected SASL to // succeed. But if we got here, SASL failed. if (runtimeException != null) { - throw new LoginException("Zookeeper client cannot authenticate using the " + explicitClientSection + - " section of the supplied JAAS configuration: '" + - System.getProperty(Environment.JAAS_CONF_KEY) + "' because of a " + - "RuntimeException: " + runtimeException); + throw new LoginException( + "Zookeeper client cannot authenticate using the " + + explicitClientSection + + " section of the supplied JAAS configuration: '" + + clientConfig.getJaasConfKey() + "' because of a " + + "RuntimeException: " + runtimeException); } else { throw new LoginException("Client cannot SASL-authenticate because the specified JAAS configuration " + "section '" + explicitClientSection + "' could not be found."); @@ -158,19 +172,26 @@ public ZooKeeperSaslClient(final String serverPrincipal) this.configStatus = msg; this.isSASLConfigured = false; } - if (System.getProperty(Environment.JAAS_CONF_KEY) != null) { - // Again, the user explicitly set something SASL-related, so they probably expected SASL to succeed. + if (clientConfig.getJaasConfKey() != null) { + // Again, the user explicitly set something SASL-related, so + // they probably expected SASL to succeed. if (runtimeException != null) { - throw new LoginException("Zookeeper client cannot authenticate using the '" + - System.getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "Client") + - "' section of the supplied JAAS configuration: '" + - System.getProperty(Environment.JAAS_CONF_KEY) + "' because of a " + - "RuntimeException: " + runtimeException); + throw new LoginException( + "Zookeeper client cannot authenticate using the '" + + clientConfig.getProperty( + ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, + ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT) + + "' section of the supplied JAAS configuration: '" + + clientConfig.getJaasConfKey() + "' because of a " + + "RuntimeException: " + runtimeException); } else { - throw new LoginException("No JAAS configuration section named '" + - System.getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "Client") + - "' was found in specified JAAS configuration file: '" + - System.getProperty(Environment.JAAS_CONF_KEY) + "'."); + throw new LoginException( + "No JAAS configuration section named '" + + clientConfig.getProperty( + ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, + ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT) + + "' was found in specified JAAS configuration file: '" + + clientConfig.getJaasConfKey() + "'."); } } } @@ -214,89 +235,25 @@ public void processResult(int rc, String path, Object ctx, byte data[], Stat sta } } - synchronized private SaslClient createSaslClient(final String servicePrincipal, - final String loginContext) throws LoginException { + private SaslClient createSaslClient(final String servicePrincipal, final String loginContext) + throws LoginException { try { - if (login == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("JAAS loginContext is: " + loginContext); - } - // note that the login object is static: it's shared amongst all zookeeper-related connections. - // createSaslClient() must be declared synchronized so that login is initialized only once. - login = new Login(loginContext, new ClientCallbackHandler(null)); - login.startThreadIfNeeded(); - } - Subject subject = login.getSubject(); - SaslClient saslClient; - // Use subject.getPrincipals().isEmpty() as an indication of which SASL mechanism to use: - // if empty, use DIGEST-MD5; otherwise, use GSSAPI. - if (subject.getPrincipals().isEmpty()) { - // no principals: must not be GSSAPI: use DIGEST-MD5 mechanism instead. - LOG.info("Client will use DIGEST-MD5 as SASL mechanism."); - String[] mechs = {"DIGEST-MD5"}; - String username = (String)(subject.getPublicCredentials().toArray()[0]); - String password = (String)(subject.getPrivateCredentials().toArray()[0]); - // "zk-sasl-md5" is a hard-wired 'domain' parameter shared with zookeeper server code (see ServerCnxnFactory.java) - saslClient = Sasl.createSaslClient(mechs, username, "zookeeper", "zk-sasl-md5", null, new ClientCallbackHandler(password)); - return saslClient; - } - else { // GSSAPI. - boolean usingNativeJgss = - Boolean.getBoolean("sun.security.jgss.native"); - if (usingNativeJgss) { - // http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html - // """ - // In addition, when performing operations as a particular - // Subject, e.g. Subject.doAs(...) or Subject.doAsPrivileged(...), - // the to-be-used GSSCredential should be added to Subject's - // private credential set. Otherwise, the GSS operations will - // fail since no credential is found. - // """ - try { - GSSManager manager = GSSManager.getInstance(); - Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2"); - GSSCredential cred = manager.createCredential(null, - GSSContext.DEFAULT_LIFETIME, - krb5Mechanism, - GSSCredential.INITIATE_ONLY); - subject.getPrivateCredentials().add(cred); - if (LOG.isDebugEnabled()) { - LOG.debug("Added private credential to subject: " + cred); - } - } catch (GSSException ex) { - LOG.warn("Cannot add private credential to subject; " + - "authentication at the server may fail", ex); - } - } - final Object[] principals = subject.getPrincipals().toArray(); - // determine client principal from subject. - final Principal clientPrincipal = (Principal)principals[0]; - final KerberosName clientKerberosName = new KerberosName(clientPrincipal.getName()); - // assume that server and client are in the same realm (by default; unless the system property - // "zookeeper.server.realm" is set). - String serverRealm = System.getProperty("zookeeper.server.realm",clientKerberosName.getRealm()); - KerberosName serviceKerberosName = new KerberosName(servicePrincipal+"@"+serverRealm); - final String serviceName = serviceKerberosName.getServiceName(); - final String serviceHostname = serviceKerberosName.getHostName(); - final String clientPrincipalName = clientKerberosName.toString(); - try { - saslClient = Subject.doAs(subject,new PrivilegedExceptionAction() { - public SaslClient run() throws SaslException { - LOG.info("Client will use GSSAPI as SASL mechanism."); - String[] mechs = {"GSSAPI"}; - LOG.debug("creating sasl client: client="+clientPrincipalName+";service="+serviceName+";serviceHostname="+serviceHostname); - SaslClient saslClient = Sasl.createSaslClient(mechs,clientPrincipalName,serviceName,serviceHostname,null,new ClientCallbackHandler(null)); - return saslClient; + if (!initializedLogin) { + synchronized (this) { + if (login == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("JAAS loginContext is: " + loginContext); } - }); - return saslClient; - } - catch (Exception e) { - LOG.error("Exception while trying to create SASL client", e); - e.printStackTrace(); - return null; + // note that the login object is static: it's shared amongst all zookeeper-related connections. + // in order to ensure the login is initialized only once, it must be synchronized the code snippet. + login = new Login(loginContext, new SaslClientCallbackHandler(null, "Client"), clientConfig); + login.startThreadIfNeeded(); + initializedLogin = true; + } } } + return SecurityUtils.createSaslClient(login.getSubject(), + servicePrincipal, "zookeeper", "zk-sasl-md5", LOG, "Client"); } catch (LoginException e) { // We throw LoginExceptions... throw e; @@ -321,7 +278,7 @@ public void respondToServer(byte[] serverToken, ClientCnxn cnxn) { } } catch (SaslException e) { LOG.error("SASL authentication failed using login context '" + - this.getLoginContext() + "'."); + this.getLoginContext() + "' with exception: {}", e); saslState = SaslState.FAILED; gotLastPacket = true; } @@ -339,7 +296,7 @@ public void respondToServer(byte[] serverToken, ClientCnxn cnxn) { // SASL authentication is completed, successfully or not: // enable the socket's writable flag so that any packets waiting for authentication to complete in // the outgoing queue will be sent to the Zookeeper server. - cnxn.enableWrite(); + cnxn.saslCompleted(); } } @@ -465,75 +422,6 @@ public void initialize(ClientCnxn cnxn) throws SaslException { } } - // The CallbackHandler interface here refers to - // javax.security.auth.callback.CallbackHandler. - // It should not be confused with Zookeeper packet callbacks like - // org.apache.zookeeper.server.auth.SaslServerCallbackHandler. - public static class ClientCallbackHandler implements CallbackHandler { - private String password = null; - - public ClientCallbackHandler(String password) { - this.password = password; - } - - public void handle(Callback[] callbacks) throws - UnsupportedCallbackException { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback) { - NameCallback nc = (NameCallback) callback; - nc.setName(nc.getDefaultName()); - } - else { - if (callback instanceof PasswordCallback) { - PasswordCallback pc = (PasswordCallback)callback; - if (password != null) { - pc.setPassword(this.password.toCharArray()); - } else { - LOG.warn("Could not login: the client is being asked for a password, but the Zookeeper" + - " client code does not currently support obtaining a password from the user." + - " Make sure that the client is configured to use a ticket cache (using" + - " the JAAS configuration setting 'useTicketCache=true)' and restart the client. If" + - " you still get this message after that, the TGT in the ticket cache has expired and must" + - " be manually refreshed. To do so, first determine if you are using a password or a" + - " keytab. If the former, run kinit in a Unix shell in the environment of the user who" + - " is running this Zookeeper client using the command" + - " 'kinit ' (where is the name of the client's Kerberos principal)." + - " If the latter, do" + - " 'kinit -k -t ' (where is the name of the Kerberos principal, and" + - " is the location of the keytab file). After manually refreshing your cache," + - " restart this client. If you continue to see this message after manually refreshing" + - " your cache, ensure that your KDC host's clock is in sync with this host's clock."); - } - } - else { - if (callback instanceof RealmCallback) { - RealmCallback rc = (RealmCallback) callback; - rc.setText(rc.getDefaultText()); - } - else { - if (callback instanceof AuthorizeCallback) { - AuthorizeCallback ac = (AuthorizeCallback) callback; - String authid = ac.getAuthenticationID(); - String authzid = ac.getAuthorizationID(); - if (authid.equals(authzid)) { - ac.setAuthorized(true); - } else { - ac.setAuthorized(false); - } - if (ac.isAuthorized()) { - ac.setAuthorizedID(authzid); - } - } - else { - throw new UnsupportedCallbackException(callback,"Unrecognized SASL ClientCallback"); - } - } - } - } - } - } - } - public boolean clientTunneledAuthenticationInProgress() { if (!isSASLConfigured) { return false; @@ -542,12 +430,10 @@ public boolean clientTunneledAuthenticationInProgress() { // variable or method in this class to determine whether the client is // configured to use SASL. (see also ZOOKEEPER-1455). try { - if ((System.getProperty(Environment.JAAS_CONF_KEY) != null) || - ((javax.security.auth.login.Configuration.getConfiguration() != null) && - (javax.security.auth.login.Configuration.getConfiguration(). - getAppConfigurationEntry(System. - getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY,"Client")) - != null))) { + if ((clientConfig.getJaasConfKey() != null) + || ((Configuration.getConfiguration() != null) && (Configuration.getConfiguration() + .getAppConfigurationEntry(clientConfig.getProperty(ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, + ZKClientConfig.LOGIN_CONTEXT_NAME_KEY_DEFAULT)) != null))) { // Client is configured to use a valid login Configuration, so // authentication is either in progress, successful, or failed. @@ -580,5 +466,12 @@ public boolean clientTunneledAuthenticationInProgress() { } } - + /** + * close login thread if running + */ + public void shutdown() { + if (null != login) { + login.shutdown(); + } + } } diff --git a/src/java/main/org/apache/zookeeper/common/AtomicFileOutputStream.java b/src/java/main/org/apache/zookeeper/common/AtomicFileOutputStream.java index 26035bf790d..740ae8f6787 100644 --- a/src/java/main/org/apache/zookeeper/common/AtomicFileOutputStream.java +++ b/src/java/main/org/apache/zookeeper/common/AtomicFileOutputStream.java @@ -35,10 +35,10 @@ * A FileOutputStream that has the property that it will only show up at its * destination once it has been entirely written and flushed to disk. While * being written, it will use a .tmp suffix. - * + * * When the output stream is closed, it is flushed, fsynced, and will be moved * into place, overwriting any file that already exists at that location. - * + * * NOTE: on Windows platforms, it will not atomically replace the target * file - instead the target file is deleted before this one is moved into * place. @@ -63,6 +63,17 @@ public AtomicFileOutputStream(File f) throws FileNotFoundException { .getAbsoluteFile(); } + /** + * The default write method in FilterOutputStream does not call the write + * method of its underlying input stream with the same arguments. Instead + * it writes the data byte by byte, override it here to make it more + * efficient. + */ + @Override + public void write(byte b[], int off, int len) throws IOException { + out.write(b, off, len); + } + @Override public void close() throws IOException { boolean triedToClose = false, success = false; diff --git a/src/java/main/org/apache/zookeeper/common/HostNameUtils.java b/src/java/main/org/apache/zookeeper/common/HostNameUtils.java deleted file mode 100644 index 8b8877890a4..00000000000 --- a/src/java/main/org/apache/zookeeper/common/HostNameUtils.java +++ /dev/null @@ -1,59 +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.common; - -import java.net.InetAddress; -import java.net.InetSocketAddress; - -/** - * A class with hostname related utility methods. - */ -public class HostNameUtils { - - private HostNameUtils() { - // non instantiable and non inheritable - } - - /** - * Returns the hostname or IP address of {@link java.net.InetSocketAddress}. - * - * This method returns the IP address if the - * {@link java.net.InetSocketAddress} was created with a literal IP address, - * and it doesn't perform a reverse DNS lookup. The goal of this method is - * to substitute {@link java.net.InetSocketAddress#getHostString()}, which - * is only available since Java 7. - * - * This method checks if the input InetSocketAddress was constructed with a - * literal IP address by calling toString() on the underlying - * {@link java.net.InetAddress}. It returns a string with the form - * "hostname/literal IP address", and the hostname part is empty if the - * input {@link java.net.InetSocketAddress} was created with an IP address. - * There are 2 implementations of {@link java.net.InetAddress}, - * {@link java.net.Inet4Address} and {@link java.net.Inet6Address}, and both - * classes are final, so we can trust the return value of the toString() - * method. - * - * @return the hostname or IP address of {@link java.net.InetSocketAddress}. - * @see java.net.InetSocketAddress#getHostString() - */ - public static String getHostString(InetSocketAddress socketAddress) { - InetAddress address = socketAddress.getAddress(); - return (address != null && address.toString().startsWith("/")) ? - address.getHostAddress() : - socketAddress.getHostName(); - } -} diff --git a/src/java/main/org/apache/zookeeper/common/Time.java b/src/java/main/org/apache/zookeeper/common/Time.java new file mode 100644 index 00000000000..83e53f056b9 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/common/Time.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.common; + +import java.util.Date; + +public class Time { + /** + * Returns time in milliseconds as does System.currentTimeMillis(), + * but uses elapsed time from an arbitrary epoch more like System.nanoTime(). + * The difference is that if somebody changes the system clock, + * Time.currentElapsedTime will change but nanoTime won't. On the other hand, + * all of ZK assumes that time is measured in milliseconds. + * @return The time in milliseconds from some arbitrary point in time. + */ + public static long currentElapsedTime() { + return System.nanoTime() / 1000000; + } + + /** + * Explicitly returns system dependent current wall time. + * @return Current time in msec. + */ + public static long currentWallTime() { + return System.currentTimeMillis(); + } + + /** + * This is to convert the elapsedTime to a Date. + * @return A date object indicated by the elapsedTime. + */ + public static Date elapsedTimeToDate(long elapsedTime) { + long wallTime = currentWallTime() + elapsedTime - currentElapsedTime(); + return new Date(wallTime); + } +} \ No newline at end of file diff --git a/src/java/main/org/apache/zookeeper/common/X509Exception.java b/src/java/main/org/apache/zookeeper/common/X509Exception.java new file mode 100644 index 00000000000..984a2abad27 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/common/X509Exception.java @@ -0,0 +1,67 @@ +/** + * 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; + +@SuppressWarnings("serial") +public class X509Exception extends Exception { + public X509Exception(String message) { + super(message); + } + + public X509Exception(Throwable cause) { + super(cause); + } + + public X509Exception(String message, Throwable cause) { + super(message, cause); + } + + public static class KeyManagerException extends X509Exception { + public KeyManagerException(String message) { + super(message); + } + + public KeyManagerException(Throwable cause) { + super(cause); + } + } + + public static class TrustManagerException extends X509Exception { + public TrustManagerException(String message) { + super(message); + } + + public TrustManagerException(Throwable cause) { + super(cause); + } + } + + public static class SSLContextException extends X509Exception { + public SSLContextException(String message) { + super(message); + } + + public SSLContextException(Throwable cause) { + super(cause); + } + + public SSLContextException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/common/X509Util.java b/src/java/main/org/apache/zookeeper/common/X509Util.java new file mode 100644 index 00000000000..cc8662e7984 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/common/X509Util.java @@ -0,0 +1,202 @@ +/** + * 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 javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.zookeeper.common.X509Exception.KeyManagerException; +import static org.apache.zookeeper.common.X509Exception.SSLContextException; +import static org.apache.zookeeper.common.X509Exception.TrustManagerException; + +/** + * Utility code for X509 handling + */ +public class X509Util { + private static final Logger LOG = LoggerFactory.getLogger(X509Util.class); + + /** + * @deprecated Use {@link ZKConfig#SSL_KEYSTORE_LOCATION} + * instead. + */ + @Deprecated + public static final String SSL_KEYSTORE_LOCATION = "zookeeper.ssl.keyStore.location"; + /** + * @deprecated Use {@link ZKConfig#SSL_KEYSTORE_PASSWD} + * instead. + */ + @Deprecated + public static final String SSL_KEYSTORE_PASSWD = "zookeeper.ssl.keyStore.password"; + /** + * @deprecated Use {@link ZKConfig#SSL_TRUSTSTORE_LOCATION} + * instead. + */ + @Deprecated + public static final String SSL_TRUSTSTORE_LOCATION = "zookeeper.ssl.trustStore.location"; + /** + * @deprecated Use {@link ZKConfig#SSL_TRUSTSTORE_PASSWD} + * instead. + */ + @Deprecated + public static final String SSL_TRUSTSTORE_PASSWD = "zookeeper.ssl.trustStore.password"; + /** + * @deprecated Use {@link ZKConfig#SSL_AUTHPROVIDER} + * instead. + */ + @Deprecated + public static final String SSL_AUTHPROVIDER = "zookeeper.ssl.authProvider"; + + public static SSLContext createSSLContext() throws SSLContextException { + /** + * Since Configuration initializes the key store and trust store related + * configuration from system property. Reading property from + * configuration will be same reading from system property + */ + ZKConfig config=new ZKConfig(); + return createSSLContext(config); + } + + public static SSLContext createSSLContext(ZKConfig config) throws SSLContextException { + KeyManager[] keyManagers = null; + TrustManager[] trustManagers = null; + + String keyStoreLocationProp = config.getProperty(ZKConfig.SSL_KEYSTORE_LOCATION); + String keyStorePasswordProp = config.getProperty(ZKConfig.SSL_KEYSTORE_PASSWD); + + // There are legal states in some use cases for null KeyManager or TrustManager. + // But if a user wanna specify one, location and password are required. + + if (keyStoreLocationProp == null && keyStorePasswordProp == null) { + LOG.warn("keystore not specified for client connection"); + } else { + if (keyStoreLocationProp == null) { + throw new SSLContextException("keystore location not specified for client connection"); + } + if (keyStorePasswordProp == null) { + throw new SSLContextException("keystore password not specified for client connection"); + } + try { + keyManagers = new KeyManager[]{ + createKeyManager(keyStoreLocationProp, keyStorePasswordProp)}; + } catch (KeyManagerException e) { + throw new SSLContextException("Failed to create KeyManager", e); + } + } + + String trustStoreLocationProp = config.getProperty(ZKConfig.SSL_TRUSTSTORE_LOCATION); + String trustStorePasswordProp = config.getProperty(ZKConfig.SSL_TRUSTSTORE_PASSWD); + + if (trustStoreLocationProp == null && trustStorePasswordProp == null) { + LOG.warn("Truststore not specified for client connection"); + } else { + if (trustStoreLocationProp == null) { + throw new SSLContextException("Truststore location not specified for client connection"); + } + if (trustStorePasswordProp == null) { + throw new SSLContextException("Truststore password not specified for client connection"); + } + try { + trustManagers = new TrustManager[]{ + createTrustManager(trustStoreLocationProp, trustStorePasswordProp)}; + } catch (TrustManagerException e) { + throw new SSLContextException("Failed to create TrustManager", e); + } + } + + SSLContext sslContext = null; + try { + sslContext = SSLContext.getInstance("TLSv1"); + sslContext.init(keyManagers, trustManagers, null); + } catch (Exception e) { + throw new SSLContextException(e); + } + return sslContext; + } + + public static X509KeyManager createKeyManager(String keyStoreLocation, String keyStorePassword) + throws KeyManagerException { + FileInputStream inputStream = null; + try { + char[] keyStorePasswordChars = keyStorePassword.toCharArray(); + File keyStoreFile = new File(keyStoreLocation); + KeyStore ks = KeyStore.getInstance("JKS"); + inputStream = new FileInputStream(keyStoreFile); + ks.load(inputStream, keyStorePasswordChars); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, keyStorePasswordChars); + + for (KeyManager km : kmf.getKeyManagers()) { + if (km instanceof X509KeyManager) { + return (X509KeyManager) km; + } + } + throw new KeyManagerException("Couldn't find X509KeyManager"); + + } catch (Exception e) { + throw new KeyManagerException(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) {} + } + } + } + + public static X509TrustManager createTrustManager(String trustStoreLocation, String trustStorePassword) + throws TrustManagerException { + FileInputStream inputStream = null; + try { + char[] trustStorePasswordChars = trustStorePassword.toCharArray(); + File trustStoreFile = new File(trustStoreLocation); + KeyStore ts = KeyStore.getInstance("JKS"); + inputStream = new FileInputStream(trustStoreFile); + ts.load(inputStream, trustStorePasswordChars); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(ts); + + for (TrustManager tm : tmf.getTrustManagers()) { + if (tm instanceof X509TrustManager) { + return (X509TrustManager) tm; + } + } + throw new TrustManagerException("Couldn't find X509TrustManager"); + } catch (Exception e) { + throw new TrustManagerException(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) {} + } + } + } +} \ No newline at end of file diff --git a/src/java/main/org/apache/zookeeper/common/ZKConfig.java b/src/java/main/org/apache/zookeeper/common/ZKConfig.java new file mode 100644 index 00000000000..8d9c001328d --- /dev/null +++ b/src/java/main/org/apache/zookeeper/common/ZKConfig.java @@ -0,0 +1,247 @@ +/** + * 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 java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + +import org.apache.zookeeper.Environment; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; +import org.apache.zookeeper.server.util.VerifyingFileFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is a base class for the configurations of both client and server. + * It supports reading client configuration from both system properties and + * configuration file. A user can override any system property by calling + * {@link #setProperty(String, String)}. + * @since 3.5.2 + */ +public class ZKConfig { + + private static final Logger LOG = LoggerFactory.getLogger(ZKConfig.class); + @SuppressWarnings("deprecation") + public static final String SSL_KEYSTORE_LOCATION = X509Util.SSL_KEYSTORE_LOCATION; + @SuppressWarnings("deprecation") + public static final String SSL_KEYSTORE_PASSWD = X509Util.SSL_KEYSTORE_PASSWD; + @SuppressWarnings("deprecation") + public static final String SSL_TRUSTSTORE_LOCATION = X509Util.SSL_TRUSTSTORE_LOCATION; + @SuppressWarnings("deprecation") + public static final String SSL_TRUSTSTORE_PASSWD = X509Util.SSL_TRUSTSTORE_PASSWD; + @SuppressWarnings("deprecation") + public static final String SSL_AUTHPROVIDER = X509Util.SSL_AUTHPROVIDER; + public static final String JUTE_MAXBUFFER = "jute.maxbuffer"; + /** + * Path to a kinit binary: {@value}. Defaults to + * "/usr/bin/kinit" + */ + public static final String KINIT_COMMAND = "zookeeper.kinit"; + public static final String JGSS_NATIVE = "sun.security.jgss.native"; + + private final Map properties = new HashMap(); + + /** + * properties, which are common to both client and server, are initialized + * from system properties + */ + public ZKConfig() { + init(); + } + + /** + * @param configPath + * Configuration file path + * @throws ConfigException + * if failed to load configuration properties + */ + + public ZKConfig(String configPath) throws ConfigException { + this(new File(configPath)); + } + + /** + * + * @param configFile + * Configuration file + * @throws ConfigException + * if failed to load configuration properties + */ + public ZKConfig(File configFile) throws ConfigException { + this(); + addConfiguration(configFile); + } + + private void init() { + /** + * backward compatibility for all currently available client properties + */ + handleBackwardCompatibility(); + } + + /** + * Now onwards client code will use properties from this class but older + * clients still be setting properties through system properties. So to make + * this change backward compatible we should set old system properties in + * this configuration. + */ + protected void handleBackwardCompatibility() { + properties.put(SSL_KEYSTORE_LOCATION, System.getProperty(SSL_KEYSTORE_LOCATION)); + properties.put(SSL_KEYSTORE_PASSWD, System.getProperty(SSL_KEYSTORE_PASSWD)); + properties.put(SSL_TRUSTSTORE_LOCATION, System.getProperty(SSL_TRUSTSTORE_LOCATION)); + properties.put(SSL_TRUSTSTORE_PASSWD, System.getProperty(SSL_TRUSTSTORE_PASSWD)); + properties.put(SSL_AUTHPROVIDER, System.getProperty(SSL_AUTHPROVIDER)); + properties.put(JUTE_MAXBUFFER, System.getProperty(JUTE_MAXBUFFER)); + properties.put(KINIT_COMMAND, System.getProperty(KINIT_COMMAND)); + properties.put(JGSS_NATIVE, System.getProperty(JGSS_NATIVE)); + } + + /** + * Get the property value + * + * @param key + * @return property value + */ + public String getProperty(String key) { + return properties.get(key); + } + + /** + * Get the property value, if it is null return default value + * + * @param key + * property key + * @param defaultValue + * @return property value or default value + */ + public String getProperty(String key, String defaultValue) { + String value = properties.get(key); + return (value == null) ? defaultValue : value; + } + + /** + * Return the value of "java.security.auth.login.config" system property + * + * @return value + */ + public String getJaasConfKey() { + return System.getProperty(Environment.JAAS_CONF_KEY); + } + + /** + * Maps the specified key to the specified value. + * key can not be null. If key is already mapped then the old + * value of the key is replaced by the specified + * value. + * + * @param key + * @param value + */ + public void setProperty(String key, String value) { + if (null == key) { + throw new IllegalArgumentException("property key is null."); + } + String oldValue = properties.put(key, value); + if (LOG.isDebugEnabled()) { + if (null != oldValue && !oldValue.equals(value)) { + LOG.debug("key {}'s value {} is replaced with new value {}", key, oldValue, value); + } + } + } + + /** + * Add a configuration resource. The properties form this configuration will + * overwrite corresponding already loaded property and system property + * + * @param configFile + * Configuration file. + */ + public void addConfiguration(File configFile) throws ConfigException { + LOG.info("Reading configuration from: {}", configFile.getAbsolutePath()); + try { + configFile = (new VerifyingFileFactory.Builder(LOG).warnForRelativePath().failForNonExistingPath().build()) + .validate(configFile); + Properties cfg = new Properties(); + FileInputStream in = new FileInputStream(configFile); + try { + cfg.load(in); + } finally { + in.close(); + } + parseProperties(cfg); + } catch (IOException | IllegalArgumentException e) { + LOG.error("Error while configuration from: {}", configFile.getAbsolutePath(), e); + throw new ConfigException("Error while processing " + configFile.getAbsolutePath(), e); + } + } + + /** + * Add a configuration resource. The properties form this configuration will + * overwrite corresponding already loaded property and system property + * + * @param configPath + * Configuration file path. + */ + public void addConfiguration(String configPath) throws ConfigException { + addConfiguration(new File(configPath)); + } + + private void parseProperties(Properties cfg) { + for (Entry entry : cfg.entrySet()) { + String key = entry.getKey().toString().trim(); + String value = entry.getValue().toString().trim(); + setProperty(key, value); + } + } + + /** + * Returns {@code true} if and only if the property named by the argument + * exists and is equal to the string {@code "true"}. + */ + public boolean getBoolean(String key) { + return Boolean.parseBoolean(getProperty(key)); + } + + /** + * Get the value of the key property as an int. If + * property is not set, the provided defaultValue is returned + * + * @param key + * property key. + * @param defaultValue + * default value. + * @throws NumberFormatException + * when the value is invalid + * @return return property value as an int, or + * defaultValue + */ + public int getInt(String key, int defaultValue) { + String value = getProperty(key); + if (value != null) { + return Integer.parseInt(value.trim()); + } + return defaultValue; + } + +} diff --git a/src/java/main/org/apache/zookeeper/jmx/MBeanRegistry.java b/src/java/main/org/apache/zookeeper/jmx/MBeanRegistry.java index 600431eaf95..79b34e95da2 100644 --- a/src/java/main/org/apache/zookeeper/jmx/MBeanRegistry.java +++ b/src/java/main/org/apache/zookeeper/jmx/MBeanRegistry.java @@ -43,7 +43,7 @@ public class MBeanRegistry { private static final Logger LOG = LoggerFactory.getLogger(MBeanRegistry.class); - private static MBeanRegistry instance = new MBeanRegistry(); + private static volatile MBeanRegistry instance = new MBeanRegistry(); private final Object LOCK = new Object(); @@ -52,6 +52,15 @@ public class MBeanRegistry { private MBeanServer mBeanServer; + /** + * Useful for unit tests. Change the MBeanRegistry instance + * + * @param instance new instance + */ + public static void setInstance(MBeanRegistry instance) { + MBeanRegistry.instance = instance; + } + public static MBeanRegistry getInstance() { return instance; } diff --git a/src/java/main/org/apache/zookeeper/jmx/ManagedUtil.java b/src/java/main/org/apache/zookeeper/jmx/ManagedUtil.java index 64e1942bdd7..43451b03133 100644 --- a/src/java/main/org/apache/zookeeper/jmx/ManagedUtil.java +++ b/src/java/main/org/apache/zookeeper/jmx/ManagedUtil.java @@ -24,47 +24,96 @@ import javax.management.MBeanServer; import javax.management.ObjectName; -import org.apache.log4j.LogManager; -import org.apache.log4j.Logger; -import org.apache.log4j.jmx.HierarchyDynamicMBean; -import org.apache.log4j.spi.LoggerRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Shared utilities */ public class ManagedUtil { + private static final Logger LOG = LoggerFactory.getLogger(ManagedUtil.class); + + private static final boolean isLog4jJmxEnabled() { + boolean enabled = false; + + try { + Class.forName("org.apache.log4j.spi.LoggerRepository"); + + if (Boolean.getBoolean("zookeeper.jmx.log4j.disable") == true) { + LOG.info("Log4j found but jmx support is disabled."); + } else { + enabled = true; + LOG.info("Log4j found with jmx enabled."); + } + + } catch (ClassNotFoundException e) { + LOG.info("Log4j not found."); + } + + return enabled; + } + + /** * Register the log4j JMX mbeans. Set environment variable * "zookeeper.jmx.log4j.disable" to true to disable registration. * @see http://logging.apache.org/log4j/1.2/apidocs/index.html?org/apache/log4j/jmx/package-summary.html * @throws JMException if registration fails */ + @SuppressWarnings("rawtypes") public static void registerLog4jMBeans() throws JMException { - if (Boolean.getBoolean("zookeeper.jmx.log4j.disable") == true) { - return; - } - - MBeanServer mbs = MBeanRegistry.getInstance().getPlatformMBeanServer(); + if (isLog4jJmxEnabled()) { + LOG.debug("registerLog4jMBeans()"); + MBeanServer mbs = MBeanRegistry.getInstance().getPlatformMBeanServer(); + + try { + // Create and Register the top level Log4J MBean + // org.apache.log4j.jmx.HierarchyDynamicMBean hdm = new org.apache.log4j.jmx.HierarchyDynamicMBean(); + Object hdm = Class.forName("org.apache.log4j.jmx.HierarchyDynamicMBean").getDeclaredConstructor().newInstance(); + + ObjectName mbo = new ObjectName("log4j:hiearchy=default"); + mbs.registerMBean(hdm, mbo); - // Create and Register the top level Log4J MBean - HierarchyDynamicMBean hdm = new HierarchyDynamicMBean(); + // Add the root logger to the Hierarchy MBean + // org.apache.log4j.Logger rootLogger = + // org.apache.log4j.Logger.getRootLogger(); + Object rootLogger = Class.forName("org.apache.log4j.Logger") + .getMethod("getRootLogger", (Class[]) null) + .invoke(null, (Object[]) null); - ObjectName mbo = new ObjectName("log4j:hiearchy=default"); - mbs.registerMBean(hdm, mbo); + // hdm.addLoggerMBean(rootLogger.getName()); + Object rootLoggerName = rootLogger.getClass() + .getMethod("getName", (Class[]) null) + .invoke(rootLogger, (Object[]) null); + hdm.getClass().getMethod("addLoggerMBean", String.class) + .invoke(hdm, rootLoggerName); - // Add the root logger to the Hierarchy MBean - Logger rootLogger = Logger.getRootLogger(); - hdm.addLoggerMBean(rootLogger.getName()); + // Get each logger from the Log4J Repository and add it to the + // Hierarchy MBean created above. + // org.apache.log4j.spi.LoggerRepository r = + // org.apache.log4j.LogManager.getLoggerRepository(); + Object r = Class.forName("org.apache.log4j.LogManager") + .getMethod("getLoggerRepository", (Class[]) null) + .invoke(null, (Object[]) null); - // Get each logger from the Log4J Repository and add it to - // the Hierarchy MBean created above. - LoggerRepository r = LogManager.getLoggerRepository(); - Enumeration enumer = r.getCurrentLoggers(); - Logger logger = null; + // Enumeration enumer = r.getCurrentLoggers(); + Enumeration enumer = (Enumeration) r.getClass() + .getMethod("getCurrentLoggers", (Class[]) null) + .invoke(r, (Object[]) null); - while (enumer.hasMoreElements()) { - logger = (Logger) enumer.nextElement(); - hdm.addLoggerMBean(logger.getName()); + while (enumer.hasMoreElements()) { + Object logger = enumer.nextElement(); + // hdm.addLoggerMBean(logger.getName()); + Object loggerName = logger.getClass() + .getMethod("getName", (Class[]) null) + .invoke(logger, (Object[]) null); + hdm.getClass().getMethod("addLoggerMBean", String.class) + .invoke(hdm, loggerName); + } + } catch (Exception e) { + LOG.error("Problems while registering log4j jmx beans!", e); + throw new JMException(e.toString()); + } } } diff --git a/src/java/main/org/apache/zookeeper/server/ConnectionBean.java b/src/java/main/org/apache/zookeeper/server/ConnectionBean.java index 917aacfdcdc..58917e05f2b 100644 --- a/src/java/main/org/apache/zookeeper/server/ConnectionBean.java +++ b/src/java/main/org/apache/zookeeper/server/ConnectionBean.java @@ -22,10 +22,10 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Arrays; -import java.util.Date; import javax.management.ObjectName; +import org.apache.zookeeper.common.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.jmx.MBeanRegistry; @@ -164,7 +164,7 @@ public String getLastZxid() { } public String getLastResponseTime() { - return new Date(stats.getLastResponseTime()).toString(); + return Time.elapsedTimeToDate(stats.getLastResponseTime()).toString(); } public long getLastLatency() { diff --git a/src/java/main/org/apache/zookeeper/server/ContainerManager.java b/src/java/main/org/apache/zookeeper/server/ContainerManager.java new file mode 100644 index 00000000000..2b41ac6ce29 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/ContainerManager.java @@ -0,0 +1,178 @@ +/** + * 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.zookeeper.ZooDefs; +import org.apache.zookeeper.common.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Manages cleanup of container ZNodes. This class is meant to only + * be run from the leader. There's no harm in running from followers/observers + * but that will be extra work that's not needed. Once started, it periodically + * checks container nodes that have a cversion > 0 and have no children. A + * delete is attempted on the node. The result of the delete is unimportant. + * If the proposal fails or the container node is not empty there's no harm. + */ +public class ContainerManager { + private static final Logger LOG = LoggerFactory.getLogger(ContainerManager.class); + private final ZKDatabase zkDb; + private final RequestProcessor requestProcessor; + private final int checkIntervalMs; + private final int maxPerMinute; + private final Timer timer; + private final AtomicReference task = new AtomicReference(null); + + /** + * @param zkDb the ZK database + * @param requestProcessor request processer - used to inject delete + * container requests + * @param checkIntervalMs how often to check containers in milliseconds + * @param maxPerMinute the max containers to delete per second - avoids + * herding of container deletions + */ + public ContainerManager(ZKDatabase zkDb, RequestProcessor requestProcessor, + int checkIntervalMs, int maxPerMinute) { + this.zkDb = zkDb; + this.requestProcessor = requestProcessor; + this.checkIntervalMs = checkIntervalMs; + this.maxPerMinute = maxPerMinute; + timer = new Timer("ContainerManagerTask", true); + + LOG.info(String.format("Using checkIntervalMs=%d maxPerMinute=%d", + checkIntervalMs, maxPerMinute)); + } + + /** + * start/restart the timer the runs the check. Can safely be called + * multiple times. + */ + public void start() { + if (task.get() == null) { + TimerTask timerTask = new TimerTask() { + @Override + public void run() { + try { + checkContainers(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOG.info("interrupted"); + cancel(); + } catch ( Throwable e ) { + LOG.error("Error checking containers", e); + } + } + }; + if (task.compareAndSet(null, timerTask)) { + timer.scheduleAtFixedRate(timerTask, checkIntervalMs, + checkIntervalMs); + } + } + } + + /** + * stop the timer if necessary. Can safely be called multiple times. + */ + public void stop() { + TimerTask timerTask = task.getAndSet(null); + if (timerTask != null) { + timerTask.cancel(); + } + timer.cancel(); + } + + /** + * Manually check the containers. Not normally used directly + */ + public void checkContainers() + throws InterruptedException { + long minIntervalMs = getMinIntervalMs(); + for (String containerPath : getCandidates()) { + long startMs = Time.currentElapsedTime(); + + ByteBuffer path = ByteBuffer.wrap(containerPath.getBytes()); + Request request = new Request(null, 0, 0, + ZooDefs.OpCode.deleteContainer, path, null); + try { + LOG.info("Attempting to delete candidate container: {}", + containerPath); + requestProcessor.processRequest(request); + } catch (Exception e) { + LOG.error("Could not delete container: {}", + containerPath, e); + } + + long elapsedMs = Time.currentElapsedTime() - startMs; + long waitMs = minIntervalMs - elapsedMs; + if (waitMs > 0) { + Thread.sleep(waitMs); + } + } + } + + // VisibleForTesting + protected long getMinIntervalMs() { + return TimeUnit.MINUTES.toMillis(1) / maxPerMinute; + } + + // VisibleForTesting + protected Collection getCandidates() { + Set candidates = new HashSet(); + for (String containerPath : zkDb.getDataTree().getContainers()) { + DataNode node = zkDb.getDataTree().getNode(containerPath); + /* + cversion > 0: keep newly created containers from being deleted + before any children have been added. If you were to create the + container just before a container cleaning period the container + would be immediately be deleted. + */ + if ((node != null) && (node.stat.getCversion() > 0) && + (node.getChildren().size() == 0)) { + candidates.add(containerPath); + } + } + for (String ttlPath : zkDb.getDataTree().getTtls()) { + DataNode node = zkDb.getDataTree().getNode(ttlPath); + if (node != null) { + Set children = node.getChildren(); + if ((children == null) || (children.size() == 0)) { + if ( EphemeralType.get(node.stat.getEphemeralOwner()) == EphemeralType.TTL ) { + long elapsed = getElapsed(node); + long ttl = EphemeralType.TTL.getValue(node.stat.getEphemeralOwner()); + if ((ttl != 0) && (getElapsed(node) > ttl)) { + candidates.add(ttlPath); + } + } + } + } + } + return candidates; + } + + // VisibleForTesting + protected long getElapsed(DataNode node) { + return Time.currentWallTime() - node.stat.getMtime(); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/DataNode.java b/src/java/main/org/apache/zookeeper/server/DataNode.java index b341a698126..0859aab22c8 100644 --- a/src/java/main/org/apache/zookeeper/server/DataNode.java +++ b/src/java/main/org/apache/zookeeper/server/DataNode.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.HashSet; import java.util.Set; +import java.util.Collections; import org.apache.jute.InputArchive; import org.apache.jute.OutputArchive; @@ -56,6 +57,8 @@ public class DataNode implements Record { */ private Set children = null; + private static final Set EMPTY_SET = Collections.emptySet(); + /** * default constructor for the datanode */ @@ -121,10 +124,15 @@ public synchronized void setChildren(HashSet children) { /** * convenience methods to get the children * - * @return the children of this datanode + * @return the children of this datanode. If the datanode has no children, empty + * set is returned */ public synchronized Set getChildren() { - return children; + if (children == null) { + return EMPTY_SET; + } + + return Collections.unmodifiableSet(children); } public synchronized long getApproximateDataSize() { @@ -140,7 +148,7 @@ synchronized public void copyStat(Stat to) { to.setMzxid(stat.getMzxid()); to.setPzxid(stat.getPzxid()); to.setVersion(stat.getVersion()); - to.setEphemeralOwner(stat.getEphemeralOwner()); + to.setEphemeralOwner(getClientEphemeralOwner(stat)); to.setDataLength(data == null ? 0 : data.length); int numChildren = 0; if (this.children != null) { @@ -153,6 +161,14 @@ synchronized public void copyStat(Stat to) { to.setNumChildren(numChildren); } + private static long getClientEphemeralOwner(StatPersisted stat) { + EphemeralType ephemeralType = EphemeralType.get(stat.getEphemeralOwner()); + if (ephemeralType != EphemeralType.NORMAL) { + return 0; + } + return stat.getEphemeralOwner(); + } + synchronized public void deserialize(InputArchive archive, String tag) throws IOException { archive.startRecord("node"); diff --git a/src/java/main/org/apache/zookeeper/server/DataTree.java b/src/java/main/org/apache/zookeeper/server/DataTree.java index 78cddb1dee1..f17be307d30 100644 --- a/src/java/main/org/apache/zookeeper/server/DataTree.java +++ b/src/java/main/org/apache/zookeeper/server/DataTree.java @@ -18,20 +18,6 @@ package org.apache.zookeeper.server; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.jute.Index; import org.apache.jute.InputArchive; import org.apache.jute.OutputArchive; import org.apache.jute.Record; @@ -43,18 +29,19 @@ import org.apache.zookeeper.StatsTrack; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.Watcher.WatcherType; -import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.Watcher.Event; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.Event.KeeperState; -import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.Watcher.WatcherType; +import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooDefs.OpCode; import org.apache.zookeeper.common.PathTrie; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.data.StatPersisted; import org.apache.zookeeper.txn.CheckVersionTxn; +import org.apache.zookeeper.txn.CreateContainerTxn; +import org.apache.zookeeper.txn.CreateTTLTxn; import org.apache.zookeeper.txn.CreateTxn; import org.apache.zookeeper.txn.DeleteTxn; import org.apache.zookeeper.txn.ErrorTxn; @@ -66,6 +53,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + /** * This class maintains the tree data structure. It doesn't have any networking * or client connection code in it so that it can be tested in a stand alone @@ -117,7 +118,7 @@ public class DataTree { /** this will be the string thats stored as a child of /zookeeper */ private static final String configChildZookeeper = configZookeeper .substring(procZookeeper.length() + 1); - + /** * the path trie that keeps track fo the quota nodes in this datatree */ @@ -130,22 +131,18 @@ public class DataTree { new ConcurrentHashMap>(); /** - * this is map from longs to acl's. It saves acl's being stored for each - * datanode. + * This set contains the paths of all container nodes */ - private final Map> longKeyMap = - new HashMap>(); + private final Set containers = + Collections.newSetFromMap(new ConcurrentHashMap()); /** - * this a map from acls to long. + * This set contains the paths of all ttl nodes */ - private final Map, Long> aclKeyMap = - new HashMap, Long>(); + private final Set ttls = + Collections.newSetFromMap(new ConcurrentHashMap()); - /** - * these are the number of acls that we have in the datatree - */ - private long aclIndex = 0; + private final ReferenceCountedACLCache aclCache = new ReferenceCountedACLCache(); @SuppressWarnings("unchecked") public Set getEphemerals(long sessionId) { @@ -160,52 +157,12 @@ public Set getEphemerals(long sessionId) { return cloned; } - int getAclSize() { - return longKeyMap.size(); - } - - private long incrementIndex() { - return ++aclIndex; + public Set getContainers() { + return new HashSet(containers); } - /** - * converts the list of acls to a list of longs. - * - * @param acls - * @return a list of longs that map to the acls - */ - public synchronized Long convertAcls(List acls) { - if (acls == null) - return -1L; - // get the value from the map - Long ret = aclKeyMap.get(acls); - // could not find the map - if (ret != null) - return ret; - long val = incrementIndex(); - longKeyMap.put(val, acls); - aclKeyMap.put(acls, val); - return val; - } - - /** - * converts a list of longs to a list of acls. - * - * @param longVal - * the list of longs - * @return a list of ACLs that map to longs - */ - public synchronized List convertLong(Long longVal) { - if (longVal == null) - return null; - if (longVal == -1L) - return Ids.OPEN_ACL_UNSAFE; - List acls = longKeyMap.get(longVal); - if (acls == null) { - LOG.error("ERROR: ACL not available for long " + longVal); - throw new RuntimeException("Failed to fetch acls for " + longVal); - } - return acls; + public Set getTtls() { + return new HashSet(ttls); } public Collection getSessions() { @@ -266,13 +223,6 @@ public long approximateDataSize() { */ private final DataNode quotaDataNode = new DataNode(new byte[0], -1L, new StatPersisted()); - /** - * create a /zookeeper/config node for maintaining the configuration (membership and quorum system) info for - * zookeeper - */ - private DataNode configDataNode = new DataNode(new byte[0], -1L, new StatPersisted()); - - public DataTree() { /* Rather than fight it, let root have an alias */ nodes.put("", root); @@ -284,19 +234,31 @@ public DataTree() { procDataNode.addChild(quotaChildZookeeper); nodes.put(quotaZookeeper, quotaDataNode); - + addConfigNode(); } - public void addConfigNode() { - DataNode zookeeperZnode = nodes.get(procZookeeper); - if (zookeeperZnode!=null) { // should always be the case - zookeeperZnode.addChild(configChildZookeeper); - } else { - LOG.error("There's no /zookeeper znode - this should never happen"); - } - nodes.put(configZookeeper, configDataNode); - } + /** + * create a /zookeeper/config node for maintaining the configuration (membership and quorum system) info for + * zookeeper + */ + public void addConfigNode() { + DataNode zookeeperZnode = nodes.get(procZookeeper); + if (zookeeperZnode != null) { // should always be the case + zookeeperZnode.addChild(configChildZookeeper); + } else { + assert false : "There's no /zookeeper znode - this should never happen."; + } + + nodes.put(configZookeeper, new DataNode(new byte[0], -1L, new StatPersisted())); + try { + // Reconfig node is access controlled by default (ZOOKEEPER-2014). + setACL(configZookeeper, ZooDefs.Ids.READ_ACL_UNSAFE, -1); + } catch (KeeperException.NoNodeException e) { + assert false : "There's no " + configZookeeper + + " znode - this should never happen."; + } + } /** * is the path one of the special paths owned by zookeeper. @@ -441,8 +403,8 @@ public void updateBytes(String lastPrefix, long diff) { * @param zxid * Transaction ID * @param time - * @throws NodeExistsException - * @throws NoNodeException + * @throws NodeExistsException + * @throws NoNodeException * @throws KeeperException */ public void createNode(final String path, byte data[], List acl, @@ -450,7 +412,7 @@ public void createNode(final String path, byte data[], List acl, throws NoNodeException, NodeExistsException { createNode(path, data, acl, ephemeralOwner, parentCVersion, zxid, time, null); } - + /** * Add a new node to the DataTree. * @param path @@ -467,8 +429,8 @@ public void createNode(final String path, byte data[], List acl, * @param time * @param outputStat * A Stat object to store Stat output results into. - * @throws NodeExistsException - * @throws NoNodeException + * @throws NodeExistsException + * @throws NoNodeException * @throws KeeperException */ public void createNode(final String path, byte data[], List acl, @@ -493,7 +455,7 @@ public void createNode(final String path, byte data[], List acl, } synchronized (parent) { Set children = parent.getChildren(); - if (children != null && children.contains(childName)) { + if (children.contains(childName)) { throw new KeeperException.NodeExistsException(); } @@ -503,11 +465,16 @@ public void createNode(final String path, byte data[], List acl, } parent.stat.setCversion(parentCVersion); parent.stat.setPzxid(zxid); - Long longval = convertAcls(acl); + Long longval = aclCache.convertAcls(acl); DataNode child = new DataNode(data, longval, stat); parent.addChild(childName); nodes.put(path, child); - if (ephemeralOwner != 0) { + EphemeralType ephemeralType = EphemeralType.get(ephemeralOwner); + if (ephemeralType == EphemeralType.CONTAINER) { + containers.add(path); + } else if (ephemeralType == EphemeralType.TTL) { + ttls.add(path); + } else if (ephemeralOwner != 0) { HashSet list = ephemerals.get(ephemeralOwner); if (list == null) { list = new HashSet(); @@ -565,6 +532,9 @@ public void deleteNode(String path, long zxid) throw new KeeperException.NoNodeException(); } nodes.remove(path); + synchronized (node) { + aclCache.removeUsage(node.acl); + } DataNode parent = nodes.get(parentName); if (parent == null) { throw new KeeperException.NoNodeException(); @@ -573,7 +543,12 @@ public void deleteNode(String path, long zxid) parent.removeChild(childName); parent.stat.setPzxid(zxid); long eowner = node.stat.getEphemeralOwner(); - if (eowner != 0) { + EphemeralType ephemeralType = EphemeralType.get(eowner); + if (ephemeralType == EphemeralType.CONTAINER) { + containers.remove(path); + } else if (ephemeralType == EphemeralType.TTL) { + ttls.remove(path); + } else if (eowner != 0) { HashSet nodes = ephemerals.get(eowner); if (nodes != null) { synchronized (nodes) { @@ -699,13 +674,7 @@ public List getChildren(String path, Stat stat, Watcher watcher) if (stat != null) { n.copyStat(stat); } - ArrayList children; - Set childs = n.getChildren(); - if (childs == null) { - children = new ArrayList(0); - } else { - children = new ArrayList(childs); - } + List children=new ArrayList(n.getChildren()); if (watcher != null) { childWatches.addWatch(path, watcher); @@ -722,8 +691,9 @@ public Stat setACL(String path, List acl, int version) throw new KeeperException.NoNodeException(); } synchronized (n) { + aclCache.removeUsage(n.acl); n.stat.setAversion(version); - n.acl = convertAcls(acl); + n.acl = aclCache.convertAcls(acl); n.copyStat(stat); return stat; } @@ -737,10 +707,20 @@ public List getACL(String path, Stat stat) } synchronized (n) { n.copyStat(stat); - return new ArrayList(convertLong(n.acl)); + return new ArrayList(aclCache.convertLong(n.acl)); + } + } + + public List getACL(DataNode node) { + synchronized (node) { + return aclCache.convertLong(node.acl); } } + public int aclCacheSize() { + return aclCache.size(); + } + static public class ProcessTxnResult { public long clientId; @@ -824,7 +804,34 @@ public ProcessTxnResult processTxn(TxnHeader header, Record txn) header.getZxid(), header.getTime(), stat); rc.stat = stat; break; + case OpCode.createTTL: + CreateTTLTxn createTtlTxn = (CreateTTLTxn) txn; + rc.path = createTtlTxn.getPath(); + stat = new Stat(); + createNode( + createTtlTxn.getPath(), + createTtlTxn.getData(), + createTtlTxn.getAcl(), + EphemeralType.TTL.toEphemeralOwner(createTtlTxn.getTtl()), + createTtlTxn.getParentCVersion(), + header.getZxid(), header.getTime(), stat); + rc.stat = stat; + break; + case OpCode.createContainer: + CreateContainerTxn createContainerTxn = (CreateContainerTxn) txn; + rc.path = createContainerTxn.getPath(); + stat = new Stat(); + createNode( + createContainerTxn.getPath(), + createContainerTxn.getData(), + createContainerTxn.getAcl(), + EphemeralType.CONTAINER_EPHEMERAL_OWNER, + createContainerTxn.getParentCVersion(), + header.getZxid(), header.getTime(), stat); + rc.stat = stat; + break; case OpCode.delete: + case OpCode.deleteContainer: DeleteTxn deleteTxn = (DeleteTxn) txn; rc.path = deleteTxn.getPath(); deleteNode(deleteTxn.getPath(), header.getZxid()); @@ -874,7 +881,14 @@ public ProcessTxnResult processTxn(TxnHeader header, Record txn) case OpCode.create: record = new CreateTxn(); break; + case OpCode.createTTL: + record = new CreateTTLTxn(); + break; + case OpCode.createContainer: + record = new CreateContainerTxn(); + break; case OpCode.delete: + case OpCode.deleteContainer: record = new DeleteTxn(); break; case OpCode.setData: @@ -1033,17 +1047,12 @@ private void getCounts(String path, Counts counts) { int len = 0; synchronized (node) { Set childs = node.getChildren(); - if (childs != null) { - children = childs.toArray(new String[childs.size()]); - } + children = childs.toArray(new String[childs.size()]); len = (node.data == null ? 0 : node.data.length); } // add itself counts.count += 1; counts.bytes += len; - if (children == null || children.length == 0) { - return; - } for (String child : children) { getCounts(path + "/" + child, counts); } @@ -1083,11 +1092,9 @@ private void traverseNode(String path) { String children[] = null; synchronized (node) { Set childs = node.getChildren(); - if (childs != null) { - children = childs.toArray(new String[childs.size()]); - } + children = childs.toArray(new String[childs.size()]); } - if (children == null || children.length == 0) { + if (children.length == 0) { // this node does not have a child // is the leaf node // check if its the leaf node @@ -1138,67 +1145,32 @@ void serializeNode(OutputArchive oa, StringBuilder path) throws IOException { return; } String children[] = null; + DataNode nodeCopy; synchronized (node) { - oa.writeString(pathString, "path"); - oa.writeRecord(node, "node"); + StatPersisted statCopy = new StatPersisted(); + copyStatPersisted(node.stat, statCopy); + //we do not need to make a copy of node.data because the contents + //are never changed + nodeCopy = new DataNode(node.data, node.acl, statCopy); Set childs = node.getChildren(); - if (childs != null) { - children = childs.toArray(new String[childs.size()]); - } + children = childs.toArray(new String[childs.size()]); } + oa.writeString(pathString, "path"); + oa.writeRecord(nodeCopy, "node"); path.append('/'); int off = path.length(); - if (children != null) { - for (String child : children) { - // since this is single buffer being resused - // we need - // to truncate the previous bytes of string. - path.delete(off, Integer.MAX_VALUE); - path.append(child); - serializeNode(oa, path); - } - } - } - - private void deserializeList(Map> longKeyMap, - InputArchive ia) throws IOException { - int i = ia.readInt("map"); - while (i > 0) { - Long val = ia.readLong("long"); - if (aclIndex < val) { - aclIndex = val; - } - List aclList = new ArrayList(); - Index j = ia.startVector("acls"); - while (!j.done()) { - ACL acl = new ACL(); - acl.deserialize(ia, "acl"); - aclList.add(acl); - j.incr(); - } - longKeyMap.put(val, aclList); - aclKeyMap.put(aclList, val); - i--; - } - } - - private synchronized void serializeList(Map> longKeyMap, - OutputArchive oa) throws IOException { - oa.writeInt(longKeyMap.size(), "map"); - Set>> set = longKeyMap.entrySet(); - for (Map.Entry> val : set) { - oa.writeLong(val.getKey(), "long"); - List aclList = val.getValue(); - oa.startVector(aclList, "acls"); - for (ACL acl : aclList) { - acl.serialize(oa, "acl"); - } - oa.endVector(aclList, "acls"); + for (String child : children) { + // since this is single buffer being resused + // we need + // to truncate the previous bytes of string. + path.delete(off, Integer.MAX_VALUE); + path.append(child); + serializeNode(oa, path); } } public void serialize(OutputArchive oa, String tag) throws IOException { - serializeList(longKeyMap, oa); + aclCache.serialize(oa); serializeNode(oa, new StringBuilder("")); // / marks end of stream // we need to check if clear had been called in between the snapshot. @@ -1208,7 +1180,7 @@ public void serialize(OutputArchive oa, String tag) throws IOException { } public void deserialize(InputArchive ia, String tag) throws IOException { - deserializeList(longKeyMap, ia); + aclCache.deserialize(ia); nodes.clear(); pTrie.clear(); String path = ia.readString("path"); @@ -1216,6 +1188,9 @@ public void deserialize(InputArchive ia, String tag) throws IOException { DataNode node = new DataNode(); ia.readRecord(node, "node"); nodes.put(path, node); + synchronized (node) { + aclCache.addUsage(node.acl); + } int lastSlash = path.lastIndexOf('/'); if (lastSlash == -1) { root = node; @@ -1228,7 +1203,12 @@ public void deserialize(InputArchive ia, String tag) throws IOException { } parent.addChild(path.substring(lastSlash + 1)); long eowner = node.stat.getEphemeralOwner(); - if (eowner != 0) { + EphemeralType ephemeralType = EphemeralType.get(eowner); + if (ephemeralType == EphemeralType.CONTAINER) { + containers.add(path); + } else if (ephemeralType == EphemeralType.TTL) { + ttls.add(path); + } else if (eowner != 0) { HashSet list = ephemerals.get(eowner); if (list == null) { list = new HashSet(); @@ -1245,6 +1225,8 @@ public void deserialize(InputArchive ia, String tag) throws IOException { // update the quotas - create path trie // and also update the stat nodes setupQuota(); + + aclCache.purgeUnused(); } /** @@ -1299,13 +1281,12 @@ public synchronized WatchesSummary getWatchesSummary() { * @param pwriter the output to write to */ public void dumpEphemerals(PrintWriter pwriter) { - Set keys = ephemerals.keySet(); pwriter.println("Sessions with Ephemerals (" - + keys.size() + "):"); - for (long k : keys) { - pwriter.print("0x" + Long.toHexString(k)); + + ephemerals.keySet().size() + "):"); + for (Entry> entry : ephemerals.entrySet()) { + pwriter.print("0x" + Long.toHexString(entry.getKey())); pwriter.println(":"); - HashSet tmp = ephemerals.get(k); + HashSet tmp = entry.getValue(); if (tmp != null) { synchronized (tmp) { for (String path : tmp) { @@ -1343,37 +1324,37 @@ public void setWatches(long relativeZxid, List dataWatches, DataNode node = getNode(path); WatchedEvent e = null; if (node == null) { - watcher.process(new WatchedEvent(EventType.NodeDeleted, + watcher.process(new WatchedEvent(EventType.NodeDeleted, KeeperState.SyncConnected, path)); } else if (node.stat.getMzxid() > relativeZxid) { - watcher.process(new WatchedEvent(EventType.NodeDataChanged, + watcher.process(new WatchedEvent(EventType.NodeDataChanged, KeeperState.SyncConnected, path)); } else { this.dataWatches.addWatch(path, watcher); - } - } + } + } for (String path : existWatches) { DataNode node = getNode(path); if (node != null) { - watcher.process(new WatchedEvent(EventType.NodeCreated, + watcher.process(new WatchedEvent(EventType.NodeCreated, KeeperState.SyncConnected, path)); } else { this.dataWatches.addWatch(path, watcher); - } - } + } + } for (String path : childWatches) { DataNode node = getNode(path); if (node == null) { - watcher.process(new WatchedEvent(EventType.NodeDeleted, + watcher.process(new WatchedEvent(EventType.NodeDeleted, KeeperState.SyncConnected, path)); } else if (node.stat.getPzxid() > relativeZxid) { - watcher.process(new WatchedEvent(EventType.NodeChildrenChanged, + watcher.process(new WatchedEvent(EventType.NodeChildrenChanged, KeeperState.SyncConnected, path)); } else { this.childWatches.addWatch(path, watcher); - } - } - } + } + } + } /** * This method sets the Cversion and Pzxid for the specified node to the @@ -1452,4 +1433,9 @@ public boolean removeWatch(String path, WatcherType type, Watcher watcher) { } return removed; } + + // visible for testing + public ReferenceCountedACLCache getReferenceCountedAclCache() { + return aclCache; + } } diff --git a/src/java/main/org/apache/zookeeper/server/DatadirCleanupManager.java b/src/java/main/org/apache/zookeeper/server/DatadirCleanupManager.java index bd928cb36cc..4a6d1a215ca 100644 --- a/src/java/main/org/apache/zookeeper/server/DatadirCleanupManager.java +++ b/src/java/main/org/apache/zookeeper/server/DatadirCleanupManager.java @@ -139,7 +139,7 @@ public void run() { try { PurgeTxnLog.purge(logsDir, snapsDir, snapRetainCount); } catch (Exception e) { - LOG.error("Error occured while purging.", e); + LOG.error("Error occurred while purging.", e); } LOG.info("Purge task completed."); } diff --git a/src/java/main/org/apache/zookeeper/server/EphemeralType.java b/src/java/main/org/apache/zookeeper/server/EphemeralType.java new file mode 100644 index 00000000000..f5d58ae8bec --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/EphemeralType.java @@ -0,0 +1,228 @@ +/** + * 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.zookeeper.CreateMode; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + *

    + * Abstraction that interprets the ephemeralOwner field of a ZNode. Originally, + * the ephemeralOwner noted that a ZNode is ephemeral and which session created the node. + * Through an optional system property (zookeeper.extendedTypesEnabled) "extended" + * features such as TTL Nodes can be enabled. Special bits of the ephemeralOwner are used to + * denote which feature is enabled and the remaining bits of the ephemeralOwner are feature + * specific. + *

    + *

    + *

    + * When the system property zookeeper.extendedTypesEnabled is true, extended types + * are enabled. An extended ephemeralOwner is defined as an ephemeralOwner whose high 8 bits are + * set (0xff00000000000000L). The two bytes that follow the high 8 bits are + * used to denote which extended feature the ephemeralOwner represents. The remaining 5 bytes are + * used by the feature for whatever purpose is needed + *

    + *

    + *

    + * Currently, the only extended feature is TTL Nodes. It is denoted by the extended feature value of 0. + * i.e. for TTL Nodes, the ephemeralOwner has the high byte set to 0xff and the next 2 bytes are 0 followed + * by 5 bytes that represent the TTL value in milliseconds. So, an ephemeralOwner with a TTL value of 1 + * millisecond is: 0xff00000000000001. + *

    + *

    + *

    + * To add new extended features: a) Add a new name to the enum, b) define a constant EXTENDED_BIT_XXXX that's next + * in line (after TTLs, that would be 0x0001), c) add a mapping to the extendedFeatureMap via the static + * initializer + *

    + *

    + *

    + * NOTE: "Container" nodes technically are extended types but as it was implemented before this feature they are + * denoted specially. An ephemeral owner with only the high bit set (0x8000000000000000L) is by definition + * a container node (irrespective of whether or not extended types are enabled). + *

    + */ +public enum EphemeralType { + /** + * Not ephemeral + */ + VOID, + /** + * Standard, pre-3.5.x EPHEMERAL + */ + NORMAL, + /** + * Container node + */ + CONTAINER, + /** + * TTL node + */ + TTL() { + @Override + public long maxValue() { + return EXTENDED_FEATURE_VALUE_MASK; // 12725 days, about 34 years + } + + @Override + public long toEphemeralOwner(long ttl) { + if ((ttl > TTL.maxValue()) || (ttl <= 0)) { + throw new IllegalArgumentException("ttl must be positive and cannot be larger than: " + TTL.maxValue()); + } + //noinspection PointlessBitwiseExpression + return EXTENDED_MASK | EXTENDED_BIT_TTL | ttl; // TTL_RESERVED_BIT is actually zero - but it serves to document that the proper extended bit needs to be set + } + + @Override + public long getValue(long ephemeralOwner) { + return getExtendedFeatureValue(ephemeralOwner); + } + }; + + /** + * For types that support it, the maximum extended value + * + * @return 0 or max + */ + public long maxValue() { + return 0; + } + + /** + * For types that support it, convert a value to an extended ephemeral owner + * + * @return 0 or extended ephemeral owner + */ + public long toEphemeralOwner(long value) { + return 0; + } + + /** + * For types that support it, return the extended value from an extended ephemeral owner + * + * @return 0 or extended value + */ + public long getValue(long ephemeralOwner) { + return 0; + } + + public static final long CONTAINER_EPHEMERAL_OWNER = Long.MIN_VALUE; + public static final long MAX_EXTENDED_SERVER_ID = 0xfe; // 254 + + private static final long EXTENDED_MASK = 0xff00000000000000L; + private static final long EXTENDED_BIT_TTL = 0x0000; + private static final long RESERVED_BITS_MASK = 0x00ffff0000000000L; + private static final long RESERVED_BITS_SHIFT = 40; + + private static final Map extendedFeatureMap; + + static { + Map map = new HashMap<>(); + map.put(EXTENDED_BIT_TTL, TTL); + extendedFeatureMap = Collections.unmodifiableMap(map); + } + + private static final long EXTENDED_FEATURE_VALUE_MASK = ~(EXTENDED_MASK | RESERVED_BITS_MASK); + + // Visible for testing + static final String EXTENDED_TYPES_ENABLED_PROPERTY = "zookeeper.extendedTypesEnabled"; + static final String TTL_3_5_3_EMULATION_PROPERTY = "zookeeper.emulate353TTLNodes"; + + /** + * Return true if extended ephemeral types are enabled + * + * @return true/false + */ + public static boolean extendedEphemeralTypesEnabled() { + return Boolean.getBoolean(EXTENDED_TYPES_ENABLED_PROPERTY); + } + + /** + * Convert a ZNode ephemeral owner to an ephemeral type. If extended types are not + * enabled, VOID or NORMAL is always returned + * + * @param ephemeralOwner the ZNode's ephemeral owner + * @return type + */ + public static EphemeralType get(long ephemeralOwner) { + if (extendedEphemeralTypesEnabled()) { + if (Boolean.getBoolean(TTL_3_5_3_EMULATION_PROPERTY)) { + if (EphemeralTypeEmulate353.get(ephemeralOwner) == EphemeralTypeEmulate353.TTL) { + return TTL; + } + } + + if ((ephemeralOwner & EXTENDED_MASK) == EXTENDED_MASK) { + long extendedFeatureBit = getExtendedFeatureBit(ephemeralOwner); + EphemeralType ephemeralType = extendedFeatureMap.get(extendedFeatureBit); + if (ephemeralType == null) { + throw new IllegalArgumentException(String.format("Invalid ephemeralOwner. [%s]", Long.toHexString(ephemeralOwner))); + } + return ephemeralType; + } + } + if (ephemeralOwner == CONTAINER_EPHEMERAL_OWNER) { + return CONTAINER; + } + return (ephemeralOwner == 0) ? VOID : NORMAL; + } + + /** + * Make sure the given server ID is compatible with the current extended ephemeral setting + * + * @param serverId Server ID + * @throws RuntimeException extendedTypesEnabled is true but Server ID is too large + */ + public static void validateServerId(long serverId) { + // TODO: in the future, serverId should be validated for all cases, not just the extendedEphemeralTypesEnabled case + // TODO: however, for now, it would be too disruptive + + if (extendedEphemeralTypesEnabled()) { + if (serverId > EphemeralType.MAX_EXTENDED_SERVER_ID) { + throw new RuntimeException("extendedTypesEnabled is true but Server ID is too large. Cannot be larger than " + EphemeralType.MAX_EXTENDED_SERVER_ID); + } + } + } + + /** + * Utility to validate a create mode and a ttl + * + * @param mode create mode + * @param ttl ttl + * @throws IllegalArgumentException if the ttl is not valid for the mode + */ + public static void validateTTL(CreateMode mode, long ttl) { + if (mode.isTTL()) { + TTL.toEphemeralOwner(ttl); + } else if (ttl >= 0) { + throw new IllegalArgumentException("ttl not valid for mode: " + mode); + } + } + + private static long getExtendedFeatureBit(long ephemeralOwner) { + return (ephemeralOwner & RESERVED_BITS_MASK) >> RESERVED_BITS_SHIFT; + } + + private static long getExtendedFeatureValue(long ephemeralOwner) { + return ephemeralOwner & EXTENDED_FEATURE_VALUE_MASK; + } +} diff --git a/src/java/main/org/apache/zookeeper/server/EphemeralTypeEmulate353.java b/src/java/main/org/apache/zookeeper/server/EphemeralTypeEmulate353.java new file mode 100644 index 00000000000..c8f384f8e83 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/EphemeralTypeEmulate353.java @@ -0,0 +1,67 @@ +/** + * 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; + +/** + * See https://issues.apache.org/jira/browse/ZOOKEEPER-2901 + * + * version 3.5.3 introduced bugs associated with how TTL nodes were implemented. version 3.5.4 + * fixes the problems but makes TTL nodes created in 3.5.3 invalid. EphemeralTypeEmulate353 is a copy + * of the old - bad - implementation that is provided as a workaround. {@link EphemeralType#TTL_3_5_3_EMULATION_PROPERTY} + * can be used to emulate support of the badly specified TTL nodes. + */ +public enum EphemeralTypeEmulate353 { + /** + * Not ephemeral + */ + VOID, + /** + * Standard, pre-3.5.x EPHEMERAL + */ + NORMAL, + /** + * Container node + */ + CONTAINER, + /** + * TTL node + */ + TTL; + + public static final long CONTAINER_EPHEMERAL_OWNER = Long.MIN_VALUE; + public static final long MAX_TTL = 0x0fffffffffffffffL; + public static final long TTL_MASK = 0x8000000000000000L; + + public static EphemeralTypeEmulate353 get(long ephemeralOwner) { + if (ephemeralOwner == CONTAINER_EPHEMERAL_OWNER) { + return CONTAINER; + } + if (ephemeralOwner < 0) { + return TTL; + } + return (ephemeralOwner == 0) ? VOID : NORMAL; + } + + public static long ttlToEphemeralOwner(long ttl) { + if ((ttl > MAX_TTL) || (ttl <= 0)) { + throw new IllegalArgumentException("ttl must be positive and cannot be larger than: " + MAX_TTL); + } + return TTL_MASK | ttl; + } +} diff --git a/src/java/main/org/apache/zookeeper/server/ExitCode.java b/src/java/main/org/apache/zookeeper/server/ExitCode.java new file mode 100644 index 00000000000..02d96cb51d4 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/ExitCode.java @@ -0,0 +1,27 @@ +/** + * 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; + +/** + * Exit code used to exit server + */ +public class ExitCode { + + /* Represents unexpected error */ + public final static int UNEXPECTED_ERROR = 1; +} diff --git a/src/java/main/org/apache/zookeeper/server/ExpiryQueue.java b/src/java/main/org/apache/zookeeper/server/ExpiryQueue.java index a037bf49235..cca1fab3cc8 100644 --- a/src/java/main/org/apache/zookeeper/server/ExpiryQueue.java +++ b/src/java/main/org/apache/zookeeper/server/ExpiryQueue.java @@ -27,6 +27,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.ConcurrentHashMap; +import org.apache.zookeeper.common.Time; + /** * ExpiryQueue tracks elements in time sorted fixed duration buckets. * It's used by SessionTrackerImpl to expire sessions and NIOServerCnxnFactory @@ -48,7 +50,7 @@ public class ExpiryQueue { public ExpiryQueue(int expirationInterval) { this.expirationInterval = expirationInterval; - nextExpirationTime.set(roundToNextInterval(System.currentTimeMillis())); + nextExpirationTime.set(roundToNextInterval(Time.currentElapsedTime())); } private long roundToNextInterval(long time) { @@ -84,7 +86,7 @@ public Long remove(E elem) { */ public Long update(E elem, int timeout) { Long prevExpiryTime = elemMap.get(elem); - long now = System.currentTimeMillis(); + long now = Time.currentElapsedTime(); Long newExpiryTime = roundToNextInterval(now + timeout); if (newExpiryTime.equals(prevExpiryTime)) { @@ -123,7 +125,7 @@ public Long update(E elem, int timeout) { * @return milliseconds until next expiration time, or 0 if has already past */ public long getWaitTime() { - long now = System.currentTimeMillis(); + long now = Time.currentElapsedTime(); long expirationTime = nextExpirationTime.get(); return now < expirationTime ? (expirationTime - now) : 0L; } @@ -137,7 +139,7 @@ public long getWaitTime() { * ready */ public Set poll() { - long now = System.currentTimeMillis(); + long now = Time.currentElapsedTime(); long expirationTime = nextExpirationTime.get(); if (now < expirationTime) { return Collections.emptySet(); @@ -168,7 +170,7 @@ public void dump(PrintWriter pwriter) { if (set != null) { pwriter.print(set.size()); pwriter.print(" expire at "); - pwriter.print(new Date(time)); + pwriter.print(Time.elapsedTimeToDate(time)); pwriter.println(":"); for (E elem : set) { pwriter.print("\t"); diff --git a/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java b/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java index a97be4a5452..921febde485 100644 --- a/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java +++ b/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java @@ -18,21 +18,21 @@ package org.apache.zookeeper.server; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.List; -import java.util.Locale; - import org.apache.jute.Record; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.KeeperException.Code; +import org.apache.zookeeper.KeeperException.SessionMovedException; import org.apache.zookeeper.MultiResponse; +import org.apache.zookeeper.OpResult; +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.Watcher.WatcherType; import org.apache.zookeeper.ZooDefs; -import org.apache.zookeeper.KeeperException.Code; -import org.apache.zookeeper.KeeperException.SessionMovedException; import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.proto.CheckWatchesRequest; @@ -60,13 +60,13 @@ import org.apache.zookeeper.server.quorum.QuorumZooKeeperServer; import org.apache.zookeeper.txn.ErrorTxn; import org.apache.zookeeper.txn.TxnHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import org.apache.zookeeper.OpResult; -import org.apache.zookeeper.OpResult.CheckResult; -import org.apache.zookeeper.OpResult.CreateResult; -import org.apache.zookeeper.OpResult.DeleteResult; -import org.apache.zookeeper.OpResult.SetDataResult; -import org.apache.zookeeper.OpResult.ErrorResult; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Locale; /** * This Request processor actually applies any transaction associated with a @@ -128,16 +128,17 @@ public void processRequest(Request request) { } } - if (request.type == OpCode.closeSession) { - ServerCnxnFactory scxn = zks.getServerCnxnFactory(); - // this might be possible since - // we might just be playing diffs from the leader - if (scxn != null && request.cnxn == null) { - // calling this if we have the cnxn results in the client's - // close session response being lost - we've already closed - // the session/socket here before we can send the closeSession - // in the switch block below - scxn.closeSession(request.sessionId); + // ZOOKEEPER-558: + // In some cases the server does not close the connection (e.g., closeconn buffer + // was not being queued — ZOOKEEPER-558) properly. This happens, for example, + // when the client closes the connection. The server should still close the session, though. + // Calling closeSession() after losing the cnxn, results in the client close session response being dropped. + if (request.type == OpCode.closeSession && connClosedByClient(request)) { + // We need to check if we can close the session id. + // Sometimes the corresponding ServerCnxnFactory could be null because + // we are just playing diffs from the leader. + if (closeSession(zks.serverCnxnFactory, request.sessionId) || + closeSession(zks.secureServerCnxnFactory, request.sessionId)) { return; } } @@ -182,7 +183,7 @@ public void processRequest(Request request) { lastOp = "PING"; cnxn.updateStatsForResponse(request.cxid, request.zxid, lastOp, - request.createTime, System.currentTimeMillis()); + request.createTime, Time.currentElapsedTime()); cnxn.sendResponse(new ReplyHeader(-2, zks.getZKDatabase().getDataTreeLastProcessedZxid(), 0), null, "response"); @@ -193,7 +194,7 @@ public void processRequest(Request request) { lastOp = "SESS"; cnxn.updateStatsForResponse(request.cxid, request.zxid, lastOp, - request.createTime, System.currentTimeMillis()); + request.createTime, Time.currentElapsedTime()); zks.finishSessionInit(request.cnxn, true); return; @@ -214,9 +215,12 @@ public void processRequest(Request request) { subResult = new CreateResult(subTxnResult.path); break; case OpCode.create2: + case OpCode.createTTL: + case OpCode.createContainer: subResult = new CreateResult(subTxnResult.path, subTxnResult.stat); break; case OpCode.delete: + case OpCode.deleteContainer: subResult = new DeleteResult(); break; case OpCode.setData: @@ -240,13 +244,16 @@ public void processRequest(Request request) { err = Code.get(rc.err); break; } - case OpCode.create2: { + case OpCode.create2: + case OpCode.createTTL: + case OpCode.createContainer: { lastOp = "CREA"; rsp = new Create2Response(rc.path, rc.stat); err = Code.get(rc.err); break; } - case OpCode.delete: { + case OpCode.delete: + case OpCode.deleteContainer: { lastOp = "DELE"; err = Code.get(rc.err); break; @@ -256,9 +263,9 @@ public void processRequest(Request request) { rsp = new SetDataResponse(rc.stat); err = Code.get(rc.err); break; - } + } case OpCode.reconfig: { - lastOp = "RECO"; + lastOp = "RECO"; rsp = new GetDataResponse(((QuorumZooKeeperServer)zks).self.getQuorumVerifier().toString().getBytes(), rc.stat); err = Code.get(rc.err); break; @@ -312,11 +319,7 @@ public void processRequest(Request request) { if (n == null) { throw new KeeperException.NoNodeException(); } - Long aclL; - synchronized(n) { - aclL = n.acl; - } - PrepRequestProcessor.checkACL(zks, zks.getZKDatabase().convertLong(aclL), + PrepRequestProcessor.checkACL(zks, zks.getZKDatabase().aclForNode(n), ZooDefs.Perms.READ, request.authInfo); Stat stat = new Stat(); @@ -358,12 +361,7 @@ public void processRequest(Request request) { if (n == null) { throw new KeeperException.NoNodeException(); } - Long aclG; - synchronized(n) { - aclG = n.acl; - - } - PrepRequestProcessor.checkACL(zks, zks.getZKDatabase().convertLong(aclG), + PrepRequestProcessor.checkACL(zks, zks.getZKDatabase().aclForNode(n), ZooDefs.Perms.READ, request.authInfo); List children = zks.getZKDatabase().getChildren( @@ -382,11 +380,7 @@ public void processRequest(Request request) { if (n == null) { throw new KeeperException.NoNodeException(); } - Long aclG; - synchronized(n) { - aclG = n.acl; - } - PrepRequestProcessor.checkACL(zks, zks.getZKDatabase().convertLong(aclG), + PrepRequestProcessor.checkACL(zks, zks.getZKDatabase().aclForNode(n), ZooDefs.Perms.READ, request.authInfo); List children = zks.getZKDatabase().getChildren( @@ -405,7 +399,7 @@ public void processRequest(Request request) { checkWatches.getPath(), type, cnxn); if (!containsWatcher) { String msg = String.format(Locale.ENGLISH, "%s (type: %s)", - new Object[] { checkWatches.getPath(), type }); + checkWatches.getPath(), type); throw new KeeperException.NoWatcherException(msg); } break; @@ -420,7 +414,7 @@ public void processRequest(Request request) { removeWatches.getPath(), type, cnxn); if (!removed) { String msg = String.format(Locale.ENGLISH, "%s (type: %s)", - new Object[] { removeWatches.getPath(), type }); + removeWatches.getPath(), type); throw new KeeperException.NoWatcherException(msg); } break; @@ -459,7 +453,7 @@ public void processRequest(Request request) { zks.serverStats().updateLatency(request.createTime); cnxn.updateStatsForResponse(request.cxid, lastZxid, lastOp, - request.createTime, System.currentTimeMillis()); + request.createTime, Time.currentElapsedTime()); try { cnxn.sendResponse(hdr, rsp, "response"); @@ -471,6 +465,17 @@ public void processRequest(Request request) { } } + private boolean closeSession(ServerCnxnFactory serverCnxnFactory, long sessionId) { + if (serverCnxnFactory == null) { + return false; + } + return serverCnxnFactory.closeSession(sessionId); + } + + private boolean connClosedByClient(Request request) { + return request.cnxn == null; + } + public void shutdown() { // we are the final link in the chain LOG.info("shutdown of request processor complete"); diff --git a/src/java/main/org/apache/zookeeper/server/LogFormatter.java b/src/java/main/org/apache/zookeeper/server/LogFormatter.java index 9be3fe3c118..3fe81e6ec2e 100644 --- a/src/java/main/org/apache/zookeeper/server/LogFormatter.java +++ b/src/java/main/org/apache/zookeeper/server/LogFormatter.java @@ -28,6 +28,7 @@ import org.apache.jute.BinaryInputArchive; import org.apache.jute.Record; +import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.server.persistence.FileHeader; @@ -35,6 +36,7 @@ import org.apache.zookeeper.server.util.SerializeUtils; import org.apache.zookeeper.txn.TxnHeader; +@InterfaceAudience.Public public class LogFormatter { private static final Logger LOG = LoggerFactory.getLogger(LogFormatter.class); diff --git a/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java b/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java index e02753f4fb9..e11ba3e74d7 100644 --- a/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java +++ b/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java @@ -29,29 +29,27 @@ import java.nio.channels.CancelledKeyException; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; -import java.util.List; +import java.security.cert.Certificate; import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.LinkedBlockingQueue; import org.apache.jute.BinaryInputArchive; import org.apache.jute.BinaryOutputArchive; import org.apache.jute.Record; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.zookeeper.Environment; -import org.apache.zookeeper.Version; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.proto.ReplyHeader; import org.apache.zookeeper.proto.RequestHeader; import org.apache.zookeeper.proto.WatcherEvent; import org.apache.zookeeper.server.NIOServerCnxnFactory.SelectorThread; -import org.apache.zookeeper.server.quorum.Leader; -import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer; -import org.apache.zookeeper.server.quorum.ReadOnlyZooKeeperServer; -import org.apache.zookeeper.server.util.OSMXBean; +import org.apache.zookeeper.server.command.CommandExecutor; +import org.apache.zookeeper.server.command.FourLetterCommands; +import org.apache.zookeeper.server.command.SetTraceMaskCommand; +import org.apache.zookeeper.server.command.NopCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class handles communication with clients using NIO. There is one per @@ -254,7 +252,7 @@ void handleWrite(SelectionKey k) throws IOException, CloseRequestException { * so we've got to slice the buffer if it's too big. */ b = (ByteBuffer) b.slice().limit( - directBuffer.remaining()); + directBuffer.remaining()); } /* * put() is going to modify the positions of both @@ -429,37 +427,13 @@ public void enableRecv() { } private void readConnectRequest() throws IOException, InterruptedException { - if (zkServer == null) { + if (!isZKServerRunning()) { throw new IOException("ZooKeeperServer not running"); } zkServer.processConnectRequest(this, incomingBuffer); initialized = true; } - /** - * clean up the socket related to a command and also make sure we flush the - * data before we do that - * - * @param pwriter - * the pwriter for a command socket - */ - private void cleanupWriterSocket(PrintWriter pwriter) { - try { - if (pwriter != null) { - pwriter.flush(); - pwriter.close(); - } - } catch (Exception e) { - LOG.info("Error closing PrintWriter ", e); - } finally { - try { - close(); - } catch (Exception e) { - LOG.error("Error closing a command socket ", e); - } - } - } - /** * This class wraps the sendBuffer method of NIOServerCnxn. It is * responsible for chunking up the response to a client. Rather @@ -499,341 +473,17 @@ public void write(char[] cbuf, int off, int len) throws IOException { checkFlush(false); } } - - private static final String ZK_NOT_SERVING = - "This ZooKeeper instance is not currently serving requests"; - - /** - * Set of threads for commmand ports. All the 4 - * letter commands are run via a thread. Each class - * maps to a corresponding 4 letter command. CommandThread - * is the abstract class from which all the others inherit. - */ - private abstract class CommandThread { - PrintWriter pw; - - CommandThread(PrintWriter pw) { - this.pw = pw; - } - - public void start() { - run(); - } - - public void run() { - try { - commandRun(); - } catch (IOException ie) { - LOG.error("Error in running command ", ie); - } finally { - cleanupWriterSocket(pw); - } - } - - public abstract void commandRun() throws IOException; - } - - private class RuokCommand extends CommandThread { - public RuokCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - pw.print("imok"); - - } - } - - private class TraceMaskCommand extends CommandThread { - TraceMaskCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - long traceMask = ZooTrace.getTextTraceLevel(); - pw.print(traceMask); - } - } - - private class SetTraceMaskCommand extends CommandThread { - long trace = 0; - SetTraceMaskCommand(PrintWriter pw, long trace) { - super(pw); - this.trace = trace; - } - - @Override - public void commandRun() { - pw.print(trace); - } - } - - private class EnvCommand extends CommandThread { - EnvCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - List env = Environment.list(); - - pw.println("Environment:"); - for(Environment.Entry e : env) { - pw.print(e.getKey()); - pw.print("="); - pw.println(e.getValue()); - } - - } - } - - private class ConfCommand extends CommandThread { - ConfCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } else { - zkServer.dumpConf(pw); - } - } - } - - private class StatResetCommand extends CommandThread { - public StatResetCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } - else { - zkServer.serverStats().reset(); - pw.println("Server stats reset."); - } - } - } - - private class CnxnStatResetCommand extends CommandThread { - public CnxnStatResetCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } else { - for(ServerCnxn c : factory.cnxns){ - c.resetStats(); - } - pw.println("Connection stats reset."); - } - } - } - - private class DumpCommand extends CommandThread { - public DumpCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } - else { - pw.println("SessionTracker dump:"); - zkServer.sessionTracker.dumpSessions(pw); - pw.println("ephemeral nodes dump:"); - zkServer.dumpEphemerals(pw); - pw.println("Connections dump:"); - factory.dumpConnections(pw); - } - } - } - - private class StatCommand extends CommandThread { - int len; - public StatCommand(PrintWriter pw, int len) { - super(pw); - this.len = len; - } - - @SuppressWarnings("unchecked") - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } - else { - pw.print("Zookeeper version: "); - pw.println(Version.getFullVersion()); - if (zkServer instanceof ReadOnlyZooKeeperServer) { - pw.println("READ-ONLY mode; serving only " + - "read-only clients"); - } - if (len == statCmd) { - LOG.info("Stat command output"); - pw.println("Clients:"); - for(ServerCnxn c : factory.cnxns){ - c.dumpConnectionInfo(pw, true); - pw.println(); - } - pw.println(); - } - pw.print(zkServer.serverStats().toString()); - pw.print("Node count: "); - pw.println(zkServer.getZKDatabase().getNodeCount()); - } - - } - } - - private class ConsCommand extends CommandThread { - public ConsCommand(PrintWriter pw) { - super(pw); - } - - @SuppressWarnings("unchecked") - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } else { - for (ServerCnxn c : factory.cnxns) { - c.dumpConnectionInfo(pw, false); - pw.println(); - } - pw.println(); - } - } - } - - private class WatchCommand extends CommandThread { - int len = 0; - public WatchCommand(PrintWriter pw, int len) { - super(pw); - this.len = len; - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } else { - DataTree dt = zkServer.getZKDatabase().getDataTree(); - if (len == wchsCmd) { - dt.dumpWatchesSummary(pw); - } else if (len == wchpCmd) { - dt.dumpWatches(pw, true); - } else { - dt.dumpWatches(pw, false); - } - pw.println(); - } - } - } - - private class MonitorCommand extends CommandThread { - - MonitorCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if(zkServer == null) { - pw.println(ZK_NOT_SERVING); - return; - } - ZKDatabase zkdb = zkServer.getZKDatabase(); - ServerStats stats = zkServer.serverStats(); - - print("version", Version.getFullVersion()); - - print("avg_latency", stats.getAvgLatency()); - print("max_latency", stats.getMaxLatency()); - print("min_latency", stats.getMinLatency()); - - print("packets_received", stats.getPacketsReceived()); - print("packets_sent", stats.getPacketsSent()); - print("num_alive_connections", stats.getNumAliveClientConnections()); - - print("outstanding_requests", stats.getOutstandingRequests()); - - print("server_state", stats.getServerState()); - print("znode_count", zkdb.getNodeCount()); - - print("watch_count", zkdb.getDataTree().getWatchCount()); - print("ephemerals_count", zkdb.getDataTree().getEphemeralsCount()); - print("approximate_data_size", zkdb.getDataTree().approximateDataSize()); - - OSMXBean osMbean = new OSMXBean(); - if (osMbean != null && osMbean.getUnix() == true) { - print("open_file_descriptor_count", osMbean.getOpenFileDescriptorCount()); - print("max_file_descriptor_count", osMbean.getMaxFileDescriptorCount()); - } - - if(stats.getServerState().equals("leader")) { - Leader leader = ((LeaderZooKeeperServer)zkServer).getLeader(); - - print("followers", leader.getLearners().size()); - print("synced_followers", leader.getForwardingFollowers().size()); - print("pending_syncs", leader.getNumPendingSyncs()); - } - } - - private void print(String key, long number) { - print(key, "" + number); - } - - private void print(String key, String value) { - pw.print("zk_"); - pw.print(key); - pw.print("\t"); - pw.println(value); - } - - } - - private class IsroCommand extends CommandThread { - - public IsroCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.print("null"); - } else if (zkServer instanceof ReadOnlyZooKeeperServer) { - pw.print("ro"); - } else { - pw.print("rw"); - } - } - } - /** Return if four letter word found and responded to, otw false **/ private boolean checkFourLetterWord(final SelectionKey k, final int len) throws IOException { // We take advantage of the limited size of the length to look // for cmds. They are all 4-bytes which fits inside of an int - String cmd = cmd2String.get(len); - if (cmd == null) { + if (!FourLetterCommands.isKnown(len)) { return false; } - LOG.info("Processing " + cmd + " command from " - + sock.socket().getRemoteSocketAddress()); + + String cmd = FourLetterCommands.getCommandString(len); packetReceived(); /** cancel the selection key to remove the socket handling @@ -855,71 +505,35 @@ private boolean checkFourLetterWord(final SelectionKey k, final int len) final PrintWriter pwriter = new PrintWriter( new BufferedWriter(new SendBufferWriter())); - if (len == ruokCmd) { - RuokCommand ruok = new RuokCommand(pwriter); - ruok.start(); - return true; - } else if (len == getTraceMaskCmd) { - TraceMaskCommand tmask = new TraceMaskCommand(pwriter); - tmask.start(); + + // ZOOKEEPER-2693: don't execute 4lw if it's not enabled. + if (!FourLetterCommands.isEnabled(cmd)) { + LOG.debug("Command {} is not executed because it is not in the whitelist.", cmd); + NopCommand nopCmd = new NopCommand(pwriter, this, cmd + + " is not executed because it is not in the whitelist."); + nopCmd.start(); return true; - } else if (len == setTraceMaskCmd) { + } + + LOG.info("Processing " + cmd + " command from " + + sock.socket().getRemoteSocketAddress()); + + if (len == FourLetterCommands.setTraceMaskCmd) { + incomingBuffer = ByteBuffer.allocate(8); int rc = sock.read(incomingBuffer); if (rc < 0) { throw new IOException("Read error"); } - incomingBuffer.flip(); long traceMask = incomingBuffer.getLong(); ZooTrace.setTextTraceLevel(traceMask); - SetTraceMaskCommand setMask = new SetTraceMaskCommand(pwriter, traceMask); + SetTraceMaskCommand setMask = new SetTraceMaskCommand(pwriter, this, traceMask); setMask.start(); return true; - } else if (len == enviCmd) { - EnvCommand env = new EnvCommand(pwriter); - env.start(); - return true; - } else if (len == confCmd) { - ConfCommand ccmd = new ConfCommand(pwriter); - ccmd.start(); - return true; - } else if (len == srstCmd) { - StatResetCommand strst = new StatResetCommand(pwriter); - strst.start(); - return true; - } else if (len == crstCmd) { - CnxnStatResetCommand crst = new CnxnStatResetCommand(pwriter); - crst.start(); - return true; - } else if (len == dumpCmd) { - DumpCommand dump = new DumpCommand(pwriter); - dump.start(); - return true; - } else if (len == statCmd || len == srvrCmd) { - StatCommand stat = new StatCommand(pwriter, len); - stat.start(); - return true; - } else if (len == consCmd) { - ConsCommand cons = new ConsCommand(pwriter); - cons.start(); - return true; - } else if (len == wchpCmd || len == wchcCmd || len == wchsCmd) { - WatchCommand wcmd = new WatchCommand(pwriter, len); - wcmd.start(); - return true; - } else if (len == mntrCmd) { - MonitorCommand mntr = new MonitorCommand(pwriter); - mntr.start(); - return true; - } else if (len == isroCmd) { - IsroCommand isro = new IsroCommand(pwriter); - isro.start(); - return true; - } else if (len == telnetCloseCmd) { - cleanupWriterSocket(null); - return true; + } else { + CommandExecutor commandExecutor = new CommandExecutor(); + return commandExecutor.execute(this, pwriter, len, zkServer, factory); } - return false; } /** Reads the first 4 bytes of lenBuffer, which could be true length or @@ -938,13 +552,20 @@ private boolean readLength(SelectionKey k) throws IOException { if (len < 0 || len > BinaryInputArchive.maxBuffer) { throw new IOException("Len error " + len); } - if (zkServer == null) { + if (!isZKServerRunning()) { throw new IOException("ZooKeeperServer not running"); } incomingBuffer = ByteBuffer.allocate(len); return true; } + /** + * @return true if the server is running, false otherwise. + */ + boolean isZKServerRunning() { + return zkServer != null && zkServer.isRunning(); + } + public long getOutstandingRequests() { return outstandingRequests.get(); } @@ -1178,4 +799,21 @@ protected ServerStats serverStats() { return zkServer.serverStats(); } + @Override + public boolean isSecure() { + return false; + } + + @Override + public Certificate[] getClientCertificateChain() { + throw new UnsupportedOperationException( + "SSL is unsupported in NIOServerCnxn"); + } + + @Override + public void setClientCertificateChain(Certificate[] chain) { + throw new UnsupportedOperationException( + "SSL is unsupported in NIOServerCnxn"); + } + } diff --git a/src/java/main/org/apache/zookeeper/server/NIOServerCnxnFactory.java b/src/java/main/org/apache/zookeeper/server/NIOServerCnxnFactory.java index 7f188c8071a..7a72757860b 100644 --- a/src/java/main/org/apache/zookeeper/server/NIOServerCnxnFactory.java +++ b/src/java/main/org/apache/zookeeper/server/NIOServerCnxnFactory.java @@ -639,7 +639,10 @@ public NIOServerCnxnFactory() { new HashSet(); @Override - public void configure(InetSocketAddress addr, int maxcc) throws IOException { + public void configure(InetSocketAddress addr, int maxcc, boolean secure) throws IOException { + if (secure) { + throw new UnsupportedOperationException("SSL isn't supported in NIOServerCnxn"); + } configureSaslLogin(); maxClientCnxns = maxcc; @@ -685,31 +688,42 @@ public void configure(InetSocketAddress addr, int maxcc) throws IOException { ss.configureBlocking(false); acceptThread = new AcceptThread(ss, addr, selectorThreads); } - + + private void tryClose(ServerSocketChannel s) { + try { + s.close(); + } catch (IOException sse) { + LOG.error("Error while closing server socket.", sse); + } + } + @Override - public void reconfigure(InetSocketAddress addr){ + public void reconfigure(InetSocketAddress addr) { ServerSocketChannel oldSS = ss; try { - this.ss = ServerSocketChannel.open(); - ss.socket().setReuseAddress(true); - LOG.info("binding to port " + addr); - ss.socket().bind(addr); - ss.configureBlocking(false); - acceptThread.setReconfiguring(); - oldSS.close(); - acceptThread.wakeupSelector(); - try { - acceptThread.join(); - } catch (InterruptedException e) { - LOG.error("Error joining old acceptThread when reconfiguring client port " + e.getMessage()); - } - acceptThread = new AcceptThread(ss, addr, selectorThreads); - acceptThread.start(); + this.ss = ServerSocketChannel.open(); + ss.socket().setReuseAddress(true); + LOG.info("binding to port " + addr); + ss.socket().bind(addr); + ss.configureBlocking(false); + acceptThread.setReconfiguring(); + tryClose(oldSS); + acceptThread.wakeupSelector(); + try { + acceptThread.join(); + } catch (InterruptedException e) { + LOG.error("Error joining old acceptThread when reconfiguring client port {}", + e.getMessage()); + Thread.currentThread().interrupt(); + } + acceptThread = new AcceptThread(ss, addr, selectorThreads); + acceptThread.start(); } catch(IOException e) { - LOG.error("Error reconfiguring client port to " + addr + " " + e.getMessage()); + LOG.error("Error reconfiguring client port to {} {}", addr, e.getMessage()); + tryClose(oldSS); } } - + /** {@inheritDoc} */ public int getMaxClientCnxnsPerHost() { return maxClientCnxns; @@ -742,12 +756,14 @@ public void start() { } @Override - public void startup(ZooKeeperServer zks) throws IOException, - InterruptedException { + public void startup(ZooKeeperServer zks, boolean startServer) + throws IOException, InterruptedException { start(); - zks.startdata(); - zks.startup(); setZooKeeperServer(zks); + if (startServer) { + zks.startdata(); + zks.startup(); + } } @Override @@ -908,11 +924,13 @@ public void addSession(long sessionId, NIOServerCnxn cnxn) { } @Override - public void closeSession(long sessionId) { + public boolean closeSession(long sessionId) { NIOServerCnxn cnxn = sessionMap.remove(sessionId); if (cnxn != null) { cnxn.close(); + return true; } + return false; } @Override diff --git a/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java b/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java index b4bdc82f8b5..9ff12e9d56c 100644 --- a/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java +++ b/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java @@ -28,31 +28,29 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; -import java.util.AbstractSet; -import java.util.HashSet; -import java.util.List; +import java.security.cert.Certificate; +import java.util.Arrays; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import org.apache.jute.BinaryInputArchive; import org.apache.jute.BinaryOutputArchive; import org.apache.jute.Record; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.zookeeper.Environment; -import org.apache.zookeeper.Version; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.proto.ReplyHeader; import org.apache.zookeeper.proto.WatcherEvent; -import org.apache.zookeeper.server.quorum.Leader; -import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer; -import org.apache.zookeeper.server.quorum.ReadOnlyZooKeeperServer; -import org.apache.zookeeper.server.util.OSMXBean; +import org.apache.zookeeper.server.command.CommandExecutor; +import org.apache.zookeeper.server.command.FourLetterCommands; +import org.apache.zookeeper.server.command.NopCommand; +import org.apache.zookeeper.server.command.SetTraceMaskCommand; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.MessageEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class NettyServerCnxn extends ServerCnxn { private static final Logger LOG = LoggerFactory.getLogger(NettyServerCnxn.class); @@ -64,6 +62,8 @@ public class NettyServerCnxn extends ServerCnxn { long sessionId; int sessionTimeout; AtomicLong outstandingCount = new AtomicLong(); + Certificate[] clientChain; + volatile boolean closingChannel; /** The ZooKeeperServer for this connection. May be null if the server * is not currently serving requests (for example if the server is not @@ -76,6 +76,7 @@ public class NettyServerCnxn extends ServerCnxn { NettyServerCnxn(Channel channel, ZooKeeperServer zks, NettyServerCnxnFactory factory) { this.channel = channel; + this.closingChannel = false; this.zkServer = zks; this.factory = factory; if (this.factory.login != null) { @@ -85,10 +86,18 @@ public class NettyServerCnxn extends ServerCnxn { @Override public void close() { + closingChannel = true; + if (LOG.isDebugEnabled()) { LOG.debug("close called for sessionid:0x" + Long.toHexString(sessionId)); } + + // ZOOKEEPER-2743: + // Always unregister connection upon close to prevent + // connection bean leak under certain race conditions. + factory.unregisterConnection(this); + synchronized(factory.cnxns){ // if this is not in cnxns then it's already closed if (!factory.cnxns.remove(this)) { @@ -111,9 +120,11 @@ public void close() { } if (channel.isOpen()) { - channel.close(); + // Since we don't check on the futures created by write calls to the channel complete we need to make sure + // that all writes have been completed before closing the channel or we risk data loss + // See: http://lists.jboss.org/pipermail/netty-users/2009-August/001122.html + channel.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } - factory.unregisterConnection(this); } @Override @@ -168,7 +179,7 @@ static class ResumeMessageEvent implements MessageEvent { @Override public void sendResponse(ReplyHeader h, Record r, String tag) throws IOException { - if (!channel.isOpen()) { + if (closingChannel || !channel.isOpen()) { return; } ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -222,29 +233,7 @@ public void sendBuffer(ByteBuffer sendBuffer) { packetSent(); } - /** - * clean up the socket related to a command and also make sure we flush the - * data before we do that - * - * @param pwriter - * the pwriter for a command socket - */ - private void cleanupWriterSocket(PrintWriter pwriter) { - try { - if (pwriter != null) { - pwriter.flush(); - pwriter.close(); - } - } catch (Exception e) { - LOG.info("Error closing PrintWriter ", e); - } finally { - try { - close(); - } catch (Exception e) { - LOG.error("Error closing a command socket ", e); - } - } - } + /** * This class wraps the sendBuffer method of NIOServerCnxn. It is @@ -286,415 +275,49 @@ public void write(char[] cbuf, int off, int len) throws IOException { } } - private static final String ZK_NOT_SERVING = - "This ZooKeeper instance is not currently serving requests"; - - /** - * Set of threads for commmand ports. All the 4 - * letter commands are run via a thread. Each class - * maps to a correspoding 4 letter command. CommandThread - * is the abstract class from which all the others inherit. - */ - private abstract class CommandThread /*extends Thread*/ { - PrintWriter pw; - - CommandThread(PrintWriter pw) { - this.pw = pw; - } - - public void start() { - run(); - } - - public void run() { - try { - commandRun(); - } catch (IOException ie) { - LOG.error("Error in running command ", ie); - } finally { - cleanupWriterSocket(pw); - } - } - - public abstract void commandRun() throws IOException; - } - - private class RuokCommand extends CommandThread { - public RuokCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - pw.print("imok"); - - } - } - - private class TraceMaskCommand extends CommandThread { - TraceMaskCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - long traceMask = ZooTrace.getTextTraceLevel(); - pw.print(traceMask); - } - } - - private class SetTraceMaskCommand extends CommandThread { - long trace = 0; - SetTraceMaskCommand(PrintWriter pw, long trace) { - super(pw); - this.trace = trace; - } - - @Override - public void commandRun() { - pw.print(trace); - } - } - - private class EnvCommand extends CommandThread { - EnvCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - List env = Environment.list(); - - pw.println("Environment:"); - for(Environment.Entry e : env) { - pw.print(e.getKey()); - pw.print("="); - pw.println(e.getValue()); - } - - } - } - - private class ConfCommand extends CommandThread { - ConfCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } else { - zkServer.dumpConf(pw); - } - } - } - - private class StatResetCommand extends CommandThread { - public StatResetCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } - else { - zkServer.serverStats().reset(); - pw.println("Server stats reset."); - } - } - } - - private class CnxnStatResetCommand extends CommandThread { - public CnxnStatResetCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } else { - synchronized(factory.cnxns){ - for(ServerCnxn c : factory.cnxns){ - c.resetStats(); - } - } - pw.println("Connection stats reset."); - } - } - } - - private class DumpCommand extends CommandThread { - public DumpCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } - else { - pw.println("SessionTracker dump:"); - zkServer.sessionTracker.dumpSessions(pw); - pw.println("ephemeral nodes dump:"); - zkServer.dumpEphemerals(pw); - } - } - } - - private class StatCommand extends CommandThread { - int len; - public StatCommand(PrintWriter pw, int len) { - super(pw); - this.len = len; - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } - else { - pw.print("Zookeeper version: "); - pw.println(Version.getFullVersion()); - if (zkServer instanceof ReadOnlyZooKeeperServer) { - pw.println("READ-ONLY mode; serving only " + - "read-only clients"); - } - if (len == statCmd) { - LOG.info("Stat command output"); - pw.println("Clients:"); - // clone should be faster than iteration - // ie give up the cnxns lock faster - HashSet cnxns; - synchronized(factory.cnxns){ - cnxns = new HashSet(factory.cnxns); - } - for(ServerCnxn c : cnxns){ - c.dumpConnectionInfo(pw, true); - pw.println(); - } - pw.println(); - } - pw.print(zkServer.serverStats().toString()); - pw.print("Node count: "); - pw.println(zkServer.getZKDatabase().getNodeCount()); - } - - } - } - - private class ConsCommand extends CommandThread { - public ConsCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } else { - // clone should be faster than iteration - // ie give up the cnxns lock faster - AbstractSet cnxns; - synchronized (factory.cnxns) { - cnxns = new HashSet(factory.cnxns); - } - for (ServerCnxn c : cnxns) { - c.dumpConnectionInfo(pw, false); - pw.println(); - } - pw.println(); - } - } - } - - private class WatchCommand extends CommandThread { - int len = 0; - public WatchCommand(PrintWriter pw, int len) { - super(pw); - this.len = len; - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.println(ZK_NOT_SERVING); - } else { - DataTree dt = zkServer.getZKDatabase().getDataTree(); - if (len == wchsCmd) { - dt.dumpWatchesSummary(pw); - } else if (len == wchpCmd) { - dt.dumpWatches(pw, true); - } else { - dt.dumpWatches(pw, false); - } - pw.println(); - } - } - } - - private class MonitorCommand extends CommandThread { - - MonitorCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if(zkServer == null) { - pw.println(ZK_NOT_SERVING); - return; - } - ZKDatabase zkdb = zkServer.getZKDatabase(); - ServerStats stats = zkServer.serverStats(); - - print("version", Version.getFullVersion()); - - print("avg_latency", stats.getAvgLatency()); - print("max_latency", stats.getMaxLatency()); - print("min_latency", stats.getMinLatency()); - - print("packets_received", stats.getPacketsReceived()); - print("packets_sent", stats.getPacketsSent()); - print("num_alive_connections", stats.getNumAliveClientConnections()); - - print("outstanding_requests", stats.getOutstandingRequests()); - - print("server_state", stats.getServerState()); - print("znode_count", zkdb.getNodeCount()); - - print("watch_count", zkdb.getDataTree().getWatchCount()); - print("ephemerals_count", zkdb.getDataTree().getEphemeralsCount()); - print("approximate_data_size", zkdb.getDataTree().approximateDataSize()); - - OSMXBean osMbean = new OSMXBean(); - if (osMbean != null && osMbean.getUnix() == true) { - print("open_file_descriptor_count", osMbean.getOpenFileDescriptorCount()); - print("max_file_descriptor_count", osMbean.getMaxFileDescriptorCount()); - } - - if(stats.getServerState().equals("leader")) { - Leader leader = ((LeaderZooKeeperServer)zkServer).getLeader(); - - print("followers", leader.getLearners().size()); - print("synced_followers", leader.getForwardingFollowers().size()); - print("pending_syncs", leader.getNumPendingSyncs()); - } - } - - private void print(String key, long number) { - print(key, "" + number); - } - - private void print(String key, String value) { - pw.print("zk_"); - pw.print(key); - pw.print("\t"); - pw.println(value); - } - - } - - private class IsroCommand extends CommandThread { - - public IsroCommand(PrintWriter pw) { - super(pw); - } - - @Override - public void commandRun() { - if (zkServer == null) { - pw.print("null"); - } else if (zkServer instanceof ReadOnlyZooKeeperServer) { - pw.print("ro"); - } else { - pw.print("rw"); - } - } - } - /** Return if four letter word found and responded to, otw false **/ private boolean checkFourLetterWord(final Channel channel, ChannelBuffer message, final int len) throws IOException { // We take advantage of the limited size of the length to look // for cmds. They are all 4-bytes which fits inside of an int - String cmd = cmd2String.get(len); - if (cmd == null) { + if (!FourLetterCommands.isKnown(len)) { return false; } + + String cmd = FourLetterCommands.getCommandString(len); + channel.setInterestOps(0).awaitUninterruptibly(); - LOG.info("Processing " + cmd + " command from " - + channel.getRemoteAddress()); packetReceived(); final PrintWriter pwriter = new PrintWriter( new BufferedWriter(new SendBufferWriter())); - if (len == ruokCmd) { - RuokCommand ruok = new RuokCommand(pwriter); - ruok.start(); - return true; - } else if (len == getTraceMaskCmd) { - TraceMaskCommand tmask = new TraceMaskCommand(pwriter); - tmask.start(); + + // ZOOKEEPER-2693: don't execute 4lw if it's not enabled. + if (!FourLetterCommands.isEnabled(cmd)) { + LOG.debug("Command {} is not executed because it is not in the whitelist.", cmd); + NopCommand nopCmd = new NopCommand(pwriter, this, cmd + + " is not executed because it is not in the whitelist."); + nopCmd.start(); return true; - } else if (len == setTraceMaskCmd) { - ByteBuffer mask = ByteBuffer.allocate(4); - message.readBytes(mask); + } + + LOG.info("Processing " + cmd + " command from " + + channel.getRemoteAddress()); - bb.flip(); + if (len == FourLetterCommands.setTraceMaskCmd) { + ByteBuffer mask = ByteBuffer.allocate(8); + message.readBytes(mask); + mask.flip(); long traceMask = mask.getLong(); ZooTrace.setTextTraceLevel(traceMask); - SetTraceMaskCommand setMask = new SetTraceMaskCommand(pwriter, traceMask); + SetTraceMaskCommand setMask = new SetTraceMaskCommand(pwriter, this, traceMask); setMask.start(); return true; - } else if (len == enviCmd) { - EnvCommand env = new EnvCommand(pwriter); - env.start(); - return true; - } else if (len == confCmd) { - ConfCommand ccmd = new ConfCommand(pwriter); - ccmd.start(); - return true; - } else if (len == srstCmd) { - StatResetCommand strst = new StatResetCommand(pwriter); - strst.start(); - return true; - } else if (len == crstCmd) { - CnxnStatResetCommand crst = new CnxnStatResetCommand(pwriter); - crst.start(); - return true; - } else if (len == dumpCmd) { - DumpCommand dump = new DumpCommand(pwriter); - dump.start(); - return true; - } else if (len == statCmd || len == srvrCmd) { - StatCommand stat = new StatCommand(pwriter, len); - stat.start(); - return true; - } else if (len == consCmd) { - ConsCommand cons = new ConsCommand(pwriter); - cons.start(); - return true; - } else if (len == wchpCmd || len == wchcCmd || len == wchsCmd) { - WatchCommand wcmd = new WatchCommand(pwriter, len); - wcmd.start(); - return true; - } else if (len == mntrCmd) { - MonitorCommand mntr = new MonitorCommand(pwriter); - mntr.start(); - return true; - } else if (len == isroCmd) { - IsroCommand isro = new IsroCommand(pwriter); - isro.start(); - return true; + } else { + CommandExecutor commandExecutor = new CommandExecutor(); + return commandExecutor.execute(this, pwriter, len, zkServer,factory); } - return false; } public void receiveMessage(ChannelBuffer message) { @@ -736,7 +359,7 @@ public void receiveMessage(ChannelBuffer message) { bb.flip(); ZooKeeperServer zks = this.zkServer; - if (zks == null) { + if (zks == null || !zks.isRunning()) { throw new IOException("ZK down"); } if (initialized) { @@ -853,4 +476,27 @@ protected ServerStats serverStats() { return zkServer.serverStats(); } + @Override + public boolean isSecure() { + return factory.secure; + } + + @Override + public Certificate[] getClientCertificateChain() { + if (clientChain == null) + { + return null; + } + return Arrays.copyOf(clientChain, clientChain.length); + } + + @Override + public void setClientCertificateChain(Certificate[] chain) { + if (chain == null) + { + clientChain = null; + } else { + clientChain = Arrays.copyOf(chain, chain.length); + } + } } diff --git a/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java b/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java index 8a05f948036..a02468953a5 100644 --- a/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java +++ b/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java @@ -23,19 +23,40 @@ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.common.X509Exception; +import org.apache.zookeeper.common.X509Exception.SSLContextException; +import org.apache.zookeeper.common.X509Util; +import org.apache.zookeeper.server.auth.ProviderRegistry; +import org.apache.zookeeper.server.auth.X509AuthenticationProvider; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandler.Sharable; import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelHandler; @@ -43,6 +64,7 @@ import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.ssl.SslHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,7 +78,7 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory { new HashMap>( ); InetSocketAddress localAddress; int maxClientCnxns = 60; - + /** * This is an inner class since we need to extend SimpleChannelHandler, but * NettyServerCnxnFactory already extends ServerCnxnFactory. By making it inner @@ -82,11 +104,19 @@ public void channelConnected(ChannelHandlerContext ctx, if (LOG.isTraceEnabled()) { LOG.trace("Channel connected " + e); } - allChannels.add(ctx.getChannel()); + NettyServerCnxn cnxn = new NettyServerCnxn(ctx.getChannel(), zkServer, NettyServerCnxnFactory.this); ctx.setAttachment(cnxn); - addCnxn(cnxn); + + if (secure) { + SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class); + ChannelFuture handshakeFuture = sslHandler.handshake(); + handshakeFuture.addListener(new CertificateVerifier(sslHandler, cnxn)); + } else { + allChannels.add(ctx.getChannel()); + addCnxn(cnxn); + } } @Override @@ -114,8 +144,8 @@ public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) if (cnxn != null) { if (LOG.isDebugEnabled()) { LOG.debug("Closing " + cnxn); - cnxn.close(); } + cnxn.close(); } } @@ -183,9 +213,11 @@ private void processMessage(MessageEvent e, NettyServerCnxn cnxn) { cnxn.queuedBuffer = dynamicBuffer(buf.readableBytes()); } cnxn.queuedBuffer.writeBytes(buf); - LOG.debug(Long.toHexString(cnxn.sessionId) - + " queuedBuffer 0x" - + ChannelBuffers.hexDump(cnxn.queuedBuffer)); + if (LOG.isTraceEnabled()) { + LOG.trace(Long.toHexString(cnxn.sessionId) + + " queuedBuffer 0x" + + ChannelBuffers.hexDump(cnxn.queuedBuffer)); + } } else { LOG.debug("not throttled"); if (cnxn.queuedBuffer != null) { @@ -236,11 +268,63 @@ public void writeComplete(ChannelHandlerContext ctx, LOG.trace("write complete " + e); } } - + + private final class CertificateVerifier + implements ChannelFutureListener { + private final SslHandler sslHandler; + private final NettyServerCnxn cnxn; + + CertificateVerifier(SslHandler sslHandler, NettyServerCnxn cnxn) { + this.sslHandler = sslHandler; + this.cnxn = cnxn; + } + + /** + * Only allow the connection to stay open if certificate passes auth + */ + public void operationComplete(ChannelFuture future) + throws SSLPeerUnverifiedException { + if (future.isSuccess()) { + LOG.debug("Successful handshake with session 0x{}", + Long.toHexString(cnxn.sessionId)); + SSLEngine eng = sslHandler.getEngine(); + SSLSession session = eng.getSession(); + cnxn.setClientCertificateChain(session.getPeerCertificates()); + + String authProviderProp + = System.getProperty(ZKConfig.SSL_AUTHPROVIDER, "x509"); + + X509AuthenticationProvider authProvider = + (X509AuthenticationProvider) + ProviderRegistry.getProvider(authProviderProp); + + if (authProvider == null) { + LOG.error("Auth provider not found: {}", authProviderProp); + cnxn.close(); + return; + } + + if (KeeperException.Code.OK != + authProvider.handleAuthentication(cnxn, null)) { + LOG.error("Authentication failed for session 0x{}", + Long.toHexString(cnxn.sessionId)); + cnxn.close(); + return; + } + + allChannels.add(future.getChannel()); + addCnxn(cnxn); + } else { + LOG.error("Unsuccessful handshake with session 0x{}", + Long.toHexString(cnxn.sessionId)); + cnxn.close(); + } + } + } } CnxnChannelHandler channelHandler = new CnxnChannelHandler(); - + NettyServerCnxnFactory() { bootstrap = new ServerBootstrap( new NioServerSocketChannelFactory( @@ -252,10 +336,54 @@ public void writeComplete(ChannelHandlerContext ctx, bootstrap.setOption("child.tcpNoDelay", true); /* set socket linger to off, so that socket close does not block */ bootstrap.setOption("child.soLinger", -1); + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline p = Channels.pipeline(); + if (secure) { + initSSL(p); + } + p.addLast("servercnxnfactory", channelHandler); + + return p; + } + }); + } + + private synchronized void initSSL(ChannelPipeline p) + throws X509Exception, KeyManagementException, NoSuchAlgorithmException { + String authProviderProp = System.getProperty(ZKConfig.SSL_AUTHPROVIDER); + SSLContext sslContext; + if (authProviderProp == null) { + sslContext = X509Util.createSSLContext(); + } else { + sslContext = SSLContext.getInstance("TLSv1"); + X509AuthenticationProvider authProvider = + (X509AuthenticationProvider)ProviderRegistry.getProvider( + System.getProperty(ZKConfig.SSL_AUTHPROVIDER, + "x509")); - bootstrap.getPipeline().addLast("servercnxnfactory", channelHandler); + if (authProvider == null) + { + LOG.error("Auth provider not found: {}", authProviderProp); + throw new SSLContextException( + "Could not create SSLContext with specified auth provider: " + + authProviderProp); + } + + sslContext.init(new X509KeyManager[] { authProvider.getKeyManager() }, + new X509TrustManager[] { authProvider.getTrustManager() }, + null); + } + + SSLEngine sslEngine = sslContext.createSSLEngine(); + sslEngine.setUseClientMode(false); + sslEngine.setNeedClientAuth(true); + + p.addLast("ssl", new SslHandler(sslEngine)); + LOG.info("SSL handler added for channel: {}", p.getChannel()); } - + @Override public void closeAll() { if (LOG.isDebugEnabled()) { @@ -279,7 +407,7 @@ public void closeAll() { } @Override - public void closeSession(long sessionId) { + public boolean closeSession(long sessionId) { if (LOG.isDebugEnabled()) { LOG.debug("closeSession sessionid:0x" + sessionId); } @@ -290,18 +418,20 @@ public void closeSession(long sessionId) { } catch (Exception e) { LOG.warn("exception during session close", e); } - break; + return true; } } + return false; } @Override - public void configure(InetSocketAddress addr, int maxClientCnxns) + public void configure(InetSocketAddress addr, int maxClientCnxns, boolean secure) throws IOException { configureSaslLogin(); localAddress = addr; this.maxClientCnxns = maxClientCnxns; + this.secure = secure; } /** {@inheritDoc} */ @@ -358,22 +488,28 @@ public void start() { parentChannel = bootstrap.bind(localAddress); } - public void reconfigure(InetSocketAddress addr) - { + public void reconfigure(InetSocketAddress addr) { Channel oldChannel = parentChannel; - LOG.info("binding to port " + addr); - parentChannel = bootstrap.bind(addr); - localAddress = addr; - oldChannel.close(); + try { + LOG.info("binding to port {}", addr); + parentChannel = bootstrap.bind(addr); + localAddress = addr; + } catch (Exception e) { + LOG.error("Error while reconfiguring", e); + } finally { + oldChannel.close(); + } } @Override - public void startup(ZooKeeperServer zks) throws IOException, - InterruptedException { + public void startup(ZooKeeperServer zks, boolean startServer) + throws IOException, InterruptedException { start(); - zks.startdata(); - zks.startup(); setZooKeeperServer(zks); + if (startServer) { + zks.startdata(); + zks.startup(); + } } @Override diff --git a/src/java/main/org/apache/zookeeper/server/ObserverBean.java b/src/java/main/org/apache/zookeeper/server/ObserverBean.java index 4e0e82a8ea6..72d724e0b23 100644 --- a/src/java/main/org/apache/zookeeper/server/ObserverBean.java +++ b/src/java/main/org/apache/zookeeper/server/ObserverBean.java @@ -34,6 +34,10 @@ public ObserverBean(Observer observer, ZooKeeperServer zks) { this.observer = observer; } + public String getName() { + return "Observer"; + } + public int getPendingRevalidationCount() { return this.observer.getPendingRevalidationsCount(); } diff --git a/src/java/main/org/apache/zookeeper/server/PrepRequestProcessor.java b/src/java/main/org/apache/zookeeper/server/PrepRequestProcessor.java index 8542790311f..a8cb0488c29 100644 --- a/src/java/main/org/apache/zookeeper/server/PrepRequestProcessor.java +++ b/src/java/main/org/apache/zookeeper/server/PrepRequestProcessor.java @@ -18,67 +18,68 @@ package org.apache.zookeeper.server; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.StringReader; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Properties; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; - -import org.apache.jute.Record; import org.apache.jute.BinaryOutputArchive; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.jute.Record; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.BadArgumentsException; +import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.MultiTransactionRecord; import org.apache.zookeeper.Op; import org.apache.zookeeper.ZooDefs; -import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.ZooDefs.OpCode; import org.apache.zookeeper.common.PathUtils; import org.apache.zookeeper.common.StringUtils; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.data.StatPersisted; +import org.apache.zookeeper.proto.CheckVersionRequest; import org.apache.zookeeper.proto.CreateRequest; -import org.apache.zookeeper.proto.Create2Request; +import org.apache.zookeeper.proto.CreateTTLRequest; import org.apache.zookeeper.proto.DeleteRequest; import org.apache.zookeeper.proto.ReconfigRequest; import org.apache.zookeeper.proto.SetACLRequest; import org.apache.zookeeper.proto.SetDataRequest; -import org.apache.zookeeper.proto.CheckVersionRequest; import org.apache.zookeeper.server.ZooKeeperServer.ChangeRecord; import org.apache.zookeeper.server.auth.AuthenticationProvider; import org.apache.zookeeper.server.auth.ProviderRegistry; +import org.apache.zookeeper.server.quorum.Leader.XidRolloverException; import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; import org.apache.zookeeper.server.quorum.QuorumPeerConfig; import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; import org.apache.zookeeper.server.quorum.flexible.QuorumMaj; import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; -import org.apache.zookeeper.server.quorum.Leader.XidRolloverException; +import org.apache.zookeeper.txn.CheckVersionTxn; +import org.apache.zookeeper.txn.CreateContainerTxn; import org.apache.zookeeper.txn.CreateSessionTxn; +import org.apache.zookeeper.txn.CreateTTLTxn; import org.apache.zookeeper.txn.CreateTxn; import org.apache.zookeeper.txn.DeleteTxn; import org.apache.zookeeper.txn.ErrorTxn; +import org.apache.zookeeper.txn.MultiTxn; import org.apache.zookeeper.txn.SetACLTxn; import org.apache.zookeeper.txn.SetDataTxn; -import org.apache.zookeeper.txn.CheckVersionTxn; import org.apache.zookeeper.txn.Txn; -import org.apache.zookeeper.txn.MultiTxn; import org.apache.zookeeper.txn.TxnHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; /** * This request processor is generally at the start of a RequestProcessor @@ -101,7 +102,7 @@ public class PrepRequestProcessor extends ZooKeeperCriticalThread implements /** * this is only for testing purposes. - * should never be useed otherwise + * should never be used otherwise */ private static boolean failCreate = false; @@ -111,9 +112,10 @@ public class PrepRequestProcessor extends ZooKeeperCriticalThread implements ZooKeeperServer zks; - public PrepRequestProcessor(ZooKeeperServer zks, RequestProcessor nextProcessor) { - super("ProcessThread(sid:" + zks.getServerId() - + " cport:" + zks.getClientPort() + "):"); + public PrepRequestProcessor(ZooKeeperServer zks, + RequestProcessor nextProcessor) { + super("ProcessThread(sid:" + zks.getServerId() + " cport:" + + zks.getClientPort() + "):", zks.getZooKeeperServerListener()); this.nextProcessor = nextProcessor; this.zks = zks; } @@ -142,15 +144,13 @@ public void run() { } pRequest(request); } - } catch (InterruptedException e) { - LOG.error("Unexpected interruption", e); } catch (RequestProcessorException e) { if (e.getCause() instanceof XidRolloverException) { LOG.info(e.getCause().getMessage()); } - LOG.error("Unexpected exception", e); + handleException(this.getName(), e); } catch (Exception e) { - LOG.error("Unexpected exception", e); + handleException(this.getName(), e); } LOG.info("PrepRequestProcessor exited loop!"); } @@ -159,26 +159,15 @@ private ChangeRecord getRecordForPath(String path) throws KeeperException.NoNode ChangeRecord lastChange = null; synchronized (zks.outstandingChanges) { lastChange = zks.outstandingChangesForPath.get(path); - /* - for (int i = 0; i < zks.outstandingChanges.size(); i++) { - ChangeRecord c = zks.outstandingChanges.get(i); - if (c.path.equals(path)) { - lastChange = c; - } - } - */ if (lastChange == null) { DataNode n = zks.getZKDatabase().getNode(path); if (n != null) { - Long acl; Set children; synchronized(n) { - acl = n.acl; children = n.getChildren(); } - lastChange = new ChangeRecord(-1, path, n.stat, - children != null ? children.size() : 0, - zks.getZKDatabase().convertLong(acl)); + lastChange = new ChangeRecord(-1, path, n.stat, children.size(), + zks.getZKDatabase().aclForNode(n)); } } } @@ -188,6 +177,12 @@ private ChangeRecord getRecordForPath(String path) throws KeeperException.NoNode return lastChange; } + private ChangeRecord getOutstandingChange(String path) { + synchronized (zks.outstandingChanges) { + return zks.outstandingChangesForPath.get(path); + } + } + private void addChangeRecord(ChangeRecord c) { synchronized (zks.outstandingChanges) { zks.outstandingChanges.add(c); @@ -202,39 +197,37 @@ private void addChangeRecord(ChangeRecord c) { * of a failed multi-op. * * @param multiRequest + * @return a map that contains previously existed records that probably need to be + * rolled back in any failure. */ private Map getPendingChanges(MultiTransactionRecord multiRequest) { HashMap pendingChangeRecords = new HashMap(); - for(Op op: multiRequest) { + for (Op op : multiRequest) { String path = op.getPath(); + ChangeRecord cr = getOutstandingChange(path); + // only previously existing records need to be rolled back. + if (cr != null) { + pendingChangeRecords.put(path, cr); + } - try { - ChangeRecord cr = getRecordForPath(path); - if (cr != null) { - pendingChangeRecords.put(path, cr); - } - - /* - * ZOOKEEPER-1624 - We need to store for parent's ChangeRecord - * of the parent node of a request. So that if this is a - * sequential node creation request, rollbackPendingChanges() - * can restore previous parent's ChangeRecord correctly. - * - * Otherwise, sequential node name generation will be incorrect - * for a subsequent request. - */ - int lastSlash = path.lastIndexOf('/'); - if (lastSlash == -1 || path.indexOf('\0') != -1) { - continue; - } - String parentPath = path.substring(0, lastSlash); - ChangeRecord parentCr = getRecordForPath(parentPath); - if (parentCr != null) { - pendingChangeRecords.put(parentPath, parentCr); - } - } catch (KeeperException.NoNodeException e) { - // ignore this one + /* + * ZOOKEEPER-1624 - We need to store for parent's ChangeRecord + * of the parent node of a request. So that if this is a + * sequential node creation request, rollbackPendingChanges() + * can restore previous parent's ChangeRecord correctly. + * + * Otherwise, sequential node name generation will be incorrect + * for a subsequent request. + */ + int lastSlash = path.lastIndexOf('/'); + if (lastSlash == -1 || path.indexOf('\0') != -1) { + continue; + } + String parentPath = path.substring(0, lastSlash); + ChangeRecord parentCr = getOutstandingChange(parentPath); + if (parentCr != null) { + pendingChangeRecords.put(parentPath, parentCr); } } @@ -252,7 +245,6 @@ private Map getPendingChanges(MultiTransactionRecord multi * @param pendingChangeRecords */ void rollbackPendingChanges(long zxid, MappendingChangeRecords) { - synchronized (zks.outstandingChanges) { // Grab a list iterator starting at the END of the list so we can iterate in reverse ListIterator iter = zks.outstandingChanges.listIterator(zks.outstandingChanges.size()); @@ -260,27 +252,30 @@ void rollbackPendingChanges(long zxid, MappendingChangeRec ChangeRecord c = iter.previous(); if (c.zxid == zxid) { iter.remove(); + // Remove all outstanding changes for paths of this multi. + // Previous records will be added back later. zks.outstandingChangesForPath.remove(c.path); } else { break; } } - boolean empty = zks.outstandingChanges.isEmpty(); - long firstZxid = 0; - if (!empty) { - firstZxid = zks.outstandingChanges.get(0).zxid; + // we don't need to roll back any records because there is nothing left. + if (zks.outstandingChanges.isEmpty()) { + return; } - Iterator priorIter = pendingChangeRecords.values().iterator(); - while (priorIter.hasNext()) { - ChangeRecord c = priorIter.next(); + long firstZxid = zks.outstandingChanges.get(0).zxid; - /* Don't apply any prior change records less than firstZxid */ - if (!empty && (c.zxid < firstZxid)) { + for (ChangeRecord c : pendingChangeRecords.values()) { + // Don't apply any prior change records less than firstZxid. + // Note that previous outstanding requests might have been removed + // once they are completed. + if (c.zxid < firstZxid) { continue; } + // add previously existing records back. zks.outstandingChangesForPath.put(c.path, c); } } @@ -343,8 +338,8 @@ private String validatePathForCreate(String path, long sessionId) throws BadArgumentsException { int lastSlash = path.lastIndexOf('/'); if (lastSlash == -1 || path.indexOf('\0') != -1 || failCreate) { - LOG.info("Invalid path " + path + " with session 0x" + - Long.toHexString(sessionId)); + LOG.info("Invalid path %s with session 0x%s", + path, Long.toHexString(sessionId)); throw new KeeperException.BadArgumentsException(path); } return path.substring(0, lastSlash); @@ -363,107 +358,33 @@ protected void pRequest2Txn(int type, long zxid, Request request, Record record, boolean deserialize) throws KeeperException, IOException, RequestProcessorException { - request.setHdr(new TxnHeader(request.sessionId, request.cxid, zxid, zks.getTime(), type)); + request.setHdr(new TxnHeader(request.sessionId, request.cxid, zxid, + Time.currentWallTime(), type)); switch (type) { - case OpCode.create: { - CreateRequest createRequest = (CreateRequest)record; - if (deserialize) { - ByteBufferInputStream.byteBuffer2Record(request.request, createRequest); - } - CreateMode createMode = CreateMode.fromFlag(createRequest.getFlags()); - validateCreateRequest(createMode, request); - String path = createRequest.getPath(); - String parentPath = validatePathForCreate(path, request.sessionId); - - List listACL = fixupACL(path, request.authInfo, createRequest.getAcl()); - ChangeRecord parentRecord = getRecordForPath(parentPath); - - checkACL(zks, parentRecord.acl, ZooDefs.Perms.CREATE, request.authInfo); - int parentCVersion = parentRecord.stat.getCversion(); - if (createMode.isSequential()) { - path = path + String.format(Locale.ENGLISH, "%010d", parentCVersion); - } - try { - PathUtils.validatePath(path); - } catch(IllegalArgumentException ie) { - LOG.info("Invalid path " + path + " with session 0x" + - Long.toHexString(request.sessionId)); - throw new KeeperException.BadArgumentsException(path); - } - try { - if (getRecordForPath(path) != null) { - throw new KeeperException.NodeExistsException(path); - } - } catch (KeeperException.NoNodeException e) { - // ignore this one - } - boolean ephemeralParent = parentRecord.stat.getEphemeralOwner() != 0; - if (ephemeralParent) { - throw new KeeperException.NoChildrenForEphemeralsException(path); - } - int newCversion = parentRecord.stat.getCversion()+1; - request.setTxn(new CreateTxn(path, createRequest.getData(), listACL, createMode.isEphemeral(), - newCversion)); - StatPersisted s = new StatPersisted(); - if (createMode.isEphemeral()) { - s.setEphemeralOwner(request.sessionId); - } - parentRecord = parentRecord.duplicate(request.getHdr().getZxid()); - parentRecord.childCount++; - parentRecord.stat.setCversion(newCversion); - addChangeRecord(parentRecord); - addChangeRecord(new ChangeRecord(request.getHdr().getZxid(), path, s, 0, listACL)); + case OpCode.create: + case OpCode.create2: + case OpCode.createTTL: + case OpCode.createContainer: { + pRequest2TxnCreate(type, request, record, deserialize); break; } - case OpCode.create2: { - Create2Request createRequest = (Create2Request)record; - if (deserialize) { - ByteBufferInputStream.byteBuffer2Record(request.request, createRequest); - } - CreateMode createMode = CreateMode.fromFlag(createRequest.getFlags()); - validateCreateRequest(createMode, request); - String path = createRequest.getPath(); - String parentPath = validatePathForCreate(path, request.sessionId); - - List listACL = fixupACL(path, request.authInfo, createRequest.getAcl()); + case OpCode.deleteContainer: { + String path = new String(request.request.array()); + String parentPath = getParentPathAndValidate(path); ChangeRecord parentRecord = getRecordForPath(parentPath); - - checkACL(zks, parentRecord.acl, ZooDefs.Perms.CREATE, request.authInfo); - int parentCVersion = parentRecord.stat.getCversion(); - if (createMode.isSequential()) { - path = path + String.format(Locale.ENGLISH, "%010d", parentCVersion); - } - try { - PathUtils.validatePath(path); - } catch(IllegalArgumentException ie) { - LOG.info("Invalid path " + path + " with session 0x" + - Long.toHexString(request.sessionId)); - throw new KeeperException.BadArgumentsException(path); - } - try { - if (getRecordForPath(path) != null) { - throw new KeeperException.NodeExistsException(path); - } - } catch (KeeperException.NoNodeException e) { - // ignore this one - } - boolean ephemeralParent = parentRecord.stat.getEphemeralOwner() != 0; - if (ephemeralParent) { - throw new KeeperException.NoChildrenForEphemeralsException(path); + ChangeRecord nodeRecord = getRecordForPath(path); + if (nodeRecord.childCount > 0) { + throw new KeeperException.NotEmptyException(path); } - int newCversion = parentRecord.stat.getCversion()+1; - request.setTxn(new CreateTxn(path, createRequest.getData(), listACL, createMode.isEphemeral(), - newCversion)); - StatPersisted s = new StatPersisted(); - if (createMode.isEphemeral()) { - s.setEphemeralOwner(request.sessionId); + if (EphemeralType.get(nodeRecord.stat.getEphemeralOwner()) == EphemeralType.NORMAL) { + throw new KeeperException.BadVersionException(path); } + request.setTxn(new DeleteTxn(path)); parentRecord = parentRecord.duplicate(request.getHdr().getZxid()); - parentRecord.childCount++; - parentRecord.stat.setCversion(newCversion); + parentRecord.childCount--; addChangeRecord(parentRecord); - addChangeRecord(new ChangeRecord(request.getHdr().getZxid(), path, s, 0, listACL)); + addChangeRecord(new ChangeRecord(request.getHdr().getZxid(), path, null, -1, null)); break; } case OpCode.delete: @@ -472,12 +393,7 @@ protected void pRequest2Txn(int type, long zxid, Request request, if(deserialize) ByteBufferInputStream.byteBuffer2Record(request.request, deleteRequest); String path = deleteRequest.getPath(); - int lastSlash = path.lastIndexOf('/'); - if (lastSlash == -1 || path.indexOf('\0') != -1 - || zks.getZKDatabase().isSpecialPath(path)) { - throw new KeeperException.BadArgumentsException(path); - } - String parentPath = path.substring(0, lastSlash); + String parentPath = getParentPathAndValidate(path); ChangeRecord parentRecord = getRecordForPath(parentPath); ChangeRecord nodeRecord = getRecordForPath(path); checkACL(zks, parentRecord.acl, ZooDefs.Perms.DELETE, request.authInfo); @@ -497,6 +413,7 @@ protected void pRequest2Txn(int type, long zxid, Request request, if(deserialize) ByteBufferInputStream.byteBuffer2Record(request.request, setDataRequest); path = setDataRequest.getPath(); + validatePath(path, request.sessionId); nodeRecord = getRecordForPath(path); checkACL(zks, nodeRecord.acl, ZooDefs.Perms.WRITE, request.authInfo); int newVersion = checkAndIncVersion(nodeRecord.stat.getVersion(), setDataRequest.getVersion(), path); @@ -506,6 +423,15 @@ protected void pRequest2Txn(int type, long zxid, Request request, addChangeRecord(nodeRecord); break; case OpCode.reconfig: + if (!QuorumPeerConfig.isReconfigEnabled()) { + LOG.error("Reconfig operation requested but reconfig feature is disabled."); + throw new KeeperException.ReconfigDisabledException(); + } + + if (skipACL) { + LOG.warn("skipACL is set, reconfig operation will skip ACL checks!"); + } + zks.sessionTracker.checkSession(request.sessionId, request.getOwner()); ReconfigRequest reconfigRequest = (ReconfigRequest)record; LeaderZooKeeperServer lzks; @@ -523,7 +449,8 @@ protected void pRequest2Txn(int type, long zxid, Request request, long configId = reconfigRequest.getCurConfigId(); if (configId != -1 && configId!=lzks.self.getLastSeenQuorumVerifier().getVersion()){ - String msg = "Reconfiguration from version " + configId + " failed -- last seen version is " + lzks.self.getLastSeenQuorumVerifier().getVersion(); + String msg = "Reconfiguration from version " + configId + " failed -- last seen version is " + + lzks.self.getLastSeenQuorumVerifier().getVersion(); throw new KeeperException.BadVersionException(msg); } @@ -540,11 +467,9 @@ protected void pRequest2Txn(int type, long zxid, Request request, props.load(new StringReader(newMembers)); request.qv = QuorumPeerConfig.parseDynamicConfig(props, lzks.self.getElectionType(), true, false); request.qv.setVersion(request.getHdr().getZxid()); - } catch (IOException e) { - throw new KeeperException.BadArgumentsException(e.getMessage()); - } catch (ConfigException e) { + } catch (IOException | ConfigException e) { throw new KeeperException.BadArgumentsException(e.getMessage()); - } + } } else { //incremental change - must be a majority quorum system LOG.info("Incremental reconfig"); @@ -588,8 +513,17 @@ protected void pRequest2Txn(int type, long zxid, Request request, if (qs.clientAddr == null || qs.electionAddr == null || qs.addr == null) { throw new KeeperException.BadArgumentsException("Wrong format of server string - each server should have 3 ports specified"); } + + // check duplication of addresses and ports + for (QuorumServer nqs: nextServers.values()) { + if (qs.id == nqs.id) { + continue; + } + qs.checkAddressDuplicate(nqs); + } + nextServers.remove(qs.id); - nextServers.put(Long.valueOf(qs.id), qs); + nextServers.put(qs.id, qs); } } } catch (ConfigException e){ @@ -627,6 +561,7 @@ protected void pRequest2Txn(int type, long zxid, Request request, if(deserialize) ByteBufferInputStream.byteBuffer2Record(request.request, setAclRequest); path = setAclRequest.getPath(); + validatePath(path, request.sessionId); List listACL = fixupACL(path, request.authInfo, setAclRequest.getAcl()); nodeRecord = getRecordForPath(path); checkACL(zks, nodeRecord.acl, ZooDefs.Perms.ADMIN, request.authInfo); @@ -682,6 +617,7 @@ protected void pRequest2Txn(int type, long zxid, Request request, if(deserialize) ByteBufferInputStream.byteBuffer2Record(request.request, checkVersionRequest); path = checkVersionRequest.getPath(); + validatePath(path, request.sessionId); nodeRecord = getRecordForPath(path); checkACL(zks, nodeRecord.acl, ZooDefs.Perms.READ, request.authInfo); request.setTxn(new CheckVersionTxn(path, checkAndIncVersion(nodeRecord.stat.getVersion(), @@ -693,6 +629,95 @@ protected void pRequest2Txn(int type, long zxid, Request request, } } + private void pRequest2TxnCreate(int type, Request request, Record record, boolean deserialize) throws IOException, KeeperException { + if (deserialize) { + ByteBufferInputStream.byteBuffer2Record(request.request, record); + } + + int flags; + String path; + List acl; + byte[] data; + long ttl; + if (type == OpCode.createTTL) { + CreateTTLRequest createTtlRequest = (CreateTTLRequest)record; + flags = createTtlRequest.getFlags(); + path = createTtlRequest.getPath(); + acl = createTtlRequest.getAcl(); + data = createTtlRequest.getData(); + ttl = createTtlRequest.getTtl(); + } else { + CreateRequest createRequest = (CreateRequest)record; + flags = createRequest.getFlags(); + path = createRequest.getPath(); + acl = createRequest.getAcl(); + data = createRequest.getData(); + ttl = -1; + } + CreateMode createMode = CreateMode.fromFlag(flags); + validateCreateRequest(path, createMode, request, ttl); + String parentPath = validatePathForCreate(path, request.sessionId); + + List listACL = fixupACL(path, request.authInfo, acl); + ChangeRecord parentRecord = getRecordForPath(parentPath); + + checkACL(zks, parentRecord.acl, ZooDefs.Perms.CREATE, request.authInfo); + int parentCVersion = parentRecord.stat.getCversion(); + if (createMode.isSequential()) { + path = path + String.format(Locale.ENGLISH, "%010d", parentCVersion); + } + validatePath(path, request.sessionId); + try { + if (getRecordForPath(path) != null) { + throw new KeeperException.NodeExistsException(path); + } + } catch (KeeperException.NoNodeException e) { + // ignore this one + } + boolean ephemeralParent = EphemeralType.get(parentRecord.stat.getEphemeralOwner()) == EphemeralType.NORMAL; + if (ephemeralParent) { + throw new KeeperException.NoChildrenForEphemeralsException(path); + } + int newCversion = parentRecord.stat.getCversion()+1; + if (type == OpCode.createContainer) { + request.setTxn(new CreateContainerTxn(path, data, listACL, newCversion)); + } else if (type == OpCode.createTTL) { + request.setTxn(new CreateTTLTxn(path, data, listACL, newCversion, ttl)); + } else { + request.setTxn(new CreateTxn(path, data, listACL, createMode.isEphemeral(), + newCversion)); + } + StatPersisted s = new StatPersisted(); + if (createMode.isEphemeral()) { + s.setEphemeralOwner(request.sessionId); + } + parentRecord = parentRecord.duplicate(request.getHdr().getZxid()); + parentRecord.childCount++; + parentRecord.stat.setCversion(newCversion); + addChangeRecord(parentRecord); + addChangeRecord(new ChangeRecord(request.getHdr().getZxid(), path, s, 0, listACL)); + } + + private void validatePath(String path, long sessionId) throws BadArgumentsException { + try { + PathUtils.validatePath(path); + } catch(IllegalArgumentException ie) { + LOG.info("Invalid path {} with session 0x{}, reason: {}", + path, Long.toHexString(sessionId), ie.getMessage()); + throw new BadArgumentsException(path); + } + } + + private String getParentPathAndValidate(String path) + throws BadArgumentsException { + int lastSlash = path.lastIndexOf('/'); + if (lastSlash == -1 || path.indexOf('\0') != -1 + || zks.getZKDatabase().isSpecialPath(path)) { + throw new BadArgumentsException(path); + } + return path.substring(0, lastSlash); + } + private static int checkAndIncVersion(int currentVersion, int expectedVersion, String path) throws KeeperException.BadVersionException { if (expectedVersion != -1 && expectedVersion != currentVersion) { @@ -715,16 +740,19 @@ protected void pRequest(Request request) throws RequestProcessorException { try { switch (request.type) { + case OpCode.createContainer: case OpCode.create: - CreateRequest createRequest = new CreateRequest(); - pRequest2Txn(request.type, zks.getNextZxid(), request, createRequest, true); - break; case OpCode.create2: - Create2Request create2Request = new Create2Request(); + CreateRequest create2Request = new CreateRequest(); pRequest2Txn(request.type, zks.getNextZxid(), request, create2Request, true); break; + case OpCode.createTTL: + CreateTTLRequest createTtlRequest = new CreateTTLRequest(); + pRequest2Txn(request.type, zks.getNextZxid(), request, createTtlRequest, true); + break; + case OpCode.deleteContainer: case OpCode.delete: - DeleteRequest deleteRequest = new DeleteRequest(); + DeleteRequest deleteRequest = new DeleteRequest(); pRequest2Txn(request.type, zks.getNextZxid(), request, deleteRequest, true); break; case OpCode.setData: @@ -750,8 +778,8 @@ protected void pRequest(Request request) throws RequestProcessorException { ByteBufferInputStream.byteBuffer2Record(request.request, multiRequest); } catch(IOException e) { request.setHdr(new TxnHeader(request.sessionId, request.cxid, zks.getNextZxid(), - zks.getTime(), OpCode.multi)); - throw e; + Time.currentWallTime(), OpCode.multi)); + throw e; } List txns = new ArrayList(); //Each op in a multi-op must have the same zxid! @@ -809,7 +837,8 @@ protected void pRequest(Request request) throws RequestProcessorException { txns.add(new Txn(type, bb.array())); } - request.setHdr(new TxnHeader(request.sessionId, request.cxid, zxid, zks.getTime(), request.type)); + request.setHdr(new TxnHeader(request.sessionId, request.cxid, zxid, + Time.currentWallTime(), request.type)); request.setTxn(new MultiTxn(txns)); break; @@ -879,19 +908,25 @@ protected void pRequest(Request request) throws RequestProcessorException { private List removeDuplicates(List acl) { - ArrayList retval = new ArrayList(); - Iterator it = acl.iterator(); - while (it.hasNext()) { - ACL a = it.next(); - if (retval.contains(a) == false) { + LinkedList retval = new LinkedList(); + for (ACL a : acl) { + if (!retval.contains(a)) { retval.add(a); } } return retval; } - - private void validateCreateRequest(CreateMode createMode, Request request) + + private void validateCreateRequest(String path, CreateMode createMode, Request request, long ttl) throws KeeperException { + if (createMode.isTTL() && !EphemeralType.extendedEphemeralTypesEnabled()) { + throw new KeeperException.UnimplementedException(); + } + try { + EphemeralType.validateTTL(createMode, ttl); + } catch (IllegalArgumentException e) { + throw new BadArgumentsException(path); + } if (createMode.isEphemeral()) { // Exception is set when local session failed to upgrade // so we just need to report the error diff --git a/src/java/main/org/apache/zookeeper/server/PurgeTxnLog.java b/src/java/main/org/apache/zookeeper/server/PurgeTxnLog.java index 244ea1b88b9..40fe18ba8a2 100644 --- a/src/java/main/org/apache/zookeeper/server/PurgeTxnLog.java +++ b/src/java/main/org/apache/zookeeper/server/PurgeTxnLog.java @@ -24,10 +24,15 @@ import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.server.persistence.FileTxnSnapLog; import org.apache.zookeeper.server.persistence.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * this class is used to clean up the @@ -37,13 +42,19 @@ * files and snapdir files keeping the last "-n" snapshot files * and the corresponding logs. */ +@InterfaceAudience.Public public class PurgeTxnLog { + private static final Logger LOG = LoggerFactory.getLogger(PurgeTxnLog.class); + + private static final String COUNT_ERR_MSG = "count should be greater than or equal to 3"; + static void printUsage(){ + System.out.println("Usage:"); System.out.println("PurgeTxnLog dataLogDir [snapDir] -n count"); System.out.println("\tdataLogDir -- path to the txn log directory"); System.out.println("\tsnapDir -- path to the snapshot directory"); - System.out.println("\tcount -- the number of old snaps/logs you want to keep"); - System.exit(1); + System.out.println("\tcount -- the number of old snaps/logs you want " + + "to keep, value should be greater than or equal to 3"); } private static final String PREFIX_SNAPSHOT = "snapshot"; @@ -62,24 +73,49 @@ static void printUsage(){ */ public static void purge(File dataDir, File snapDir, int num) throws IOException { if (num < 3) { - throw new IllegalArgumentException("count should be greater than 3"); + throw new IllegalArgumentException(COUNT_ERR_MSG); } FileTxnSnapLog txnLog = new FileTxnSnapLog(dataDir, snapDir); List snaps = txnLog.findNRecentSnapshots(num); - retainNRecentSnapshots(txnLog, snaps); + int numSnaps = snaps.size(); + if (numSnaps > 0) { + purgeOlderSnapshots(txnLog, snaps.get(numSnaps - 1)); + } } // VisibleForTesting - static void retainNRecentSnapshots(FileTxnSnapLog txnLog, List snaps) { - // found any valid recent snapshots? - if (snaps.size() == 0) - return; - File snapShot = snaps.get(snaps.size() -1); + static void purgeOlderSnapshots(FileTxnSnapLog txnLog, File snapShot) { final long leastZxidToBeRetain = Util.getZxidFromName( snapShot.getName(), PREFIX_SNAPSHOT); + /** + * We delete all files with a zxid in their name that is less than leastZxidToBeRetain. + * This rule applies to both snapshot files as well as log files, with the following + * exception for log files. + * + * A log file with zxid less than X may contain transactions with zxid larger than X. More + * precisely, a log file named log.(X-a) may contain transactions newer than snapshot.X if + * there are no other log files with starting zxid in the interval (X-a, X]. Assuming the + * latter condition is true, log.(X-a) must be retained to ensure that snapshot.X is + * recoverable. In fact, this log file may very well extend beyond snapshot.X to newer + * snapshot files if these newer snapshots were not accompanied by log rollover (possible in + * the learner state machine at the time of this writing). We can make more precise + * determination of whether log.(leastZxidToBeRetain-a) for the smallest 'a' is actually + * needed or not (e.g. not needed if there's a log file named log.(leastZxidToBeRetain+1)), + * but the complexity quickly adds up with gains only in uncommon scenarios. It's safe and + * simple to just preserve log.(leastZxidToBeRetain-a) for the smallest 'a' to ensure + * recoverability of all snapshots being retained. We determine that log file here by + * calling txnLog.getSnapshotLogs(). + */ + final Set retainedTxnLogs = new HashSet(); + retainedTxnLogs.addAll(Arrays.asList(txnLog.getSnapshotLogs(leastZxidToBeRetain))); + + /** + * Finds all candidates for deletion, which are files with a zxid in their name that is less + * than leastZxidToBeRetain. There's an exception to this rule, as noted above. + */ class MyFileFilter implements FileFilter{ private final String prefix; MyFileFilter(String prefix){ @@ -88,6 +124,9 @@ class MyFileFilter implements FileFilter{ public boolean accept(File f){ if(!f.getName().startsWith(prefix + ".")) return false; + if (retainedTxnLogs.contains(f)) { + return false; + } long fZxid = Util.getZxidFromName(f.getName(), prefix); if (fZxid >= leastZxidToBeRetain) { return false; @@ -96,17 +135,26 @@ public boolean accept(File f){ } } // add all non-excluded log files - List files = new ArrayList(Arrays.asList(txnLog - .getDataDir().listFiles(new MyFileFilter(PREFIX_LOG)))); + File[] logs = txnLog.getDataDir().listFiles(new MyFileFilter(PREFIX_LOG)); + List files = new ArrayList<>(); + if (logs != null) { + files.addAll(Arrays.asList(logs)); + } + // add all non-excluded snapshot files to the deletion list - files.addAll(Arrays.asList(txnLog.getSnapDir().listFiles( - new MyFileFilter(PREFIX_SNAPSHOT)))); + File[] snapshots = txnLog.getSnapDir().listFiles(new MyFileFilter(PREFIX_SNAPSHOT)); + if (snapshots != null) { + files.addAll(Arrays.asList(snapshots)); + } + // remove the old files for(File f: files) { - System.out.println("Removing file: "+ + final String msg = "Removing file: "+ DateFormat.getDateTimeInstance().format(f.lastModified())+ - "\t"+f.getPath()); + "\t"+f.getPath(); + LOG.info(msg); + System.out.println(msg); if(!f.delete()){ System.err.println("Failed to remove "+f.getPath()); } @@ -115,22 +163,74 @@ public boolean accept(File f){ } /** - * @param args PurgeTxnLog dataLogDir - * dataLogDir -- txn log directory - * -n num (number of snapshots to keep) + * @param args dataLogDir [snapDir] -n count + * dataLogDir -- path to the txn log directory + * snapDir -- path to the snapshot directory + * count -- the number of old snaps/logs you want to keep, value should be greater than or equal to 3
    */ public static void main(String[] args) throws IOException { - if(args.length<3 || args.length>4) - printUsage(); - int i = 0; - File dataDir=new File(args[0]); - File snapDir=dataDir; - if(args.length==4){ - i++; - snapDir=new File(args[i]); + if (args.length < 3 || args.length > 4) { + printUsageThenExit(); + } + File dataDir = validateAndGetFile(args[0]); + File snapDir = dataDir; + int num = -1; + String countOption = ""; + if (args.length == 3) { + countOption = args[1]; + num = validateAndGetCount(args[2]); + } else { + snapDir = validateAndGetFile(args[1]); + countOption = args[2]; + num = validateAndGetCount(args[3]); + } + if (!"-n".equals(countOption)) { + printUsageThenExit(); } - i++; i++; - int num = Integer.parseInt(args[i]); purge(dataDir, snapDir, num); } + + /** + * validates file existence and returns the file + * + * @param path + * @return File + */ + private static File validateAndGetFile(String path) { + File file = new File(path); + if (!file.exists()) { + System.err.println("Path '" + file.getAbsolutePath() + + "' does not exist. "); + printUsageThenExit(); + } + return file; + } + + /** + * Returns integer if parsed successfully and it is valid otherwise prints + * error and usage and then exits + * + * @param number + * @return count + */ + private static int validateAndGetCount(String number) { + int result = 0; + try { + result = Integer.parseInt(number); + if (result < 3) { + System.err.println(COUNT_ERR_MSG); + printUsageThenExit(); + } + } catch (NumberFormatException e) { + System.err + .println("'" + number + "' can not be parsed to integer."); + printUsageThenExit(); + } + return result; + } + + private static void printUsageThenExit() { + printUsage(); + System.exit(1); + } } diff --git a/src/java/main/org/apache/zookeeper/server/RateLogger.java b/src/java/main/org/apache/zookeeper/server/RateLogger.java index fc951cf5147..acbd522624c 100644 --- a/src/java/main/org/apache/zookeeper/server/RateLogger.java +++ b/src/java/main/org/apache/zookeeper/server/RateLogger.java @@ -18,6 +18,7 @@ package org.apache.zookeeper.server; +import org.apache.zookeeper.common.Time; import org.slf4j.Logger; public class RateLogger { @@ -43,7 +44,7 @@ public void flush() { } public void rateLimitLog(String newMsg) { - long now = System.currentTimeMillis(); + long now = Time.currentElapsedTime(); if (newMsg.equals(msg)) { ++count; if (now - timestamp >= 100) { diff --git a/src/java/main/org/apache/zookeeper/server/ReferenceCountedACLCache.java b/src/java/main/org/apache/zookeeper/server/ReferenceCountedACLCache.java new file mode 100644 index 00000000000..384d23ab041 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/ReferenceCountedACLCache.java @@ -0,0 +1,226 @@ +/** + * 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.Index; +import org.apache.jute.InputArchive; +import org.apache.jute.OutputArchive; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +public class ReferenceCountedACLCache { + private static final Logger LOG = LoggerFactory.getLogger(ReferenceCountedACLCache.class); + + final Map> longKeyMap = + new HashMap>(); + + final Map, Long> aclKeyMap = + new HashMap, Long>(); + + final Map referenceCounter = + new HashMap(); + private static final long OPEN_UNSAFE_ACL_ID = -1L; + + /** + * these are the number of acls that we have in the datatree + */ + long aclIndex = 0; + + /** + * converts the list of acls to a long. + * Increments the reference counter for this ACL. + * @param acls + * @return a long that map to the acls + */ + public synchronized Long convertAcls(List acls) { + if (acls == null) + return OPEN_UNSAFE_ACL_ID; + + // get the value from the map + Long ret = aclKeyMap.get(acls); + if (ret == null) { + ret = incrementIndex(); + longKeyMap.put(ret, acls); + aclKeyMap.put(acls, ret); + } + + addUsage(ret); + + return ret; + } + + /** + * converts a long to a list of acls. + * + * @param longVal + * @return a list of ACLs that map to the long + */ + public synchronized List convertLong(Long longVal) { + if (longVal == null) + return null; + if (longVal == OPEN_UNSAFE_ACL_ID) + return ZooDefs.Ids.OPEN_ACL_UNSAFE; + List acls = longKeyMap.get(longVal); + if (acls == null) { + LOG.error("ERROR: ACL not available for long " + longVal); + throw new RuntimeException("Failed to fetch acls for " + longVal); + } + return acls; + } + + private long incrementIndex() { + return ++aclIndex; + } + + public synchronized void deserialize(InputArchive ia) throws IOException { + clear(); + int i = ia.readInt("map"); + while (i > 0) { + Long val = ia.readLong("long"); + if (aclIndex < val) { + aclIndex = val; + } + List aclList = new ArrayList(); + Index j = ia.startVector("acls"); + if (j == null) { + throw new RuntimeException("Incorrent format of InputArchive when deserialize DataTree - missing acls"); + } + while (!j.done()) { + ACL acl = new ACL(); + acl.deserialize(ia, "acl"); + aclList.add(acl); + j.incr(); + } + longKeyMap.put(val, aclList); + aclKeyMap.put(aclList, val); + referenceCounter.put(val, new AtomicLongWithEquals(0)); + i--; + } + } + + public synchronized void serialize(OutputArchive oa) throws IOException { + oa.writeInt(longKeyMap.size(), "map"); + Set>> set = longKeyMap.entrySet(); + for (Map.Entry> val : set) { + oa.writeLong(val.getKey(), "long"); + List aclList = val.getValue(); + oa.startVector(aclList, "acls"); + for (ACL acl : aclList) { + acl.serialize(oa, "acl"); + } + oa.endVector(aclList, "acls"); + } + } + + public int size() { + return aclKeyMap.size(); + } + + private void clear() { + aclKeyMap.clear(); + longKeyMap.clear(); + referenceCounter.clear(); + } + + public synchronized void addUsage(Long acl) { + if (acl == OPEN_UNSAFE_ACL_ID) { + return; + } + + if (!longKeyMap.containsKey(acl)) { + LOG.info("Ignoring acl " + acl + " as it does not exist in the cache"); + return; + } + + AtomicLong count = referenceCounter.get(acl); + if (count == null) { + referenceCounter.put(acl, new AtomicLongWithEquals(1)); + } else { + count.incrementAndGet(); + } + } + + public synchronized void removeUsage(Long acl) { + if (acl == OPEN_UNSAFE_ACL_ID) { + return; + } + + if (!longKeyMap.containsKey(acl)) { + LOG.info("Ignoring acl " + acl + " as it does not exist in the cache"); + return; + } + + long newCount = referenceCounter.get(acl).decrementAndGet(); + if (newCount <= 0) { + referenceCounter.remove(acl); + aclKeyMap.remove(longKeyMap.get(acl)); + longKeyMap.remove(acl); + } + } + + public synchronized void purgeUnused() { + Iterator> refCountIter = referenceCounter.entrySet().iterator(); + while (refCountIter.hasNext()) { + Map.Entry entry = refCountIter.next(); + if (entry.getValue().get() <= 0) { + Long acl = entry.getKey(); + aclKeyMap.remove(longKeyMap.get(acl)); + longKeyMap.remove(acl); + refCountIter.remove(); + } + } + } + + private static class AtomicLongWithEquals extends AtomicLong { + + private static final long serialVersionUID = 3355155896813725462L; + + public AtomicLongWithEquals(long i) { + super(i); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + return equals((AtomicLongWithEquals) o); + } + + public boolean equals(AtomicLongWithEquals that) { + return get() == that.get(); + } + + @Override + public int hashCode() { + return 31 * Long.valueOf(get()).hashCode(); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/Request.java b/src/java/main/org/apache/zookeeper/server/Request.java index ee01dcfa637..ede9280441a 100644 --- a/src/java/main/org/apache/zookeeper/server/Request.java +++ b/src/java/main/org/apache/zookeeper/server/Request.java @@ -24,6 +24,7 @@ import org.apache.jute.Record; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; import org.apache.zookeeper.txn.TxnHeader; @@ -75,7 +76,7 @@ public Request(long sessionId, int xid, int type, TxnHeader hdr, Record txn, lon public final List authInfo; - public final long createTime = System.currentTimeMillis(); + public final long createTime = Time.currentElapsedTime(); private Object owner; @@ -136,8 +137,11 @@ static boolean isValid(int type) { case OpCode.closeSession: case OpCode.create: case OpCode.create2: + case OpCode.createTTL: + case OpCode.createContainer: case OpCode.createSession: case OpCode.delete: + case OpCode.deleteContainer: case OpCode.exists: case OpCode.getACL: case OpCode.getChildren: @@ -168,8 +172,11 @@ public boolean isQuorum() { return false; case OpCode.create: case OpCode.create2: + case OpCode.createTTL: + case OpCode.createContainer: case OpCode.error: case OpCode.delete: + case OpCode.deleteContainer: case OpCode.setACL: case OpCode.setData: case OpCode.check: @@ -192,10 +199,16 @@ static String op2String(int op) { return "create"; case OpCode.create2: return "create2"; + case OpCode.createTTL: + return "createTtl"; + case OpCode.createContainer: + return "createContainer"; case OpCode.setWatches: return "setWatches"; case OpCode.delete: return "delete"; + case OpCode.deleteContainer: + return "deleteContainer"; case OpCode.exists: return "exists"; case OpCode.getData: diff --git a/src/java/main/org/apache/zookeeper/server/ServerCnxn.java b/src/java/main/org/apache/zookeeper/server/ServerCnxn.java index a47d8566297..acc5da891b8 100644 --- a/src/java/main/org/apache/zookeeper/server/ServerCnxn.java +++ b/src/java/main/org/apache/zookeeper/server/ServerCnxn.java @@ -23,10 +23,10 @@ import java.io.StringWriter; import java.net.InetSocketAddress; import java.nio.ByteBuffer; +import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -38,6 +38,8 @@ import org.apache.zookeeper.data.Id; import org.apache.zookeeper.proto.ReplyHeader; import org.apache.zookeeper.proto.RequestHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Interface to a Server connection - represents a connection from a client @@ -47,6 +49,7 @@ public abstract class ServerCnxn implements Stats, Watcher { // This is just an arbitrary object to represent requests issued by // (aka owned by) this class final public static Object me = new Object(); + private static final Logger LOG = LoggerFactory.getLogger(ServerCnxn.class); protected ArrayList authInfo = new ArrayList(); @@ -69,7 +72,7 @@ public abstract void sendResponse(ReplyHeader h, Record r, String tag) public abstract void process(WatchedEvent event); - abstract long getSessionId(); + public abstract long getSessionId(); abstract void setSessionId(long sessionId); @@ -118,149 +121,6 @@ public String toString() { } } - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int confCmd = - ByteBuffer.wrap("conf".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int consCmd = - ByteBuffer.wrap("cons".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int crstCmd = - ByteBuffer.wrap("crst".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int dumpCmd = - ByteBuffer.wrap("dump".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int enviCmd = - ByteBuffer.wrap("envi".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int getTraceMaskCmd = - ByteBuffer.wrap("gtmk".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int ruokCmd = - ByteBuffer.wrap("ruok".getBytes()).getInt(); - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int setTraceMaskCmd = - ByteBuffer.wrap("stmk".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int srvrCmd = - ByteBuffer.wrap("srvr".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int srstCmd = - ByteBuffer.wrap("srst".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int statCmd = - ByteBuffer.wrap("stat".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int wchcCmd = - ByteBuffer.wrap("wchc".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int wchpCmd = - ByteBuffer.wrap("wchp".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int wchsCmd = - ByteBuffer.wrap("wchs".getBytes()).getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int mntrCmd = ByteBuffer.wrap("mntr".getBytes()) - .getInt(); - - /* - * See - * Zk Admin. this link is for all the commands. - */ - protected final static int isroCmd = ByteBuffer.wrap("isro".getBytes()) - .getInt(); - - /* - * The control sequence sent by the telnet program when it closes a - * connection. Include simply to keep the logs cleaner (the server would - * close the connection anyway because it would parse this as a negative - * length). - */ - protected final static int telnetCloseCmd = 0xfff4fffd; - - protected final static HashMap cmd2String = - new HashMap(); - - // specify all of the commands that are available - static { - cmd2String.put(confCmd, "conf"); - cmd2String.put(consCmd, "cons"); - cmd2String.put(crstCmd, "crst"); - cmd2String.put(dumpCmd, "dump"); - cmd2String.put(enviCmd, "envi"); - cmd2String.put(getTraceMaskCmd, "gtmk"); - cmd2String.put(ruokCmd, "ruok"); - cmd2String.put(setTraceMaskCmd, "stmk"); - cmd2String.put(srstCmd, "srst"); - cmd2String.put(srvrCmd, "srvr"); - cmd2String.put(statCmd, "stat"); - cmd2String.put(wchcCmd, "wchc"); - cmd2String.put(wchpCmd, "wchp"); - cmd2String.put(wchsCmd, "wchs"); - cmd2String.put(mntrCmd, "mntr"); - cmd2String.put(isroCmd, "isro"); - cmd2String.put(telnetCloseCmd, "telnet close"); - } - protected void packetReceived() { incrPacketsReceived(); ServerStats serverStats = serverStats(); @@ -407,13 +267,16 @@ public String toString() { public abstract InetSocketAddress getRemoteSocketAddress(); public abstract int getInterestOps(); + public abstract boolean isSecure(); + public abstract Certificate[] getClientCertificateChain(); + public abstract void setClientCertificateChain(Certificate[] chain); /** * Print information about the connection. * @param brief iff true prints brief details, otw full detail * @return information about this connection */ - protected synchronized void + public synchronized void dumpConnectionInfo(PrintWriter pwriter, boolean brief) { pwriter.print(" "); pwriter.print(getRemoteSocketAddress()); @@ -482,4 +345,28 @@ public synchronized Map getConnectionInfo(boolean brief) { } return info; } + + /** + * clean up the socket related to a command and also make sure we flush the + * data before we do that + * + * @param pwriter + * the pwriter for a command socket + */ + public void cleanupWriterSocket(PrintWriter pwriter) { + try { + if (pwriter != null) { + pwriter.flush(); + pwriter.close(); + } + } catch (Exception e) { + LOG.info("Error closing PrintWriter ", e); + } finally { + try { + close(); + } catch (Exception e) { + LOG.error("Error closing a command socket ", e); + } + } + } } diff --git a/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java b/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java index 14037722c56..dbe47a261b7 100644 --- a/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java +++ b/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java @@ -23,7 +23,6 @@ import java.nio.ByteBuffer; import java.util.Collections; import java.util.Set; -import java.util.HashSet; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -34,6 +33,7 @@ import org.apache.zookeeper.Environment; import org.apache.zookeeper.Login; +import org.apache.zookeeper.common.ZKConfig; import org.apache.zookeeper.jmx.MBeanRegistry; import org.apache.zookeeper.server.auth.SaslServerCallbackHandler; import org.slf4j.Logger; @@ -43,13 +43,12 @@ public abstract class ServerCnxnFactory { public static final String ZOOKEEPER_SERVER_CNXN_FACTORY = "zookeeper.serverCnxnFactory"; - - public interface PacketProcessor { - public void processPacket(ByteBuffer packet, ServerCnxn src); - } private static final Logger LOG = LoggerFactory.getLogger(ServerCnxnFactory.class); + // Tells whether SSL is enabled on this ServerCnxnFactory + protected boolean secure; + /** * The buffer will cause the connection to be close when we do a send. */ @@ -67,14 +66,21 @@ public ZooKeeperServer getZooKeeperServer() { return zkServer; } - public abstract void closeSession(long sessionId); + /** + * @return true if the cnxn that contains the sessionId exists in this ServerCnxnFactory + * and it's closed. Otherwise false. + */ + public abstract boolean closeSession(long sessionId); + + public void configure(InetSocketAddress addr, int maxcc) throws IOException { + configure(addr, maxcc, false); + } + + public abstract void configure(InetSocketAddress addr, int maxcc, boolean secure) + throws IOException; - public abstract void configure(InetSocketAddress addr, - int maxClientCnxns) throws IOException; - public abstract void reconfigure(InetSocketAddress addr); - - + protected SaslServerCallbackHandler saslServerCallbackHandler; public Login login; @@ -84,8 +90,18 @@ public abstract void configure(InetSocketAddress addr, /** Maximum number of connections allowed from particular host (ip) */ public abstract void setMaxClientCnxnsPerHost(int max); - public abstract void startup(ZooKeeperServer zkServer) - throws IOException, InterruptedException; + public boolean isSecure() { + return secure; + } + + public void startup(ZooKeeperServer zkServer) throws IOException, InterruptedException { + startup(zkServer, true); + } + + // This method is to maintain compatiblity of startup(zks) and enable sharing of zks + // when we add secureCnxnFactory. + public abstract void startup(ZooKeeperServer zkServer, boolean startServer) + throws IOException, InterruptedException; public abstract void join() throws InterruptedException; @@ -94,10 +110,14 @@ public abstract void startup(ZooKeeperServer zkServer) public abstract void start(); protected ZooKeeperServer zkServer; - final public void setZooKeeperServer(ZooKeeperServer zk) { - this.zkServer = zk; - if (zk != null) { - zk.setServerCnxnFactory(this); + final public void setZooKeeperServer(ZooKeeperServer zks) { + this.zkServer = zks; + if (zks != null) { + if (secure) { + zks.setSecureServerCnxnFactory(this); + } else { + zks.setServerCnxnFactory(this); + } } } @@ -110,8 +130,10 @@ static public ServerCnxnFactory createFactory() throws IOException { serverCnxnFactoryName = NIOServerCnxnFactory.class.getName(); } try { - return (ServerCnxnFactory) Class.forName(serverCnxnFactoryName) - .newInstance(); + ServerCnxnFactory serverCnxnFactory = (ServerCnxnFactory) Class.forName(serverCnxnFactoryName) + .getDeclaredConstructor().newInstance(); + LOG.info("Using {} as server connection factory", serverCnxnFactoryName); + return serverCnxnFactory; } catch (Exception e) { IOException ioe = new IOException("Couldn't instantiate " + serverCnxnFactoryName); @@ -215,7 +237,7 @@ protected void configureSaslLogin() throws IOException { // jaas.conf entry available try { saslServerCallbackHandler = new SaslServerCallbackHandler(Configuration.getConfiguration()); - login = new Login(serverSection, saslServerCallbackHandler); + login = new Login(serverSection, saslServerCallbackHandler, new ZKConfig() ); login.startThreadIfNeeded(); } catch (LoginException e) { throw new IOException("Could not configure server because SASL configuration did not allow the " diff --git a/src/java/main/org/apache/zookeeper/server/ServerConfig.java b/src/java/main/org/apache/zookeeper/server/ServerConfig.java index f2b8463e871..dd3f1da9d0c 100644 --- a/src/java/main/org/apache/zookeeper/server/ServerConfig.java +++ b/src/java/main/org/apache/zookeeper/server/ServerConfig.java @@ -22,6 +22,7 @@ import java.net.InetSocketAddress; import java.util.Arrays; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.server.quorum.QuorumPeerConfig; import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; @@ -31,12 +32,14 @@ * We use this instead of Properties as it's typed. * */ +@InterfaceAudience.Public public class ServerConfig { //// //// If you update the configuration parameters be sure //// to update the "conf" 4letter word //// protected InetSocketAddress clientPortAddress; + protected InetSocketAddress secureClientPortAddress; protected File dataDir; protected File dataLogDir; protected int tickTime = ZooKeeperServer.DEFAULT_TICK_TIME; @@ -48,20 +51,19 @@ public class ServerConfig { /** * Parse arguments for server configuration - * @param args clientPort dataDir and optional tickTime + * @param args clientPort dataDir and optional tickTime and maxClientCnxns * @return ServerConfig configured wrt arguments * @throws IllegalArgumentException on invalid usage */ public void parse(String[] args) { if (args.length < 2 || args.length > 4) { - throw new IllegalArgumentException("Invalid args:" - + Arrays.toString(args)); + throw new IllegalArgumentException("Invalid number of arguments:" + Arrays.toString(args)); } clientPortAddress = new InetSocketAddress(Integer.parseInt(args[0])); dataDir = new File(args[1]); dataLogDir = dataDir; - if (args.length == 3) { + if (args.length >= 3) { tickTime = Integer.parseInt(args[2]); } if (args.length == 4) { @@ -89,18 +91,22 @@ public void parse(String path) throws ConfigException { * @param config */ public void readFrom(QuorumPeerConfig config) { - clientPortAddress = config.getClientPortAddress(); - dataDir = config.getDataDir(); - dataLogDir = config.getDataLogDir(); - tickTime = config.getTickTime(); - maxClientCnxns = config.getMaxClientCnxns(); - minSessionTimeout = config.getMinSessionTimeout(); - maxSessionTimeout = config.getMaxSessionTimeout(); + clientPortAddress = config.getClientPortAddress(); + secureClientPortAddress = config.getSecureClientPortAddress(); + dataDir = config.getDataDir(); + dataLogDir = config.getDataLogDir(); + tickTime = config.getTickTime(); + maxClientCnxns = config.getMaxClientCnxns(); + minSessionTimeout = config.getMinSessionTimeout(); + maxSessionTimeout = config.getMaxSessionTimeout(); } public InetSocketAddress getClientPortAddress() { return clientPortAddress; } + public InetSocketAddress getSecureClientPortAddress() { + return secureClientPortAddress; + } public File getDataDir() { return dataDir; } public File getDataLogDir() { return dataLogDir; } public int getTickTime() { return tickTime; } diff --git a/src/java/main/org/apache/zookeeper/server/ServerStats.java b/src/java/main/org/apache/zookeeper/server/ServerStats.java index c3246293e40..83efb093f92 100644 --- a/src/java/main/org/apache/zookeeper/server/ServerStats.java +++ b/src/java/main/org/apache/zookeeper/server/ServerStats.java @@ -20,6 +20,8 @@ +import org.apache.zookeeper.common.Time; + /** * Basic Server Statistics */ @@ -38,6 +40,8 @@ public interface Provider { public long getLastProcessedZxid(); public String getState(); public int getNumAliveConnections(); + public long getDataDirSize(); + public long getLogDirSize(); } public ServerStats(Provider provider) { @@ -67,6 +71,14 @@ public long getOutstandingRequests() { public long getLastProcessedZxid(){ return provider.getLastProcessedZxid(); } + + public long getDataDirSize() { + return provider.getDataDirSize(); + } + + public long getLogDirSize() { + return provider.getLogDirSize(); + } synchronized public long getPacketsReceived() { return packetsReceived; @@ -107,7 +119,7 @@ public String toString(){ } // mutators synchronized void updateLatency(long requestCreateTime) { - long latency = System.currentTimeMillis() - requestCreateTime; + long latency = Time.currentElapsedTime() - requestCreateTime; totalLatency += latency; count++; if (latency < minLatency) { diff --git a/src/java/main/org/apache/zookeeper/server/SessionTrackerImpl.java b/src/java/main/org/apache/zookeeper/server/SessionTrackerImpl.java index 0c2c042e276..6fc6eb50498 100644 --- a/src/java/main/org/apache/zookeeper/server/SessionTrackerImpl.java +++ b/src/java/main/org/apache/zookeeper/server/SessionTrackerImpl.java @@ -32,6 +32,7 @@ import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.SessionExpiredException; +import org.apache.zookeeper.common.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -81,8 +82,11 @@ public String toString() { */ public static long initializeNextSession(long id) { long nextSid; - nextSid = (System.currentTimeMillis() << 24) >>> 8; + nextSid = (Time.currentElapsedTime() << 24) >>> 8; nextSid = nextSid | (id <<56); + if (nextSid == EphemeralType.CONTAINER_EPHEMERAL_OWNER) { + ++nextSid; // this is an unlikely edge case, but check it just in case + } return nextSid; } @@ -90,9 +94,9 @@ public static long initializeNextSession(long id) { public SessionTrackerImpl(SessionExpirer expirer, ConcurrentMap sessionsWithTimeout, int tickTime, - long serverId) + long serverId, ZooKeeperServerListener listener) { - super("SessionTracker"); + super("SessionTracker", listener); this.expirer = expirer; this.sessionExpiryQueue = new ExpiryQueue(tickTime); this.sessionsWithTimeout = sessionsWithTimeout; @@ -100,6 +104,8 @@ public SessionTrackerImpl(SessionExpirer expirer, for (Entry e : sessionsWithTimeout.entrySet()) { addSession(e.getKey(), e.getValue()); } + + EphemeralType.validateServerId(serverId); } volatile boolean running = true; @@ -152,7 +158,7 @@ public void run() { } } } catch (InterruptedException e) { - LOG.error("Unexpected interruption", e); + handleException(this.getName(), e); } LOG.info("SessionTrackerImpl exited loop!"); } diff --git a/src/java/main/org/apache/zookeeper/server/SnapshotFormatter.java b/src/java/main/org/apache/zookeeper/server/SnapshotFormatter.java index f94c54ddffd..1b131a3040a 100644 --- a/src/java/main/org/apache/zookeeper/server/SnapshotFormatter.java +++ b/src/java/main/org/apache/zookeeper/server/SnapshotFormatter.java @@ -31,12 +31,14 @@ import org.apache.jute.BinaryInputArchive; import org.apache.jute.InputArchive; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.data.StatPersisted; import org.apache.zookeeper.server.persistence.FileSnap; /** * Dump a snapshot file to stdout. */ +@InterfaceAudience.Public public class SnapshotFormatter { /** @@ -94,10 +96,8 @@ private void printZnode(DataTree dataTree, String name) { } children = n.getChildren(); } - if (children != null) { - for (String child : children) { - printZnode(dataTree, name + (name.equals("/") ? "" : "/") + child); - } + for (String child : children) { + printZnode(dataTree, name + (name.equals("/") ? "" : "/") + child); } } diff --git a/src/java/main/org/apache/zookeeper/server/SyncRequestProcessor.java b/src/java/main/org/apache/zookeeper/server/SyncRequestProcessor.java index 6948b293c1e..47701dc2297 100644 --- a/src/java/main/org/apache/zookeeper/server/SyncRequestProcessor.java +++ b/src/java/main/org/apache/zookeeper/server/SyncRequestProcessor.java @@ -70,9 +70,9 @@ public class SyncRequestProcessor extends ZooKeeperCriticalThread implements private final Request requestOfDeath = Request.requestOfDeath; public SyncRequestProcessor(ZooKeeperServer zks, - RequestProcessor nextProcessor) - { - super("SyncThread:" + zks.getServerId()); + RequestProcessor nextProcessor) { + super("SyncThread:" + zks.getServerId(), zks + .getZooKeeperServerListener()); this.zks = zks; this.nextProcessor = nextProcessor; running = true; @@ -162,7 +162,7 @@ public void run() { } } } catch (Throwable t) { - super.handleException(this.getName(), t); + handleException(this.getName(), t); } finally{ running = false; } diff --git a/src/java/main/org/apache/zookeeper/server/TraceFormatter.java b/src/java/main/org/apache/zookeeper/server/TraceFormatter.java index 582383d6bf6..63a3edd6ec2 100644 --- a/src/java/main/org/apache/zookeeper/server/TraceFormatter.java +++ b/src/java/main/org/apache/zookeeper/server/TraceFormatter.java @@ -37,8 +37,14 @@ public static String op2String(int op) { return "create"; case OpCode.create2: return "create2"; + case OpCode.createTTL: + return "createTtl"; + case OpCode.createContainer: + return "createContainer"; case OpCode.delete: return "delete"; + case OpCode.deleteContainer: + return "deleteContainer"; case OpCode.exists: return "exists"; case OpCode.getData: diff --git a/src/java/main/org/apache/zookeeper/server/WorkerService.java b/src/java/main/org/apache/zookeeper/server/WorkerService.java index c55ff48f92e..416e3af29b8 100644 --- a/src/java/main/org/apache/zookeeper/server/WorkerService.java +++ b/src/java/main/org/apache/zookeeper/server/WorkerService.java @@ -26,6 +26,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import org.apache.zookeeper.common.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -227,11 +228,11 @@ public void stop() { public void join(long shutdownTimeoutMS) { // Give the worker threads time to finish executing - long now = System.currentTimeMillis(); + long now = Time.currentElapsedTime(); long endTime = now + shutdownTimeoutMS; for(ExecutorService worker : workers) { boolean terminated = false; - while ((now = System.currentTimeMillis()) <= endTime) { + while ((now = Time.currentElapsedTime()) <= endTime) { try { terminated = worker.awaitTermination( endTime - now, TimeUnit.MILLISECONDS); diff --git a/src/java/main/org/apache/zookeeper/server/ZKDatabase.java b/src/java/main/org/apache/zookeeper/server/ZKDatabase.java index f336049f0af..7b00715ac0c 100644 --- a/src/java/main/org/apache/zookeeper/server/ZKDatabase.java +++ b/src/java/main/org/apache/zookeeper/server/ZKDatabase.java @@ -18,7 +18,7 @@ package org.apache.zookeeper.server; -import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; @@ -32,7 +32,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; -import org.apache.jute.BinaryOutputArchive; import org.apache.jute.InputArchive; import org.apache.jute.OutputArchive; import org.apache.jute.Record; @@ -41,6 +40,7 @@ import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.WatcherType; import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.DataTree.ProcessTxnResult; @@ -74,13 +74,14 @@ public class ZKDatabase { protected ConcurrentHashMap sessionsWithTimeouts; protected FileTxnSnapLog snapLog; protected long minCommittedLog, maxCommittedLog; - + /** * Default value is to use snapshot if txnlog size exceeds 1/3 the size of snapshot */ public static final String SNAPSHOT_SIZE_FACTOR = "zookeeper.snapshotSizeFactor"; - private double snapshotSizeFactor = 0.33; - + public static final double DEFAULT_SNAPSHOT_SIZE_FACTOR = 0.33; + private double snapshotSizeFactor; + public static final int commitLogCount = 500; protected static int commitLogBuffer = 700; protected LinkedList committedLog = new LinkedList(); @@ -97,6 +98,23 @@ public ZKDatabase(FileTxnSnapLog snapLog) { dataTree = new DataTree(); sessionsWithTimeouts = new ConcurrentHashMap(); this.snapLog = snapLog; + + try { + snapshotSizeFactor = Double.parseDouble( + System.getProperty(SNAPSHOT_SIZE_FACTOR, + Double.toString(DEFAULT_SNAPSHOT_SIZE_FACTOR))); + if (snapshotSizeFactor > 1) { + snapshotSizeFactor = DEFAULT_SNAPSHOT_SIZE_FACTOR; + LOG.warn("The configured {} is invalid, going to use " + + "the default {}", SNAPSHOT_SIZE_FACTOR, + DEFAULT_SNAPSHOT_SIZE_FACTOR); + } + } catch (NumberFormatException e) { + LOG.error("Error parsing {}, using default value {}", + SNAPSHOT_SIZE_FACTOR, DEFAULT_SNAPSHOT_SIZE_FACTOR); + snapshotSizeFactor = DEFAULT_SNAPSHOT_SIZE_FACTOR; + } + LOG.info("{} = {}", SNAPSHOT_SIZE_FACTOR, snapshotSizeFactor); } /** @@ -206,6 +224,11 @@ public ConcurrentHashMap getSessionWithTimeOuts() { return sessionsWithTimeouts; } + private final PlayBackListener commitProposalPlaybackListener = new PlayBackListener() { + public void onTxnLoaded(TxnHeader hdr, Record txn){ + addCommittedProposal(hdr, txn); + } + }; /** * load the database from the disk onto memory and also add @@ -214,18 +237,27 @@ public ConcurrentHashMap getSessionWithTimeOuts() { * @throws IOException */ public long loadDataBase() throws IOException { - PlayBackListener listener=new PlayBackListener(){ - public void onTxnLoaded(TxnHeader hdr,Record txn){ - Request r = new Request(0, hdr.getCxid(),hdr.getType(), hdr, txn, hdr.getZxid()); - addCommittedProposal(r); - } - }; + long zxid = snapLog.restore(dataTree, sessionsWithTimeouts, commitProposalPlaybackListener); + initialized = true; + return zxid; + } - long zxid = snapLog.restore(dataTree,sessionsWithTimeouts,listener); + /** + * Fast forward the database adding transactions from the committed log into memory. + * @return the last valid zxid. + * @throws IOException + */ + public long fastForwardDataBase() throws IOException { + long zxid = snapLog.fastForwardFromEdits(dataTree, sessionsWithTimeouts, commitProposalPlaybackListener); initialized = true; return zxid; } + private void addCommittedProposal(TxnHeader hdr, Record txn) { + Request r = new Request(0, hdr.getCxid(), hdr.getType(), hdr, txn, hdr.getZxid()); + addCommittedProposal(r); + } + /** * maintains a list of last committedLog * or so committed requests. This is used for @@ -245,19 +277,8 @@ public void addCommittedProposal(Request request) { maxCommittedLog = request.zxid; } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos); - try { - request.getHdr().serialize(boa, "hdr"); - if (request.getTxn() != null) { - request.getTxn().serialize(boa, "txn"); - } - baos.close(); - } catch (IOException e) { - LOG.error("This really should be impossible", e); - } - QuorumPacket pp = new QuorumPacket(Leader.PROPOSAL, request.zxid, - baos.toByteArray(), null); + byte[] data = SerializeUtils.serializeRequest(request); + QuorumPacket pp = new QuorumPacket(Leader.PROPOSAL, request.zxid, data, null); Proposal p = new Proposal(); p.packet = pp; p.request = request; @@ -267,15 +288,25 @@ public void addCommittedProposal(Request request) { wl.unlock(); } } - - public double getSnapshotSizeFactor() { - return snapshotSizeFactor; + + public boolean isTxnLogSyncEnabled() { + boolean enabled = snapshotSizeFactor >= 0; + if (enabled) { + LOG.info("On disk txn sync enabled with snapshotSizeFactor " + + snapshotSizeFactor); + } else { + LOG.info("On disk txn sync disabled"); + } + return enabled; } public long calculateTxnLogSizeLimit() { long snapSize = 0; try { - snapSize = snapLog.findMostRecentSnapshot().length(); + File snapFile = snapLog.findMostRecentSnapshot(); + if (snapFile != null) { + snapSize = snapFile.length(); + } } catch (IOException e) { LOG.error("Unable to get size of most recent snapshot"); } @@ -334,7 +365,10 @@ public Iterator getProposalsFromTxnLog(long startZxid, } return new TxnLogProposalIterator(itr); } - + + public List aclForNode(DataNode n) { + return dataTree.getACL(n); + } /** * remove a cnxn from the datatree * @param cnxn the cnxn to remove from the datatree @@ -420,15 +454,6 @@ public DataNode getNode(String path) { return dataTree.getNode(path); } - /** - * convert from long to the acl entry - * @param aclL the long for which to get the acl - * @return the acl corresponding to this long entry - */ - public List convertLong(Long aclL) { - return dataTree.convertLong(aclL); - } - /** * get data and stat for a path * @param path the path being queried @@ -493,7 +518,7 @@ public boolean isSpecialPath(String path) { * @return the acl size of the datatree */ public int getAclSize() { - return dataTree.getAclSize(); + return dataTree.aclCacheSize(); } /** @@ -570,20 +595,20 @@ public void close() throws IOException { this.snapLog.close(); } - public synchronized void initConfigInZKDatabase(QuorumVerifier qv) { - if (qv == null) return; // only happens during tests + public synchronized void initConfigInZKDatabase(QuorumVerifier qv) { + if (qv == null) return; // only happens during tests try { - if (this.dataTree.getNode(ZooDefs.CONFIG_NODE) == null) { - // should only happen during upgrade - LOG.warn("configuration znode missing (hould only happen during upgrade), creating the node"); - this.dataTree.addConfigNode(); - } - this.dataTree.setData(ZooDefs.CONFIG_NODE, qv.toString().getBytes(), -1, qv.getVersion(), System.currentTimeMillis()); + if (this.dataTree.getNode(ZooDefs.CONFIG_NODE) == null) { + // should only happen during upgrade + LOG.warn("configuration znode missing (should only happen during upgrade), creating the node"); + this.dataTree.addConfigNode(); + } + this.dataTree.setData(ZooDefs.CONFIG_NODE, qv.toString().getBytes(), -1, qv.getVersion(), Time.currentWallTime()); } catch (NoNodeException e) { - System.out.println("configuration node missing - should not happen"); + System.out.println("configuration node missing - should not happen"); } } - + /** * Use for unit testing, so we can turn this feature on/off * @param snapshotSizeFactor Set to minus value to turn this off. @@ -608,7 +633,7 @@ public boolean containsWatcher(String path, WatcherType type, Watcher watcher) { /** * Remove watch from the datatree - * + * * @param path * node to remove watches from * @param type diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperCriticalThread.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperCriticalThread.java index 138d22ea8e1..4fefbd38250 100644 --- a/src/java/main/org/apache/zookeeper/server/ZooKeeperCriticalThread.java +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperCriticalThread.java @@ -27,24 +27,25 @@ public class ZooKeeperCriticalThread extends ZooKeeperThread { private static final Logger LOG = LoggerFactory .getLogger(ZooKeeperCriticalThread.class); - private static final int DEFAULT_EXIT_CODE = 1; + private final ZooKeeperServerListener listener; - public ZooKeeperCriticalThread(String threadName) { + public ZooKeeperCriticalThread(String threadName, ZooKeeperServerListener listener) { super(threadName); + this.listener = listener; } /** * This will be used by the uncaught exception handler and make the system * exit. - * - * @param thName + * + * @param threadName * - thread name * @param e * - exception object */ @Override - protected void handleException(String thName, Throwable e) { - LOG.error("Severe unrecoverable error, from thread : {}", thName, e); - System.exit(DEFAULT_EXIT_CODE); + protected void handleException(String threadName, Throwable e) { + LOG.error("Severe unrecoverable error, from thread : {}", threadName, e); + listener.notifyStopping(threadName, ExitCode.UNEXPECTED_ERROR); } } diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperSaslServer.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperSaslServer.java index 60711eec025..af19e5d2b2c 100644 --- a/src/java/main/org/apache/zookeeper/server/ZooKeeperSaslServer.java +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperSaslServer.java @@ -18,22 +18,12 @@ package org.apache.zookeeper.server; -import java.security.Principal; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; - import javax.security.auth.Subject; -import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; import org.apache.zookeeper.Login; -import org.ietf.jgss.GSSContext; -import org.ietf.jgss.GSSCredential; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.GSSName; -import org.ietf.jgss.Oid; +import org.apache.zookeeper.util.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,107 +41,9 @@ public class ZooKeeperSaslServer { private SaslServer createSaslServer(final Login login) { synchronized (login) { Subject subject = login.getSubject(); - if (subject != null) { - // server is using a JAAS-authenticated subject: determine service principal name and hostname from zk server's subject. - if (subject.getPrincipals().size() > 0) { - try { - final Object[] principals = subject.getPrincipals().toArray(); - final Principal servicePrincipal = (Principal)principals[0]; - - // e.g. servicePrincipalNameAndHostname := "zookeeper/myhost.foo.com@FOO.COM" - final String servicePrincipalNameAndHostname = servicePrincipal.getName(); - - int indexOf = servicePrincipalNameAndHostname.indexOf("/"); - - // e.g. servicePrincipalName := "zookeeper" - final String servicePrincipalName = servicePrincipalNameAndHostname.substring(0, indexOf); - - // e.g. serviceHostnameAndKerbDomain := "myhost.foo.com@FOO.COM" - final String serviceHostnameAndKerbDomain = servicePrincipalNameAndHostname.substring(indexOf+1,servicePrincipalNameAndHostname.length()); - - indexOf = serviceHostnameAndKerbDomain.indexOf("@"); - // e.g. serviceHostname := "myhost.foo.com" - final String serviceHostname = serviceHostnameAndKerbDomain.substring(0,indexOf); - - final String mech = "GSSAPI"; // TODO: should depend on zoo.cfg specified mechs, but if subject is non-null, it can be assumed to be GSSAPI. - - LOG.debug("serviceHostname is '"+ serviceHostname + "'"); - LOG.debug("servicePrincipalName is '"+ servicePrincipalName + "'"); - LOG.debug("SASL mechanism(mech) is '"+ mech +"'"); - - boolean usingNativeJgss = - Boolean.getBoolean("sun.security.jgss.native"); - if (usingNativeJgss) { - // http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html - // """ - // In addition, when performing operations as a particular - // Subject, e.g. Subject.doAs(...) or - // Subject.doAsPrivileged(...), the to-be-used - // GSSCredential should be added to Subject's - // private credential set. Otherwise, the GSS operations - // will fail since no credential is found. - // """ - try { - GSSManager manager = GSSManager.getInstance(); - Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2"); - GSSName gssName = manager.createName( - servicePrincipalName + "@" + serviceHostname, - GSSName.NT_HOSTBASED_SERVICE); - GSSCredential cred = manager.createCredential(gssName, - GSSContext.DEFAULT_LIFETIME, - krb5Mechanism, - GSSCredential.ACCEPT_ONLY); - subject.getPrivateCredentials().add(cred); - if (LOG.isDebugEnabled()) { - LOG.debug("Added private credential to subject: " + cred); - } - } catch (GSSException ex) { - LOG.warn("Cannot add private credential to subject; " + - "clients authentication may fail", ex); - } - } - try { - return Subject.doAs(subject,new PrivilegedExceptionAction() { - public SaslServer run() { - try { - SaslServer saslServer; - saslServer = Sasl.createSaslServer(mech, servicePrincipalName, serviceHostname, null, login.callbackHandler); - return saslServer; - } - catch (SaslException e) { - LOG.error("Zookeeper Server failed to create a SaslServer to interact with a client during session initiation: " + e); - e.printStackTrace(); - return null; - } - } - } - ); - } - catch (PrivilegedActionException e) { - // TODO: exit server at this point(?) - LOG.error("Zookeeper Quorum member experienced a PrivilegedActionException exception while creating a SaslServer using a JAAS principal context:" + e); - e.printStackTrace(); - } - } - catch (IndexOutOfBoundsException e) { - LOG.error("server principal name/hostname determination error: ", e); - } - } - else { - // JAAS non-GSSAPI authentication: assuming and supporting only DIGEST-MD5 mechanism for now. - // TODO: use 'authMech=' value in zoo.cfg. - try { - SaslServer saslServer = Sasl.createSaslServer("DIGEST-MD5","zookeeper","zk-sasl-md5",null, login.callbackHandler); - return saslServer; - } - catch (SaslException e) { - LOG.error("Zookeeper Quorum member failed to create a SaslServer to interact with a client during session initiation", e); - } - } - } + return SecurityUtils.createSaslServer(subject, "zookeeper", + "zk-sasl-md5", login.callbackHandler, LOG); } - LOG.error("failed to create saslServer object."); - return null; } public byte[] evaluateResponse(byte[] response) throws SaslException { diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperServer.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperServer.java index 30a0ed390bb..a21140dcdff 100644 --- a/src/java/main/org/apache/zookeeper/server/ZooKeeperServer.java +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperServer.java @@ -27,7 +27,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -101,7 +100,11 @@ public class ZooKeeperServer implements SessionExpirer, ServerStats.Provider { private final AtomicLong hzxid = new AtomicLong(0); public final static Exception ok = new Exception("No prob"); protected RequestProcessor firstProcessor; - protected volatile boolean running; + protected volatile State state = State.INITIAL; + + protected enum State { + INITIAL, RUNNING, SHUTDOWN, ERROR + } /** * This is the secret that we use to generate passwords, for the moment it @@ -115,9 +118,13 @@ public class ZooKeeperServer implements SessionExpirer, ServerStats.Provider { final HashMap outstandingChangesForPath = new HashMap(); - private ServerCnxnFactory serverCnxnFactory; + protected ServerCnxnFactory serverCnxnFactory; + protected ServerCnxnFactory secureServerCnxnFactory; private final ServerStats serverStats; + private final ZooKeeperServerListener listener; + private ZooKeeperServerShutdownHandler zkShutdownHandler; + private volatile int createSessionTrackerServerId = 1; void removeCnxn(ServerCnxn cnxn) { zkDb.removeCnxn(cnxn); @@ -132,6 +139,7 @@ void removeCnxn(ServerCnxn cnxn) { */ public ZooKeeperServer() { serverStats = new ServerStats(this); + listener = new ZooKeeperServerListenerImpl(this); } /** @@ -148,7 +156,7 @@ public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime, this.tickTime = tickTime; setMinSessionTimeout(minSessionTimeout); setMaxSessionTimeout(maxSessionTimeout); - + listener = new ZooKeeperServerListenerImpl(this); LOG.info("Created server with tickTime " + tickTime + " minSessionTimeout " + getMinSessionTimeout() + " maxSessionTimeout " + getMaxSessionTimeout() @@ -160,10 +168,10 @@ public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime, * creates a zookeeperserver instance. * @param txnLogFactory the file transaction snapshot logging class * @param tickTime the ticktime for the server - * @param treeBuilder the datatree builder * @throws IOException */ - public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime) throws IOException { + public ZooKeeperServer(FileTxnSnapLog txnLogFactory, int tickTime) + throws IOException { this(txnLogFactory, tickTime, -1, -1, new ZKDatabase(txnLogFactory)); } @@ -174,14 +182,20 @@ public ServerStats serverStats() { public void dumpConf(PrintWriter pwriter) { pwriter.print("clientPort="); pwriter.println(getClientPort()); + pwriter.print("secureClientPort="); + pwriter.println(getSecureClientPort()); pwriter.print("dataDir="); pwriter.println(zkDb.snapLog.getSnapDir().getAbsolutePath()); + pwriter.print("dataDirSize="); + pwriter.println(getDataDirSize()); pwriter.print("dataLogDir="); pwriter.println(zkDb.snapLog.getDataDir().getAbsolutePath()); + pwriter.print("dataLogSize="); + pwriter.println(getLogDirSize()); pwriter.print("tickTime="); pwriter.println(getTickTime()); pwriter.print("maxClientCnxns="); - pwriter.println(serverCnxnFactory.getMaxClientCnxnsPerHost()); + pwriter.println(getMaxClientCnxnsPerHost()); pwriter.print("minSessionTimeout="); pwriter.println(getMinSessionTimeout()); pwriter.print("maxSessionTimeout="); @@ -215,7 +229,7 @@ public ZooKeeperServer(File snapDir, File logDir, int tickTime) } /** - * Default constructor, relies on the config for its agrument values + * Default constructor, relies on the config for its argument values * * @throws IOException */ @@ -298,6 +312,39 @@ public void takeSnapshot(){ } } + @Override + public long getDataDirSize() { + if (zkDb == null) { + return 0L; + } + File path = zkDb.snapLog.getDataDir(); + return getDirSize(path); + } + + @Override + public long getLogDirSize() { + if (zkDb == null) { + return 0L; + } + File path = zkDb.snapLog.getSnapDir(); + return getDirSize(path); + } + + private long getDirSize(File file) { + long size = 0L; + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) { + size += getDirSize(f); + } + } + } else { + size = file.length(); + } + return size; + } + public long getZxid() { return hzxid.get(); } @@ -314,10 +361,6 @@ public void setZxid(long zxid) { hzxid.set(zxid); } - long getTime() { - return System.currentTimeMillis(); - } - private void close(long sessionId) { Request si = new Request(null, sessionId, 0, OpCode.closeSession, null, null); setLocalSessionFlag(si); @@ -402,7 +445,7 @@ public void startdata() } } - public void startup() { + public synchronized void startup() { if (sessionTracker == null) { createSessionTracker(); } @@ -411,10 +454,8 @@ public void startup() { registerJMX(); - synchronized (this) { - running = true; - notifyAll(); - } + setState(State.RUNNING); + notifyAll(); } protected void setupRequestProcessors() { @@ -426,24 +467,92 @@ protected void setupRequestProcessors() { ((PrepRequestProcessor)firstProcessor).start(); } + public ZooKeeperServerListener getZooKeeperServerListener() { + return listener; + } + + /** + * Change the server ID used by {@link #createSessionTracker()}. Must be called prior to + * {@link #startup()} being called + * + * @param newId ID to use + */ + public void setCreateSessionTrackerServerId(int newId) { + createSessionTrackerServerId = newId; + } + protected void createSessionTracker() { sessionTracker = new SessionTrackerImpl(this, zkDb.getSessionWithTimeOuts(), - tickTime, 1); + tickTime, createSessionTrackerServerId, getZooKeeperServerListener()); } protected void startSessionTracker() { ((SessionTrackerImpl)sessionTracker).start(); } + /** + * Sets the state of ZooKeeper server. After changing the state, it notifies + * the server state change to a registered shutdown handler, if any. + *

    + * The following are the server state transitions: + *

  • During startup the server will be in the INITIAL state.
  • + *
  • After successfully starting, the server sets the state to RUNNING. + *
  • + *
  • The server transitions to the ERROR state if it hits an internal + * error. {@link ZooKeeperServerListenerImpl} notifies any critical resource + * error events, e.g., SyncRequestProcessor not being able to write a txn to + * disk.
  • + *
  • During shutdown the server sets the state to SHUTDOWN, which + * corresponds to the server not running.
  • + * + * @param state new server state. + */ + protected void setState(State state) { + this.state = state; + // Notify server state changes to the registered shutdown handler, if any. + if (zkShutdownHandler != null) { + zkShutdownHandler.handle(state); + } else { + LOG.debug("ZKShutdownHandler is not registered, so ZooKeeper server " + + "won't take any action on ERROR or SHUTDOWN server state changes"); + } + } + + /** + * This can be used while shutting down the server to see whether the server + * is already shutdown or not. + * + * @return true if the server is running or server hits an error, false + * otherwise. + */ + protected boolean canShutdown() { + return state == State.RUNNING || state == State.ERROR; + } + + /** + * @return true if the server is running, false otherwise. + */ public boolean isRunning() { - return running; + return state == State.RUNNING; } public void shutdown() { + shutdown(false); + } + + /** + * Shut down the server instance + * @param fullyShutDown true if another server using the same database will not replace this one in the same process + */ + public synchronized void shutdown(boolean fullyShutDown) { + if (!canShutdown()) { + LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!"); + return; + } LOG.info("shutting down"); // new RuntimeException("Calling shutdown").printStackTrace(); - this.running = false; + setState(State.SHUTDOWN); // Since sessionTracker and syncThreads poll we just have to // set running to false and they will detect it during the poll // interval. @@ -453,8 +562,24 @@ public void shutdown() { if (firstProcessor != null) { firstProcessor.shutdown(); } + if (zkDb != null) { - zkDb.clear(); + if (fullyShutDown) { + zkDb.clear(); + } else { + // else there is no need to clear the database + // * When a new quorum is established we can still apply the diff + // on top of the same zkDb data + // * If we fetch a new snapshot from leader, the zkDb will be + // cleared anyway before loading the snapshot + try { + //This will fast forward the database to the latest recorded transactions + zkDb.fastForwardDataBase(); + } catch (IOException e) { + LOG.error("Error updating DB", e); + zkDb.clear(); + } + } } unregisterJMX(); @@ -569,9 +694,9 @@ protected void revalidateSession(ServerCnxn cnxn, long sessionId, int sessionTimeout) throws IOException { boolean rc = sessionTracker.touchSession(sessionId, sessionTimeout); if (LOG.isTraceEnabled()) { - ZooTrace.logTraceMessage(LOG,ZooTrace.SESSION_TRACE_MASK, - "Session 0x" + Long.toHexString(sessionId) + - " is valid: " + rc); + ZooTrace.logTraceMessage(LOG, ZooTrace.SESSION_TRACE_MASK, + "Session 0x" + Long.toHexString(sessionId) + + " is valid: " + rc); } finishSessionInit(cnxn, rc); } @@ -591,10 +716,14 @@ public void finishSessionInit(ServerCnxn cnxn, boolean valid) { // register with JMX try { if (valid) { - serverCnxnFactory.registerConnection(cnxn); + if (serverCnxnFactory != null && serverCnxnFactory.cnxns.contains(cnxn)) { + serverCnxnFactory.registerConnection(cnxn); + } else if (secureServerCnxnFactory != null && secureServerCnxnFactory.cnxns.contains(cnxn)) { + secureServerCnxnFactory.registerConnection(cnxn); + } } } catch (Exception e) { - LOG.warn("Failed to register with JMX", e); + LOG.warn("Failed to register with JMX", e); } try { @@ -660,13 +789,17 @@ public void submitRequest(Request si) { if (firstProcessor == null) { synchronized (this) { try { - while (!running) { + // Since all requests are passed to the request + // processor it should wait for setting up the request + // processor chain. The state will be updated to RUNNING + // after the setup. + while (state == State.INITIAL) { wait(1000); } } catch (InterruptedException e) { LOG.warn("Unexpected interruption", e); } - if (firstProcessor == null) { + if (firstProcessor == null || state != State.RUNNING) { throw new RuntimeException("Not started"); } } @@ -727,6 +860,10 @@ public ServerCnxnFactory getServerCnxnFactory() { return serverCnxnFactory; } + public void setSecureServerCnxnFactory(ServerCnxnFactory factory) { + secureServerCnxnFactory = factory; + } + /** * return the last proceesed id from the * datatree @@ -749,7 +886,17 @@ public long getOutstandingRequests() { * to this server */ public int getNumAliveConnections() { - return serverCnxnFactory.getNumAliveConnections(); + int numAliveConnections = 0; + + if (serverCnxnFactory != null) { + numAliveConnections += serverCnxnFactory.getNumAliveConnections(); + } + + if (secureServerCnxnFactory != null) { + numAliveConnections += secureServerCnxnFactory.getNumAliveConnections(); + } + + return numAliveConnections; } /** @@ -794,6 +941,21 @@ public int getClientPort() { return serverCnxnFactory != null ? serverCnxnFactory.getLocalPort() : -1; } + public int getSecureClientPort() { + return secureServerCnxnFactory != null ? secureServerCnxnFactory.getLocalPort() : -1; + } + + /** Maximum number of connections allowed from particular host (ip) */ + public int getMaxClientCnxnsPerHost() { + if (serverCnxnFactory != null) { + return serverCnxnFactory.getMaxClientCnxnsPerHost(); + } + if (secureServerCnxnFactory != null) { + return secureServerCnxnFactory.getMaxClientCnxnsPerHost(); + } + return -1; + } + public void setTxnLogFactory(FileTxnSnapLog txnLog) { this.txnLogFactory = txnLog; } @@ -802,6 +964,13 @@ public FileTxnSnapLog getTxnLogFactory() { return this.txnLogFactory; } + /** + * Returns the elapsed sync of time of transaction log in milliseconds. + */ + public long getTxnLogElapsedSyncTime() { + return txnLogFactory.getTxnLogElapsedSyncTime(); + } + public String getState() { return "standalone"; } @@ -877,7 +1046,12 @@ public void processConnectRequest(ServerCnxn cnxn, ByteBuffer incomingBuffer) th LOG.info("Client attempting to renew session 0x" + Long.toHexString(clientSessionId) + " at " + cnxn.getRemoteSocketAddress()); - serverCnxnFactory.closeSession(sessionId); + if (serverCnxnFactory != null) { + serverCnxnFactory.closeSession(sessionId); + } + if (secureServerCnxnFactory != null) { + secureServerCnxnFactory.closeSession(sessionId); + } cnxn.setSessionId(sessionId); reopenSession(cnxn, sessionId, passwd, sessionTimeout); } @@ -940,23 +1114,22 @@ public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOE cnxn.disableRecv(); } return; + } else if (h.getType() == OpCode.sasl) { + Record rsp = processSasl(incomingBuffer,cnxn); + ReplyHeader rh = new ReplyHeader(h.getXid(), 0, KeeperException.Code.OK.intValue()); + cnxn.sendResponse(rh,rsp, "response"); // not sure about 3rd arg..what is it? + return; } else { - if (h.getType() == OpCode.sasl) { - Record rsp = processSasl(incomingBuffer,cnxn); - ReplyHeader rh = new ReplyHeader(h.getXid(), 0, KeeperException.Code.OK.intValue()); - cnxn.sendResponse(rh,rsp, "response"); // not sure about 3rd arg..what is it? - } - else { - Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(), - h.getType(), incomingBuffer, cnxn.getAuthInfo()); - si.setOwner(ServerCnxn.me); - // Always treat packet from the client as a possible - // local request. - setLocalSessionFlag(si); - submitRequest(si); - } + cnxn.incrOutstandingRequests(h); + Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(), + h.getType(), incomingBuffer, cnxn.getAuthInfo()); + si.setOwner(ServerCnxn.me); + // Always treat packet from the client as a possible + // local request. + setLocalSessionFlag(si); + submitRequest(si); + return; } - cnxn.incrOutstandingRequests(h); } private Record processSasl(ByteBuffer incomingBuffer, ServerCnxn cnxn) throws IOException { @@ -977,10 +1150,14 @@ private Record processSasl(ByteBuffer incomingBuffer, ServerCnxn cnxn) throws IO String authorizationID = saslServer.getAuthorizationID(); LOG.info("adding SASL authorization for authorizationID: " + authorizationID); cnxn.addAuthInfo(new Id("sasl",authorizationID)); + if (System.getProperty("zookeeper.superUser") != null && + authorizationID.equals(System.getProperty("zookeeper.superUser"))) { + cnxn.addAuthInfo(new Id("super", "")); + } } } catch (SaslException e) { - LOG.warn("Client failed to SASL authenticate: " + e); + LOG.warn("Client failed to SASL authenticate: " + e, e); if ((System.getProperty("zookeeper.allowSaslFailedClients") != null) && (System.getProperty("zookeeper.allowSaslFailedClients").equals("true"))) { @@ -1045,4 +1222,15 @@ public Map> getSessionExpiryMap() { return sessionTracker.getSessionExpiryMap(); } + /** + * This method is used to register the ZooKeeperServerShutdownHandler to get + * server's error or shutdown state change notifications. + * {@link ZooKeeperServerShutdownHandler#handle(State)} will be called for + * every server state changes {@link #setState(State)}. + * + * @param zkShutdownHandler shutdown handler + */ + void registerServerShutdownHandler(ZooKeeperServerShutdownHandler zkShutdownHandler) { + this.zkShutdownHandler = zkShutdownHandler; + } } diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerBean.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerBean.java index 0eb5c7f9791..3674066194e 100644 --- a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerBean.java +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerBean.java @@ -18,10 +18,9 @@ package org.apache.zookeeper.server; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.Date; +import org.apache.jute.BinaryInputArchive; import org.apache.zookeeper.Version; import org.apache.zookeeper.jmx.ZKMBeanInfo; @@ -41,12 +40,7 @@ public ZooKeeperServerBean(ZooKeeperServer zks) { } public String getClientPort() { - try { - return InetAddress.getLocalHost().getHostAddress() + ":" - + zks.getClientPort(); - } catch (UnknownHostException e) { - return "localhost:" + zks.getClientPort(); - } + return Integer.toString(zks.getClientPort()); } public String getName() { @@ -90,16 +84,16 @@ public void setTickTime(int tickTime) { } public int getMaxClientCnxnsPerHost() { - ServerCnxnFactory fac = zks.getServerCnxnFactory(); - if (fac == null) { - return -1; - } - return fac.getMaxClientCnxnsPerHost(); + return zks.getMaxClientCnxnsPerHost(); } public void setMaxClientCnxnsPerHost(int max) { - // if fac is null the exception will be propagated to the client - zks.getServerCnxnFactory().setMaxClientCnxnsPerHost(max); + if (zks.serverCnxnFactory != null) { + zks.serverCnxnFactory.setMaxClientCnxnsPerHost(max); + } + if (zks.secureServerCnxnFactory != null) { + zks.secureServerCnxnFactory.setMaxClientCnxnsPerHost(max); + } } public int getMinSessionTimeout() { @@ -118,6 +112,13 @@ public void setMaxSessionTimeout(int max) { zks.setMaxSessionTimeout(max); } + public long getDataDirSize() { + return zks.getDataDirSize(); + } + + public long getLogDirSize() { + return zks.getLogDirSize(); + } public long getPacketsReceived() { return zks.serverStats().getPacketsReceived(); @@ -144,4 +145,32 @@ public void resetStatistics() { public long getNumAliveConnections() { return zks.getNumAliveConnections(); } + + @Override + public String getSecureClientPort() { + if (zks.secureServerCnxnFactory != null) { + return Integer.toString(zks.secureServerCnxnFactory.getLocalPort()); + } + return ""; + } + + @Override + public String getSecureClientAddress() { + if (zks.secureServerCnxnFactory != null) { + return String.format("%s:%d", zks.secureServerCnxnFactory + .getLocalAddress().getHostString(), + zks.secureServerCnxnFactory.getLocalPort()); + } + return ""; + } + + @Override + public long getTxnLogElapsedSyncTime() { + return zks.getTxnLogElapsedSyncTime(); + } + + @Override + public int getJuteMaxBufferSize() { + return BinaryInputArchive.maxBuffer; + } } diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerListener.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerListener.java new file mode 100644 index 00000000000..f19bfd948d8 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerListener.java @@ -0,0 +1,35 @@ +/** + * 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; + +/** + * Listener for the critical resource events. + */ +public interface ZooKeeperServerListener { + + /** + * This will notify the server that some critical thread has stopped. + * It usually takes place when fatal error occurred. + * + * @param threadName + * - name of the thread + * @param errorCode + * - error code + */ + void notifyStopping(String threadName, int errorCode); +} diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerListenerImpl.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerListenerImpl.java new file mode 100644 index 00000000000..08f493405c7 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerListenerImpl.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 org.apache.zookeeper.server.ZooKeeperServer.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default listener implementation, which will be used to notify internal + * errors. For example, if some critical thread has stopped due to fatal errors, + * then it will get notifications and will change the state of ZooKeeper server + * to ERROR representing an error status. + */ +class ZooKeeperServerListenerImpl implements ZooKeeperServerListener { + private static final Logger LOG = LoggerFactory + .getLogger(ZooKeeperServerListenerImpl.class); + + private final ZooKeeperServer zkServer; + + ZooKeeperServerListenerImpl(ZooKeeperServer zkServer) { + this.zkServer = zkServer; + } + + @Override + public void notifyStopping(String threadName, int exitCode) { + LOG.info("Thread {} exits, error code {}", threadName, exitCode); + zkServer.setState(State.ERROR); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMXBean.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMXBean.java index 127ead8a033..a5921319281 100644 --- a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMXBean.java +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMXBean.java @@ -107,4 +107,32 @@ public interface ZooKeeperServerMXBean { * @return number of alive client connections */ public long getNumAliveConnections(); + + /** + * @return estimated size of data directory in bytes + */ + public long getDataDirSize(); + /** + * @return estimated size of log directory in bytes + */ + public long getLogDirSize(); + + /** + * @return secure client port + */ + public String getSecureClientPort(); + /** + * @return secure client address + */ + public String getSecureClientAddress(); + + /** + * Returns the elapsed sync of time of transaction log in milliseconds. + */ + public long getTxnLogElapsedSyncTime(); + + /** + * @return Returns the value of the following config setting: jute.maxbuffer + */ + public int getJuteMaxBufferSize(); } diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java index b756d349abe..f92b189cd39 100644 --- a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java @@ -19,9 +19,12 @@ package org.apache.zookeeper.server; import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import javax.management.JMException; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.jmx.ManagedUtil; import org.apache.zookeeper.server.admin.AdminServer; import org.apache.zookeeper.server.admin.AdminServer.AdminServerException; @@ -35,6 +38,7 @@ /** * This class starts and runs a standalone ZooKeeperServer. */ +@InterfaceAudience.Public public class ZooKeeperServerMain { private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperServerMain.class); @@ -42,7 +46,10 @@ public class ZooKeeperServerMain { private static final String USAGE = "Usage: ZooKeeperServerMain configfile | port datadir [ticktime] [maxcnxns]"; + // ZooKeeper server supports two kinds of connection: unencrypted and encrypted. private ServerCnxnFactory cnxnFactory; + private ServerCnxnFactory secureCnxnFactory; + private ContainerManager containerManager; private AdminServer adminServer; @@ -105,7 +112,8 @@ protected void initializeAndRun(String[] args) * @throws IOException * @throws AdminServerException */ - public void runFromConfig(ServerConfig config) throws IOException, AdminServerException { + public void runFromConfig(ServerConfig config) + throws IOException, AdminServerException { LOG.info("Starting server"); FileTxnSnapLog txnLog = null; try { @@ -114,21 +122,54 @@ public void runFromConfig(ServerConfig config) throws IOException, AdminServerEx // run() in this thread. // create a file logger url from the command line args txnLog = new FileTxnSnapLog(config.dataLogDir, config.dataDir); - ZooKeeperServer zkServer = new ZooKeeperServer( txnLog, + final ZooKeeperServer zkServer = new ZooKeeperServer(txnLog, config.tickTime, config.minSessionTimeout, config.maxSessionTimeout, null); + // Registers shutdown handler which will be used to know the + // server error or shutdown state changes. + final CountDownLatch shutdownLatch = new CountDownLatch(1); + zkServer.registerServerShutdownHandler( + new ZooKeeperServerShutdownHandler(shutdownLatch)); + // Start Admin server adminServer = AdminServerFactory.createAdminServer(); adminServer.setZooKeeperServer(zkServer); adminServer.start(); - cnxnFactory = ServerCnxnFactory.createFactory(); - cnxnFactory.configure(config.getClientPortAddress(), - config.getMaxClientCnxns()); - cnxnFactory.startup(zkServer); - cnxnFactory.join(); - if (zkServer.isRunning()) { - zkServer.shutdown(); + boolean needStartZKServer = true; + if (config.getClientPortAddress() != null) { + cnxnFactory = ServerCnxnFactory.createFactory(); + cnxnFactory.configure(config.getClientPortAddress(), config.getMaxClientCnxns(), false); + cnxnFactory.startup(zkServer); + // zkServer has been started. So we don't need to start it again in secureCnxnFactory. + needStartZKServer = false; + } + if (config.getSecureClientPortAddress() != null) { + secureCnxnFactory = ServerCnxnFactory.createFactory(); + secureCnxnFactory.configure(config.getSecureClientPortAddress(), config.getMaxClientCnxns(), true); + secureCnxnFactory.startup(zkServer, needStartZKServer); + } + + containerManager = new ContainerManager(zkServer.getZKDatabase(), zkServer.firstProcessor, + Integer.getInteger("znode.container.checkIntervalMs", (int) TimeUnit.MINUTES.toMillis(1)), + Integer.getInteger("znode.container.maxPerMinute", 10000) + ); + containerManager.start(); + + // Watch status of ZooKeeper server. It will do a graceful shutdown + // if the server is not running or hits an internal error. + shutdownLatch.await(); + + shutdown(); + + if (cnxnFactory != null) { + cnxnFactory.join(); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.join(); + } + if (zkServer.canShutdown()) { + zkServer.shutdown(true); } } catch (InterruptedException e) { // warn, but generally this is ok @@ -144,11 +185,26 @@ public void runFromConfig(ServerConfig config) throws IOException, AdminServerEx * Shutdown the serving instance */ protected void shutdown() { - cnxnFactory.shutdown(); + if (containerManager != null) { + containerManager.stop(); + } + if (cnxnFactory != null) { + cnxnFactory.shutdown(); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.shutdown(); + } try { - adminServer.shutdown(); + if (adminServer != null) { + adminServer.shutdown(); + } } catch (AdminServerException e) { LOG.warn("Problem stopping AdminServer", e); } } + + // VisibleForTesting + ServerCnxnFactory getCnxnFactory() { + return cnxnFactory; + } } diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerShutdownHandler.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerShutdownHandler.java new file mode 100644 index 00000000000..499cacb257b --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerShutdownHandler.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.server; + +import java.util.concurrent.CountDownLatch; + +import org.apache.zookeeper.server.ZooKeeperServer.State; + +/** + * ZooKeeper server shutdown handler which will be used to handle ERROR or + * SHUTDOWN server state transitions, which in turn releases the associated + * shutdown latch. + */ +class ZooKeeperServerShutdownHandler { + private final CountDownLatch shutdownLatch; + + ZooKeeperServerShutdownHandler(CountDownLatch shutdownLatch) { + this.shutdownLatch = shutdownLatch; + } + + /** + * This will be invoked when the server transition to a new server state. + * + * @param state new server state + */ + void handle(State state) { + if (state == State.ERROR || state == State.SHUTDOWN) { + shutdownLatch.countDown(); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperThread.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperThread.java index 5f11ebcc88a..189adaff76d 100644 --- a/src/java/main/org/apache/zookeeper/server/ZooKeeperThread.java +++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperThread.java @@ -52,6 +52,6 @@ public ZooKeeperThread(String threadName) { * - exception object */ protected void handleException(String thName, Throwable e) { - LOG.warn("Exception occured from thread {}", thName, e); + LOG.warn("Exception occurred from thread {}", thName, e); } } diff --git a/src/java/main/org/apache/zookeeper/server/ZooTrace.java b/src/java/main/org/apache/zookeeper/server/ZooTrace.java index 787ae1aac4b..12ce7d07bd0 100644 --- a/src/java/main/org/apache/zookeeper/server/ZooTrace.java +++ b/src/java/main/org/apache/zookeeper/server/ZooTrace.java @@ -20,7 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - +import org.apache.zookeeper.server.quorum.LearnerHandler; import org.apache.zookeeper.server.quorum.QuorumPacket; /** @@ -75,12 +75,10 @@ public static void logTraceMessage(Logger log, long mask, String msg) { static public void logQuorumPacket(Logger log, long mask, char direction, QuorumPacket qp) { - return; - - // if (isTraceEnabled(log, mask)) { - // logTraceMessage(LOG, mask, direction + " " - // + FollowerHandler.packetToString(qp)); - // } + if (isTraceEnabled(log, mask)) { + logTraceMessage(log, mask, direction + + " " + LearnerHandler.packetToString(qp)); + } } static public void logRequest(Logger log, long mask, diff --git a/src/java/main/org/apache/zookeeper/server/admin/AdminServer.java b/src/java/main/org/apache/zookeeper/server/admin/AdminServer.java index cb61f79c8f1..81410c44148 100644 --- a/src/java/main/org/apache/zookeeper/server/admin/AdminServer.java +++ b/src/java/main/org/apache/zookeeper/server/admin/AdminServer.java @@ -18,6 +18,7 @@ package org.apache.zookeeper.server.admin; +import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.server.ZooKeeperServer; /** @@ -25,12 +26,15 @@ * functional implementation, JettyAdminServer. DummyAdminServer, which does * nothing, is used when we do not wish to run a server. */ +@InterfaceAudience.Public public interface AdminServer { public void start() throws AdminServerException; public void shutdown() throws AdminServerException; public void setZooKeeperServer(ZooKeeperServer zkServer); + @InterfaceAudience.Public public class AdminServerException extends Exception { + private static final long serialVersionUID = 1L; public AdminServerException(String message, Throwable cause) { super(message, cause); } @@ -38,4 +42,4 @@ public AdminServerException(Throwable cause) { super(cause); } } -} +} \ No newline at end of file diff --git a/src/java/main/org/apache/zookeeper/server/admin/Commands.java b/src/java/main/org/apache/zookeeper/server/admin/Commands.java index ff16c2dd60e..4f96de3747c 100644 --- a/src/java/main/org/apache/zookeeper/server/admin/Commands.java +++ b/src/java/main/org/apache/zookeeper/server/admin/Commands.java @@ -86,7 +86,7 @@ public static CommandResponse runCommand(String cmdName, ZooKeeperServer zkServe if (!commands.containsKey(cmdName)) { return new CommandResponse(cmdName, "Unknown command: " + cmdName); } - if (zkServer == null) { + if (zkServer == null || !zkServer.isRunning()) { return new CommandResponse(cmdName, "This ZooKeeper instance is not currently serving requests"); } return commands.get(cmdName).run(zkServer, kwargs); @@ -111,6 +111,7 @@ public static Command getCommand(String cmdName) { registerCommand(new CnxnStatResetCommand()); registerCommand(new ConfCommand()); registerCommand(new ConsCommand()); + registerCommand(new DirsCommand()); registerCommand(new DumpCommand()); registerCommand(new EnvCommand()); registerCommand(new GetTraceMaskCommand()); @@ -178,6 +179,23 @@ public CommandResponse run(ZooKeeperServer zkServer, Map kwargs) } } + /** + * Information on ZK datadir and snapdir size in bytes + */ + public static class DirsCommand extends CommandBase { + public DirsCommand() { + super(Arrays.asList("dirs")); + } + + @Override + public CommandResponse run(ZooKeeperServer zkServer, Map kwargs) { + CommandResponse response = initializeResponse(); + response.put("datadir_size", zkServer.getDataDirSize()); + response.put("logdir_size", zkServer.getLogDirSize()); + return response; + } + } + /** * Information on session expirations and ephemerals. Returned map contains: * - "expiry_time_to_session_ids": Map> diff --git a/src/java/main/org/apache/zookeeper/server/admin/JettyAdminServer.java b/src/java/main/org/apache/zookeeper/server/admin/JettyAdminServer.java index 4691558b73f..ff3de41ce3d 100644 --- a/src/java/main/org/apache/zookeeper/server/admin/JettyAdminServer.java +++ b/src/java/main/org/apache/zookeeper/server/admin/JettyAdminServer.java @@ -31,9 +31,11 @@ import javax.servlet.http.HttpServletResponse; import org.apache.zookeeper.server.ZooKeeperServer; -import org.mortbay.jetty.Server; -import org.mortbay.jetty.servlet.Context; -import org.mortbay.jetty.servlet.ServletHolder; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,25 +56,41 @@ public class JettyAdminServer implements AdminServer { static final Logger LOG = LoggerFactory.getLogger(JettyAdminServer.class); public static final int DEFAULT_PORT = 8080; + public static final int DEFAULT_IDLE_TIMEOUT = 30000; public static final String DEFAULT_COMMAND_URL = "/commands"; + private static final String DEFAULT_ADDRESS = "0.0.0.0"; private final Server server; - private ZooKeeperServer zkServer; + private final String address; private final int port; + private final int idleTimeout; private final String commandUrl; + private ZooKeeperServer zkServer; public JettyAdminServer() throws AdminServerException { - this(Integer.getInteger("zookeeper.admin.serverPort", DEFAULT_PORT), + this(System.getProperty("zookeeper.admin.serverAddress", DEFAULT_ADDRESS), + Integer.getInteger("zookeeper.admin.serverPort", DEFAULT_PORT), + Integer.getInteger("zookeeper.admin.idleTimeout", DEFAULT_IDLE_TIMEOUT), System.getProperty("zookeeper.admin.commandURL", DEFAULT_COMMAND_URL)); } - public JettyAdminServer(int port, String commandUrl) { + public JettyAdminServer(String address, int port, int timeout, String commandUrl) { this.port = port; + this.idleTimeout = timeout; this.commandUrl = commandUrl; + this.address = address; - server = new Server(port); - Context context = new Context(server, "/"); + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setHost(address); + connector.setPort(port); + connector.setIdleTimeout(idleTimeout); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/*"); server.setHandler(context); + context.addServlet(new ServletHolder(new CommandServlet()), commandUrl + "/*"); } @@ -86,12 +104,13 @@ public void start() throws AdminServerException { } catch (Exception e) { // Server.start() only throws Exception, so let's at least wrap it // in an identifiable subclass - throw new AdminServerException( - String.format("Problem starting AdminServer on port %d, command URL %s", - port, commandUrl), e); + throw new AdminServerException(String.format( + "Problem starting AdminServer on address %s," + + " port %d and command URL %s", address, port, + commandUrl), e); } - LOG.info(String.format("Started AdminServer on port %d, command URL %s", - port, commandUrl)); + LOG.info(String.format("Started AdminServer on address %s, port %d" + + " and command URL %s", address, port, commandUrl)); } /** @@ -106,9 +125,10 @@ public void shutdown() throws AdminServerException { try { server.stop(); } catch (Exception e) { - throw new AdminServerException( - String.format("Problem stopping AdminServer on port %d, command URL %s", - port, commandUrl), e); + throw new AdminServerException(String.format( + "Problem stopping AdminServer on address %s," + + " port %d and command URL %s", address, port, commandUrl), + e); } } @@ -127,6 +147,8 @@ public void setZooKeeperServer(ZooKeeperServer zkServer) { } private class CommandServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Capture the command name from the URL String cmd = request.getPathInfo(); @@ -134,7 +156,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t // No command specified, print links to all commands instead for (String link : commandLinks()) { response.getWriter().println(link); - response.getWriter().println("
    "); + response.getWriter().println("
    "); } return; } diff --git a/src/java/main/org/apache/zookeeper/server/auth/ProviderRegistry.java b/src/java/main/org/apache/zookeeper/server/auth/ProviderRegistry.java index 406015f84a5..9bce71e54e3 100644 --- a/src/java/main/org/apache/zookeeper/server/auth/ProviderRegistry.java +++ b/src/java/main/org/apache/zookeeper/server/auth/ProviderRegistry.java @@ -49,7 +49,7 @@ public static void initialize() { try { Class c = ZooKeeperServer.class.getClassLoader() .loadClass(className); - AuthenticationProvider ap = (AuthenticationProvider) c + AuthenticationProvider ap = (AuthenticationProvider) c.getDeclaredConstructor() .newInstance(); authenticationProviders.put(ap.getScheme(), ap); } catch (Exception e) { diff --git a/src/java/main/org/apache/zookeeper/server/auth/SASLAuthenticationProvider.java b/src/java/main/org/apache/zookeeper/server/auth/SASLAuthenticationProvider.java index 4690e696b77..1f60b39e0a8 100644 --- a/src/java/main/org/apache/zookeeper/server/auth/SASLAuthenticationProvider.java +++ b/src/java/main/org/apache/zookeeper/server/auth/SASLAuthenticationProvider.java @@ -38,11 +38,6 @@ public String getScheme() { } public boolean matches(String id,String aclExpr) { - if (System.getProperty("zookeeper.superUser") != null) { - if (id.equals(System.getProperty("zookeeper.superUser")) || id.equals(aclExpr)) { - return true; - } - } if ((id.equals("super") || id.equals(aclExpr))) { return true; } diff --git a/src/java/main/org/apache/zookeeper/server/auth/SaslServerCallbackHandler.java b/src/java/main/org/apache/zookeeper/server/auth/SaslServerCallbackHandler.java index 2fbd6eda4e7..9f53a4d1c17 100644 --- a/src/java/main/org/apache/zookeeper/server/auth/SaslServerCallbackHandler.java +++ b/src/java/main/org/apache/zookeeper/server/auth/SaslServerCallbackHandler.java @@ -46,13 +46,15 @@ public class SaslServerCallbackHandler implements CallbackHandler { private String userName; private final Map credentials = new HashMap(); - public SaslServerCallbackHandler(Configuration configuration) throws IOException { - String serverSection = System.getProperty(ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY, - ZooKeeperSaslServer.DEFAULT_LOGIN_CONTEXT_NAME); + public SaslServerCallbackHandler(Configuration configuration) + throws IOException { + String serverSection = System.getProperty( + ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY, + ZooKeeperSaslServer.DEFAULT_LOGIN_CONTEXT_NAME); AppConfigurationEntry configurationEntries[] = configuration.getAppConfigurationEntry(serverSection); if (configurationEntries == null) { - String errorMessage = "Could not find a 'Server' entry in this configuration: Server cannot start."; + String errorMessage = "Could not find a '" + serverSection + "' entry in this configuration: Server cannot start."; LOG.error(errorMessage); throw new IOException(errorMessage); } @@ -134,7 +136,7 @@ private void handleAuthorizeCallback(AuthorizeCallback ac) { LOG.info("Setting authorizedID: " + userNameBuilder); ac.setAuthorizedID(userNameBuilder.toString()); } catch (IOException e) { - LOG.error("Failed to set name based on Kerberos authentication rules."); + LOG.error("Failed to set name based on Kerberos authentication rules.", e); } } diff --git a/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java b/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java new file mode 100644 index 00000000000..93bc8fcd390 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java @@ -0,0 +1,247 @@ +/** + * 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.auth; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.common.X509Exception; +import org.apache.zookeeper.common.X509Exception.KeyManagerException; +import org.apache.zookeeper.common.X509Exception.TrustManagerException; +import org.apache.zookeeper.common.X509Util; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.server.ServerCnxn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An AuthenticationProvider backed by an X509TrustManager and an X509KeyManager + * to perform remote host certificate authentication. The default algorithm is + * SunX509 and a JKS KeyStore. To specify the locations of the key store and + * trust store, set the following system properties: + *
    zookeeper.ssl.keyStore.location + *
    zookeeper.ssl.trustStore.location + *
    To specify store passwords, set the following system properties: + *
    zookeeper.ssl.keyStore.password + *
    zookeeper.ssl.trustStore.password + *
    Alternatively, this can be plugged with any X509TrustManager and + * X509KeyManager implementation. + */ +public class X509AuthenticationProvider implements AuthenticationProvider { + static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER + = "zookeeper.X509AuthenticationProvider.superUser"; + private static final Logger LOG + = LoggerFactory.getLogger(X509AuthenticationProvider.class); + private final X509TrustManager trustManager; + private final X509KeyManager keyManager; + + /** + * Initialize the X509AuthenticationProvider with a JKS KeyStore and JKS + * TrustStore according to the following system properties: + *
    zookeeper.ssl.keyStore.location + *
    zookeeper.ssl.trustStore.location + *
    zookeeper.ssl.keyStore.password + *
    zookeeper.ssl.trustStore.password + */ + public X509AuthenticationProvider() throws X509Exception { + String keyStoreLocationProp = System.getProperty( + ZKConfig.SSL_KEYSTORE_LOCATION); + String keyStorePasswordProp = System.getProperty( + ZKConfig.SSL_KEYSTORE_PASSWD); + + X509KeyManager km = null; + X509TrustManager tm = null; + if (keyStoreLocationProp == null && keyStorePasswordProp == null) { + LOG.warn("keystore not specified for client connection"); + } else { + if (keyStoreLocationProp == null) { + throw new X509Exception("keystore location not specified for client connection"); + } + if (keyStorePasswordProp == null) { + throw new X509Exception("keystore password not specified for client connection"); + } + try { + km = X509Util.createKeyManager( + keyStoreLocationProp, keyStorePasswordProp); + } catch (KeyManagerException e) { + LOG.error("Failed to create key manager", e); + } + } + + String trustStoreLocationProp = System.getProperty( + ZKConfig.SSL_TRUSTSTORE_LOCATION); + String trustStorePasswordProp = System.getProperty( + ZKConfig.SSL_TRUSTSTORE_PASSWD); + + if (trustStoreLocationProp == null && trustStorePasswordProp == null) { + LOG.warn("Truststore not specified for client connection"); + } else { + if (trustStoreLocationProp == null) { + throw new X509Exception("Truststore location not specified for client connection"); + } + if (trustStorePasswordProp == null) { + throw new X509Exception("Truststore password not specified for client connection"); + } + try { + tm = X509Util.createTrustManager( + trustStoreLocationProp, trustStorePasswordProp); + } catch (TrustManagerException e) { + LOG.error("Failed to create trust manager", e); + } + } + this.keyManager = km; + this.trustManager = tm; + } + + /** + * Initialize the X509AuthenticationProvider with the provided + * X509TrustManager and X509KeyManager. + * + * @param trustManager X509TrustManager implementation to use for remote + * host authentication. + * @param keyManager X509KeyManager implementation to use for certificate + * management. + */ + public X509AuthenticationProvider(X509TrustManager trustManager, + X509KeyManager keyManager) { + this.trustManager = trustManager; + this.keyManager = keyManager; + } + + @Override + public String getScheme() { + return "x509"; + } + + @Override + public KeeperException.Code handleAuthentication(ServerCnxn cnxn, + byte[] authData) { + X509Certificate[] certChain + = (X509Certificate[]) cnxn.getClientCertificateChain(); + + if (certChain == null || certChain.length == 0) { + return KeeperException.Code.AUTHFAILED; + } + + if (trustManager == null) { + LOG.error("No trust manager available to authenticate session 0x{}", + Long.toHexString(cnxn.getSessionId())); + return KeeperException.Code.AUTHFAILED; + } + + X509Certificate clientCert = certChain[0]; + + try { + // Authenticate client certificate + trustManager.checkClientTrusted(certChain, + clientCert.getPublicKey().getAlgorithm()); + } catch (CertificateException ce) { + LOG.error("Failed to trust certificate for session 0x" + + Long.toHexString(cnxn.getSessionId()), ce); + return KeeperException.Code.AUTHFAILED; + } + + String clientId = getClientId(clientCert); + + if (clientId.equals(System.getProperty( + ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER))) { + cnxn.addAuthInfo(new Id("super", clientId)); + LOG.info("Authenticated Id '{}' as super user", clientId); + } + + Id authInfo = new Id(getScheme(), clientId); + cnxn.addAuthInfo(authInfo); + + LOG.info("Authenticated Id '{}' for Scheme '{}'", + authInfo.getId(), authInfo.getScheme()); + return KeeperException.Code.OK; + } + + /** + * Determine the string to be used as the remote host session Id for + * authorization purposes. Associate this client identifier with a + * ServerCnxn that has been authenticated over SSL, and any ACLs that refer + * to the authenticated client. + * + * @param clientCert Authenticated X509Certificate associated with the + * remote host. + * @return Identifier string to be associated with the client. + */ + protected String getClientId(X509Certificate clientCert) { + return clientCert.getSubjectX500Principal().getName(); + } + + @Override + public boolean matches(String id, String aclExpr) { + if (System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER) != null) { + return (id.equals(System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_SUPERUSER)) + || id.equals(aclExpr)); + } + + return (id.equals(aclExpr)); + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public boolean isValid(String id) { + try { + new X500Principal(id); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * Get the X509TrustManager implementation used for remote host + * authentication. + * + * @return The X509TrustManager. + * @throws TrustManagerException When there is no trust manager available. + */ + public X509TrustManager getTrustManager() throws TrustManagerException { + if (trustManager == null) { + throw new TrustManagerException("No trust manager available"); + } + return trustManager; + } + + /** + * Get the X509KeyManager implementation used for certificate management. + * + * @return The X509KeyManager. + * @throws KeyManagerException When there is no key manager available. + */ + public X509KeyManager getKeyManager() throws KeyManagerException { + if (keyManager == null) { + throw new KeyManagerException("No key manager available"); + } + return keyManager; + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/AbstractFourLetterCommand.java b/src/java/main/org/apache/zookeeper/server/command/AbstractFourLetterCommand.java new file mode 100644 index 00000000000..fe784022d2c --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/AbstractFourLetterCommand.java @@ -0,0 +1,81 @@ +/** + * 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.command; + +import java.io.IOException; +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.ZooKeeperServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Set of threads for command ports. All the 4 letter commands are run via a + * thread. Each class maps to a correspoding 4 letter command. CommandThread is + * the abstract class from which all the others inherit. + */ +public abstract class AbstractFourLetterCommand { + private static final Logger LOG = LoggerFactory + .getLogger(AbstractFourLetterCommand.class); + + public static final String ZK_NOT_SERVING = "This ZooKeeper instance is not currently serving requests"; + + protected PrintWriter pw; + protected ServerCnxn serverCnxn; + protected ZooKeeperServer zkServer; + protected ServerCnxnFactory factory; + + public AbstractFourLetterCommand(PrintWriter pw, ServerCnxn serverCnxn) { + this.pw = pw; + this.serverCnxn = serverCnxn; + } + + public void start() { + run(); + } + + public void run() { + try { + commandRun(); + } catch (IOException ie) { + LOG.error("Error in running command ", ie); + } finally { + serverCnxn.cleanupWriterSocket(pw); + } + } + + public void setZkServer(ZooKeeperServer zkServer) { + this.zkServer = zkServer; + } + + /** + * @return true if the server is running, false otherwise. + */ + boolean isZKServerRunning() { + return zkServer != null && zkServer.isRunning(); + } + + public void setFactory(ServerCnxnFactory factory) { + this.factory = factory; + } + + public abstract void commandRun() throws IOException; +} diff --git a/src/java/main/org/apache/zookeeper/server/command/CnxnStatResetCommand.java b/src/java/main/org/apache/zookeeper/server/command/CnxnStatResetCommand.java new file mode 100644 index 00000000000..06e82b6b7cb --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/CnxnStatResetCommand.java @@ -0,0 +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.server.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; + +public class CnxnStatResetCommand extends AbstractFourLetterCommand { + public CnxnStatResetCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() { + if (!isZKServerRunning()) { + pw.println(ZK_NOT_SERVING); + } else { + factory.resetAllConnectionStats(); + pw.println("Connection stats reset."); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/CommandExecutor.java b/src/java/main/org/apache/zookeeper/server/command/CommandExecutor.java new file mode 100644 index 00000000000..52eeda2ba28 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/CommandExecutor.java @@ -0,0 +1,81 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.ZooKeeperServer; + +public class CommandExecutor { + /** + * This class decides which command to be executed and then executes + */ + public boolean execute(ServerCnxn serverCnxn, PrintWriter pwriter, + final int commandCode, ZooKeeperServer zkServer, ServerCnxnFactory factory) { + AbstractFourLetterCommand command = getCommand(serverCnxn,pwriter, commandCode); + + if (command == null) { + return false; + } + + command.setZkServer(zkServer); + command.setFactory(factory); + command.start(); + return true; + } + + private AbstractFourLetterCommand getCommand(ServerCnxn serverCnxn, + PrintWriter pwriter, final int commandCode) { + AbstractFourLetterCommand command = null; + if (commandCode == FourLetterCommands.ruokCmd) { + command = new RuokCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.getTraceMaskCmd) { + command = new TraceMaskCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.enviCmd) { + command = new EnvCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.confCmd) { + command = new ConfCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.srstCmd) { + command = new StatResetCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.crstCmd) { + command = new CnxnStatResetCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.dirsCmd) { + command = new DirsCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.dumpCmd) { + command = new DumpCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.statCmd + || commandCode == FourLetterCommands.srvrCmd) { + command = new StatCommand(pwriter, serverCnxn, commandCode); + } else if (commandCode == FourLetterCommands.consCmd) { + command = new ConsCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.wchpCmd + || commandCode == FourLetterCommands.wchcCmd + || commandCode == FourLetterCommands.wchsCmd) { + command = new WatchCommand(pwriter, serverCnxn, commandCode); + } else if (commandCode == FourLetterCommands.mntrCmd) { + command = new MonitorCommand(pwriter, serverCnxn); + } else if (commandCode == FourLetterCommands.isroCmd) { + command = new IsroCommand(pwriter, serverCnxn); + } + return command; + } + +} diff --git a/src/java/main/org/apache/zookeeper/server/command/ConfCommand.java b/src/java/main/org/apache/zookeeper/server/command/ConfCommand.java new file mode 100644 index 00000000000..1cfa78d749c --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/ConfCommand.java @@ -0,0 +1,38 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; + +public class ConfCommand extends AbstractFourLetterCommand { + ConfCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw,serverCnxn); + } + + @Override + public void commandRun() { + if (!isZKServerRunning()) { + pw.println(ZK_NOT_SERVING); + } else { + zkServer.dumpConf(pw); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/ConsCommand.java b/src/java/main/org/apache/zookeeper/server/command/ConsCommand.java new file mode 100644 index 00000000000..36e40ad1d58 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/ConsCommand.java @@ -0,0 +1,42 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; + +public class ConsCommand extends AbstractFourLetterCommand { + public ConsCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() { + if (!isZKServerRunning()) { + pw.println(ZK_NOT_SERVING); + } else { + for (ServerCnxn c : factory.getConnections()) { + c.dumpConnectionInfo(pw, false); + pw.println(); + } + pw.println(); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/DirsCommand.java b/src/java/main/org/apache/zookeeper/server/command/DirsCommand.java new file mode 100644 index 00000000000..0f82a2d762a --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/DirsCommand.java @@ -0,0 +1,41 @@ +/** + * 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.command; + +import java.io.IOException; +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; + +public class DirsCommand extends AbstractFourLetterCommand { + + public DirsCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() throws IOException { + if (!isZKServerRunning()) { + pw.println(ZK_NOT_SERVING); + return; + } + pw.println("datadir_size: " + zkServer.getDataDirSize()); + pw.println("logdir_size: " + zkServer.getLogDirSize()); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/DumpCommand.java b/src/java/main/org/apache/zookeeper/server/command/DumpCommand.java new file mode 100644 index 00000000000..a52ebea3766 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/DumpCommand.java @@ -0,0 +1,47 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.NIOServerCnxnFactory; +import org.apache.zookeeper.server.ServerCnxn; + +public class DumpCommand extends AbstractFourLetterCommand { + public DumpCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() { + if (!isZKServerRunning()) { + pw.println(ZK_NOT_SERVING); + } else { + pw.println("SessionTracker dump:"); + zkServer.getSessionTracker().dumpSessions(pw); + pw.println("ephemeral nodes dump:"); + zkServer.dumpEphemerals(pw); + pw.println("Connections dump:"); + //dumpConnections connection is implemented only in NIOServerCnxnFactory + if (factory instanceof NIOServerCnxnFactory) { + ((NIOServerCnxnFactory)factory).dumpConnections(pw); + } + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/EnvCommand.java b/src/java/main/org/apache/zookeeper/server/command/EnvCommand.java new file mode 100644 index 00000000000..c35d9d513ab --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/EnvCommand.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.zookeeper.server.command; + +import java.io.PrintWriter; +import java.util.List; + +import org.apache.zookeeper.Environment; +import org.apache.zookeeper.server.ServerCnxn; + +public class EnvCommand extends AbstractFourLetterCommand { + EnvCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() { + List env = Environment.list(); + + pw.println("Environment:"); + for (Environment.Entry e : env) { + pw.print(e.getKey()); + pw.print("="); + pw.println(e.getValue()); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/FourLetterCommands.java b/src/java/main/org/apache/zookeeper/server/command/FourLetterCommands.java new file mode 100644 index 00000000000..45bd942479a --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/FourLetterCommands.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.command; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.HashSet; +import java.util.Set; +import java.util.Arrays; + +/** + * This class contains constants for all the four letter commands + */ +public class FourLetterCommands { + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int confCmd = + ByteBuffer.wrap("conf".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int consCmd = + ByteBuffer.wrap("cons".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int crstCmd = + ByteBuffer.wrap("crst".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int dirsCmd = + ByteBuffer.wrap("dirs".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int dumpCmd = + ByteBuffer.wrap("dump".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int enviCmd = + ByteBuffer.wrap("envi".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int getTraceMaskCmd = + ByteBuffer.wrap("gtmk".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int ruokCmd = + ByteBuffer.wrap("ruok".getBytes()).getInt(); + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int setTraceMaskCmd = + ByteBuffer.wrap("stmk".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int srvrCmd = + ByteBuffer.wrap("srvr".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int srstCmd = + ByteBuffer.wrap("srst".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int statCmd = + ByteBuffer.wrap("stat".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int wchcCmd = + ByteBuffer.wrap("wchc".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int wchpCmd = + ByteBuffer.wrap("wchp".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int wchsCmd = + ByteBuffer.wrap("wchs".getBytes()).getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int mntrCmd = ByteBuffer.wrap("mntr".getBytes()) + .getInt(); + + /* + * See + * Zk Admin. this link is for all the commands. + */ + public final static int isroCmd = ByteBuffer.wrap("isro".getBytes()) + .getInt(); + + /* + * The control sequence sent by the telnet program when it closes a + * connection. Include simply to keep the logs cleaner (the server would + * close the connection anyway because it would parse this as a negative + * length). + */ + public final static int telnetCloseCmd = 0xfff4fffd; + + private static final String ZOOKEEPER_4LW_COMMANDS_WHITELIST = "zookeeper.4lw.commands.whitelist"; + + private static final Logger LOG = LoggerFactory.getLogger(FourLetterCommands.class); + + private static final Map cmd2String = new HashMap(); + + private static final Set whiteListedCommands = new HashSet(); + + private static boolean whiteListInitialized = false; + + // @VisibleForTesting + public synchronized static void resetWhiteList() { + whiteListInitialized = false; + whiteListedCommands.clear(); + } + + /** + * Return the string representation of the specified command code. + */ + public static String getCommandString(int command) { + return cmd2String.get(command); + } + + /** + * Check if the specified command code is from a known command. + * + * @param command The integer code of command. + * @return true if the specified command is known, false otherwise. + */ + public static boolean isKnown(int command) { + return cmd2String.containsKey(command); + } + + /** + * Check if the specified command is enabled. + * + * In ZOOKEEPER-2693 we introduce a configuration option to only + * allow a specific set of white listed commands to execute. + * A command will only be executed if it is also configured + * in the white list. + * + * @param command The command string. + * @return true if the specified command is enabled + */ + public synchronized static boolean isEnabled(String command) { + if (whiteListInitialized) { + return whiteListedCommands.contains(command); + } + + String commands = System.getProperty(ZOOKEEPER_4LW_COMMANDS_WHITELIST); + if (commands != null) { + String[] list = commands.split(","); + for (String cmd : list) { + if (cmd.trim().equals("*")) { + for (Map.Entry entry : cmd2String.entrySet()) { + whiteListedCommands.add(entry.getValue()); + } + break; + } + if (!cmd.trim().isEmpty()) { + whiteListedCommands.add(cmd.trim()); + } + } + } + + // It is sad that isro and srvr are used by ZooKeeper itself. Need fix this + // before deprecating 4lw. + if (System.getProperty("readonlymode.enabled", "false").equals("true")) { + whiteListedCommands.add("isro"); + } + // zkServer.sh depends on "srvr". + whiteListedCommands.add("srvr"); + whiteListInitialized = true; + LOG.info("The list of known four letter word commands is : {}", Arrays.asList(cmd2String)); + LOG.info("The list of enabled four letter word commands is : {}", Arrays.asList(whiteListedCommands)); + return whiteListedCommands.contains(command); + } + + // specify all of the commands that are available + static { + cmd2String.put(confCmd, "conf"); + cmd2String.put(consCmd, "cons"); + cmd2String.put(crstCmd, "crst"); + cmd2String.put(dirsCmd, "dirs"); + cmd2String.put(dumpCmd, "dump"); + cmd2String.put(enviCmd, "envi"); + cmd2String.put(getTraceMaskCmd, "gtmk"); + cmd2String.put(ruokCmd, "ruok"); + cmd2String.put(setTraceMaskCmd, "stmk"); + cmd2String.put(srstCmd, "srst"); + cmd2String.put(srvrCmd, "srvr"); + cmd2String.put(statCmd, "stat"); + cmd2String.put(wchcCmd, "wchc"); + cmd2String.put(wchpCmd, "wchp"); + cmd2String.put(wchsCmd, "wchs"); + cmd2String.put(mntrCmd, "mntr"); + cmd2String.put(isroCmd, "isro"); + cmd2String.put(telnetCloseCmd, "telnet close"); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/IsroCommand.java b/src/java/main/org/apache/zookeeper/server/command/IsroCommand.java new file mode 100644 index 00000000000..a8f9f217933 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/IsroCommand.java @@ -0,0 +1,42 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.quorum.ReadOnlyZooKeeperServer; + +public class IsroCommand extends AbstractFourLetterCommand { + + public IsroCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() { + if (!isZKServerRunning()) { + pw.print("null"); + } else if (zkServer instanceof ReadOnlyZooKeeperServer) { + pw.print("ro"); + } else { + pw.print("rw"); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/MonitorCommand.java b/src/java/main/org/apache/zookeeper/server/command/MonitorCommand.java new file mode 100644 index 00000000000..c32de4ca1a8 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/MonitorCommand.java @@ -0,0 +1,95 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.Version; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerStats; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.quorum.Leader; +import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer; +import org.apache.zookeeper.server.util.OSMXBean; + +public class MonitorCommand extends AbstractFourLetterCommand { + + MonitorCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() { + if (!isZKServerRunning()) { + pw.println(ZK_NOT_SERVING); + return; + } + ZKDatabase zkdb = zkServer.getZKDatabase(); + ServerStats stats = zkServer.serverStats(); + + print("version", Version.getFullVersion()); + + print("avg_latency", stats.getAvgLatency()); + print("max_latency", stats.getMaxLatency()); + print("min_latency", stats.getMinLatency()); + + print("packets_received", stats.getPacketsReceived()); + print("packets_sent", stats.getPacketsSent()); + print("num_alive_connections", stats.getNumAliveClientConnections()); + + print("outstanding_requests", stats.getOutstandingRequests()); + + print("server_state", stats.getServerState()); + print("znode_count", zkdb.getNodeCount()); + + print("watch_count", zkdb.getDataTree().getWatchCount()); + print("ephemerals_count", zkdb.getDataTree().getEphemeralsCount()); + print("approximate_data_size", zkdb.getDataTree().approximateDataSize()); + + OSMXBean osMbean = new OSMXBean(); + if (osMbean != null && osMbean.getUnix() == true) { + print("open_file_descriptor_count", osMbean.getOpenFileDescriptorCount()); + print("max_file_descriptor_count", osMbean.getMaxFileDescriptorCount()); + } + + if (stats.getServerState().equals("leader")) { + Leader leader = ((LeaderZooKeeperServer)zkServer).getLeader(); + + print("followers", leader.getLearners().size()); + print("synced_followers", leader.getForwardingFollowers().size()); + print("pending_syncs", leader.getNumPendingSyncs()); + + print("last_proposal_size", leader.getProposalStats().getLastProposalSize()); + print("max_proposal_size", leader.getProposalStats().getMaxProposalSize()); + print("min_proposal_size", leader.getProposalStats().getMinProposalSize()); + } + } + + private void print(String key, long number) { + print(key, "" + number); + } + + private void print(String key, String value) { + pw.print("zk_"); + pw.print(key); + pw.print("\t"); + pw.println(value); + } + +} diff --git a/src/java/main/org/apache/zookeeper/server/command/NopCommand.java b/src/java/main/org/apache/zookeeper/server/command/NopCommand.java new file mode 100644 index 00000000000..4924fcf4ce4 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/NopCommand.java @@ -0,0 +1,41 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; + +/** + * A command that does not do anything except reply to client with predefined message. + * It is used to inform clients who execute none white listed four letter word commands. + */ +public class NopCommand extends AbstractFourLetterCommand { + private String msg; + + public NopCommand(PrintWriter pw, ServerCnxn serverCnxn, String msg) { + super(pw, serverCnxn); + this.msg = msg; + } + + @Override + public void commandRun() { + pw.println(msg); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/RuokCommand.java b/src/java/main/org/apache/zookeeper/server/command/RuokCommand.java new file mode 100644 index 00000000000..cbcbde3264a --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/RuokCommand.java @@ -0,0 +1,34 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; + +public class RuokCommand extends AbstractFourLetterCommand { + public RuokCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() { + pw.print("imok"); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/SetTraceMaskCommand.java b/src/java/main/org/apache/zookeeper/server/command/SetTraceMaskCommand.java new file mode 100644 index 00000000000..74377b667d1 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/SetTraceMaskCommand.java @@ -0,0 +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.server.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; + +public class SetTraceMaskCommand extends AbstractFourLetterCommand { + long trace = 0; + public SetTraceMaskCommand(PrintWriter pw, ServerCnxn serverCnxn, long trace) { + super(pw, serverCnxn); + this.trace = trace; + } + + @Override + public void commandRun() { + pw.print(trace); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/StatCommand.java b/src/java/main/org/apache/zookeeper/server/command/StatCommand.java new file mode 100644 index 00000000000..d04f2f71cb5 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/StatCommand.java @@ -0,0 +1,72 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.Version; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerStats; +import org.apache.zookeeper.server.quorum.Leader; +import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer; +import org.apache.zookeeper.server.quorum.ProposalStats; +import org.apache.zookeeper.server.quorum.ReadOnlyZooKeeperServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StatCommand extends AbstractFourLetterCommand { + private static final Logger LOG = LoggerFactory + .getLogger(AbstractFourLetterCommand.class); + private int len; + public StatCommand(PrintWriter pw, ServerCnxn serverCnxn, int len) { + super(pw, serverCnxn); + this.len = len; + } + + @Override + public void commandRun() { + if (!isZKServerRunning()) { + pw.println(ZK_NOT_SERVING); + } else { + pw.print("Zookeeper version: "); + pw.println(Version.getFullVersion()); + if (zkServer instanceof ReadOnlyZooKeeperServer) { + pw.println("READ-ONLY mode; serving only read-only clients"); + } + if (len == FourLetterCommands.statCmd) { + LOG.info("Stat command output"); + pw.println("Clients:"); + for(ServerCnxn c : factory.getConnections()){ + c.dumpConnectionInfo(pw, true); + pw.println(); + } + pw.println(); + } + ServerStats serverStats = zkServer.serverStats(); + pw.print(serverStats.toString()); + pw.print("Node count: "); + pw.println(zkServer.getZKDatabase().getNodeCount()); + if (serverStats.getServerState().equals("leader")) { + Leader leader = ((LeaderZooKeeperServer)zkServer).getLeader(); + ProposalStats proposalStats = leader.getProposalStats(); + pw.printf("Proposal sizes last/min/max: %s%n", proposalStats.toString()); + } + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/StatResetCommand.java b/src/java/main/org/apache/zookeeper/server/command/StatResetCommand.java new file mode 100644 index 00000000000..2b2fa06f7c1 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/StatResetCommand.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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerStats; +import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer; + +public class StatResetCommand extends AbstractFourLetterCommand { + public StatResetCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() { + if (!isZKServerRunning()) { + pw.println(ZK_NOT_SERVING); + } else { + ServerStats serverStats = zkServer.serverStats(); + serverStats.reset(); + if (serverStats.getServerState().equals("leader")) { + ((LeaderZooKeeperServer)zkServer).getLeader().getProposalStats().reset(); + } + pw.println("Server stats reset."); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/TraceMaskCommand.java b/src/java/main/org/apache/zookeeper/server/command/TraceMaskCommand.java new file mode 100644 index 00000000000..63b0c1cb76c --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/TraceMaskCommand.java @@ -0,0 +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.server.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ZooTrace; + +public class TraceMaskCommand extends AbstractFourLetterCommand { + TraceMaskCommand(PrintWriter pw, ServerCnxn serverCnxn) { + super(pw, serverCnxn); + } + + @Override + public void commandRun() { + long traceMask = ZooTrace.getTextTraceLevel(); + pw.print(traceMask); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/command/WatchCommand.java b/src/java/main/org/apache/zookeeper/server/command/WatchCommand.java new file mode 100644 index 00000000000..ac0476e1dd9 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/WatchCommand.java @@ -0,0 +1,49 @@ +/** + * 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.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.DataTree; +import org.apache.zookeeper.server.ServerCnxn; + +public class WatchCommand extends AbstractFourLetterCommand { + int len = 0; + public WatchCommand(PrintWriter pw, ServerCnxn serverCnxn, int len) { + super(pw, serverCnxn); + this.len = len; + } + + @Override + public void commandRun() { + if (!isZKServerRunning()) { + pw.println(ZK_NOT_SERVING); + } else { + DataTree dt = zkServer.getZKDatabase().getDataTree(); + if (len == FourLetterCommands.wchsCmd) { + dt.dumpWatchesSummary(pw); + } else if (len == FourLetterCommands.wchpCmd) { + dt.dumpWatches(pw, true); + } else { + dt.dumpWatches(pw, false); + } + pw.println(); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/persistence/FilePadding.java b/src/java/main/org/apache/zookeeper/server/persistence/FilePadding.java new file mode 100644 index 00000000000..49fde24e76c --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/persistence/FilePadding.java @@ -0,0 +1,112 @@ +/** + * 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.persistence; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +public class FilePadding { + private static final Logger LOG; + private static long preAllocSize = 65536 * 1024; + private static final ByteBuffer fill = ByteBuffer.allocateDirect(1); + + static { + LOG = LoggerFactory.getLogger(FileTxnLog.class); + + String size = System.getProperty("zookeeper.preAllocSize"); + if (size != null) { + try { + preAllocSize = Long.parseLong(size) * 1024; + } catch (NumberFormatException e) { + LOG.warn(size + " is not a valid value for preAllocSize"); + } + } + } + + private long currentSize; + + /** + * Getter of preAllocSize has been added for testing + */ + public static long getPreAllocSize() { + return preAllocSize; + } + + /** + * method to allow setting preallocate size + * of log file to pad the file. + * + * @param size the size to set to in bytes + */ + public static void setPreallocSize(long size) { + preAllocSize = size; + } + + public void setCurrentSize(long currentSize) { + this.currentSize = currentSize; + } + + /** + * pad the current file to increase its size to the next multiple of preAllocSize greater than the current size and position + * + * @param fileChannel the fileChannel of the file to be padded + * @throws IOException + */ + long padFile(FileChannel fileChannel) throws IOException { + long newFileSize = calculateFileSizeWithPadding(fileChannel.position(), currentSize, preAllocSize); + if (currentSize != newFileSize) { + fileChannel.write((ByteBuffer) fill.position(0), newFileSize - fill.remaining()); + currentSize = newFileSize; + } + return currentSize; + } + + /** + * Calculates a new file size with padding. We only return a new size if + * the current file position is sufficiently close (less than 4K) to end of + * file and preAllocSize is > 0. + * + * @param position the point in the file we have written to + * @param fileSize application keeps track of the current file size + * @param preAllocSize how many bytes to pad + * @return the new file size. It can be the same as fileSize if no + * padding was done. + * @throws IOException + */ + // VisibleForTesting + public static long calculateFileSizeWithPadding(long position, long fileSize, long preAllocSize) { + // If preAllocSize is positive and we are within 4KB of the known end of the file calculate a new file size + if (preAllocSize > 0 && position + 4096 >= fileSize) { + // If we have written more than we have previously preallocated we need to make sure the new + // file size is larger than what we already have + if (position > fileSize) { + fileSize = position + preAllocSize; + fileSize -= fileSize % preAllocSize; + } else { + fileSize += preAllocSize; + } + } + + return fileSize; + } +} diff --git a/src/java/main/org/apache/zookeeper/server/persistence/FileSnap.java b/src/java/main/org/apache/zookeeper/server/persistence/FileSnap.java index 8f57338417e..f6f2ac511f2 100644 --- a/src/java/main/org/apache/zookeeper/server/persistence/FileSnap.java +++ b/src/java/main/org/apache/zookeeper/server/persistence/FileSnap.java @@ -51,11 +51,14 @@ public class FileSnap implements SnapShot { File snapDir; private volatile boolean close = false; - private static final int VERSION=2; - private static final long dbId=-1; + private static final int VERSION = 2; + private static final long dbId = -1; private static final Logger LOG = LoggerFactory.getLogger(FileSnap.class); public final static int SNAP_MAGIC - = ByteBuffer.wrap("ZKSN".getBytes()).getInt(); + = ByteBuffer.wrap("ZKSN".getBytes()).getInt(); + + public static final String SNAPSHOT_FILE_PREFIX = "snapshot"; + public FileSnap(File snapDir) { this.snapDir = snapDir; } @@ -63,7 +66,7 @@ public FileSnap(File snapDir) { /** * deserialize a data tree from the most recent snapshot * @return the zxid of the snapshot - */ + */ public long deserialize(DataTree dt, Map sessions) throws IOException { // we run through 100 snapshots (not all of them) @@ -75,16 +78,13 @@ public long deserialize(DataTree dt, Map sessions) } File snap = null; boolean foundValid = false; - for (int i = 0; i < snapList.size(); i++) { + for (int i = 0, snapListSize = snapList.size(); i < snapListSize; i++) { snap = snapList.get(i); - InputStream snapIS = null; - CheckedInputStream crcIn = null; - try { - LOG.info("Reading snapshot " + snap); - snapIS = new BufferedInputStream(new FileInputStream(snap)); - crcIn = new CheckedInputStream(snapIS, new Adler32()); + LOG.info("Reading snapshot " + snap); + try (InputStream snapIS = new BufferedInputStream(new FileInputStream(snap)); + CheckedInputStream crcIn = new CheckedInputStream(snapIS, new Adler32())) { InputArchive ia = BinaryInputArchive.getArchive(crcIn); - deserialize(dt,sessions, ia); + deserialize(dt, sessions, ia); long checkSum = crcIn.getChecksum().getValue(); long val = ia.readLong("val"); if (val != checkSum) { @@ -92,19 +92,14 @@ public long deserialize(DataTree dt, Map sessions) } foundValid = true; break; - } catch(IOException e) { + } catch (IOException e) { LOG.warn("problem reading snap file " + snap, e); - } finally { - if (snapIS != null) - snapIS.close(); - if (crcIn != null) - crcIn.close(); - } + } } if (!foundValid) { throw new IOException("Not able to find valid snapshots in " + snapDir); } - dt.lastProcessedZxid = Util.getZxidFromName(snap.getName(), "snapshot"); + dt.lastProcessedZxid = Util.getZxidFromName(snap.getName(), SNAPSHOT_FILE_PREFIX); return dt.lastProcessedZxid; } @@ -121,7 +116,7 @@ public void deserialize(DataTree dt, Map sessions, header.deserialize(ia, "fileheader"); if (header.getMagic() != SNAP_MAGIC) { throw new IOException("mismatching magic headers " - + header.getMagic() + + + header.getMagic() + " != " + FileSnap.SNAP_MAGIC); } SerializeUtils.deserializeSnapshot(dt,ia,sessions); @@ -138,7 +133,7 @@ public File findMostRecentSnapshot() throws IOException { } return files.get(0); } - + /** * find the last (maybe) valid n snapshots. this does some * minor checks on the validity of the snapshots. It just @@ -152,7 +147,7 @@ public File findMostRecentSnapshot() throws IOException { * @throws IOException */ private List findNValidSnapshots(int n) throws IOException { - List files = Util.sortDataDir(snapDir.listFiles(),"snapshot", false); + List files = Util.sortDataDir(snapDir.listFiles(), SNAPSHOT_FILE_PREFIX, false); int count = 0; List list = new ArrayList(); for (File f : files) { @@ -177,19 +172,21 @@ private List findNValidSnapshots(int n) throws IOException { /** * find the last n snapshots. this does not have * any checks if the snapshot might be valid or not - * @param the number of most recent snapshots + * @param n the number of most recent snapshots * @return the last n snapshots * @throws IOException */ public List findNRecentSnapshots(int n) throws IOException { - List files = Util.sortDataDir(snapDir.listFiles(), "snapshot", false); - int i = 0; + List files = Util.sortDataDir(snapDir.listFiles(), SNAPSHOT_FILE_PREFIX, false); + int count = 0; List list = new ArrayList(); for (File f: files) { - if (i==n) + if (count == n) break; - i++; - list.add(f); + if (Util.getZxidFromName(f.getName(), SNAPSHOT_FILE_PREFIX) != -1) { + count++; + list.add(f); + } } return list; } @@ -222,18 +219,17 @@ protected void serialize(DataTree dt,Map sessions, public synchronized void serialize(DataTree dt, Map sessions, File snapShot) throws IOException { if (!close) { - OutputStream sessOS = new BufferedOutputStream(new FileOutputStream(snapShot)); - CheckedOutputStream crcOut = new CheckedOutputStream(sessOS, new Adler32()); - //CheckedOutputStream cout = new CheckedOutputStream() - OutputArchive oa = BinaryOutputArchive.getArchive(crcOut); - FileHeader header = new FileHeader(SNAP_MAGIC, VERSION, dbId); - serialize(dt,sessions,oa, header); - long val = crcOut.getChecksum().getValue(); - oa.writeLong(val, "val"); - oa.writeString("/", "path"); - sessOS.flush(); - crcOut.close(); - sessOS.close(); + try (OutputStream sessOS = new BufferedOutputStream(new FileOutputStream(snapShot)); + CheckedOutputStream crcOut = new CheckedOutputStream(sessOS, new Adler32())) { + //CheckedOutputStream cout = new CheckedOutputStream() + OutputArchive oa = BinaryOutputArchive.getArchive(crcOut); + FileHeader header = new FileHeader(SNAP_MAGIC, VERSION, dbId); + serialize(dt, sessions, oa, header); + long val = crcOut.getChecksum().getValue(); + oa.writeLong(val, "val"); + oa.writeString("/", "path"); + sessOS.flush(); + } } } @@ -247,4 +243,4 @@ public synchronized void close() throws IOException { close = true; } - } +} diff --git a/src/java/main/org/apache/zookeeper/server/persistence/FileTxnLog.java b/src/java/main/org/apache/zookeeper/server/persistence/FileTxnLog.java index 00a1c7fcfcb..f79521b0bb6 100644 --- a/src/java/main/org/apache/zookeeper/server/persistence/FileTxnLog.java +++ b/src/java/main/org/apache/zookeeper/server/persistence/FileTxnLog.java @@ -28,6 +28,7 @@ import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -40,7 +41,6 @@ import org.apache.jute.InputArchive; import org.apache.jute.OutputArchive; import org.apache.jute.Record; -import org.apache.zookeeper.server.persistence.TxnLog.TxnIterator; import org.apache.zookeeper.server.util.SerializeUtils; import org.apache.zookeeper.txn.TxnHeader; import org.slf4j.Logger; @@ -54,25 +54,25 @@ *
      * LogFile:
      *     FileHeader TxnList ZeroPad
    - * 
    + *
      * FileHeader: {
      *     magic 4bytes (ZKLG)
      *     version 4bytes
      *     dbid 8bytes
      *   }
    - * 
    + *
      * TxnList:
      *     Txn || Txn TxnList
    - *     
    + *
      * Txn:
      *     checksum Txnlen TxnHeader Record 0x42
    - * 
    + *
      * checksum: 8bytes Adler32 is currently used
      *   calculated across payload -- Txnlen, TxnHeader, Record and 0x42
    - * 
    + *
      * Txnlen:
      *     len 4bytes
    - * 
    + *
      * TxnHeader: {
      *     sessionid 8bytes
      *     cxid 4bytes
    @@ -80,39 +80,35 @@
      *     time 8bytes
      *     type 4bytes
      *   }
    - *     
    + *
      * Record:
      *     See Jute definition file for details on the various record types
    - *      
    + *
      * ZeroPad:
      *     0 padded to EOF (filled during preallocation stage)
    - * 
    + * */ public class FileTxnLog implements TxnLog { private static final Logger LOG; - static long preAllocSize = 65536 * 1024; - public final static int TXNLOG_MAGIC = ByteBuffer.wrap("ZKLG".getBytes()).getInt(); public final static int VERSION = 2; + public static final String LOG_FILE_PREFIX = "log"; + /** Maximum time we allow for elapsed fsync before WARNing */ private final static long fsyncWarningThresholdMS; static { LOG = LoggerFactory.getLogger(FileTxnLog.class); - String size = System.getProperty("zookeeper.preAllocSize"); - if (size != null) { - try { - preAllocSize = Long.parseLong(size) * 1024; - } catch (NumberFormatException e) { - LOG.warn(size + " is not a valid value for preAllocSize"); - } - } - fsyncWarningThresholdMS = Long.getLong("fsync.warningthresholdms", 1000); + /** Local variable to read fsync.warningthresholdms into */ + Long fsyncWarningThreshold; + if ((fsyncWarningThreshold = Long.getLong("zookeeper.fsync.warningthresholdms")) == null) + fsyncWarningThreshold = Long.getLong("fsync.warningthresholdms", 1000); + fsyncWarningThresholdMS = fsyncWarningThreshold; } long lastZxidSeen; @@ -121,12 +117,14 @@ public class FileTxnLog implements TxnLog { volatile FileOutputStream fos = null; File logDir; - private final boolean forceSync = !System.getProperty("zookeeper.forceSync", "yes").equals("no");; + private final boolean forceSync = !System.getProperty("zookeeper.forceSync", "yes").equals("no"); long dbId; private LinkedList streamsToFlush = new LinkedList(); - long currentSize; File logFileWrite = null; + private FilePadding filePadding = new FilePadding(); + + private volatile long syncElapsedMS = -1L; /** * constructor for FileTxnLog. Take the directory @@ -138,23 +136,22 @@ public FileTxnLog(File logDir) { } /** - * method to allow setting preallocate size - * of log file to pad the file. - * @param size the size to set to in bytes - */ + * method to allow setting preallocate size + * of log file to pad the file. + * @param size the size to set to in bytes + */ public static void setPreallocSize(long size) { - preAllocSize = size; + FilePadding.setPreallocSize(size); } /** - * creates a checksum alogrithm to be used + * creates a checksum algorithm to be used * @return the checksum used for this txnlog */ protected Checksum makeChecksumAlgorithm(){ return new Adler32(); } - /** * rollover the current log file to a new one. * @throws IOException @@ -170,7 +167,7 @@ public synchronized void rollLog() throws IOException { /** * close all the open file handles * @throws IOException - */ + */ public synchronized void close() throws IOException { if (logStream != null) { logStream.close(); @@ -179,63 +176,54 @@ public synchronized void close() throws IOException { log.close(); } } - + /** * append an entry to the transaction log * @param hdr the header of the transaction * @param txn the transaction part of the entry - * returns true iff something appended, otw false + * returns true iff something appended, otw false */ public synchronized boolean append(TxnHeader hdr, Record txn) throws IOException { - if (hdr != null) { - if (hdr.getZxid() <= lastZxidSeen) { - LOG.warn("Current zxid " + hdr.getZxid() - + " is <= " + lastZxidSeen + " for " - + hdr.getType()); - } - if (logStream==null) { - if(LOG.isInfoEnabled()){ - LOG.info("Creating new log file: log." + - Long.toHexString(hdr.getZxid())); - } - - logFileWrite = new File(logDir, ("log." + - Long.toHexString(hdr.getZxid()))); - fos = new FileOutputStream(logFileWrite); - logStream=new BufferedOutputStream(fos); - oa = BinaryOutputArchive.getArchive(logStream); - FileHeader fhdr = new FileHeader(TXNLOG_MAGIC,VERSION, dbId); - fhdr.serialize(oa, "fileheader"); - // Make sure that the magic number is written before padding. - logStream.flush(); - currentSize = fos.getChannel().position(); - streamsToFlush.add(fos); - } - padFile(fos); - byte[] buf = Util.marshallTxnEntry(hdr, txn); - if (buf == null || buf.length == 0) { - throw new IOException("Faulty serialization for header " + - "and txn"); - } - Checksum crc = makeChecksumAlgorithm(); - crc.update(buf, 0, buf.length); - oa.writeLong(crc.getValue(), "txnEntryCRC"); - Util.writeTxnBytes(oa, buf); - - return true; + if (hdr == null) { + return false; } - return false; - } + if (hdr.getZxid() <= lastZxidSeen) { + LOG.warn("Current zxid " + hdr.getZxid() + + " is <= " + lastZxidSeen + " for " + + hdr.getType()); + } else { + lastZxidSeen = hdr.getZxid(); + } + if (logStream==null) { + if(LOG.isInfoEnabled()){ + LOG.info("Creating new log file: " + Util.makeLogName(hdr.getZxid())); + } + + logFileWrite = new File(logDir, Util.makeLogName(hdr.getZxid())); + fos = new FileOutputStream(logFileWrite); + logStream=new BufferedOutputStream(fos); + oa = BinaryOutputArchive.getArchive(logStream); + FileHeader fhdr = new FileHeader(TXNLOG_MAGIC,VERSION, dbId); + fhdr.serialize(oa, "fileheader"); + // Make sure that the magic number is written before padding. + logStream.flush(); + filePadding.setCurrentSize(fos.getChannel().position()); + streamsToFlush.add(fos); + } + filePadding.padFile(fos.getChannel()); + byte[] buf = Util.marshallTxnEntry(hdr, txn); + if (buf == null || buf.length == 0) { + throw new IOException("Faulty serialization for header " + + "and txn"); + } + Checksum crc = makeChecksumAlgorithm(); + crc.update(buf, 0, buf.length); + oa.writeLong(crc.getValue(), "txnEntryCRC"); + Util.writeTxnBytes(oa, buf); - /** - * pad the current file to increase its size - * @param out the outputstream to be padded - * @throws IOException - */ - private void padFile(FileOutputStream out) throws IOException { - currentSize = Util.padLogFile(out, currentSize, preAllocSize); + return true; } /** @@ -247,12 +235,12 @@ private void padFile(FileOutputStream out) throws IOException { * @return */ public static File[] getLogFiles(File[] logDirList,long snapshotZxid) { - List files = Util.sortDataDir(logDirList, "log", true); + List files = Util.sortDataDir(logDirList, LOG_FILE_PREFIX, true); long logZxid = 0; // Find the log file that starts before or at the same time as the // zxid of the snapshot for (File f : files) { - long fzxid = Util.getZxidFromName(f.getName(), "log"); + long fzxid = Util.getZxidFromName(f.getName(), LOG_FILE_PREFIX); if (fzxid > snapshotZxid) { continue; } @@ -264,7 +252,7 @@ public static File[] getLogFiles(File[] logDirList,long snapshotZxid) { } List v=new ArrayList(5); for (File f : files) { - long fzxid = Util.getZxidFromName(f.getName(), "log"); + long fzxid = Util.getZxidFromName(f.getName(), LOG_FILE_PREFIX); if (fzxid < logZxid) { continue; } @@ -281,7 +269,7 @@ public static File[] getLogFiles(File[] logDirList,long snapshotZxid) { public long getLastLoggedZxid() { File[] files = getLogFiles(logDir.listFiles(), 0); long maxLog=files.length>0? - Util.getZxidFromName(files[files.length-1].getName(),"log"):-1; + Util.getZxidFromName(files[files.length-1].getName(),LOG_FILE_PREFIX):-1; // if a log file is more recent we must scan it to find // the highest zxid @@ -315,7 +303,7 @@ private void close(TxnIterator itr) { } /** - * commit the logs. make sure that evertyhing hits the + * commit the logs. make sure that everything hits the * disk */ public synchronized void commit() throws IOException { @@ -327,15 +315,16 @@ public synchronized void commit() throws IOException { if (forceSync) { long startSyncNS = System.nanoTime(); - log.getChannel().force(false); + FileChannel channel = log.getChannel(); + channel.force(false); - long syncElapsedMS = - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startSyncNS); + syncElapsedMS = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startSyncNS); if (syncElapsedMS > fsyncWarningThresholdMS) { LOG.warn("fsync-ing the write ahead log in " + Thread.currentThread().getName() + " took " + syncElapsedMS + "ms which will adversely effect operation latency. " + + "File size is " + channel.size() + " bytes. " + "See the ZooKeeper troubleshooting guide"); } } @@ -345,6 +334,14 @@ public synchronized void commit() throws IOException { } } + /** + * + * @return elapsed sync time of transaction log in milliseconds + */ + public long getTxnLogSyncElapsedTime() { + return syncElapsedMS; + } + /** * start reading all the transactions from the given zxid * @param zxid the zxid to start reading transactions from @@ -402,7 +399,7 @@ public boolean truncate(long zxid) throws IOException { /** * read the header of the transaction file * @param file the transaction file to read - * @return header that was read fomr the file + * @return header that was read from the file * @throws IOException */ private static FileHeader readHeader(File file) throws IOException { @@ -444,10 +441,10 @@ public boolean isForceSync() { } /** - * a class that keeps track of the position + * a class that keeps track of the position * in the input stream. The position points to offset - * that has been consumed by the applications. It can - * wrap buffered input streams to provide the right offset + * that has been consumed by the applications. It can + * wrap buffered input streams to provide the right offset * for the application. */ static class PositionInputStream extends FilterInputStream { @@ -456,7 +453,7 @@ protected PositionInputStream(InputStream in) { super(in); position = 0; } - + @Override public int read() throws IOException { int rc = super.read(); @@ -471,9 +468,9 @@ public int read(byte[] b) throws IOException { if (rc > 0) { position += rc; } - return rc; + return rc; } - + @Override public int read(byte[] b, int off, int len) throws IOException { int rc = super.read(b, off, len); @@ -482,7 +479,7 @@ public int read(byte[] b, int off, int len) throws IOException { } return rc; } - + @Override public long skip(long n) throws IOException { long rc = super.skip(n); @@ -510,7 +507,7 @@ public void reset() { throw new UnsupportedOperationException("reset"); } } - + /** * this class implements the txnlog iterator interface * which is used for reading the transaction logs @@ -523,7 +520,7 @@ public static class FileTxnIterator implements TxnLog.TxnIterator { File logFile; InputArchive ia; static final String CRC_ERROR="CRC check failed"; - + PositionInputStream inputStream=null; //stored files is the list of files greater than //the zxid we are looking for. @@ -552,7 +549,7 @@ public FileTxnIterator(File logDir, long zxid, boolean fastForward) } } } - + /** * create an iterator over a transaction database directory * @param logDir the transaction database directory @@ -570,22 +567,21 @@ public FileTxnIterator(File logDir, long zxid) throws IOException { */ void init() throws IOException { storedFiles = new ArrayList(); - List files = Util.sortDataDir(FileTxnLog.getLogFiles(logDir.listFiles(), 0), "log", false); + List files = Util.sortDataDir(FileTxnLog.getLogFiles(logDir.listFiles(), 0), LOG_FILE_PREFIX, false); for (File f: files) { - if (Util.getZxidFromName(f.getName(), "log") >= zxid) { + if (Util.getZxidFromName(f.getName(), LOG_FILE_PREFIX) >= zxid) { storedFiles.add(f); } // add the last logfile that is less than the zxid - else if (Util.getZxidFromName(f.getName(), "log") < zxid) { + else if (Util.getZxidFromName(f.getName(), LOG_FILE_PREFIX) < zxid) { storedFiles.add(f); break; } } goToNextLog(); - if (!next()) - return; + next(); } - + /** * Return total storage size of txnlog that will return by this iterator. */ @@ -623,7 +619,7 @@ protected void inStreamCreated(InputArchive ia, InputStream is) FileHeader header= new FileHeader(); header.deserialize(ia, "fileheader"); if (header.getMagic() != FileTxnLog.TXNLOG_MAGIC) { - throw new IOException("Transaction log: " + this.logFile + " has invalid magic number " + throw new IOException("Transaction log: " + this.logFile + " has invalid magic number " + header.getMagic() + " != " + FileTxnLog.TXNLOG_MAGIC); } @@ -679,7 +675,7 @@ public boolean next() throws IOException { hdr = new TxnHeader(); record = SerializeUtils.deserializeTxn(bytes, hdr); } catch (EOFException e) { - LOG.debug("EOF excepton " + e); + LOG.debug("EOF exception " + e); inputStream.close(); inputStream = null; ia = null; @@ -699,7 +695,7 @@ record = SerializeUtils.deserializeTxn(bytes, hdr); } /** - * reutrn the current header + * return the current header * @return the current header that * is read */ diff --git a/src/java/main/org/apache/zookeeper/server/persistence/FileTxnSnapLog.java b/src/java/main/org/apache/zookeeper/server/persistence/FileTxnSnapLog.java index c9a06d79b91..458f4a24e96 100644 --- a/src/java/main/org/apache/zookeeper/server/persistence/FileTxnSnapLog.java +++ b/src/java/main/org/apache/zookeeper/server/persistence/FileTxnSnapLog.java @@ -19,6 +19,7 @@ package org.apache.zookeeper.server.persistence; import java.io.File; +import java.io.FilenameFilter; import java.io.IOException; import java.util.List; import java.util.Map; @@ -77,7 +78,7 @@ public interface PlayBackListener { /** * the constructor which takes the datadir and * snapdir. - * @param dataDir the trasaction directory + * @param dataDir the transaction directory * @param snapDir the snapshot directory */ public FileTxnSnapLog(File dataDir, File snapDir) throws IOException { @@ -106,6 +107,10 @@ public FileTxnSnapLog(File dataDir, File snapDir) throws IOException { + this.dataDir); } } + if (!this.dataDir.canWrite()) { + throw new DatadirException("Cannot write to data directory " + this.dataDir); + } + if (!this.snapDir.exists()) { // by default create this directory, but otherwise complain instead // See ZOOKEEPER-1161 for more details @@ -122,10 +127,45 @@ public FileTxnSnapLog(File dataDir, File snapDir) throws IOException { + this.snapDir); } } + if (!this.snapDir.canWrite()) { + throw new DatadirException("Cannot write to snap directory " + this.snapDir); + } + + // check content of transaction log and snapshot dirs if they are two different directories + // See ZOOKEEPER-2967 for more details + if(!this.dataDir.getPath().equals(this.snapDir.getPath())){ + checkLogDir(); + checkSnapDir(); + } + txnLog = new FileTxnLog(this.dataDir); snapLog = new FileSnap(this.snapDir); } + private void checkLogDir() throws LogDirContentCheckException { + File[] files = this.dataDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return Util.isSnapshotFileName(name); + } + }); + if (files != null && files.length > 0) { + throw new LogDirContentCheckException("Log directory has snapshot files. Check if dataLogDir and dataDir configuration is correct."); + } + } + + private void checkSnapDir() throws SnapDirContentCheckException { + File[] files = this.snapDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return Util.isLogFileName(name); + } + }); + if (files != null && files.length > 0) { + throw new SnapDirContentCheckException("Snapshot directory has log files. Check if dataLogDir and dataDir configuration is correct."); + } + } + /** * get the datadir used by this filetxn * snap log @@ -157,8 +197,38 @@ public File getSnapDir() { */ public long restore(DataTree dt, Map sessions, PlayBackListener listener) throws IOException { - snapLog.deserialize(dt, sessions); + long deserializeResult = snapLog.deserialize(dt, sessions); FileTxnLog txnLog = new FileTxnLog(dataDir); + if (-1L == deserializeResult) { + /* this means that we couldn't find any snapshot, so we need to + * initialize an empty database (reported in ZOOKEEPER-2325) */ + if (txnLog.getLastLoggedZxid() != -1) { + throw new IOException( + "No snapshot found, but there are log entries. " + + "Something is broken!"); + } + /* TODO: (br33d) we should either put a ConcurrentHashMap on restore() + * or use Map on save() */ + save(dt, (ConcurrentHashMap)sessions); + /* return a zxid of zero, since we the database is empty */ + return 0; + } + return fastForwardFromEdits(dt, sessions, listener); + } + + /** + * This function will fast forward the server database to have the latest + * transactions in it. This is the same as restore, but only reads from + * the transaction logs and not restores from a snapshot. + * @param dt the datatree to write transactions to. + * @param sessions the sessions to be restored. + * @param listener the playback listener to run on the + * database transactions. + * @return the highest zxid restored. + * @throws IOException + */ + public long fastForwardFromEdits(DataTree dt, Map sessions, + PlayBackListener listener) throws IOException { TxnIterator itr = txnLog.read(dt.lastProcessedZxid+1); long highestZxid = dt.lastProcessedZxid; TxnHeader hdr; @@ -172,9 +242,8 @@ public long restore(DataTree dt, Map sessions, return dt.lastProcessedZxid; } if (hdr.getZxid() < highestZxid && highestZxid != 0) { - LOG.error("{}(higestZxid) > {}(next log) for type {}", - new Object[] { highestZxid, hdr.getZxid(), - hdr.getType() }); + LOG.error("{}(highestZxid) > {}(next log) for type {}", + highestZxid, hdr.getZxid(), hdr.getType()); } else { highestZxid = hdr.getZxid(); } @@ -286,7 +355,7 @@ public long getLastLoggedZxid() { /** * save the datatree and the sessions into a snapshot * @param dataTree the datatree to be serialized onto disk - * @param sessionsWithTimeouts the sesssion timeouts to be + * @param sessionsWithTimeouts the session timeouts to be * serialized onto disk * @throws IOException */ @@ -352,8 +421,10 @@ public List findNRecentSnapshots(int n) throws IOException { } /** - * get the snapshot logs that are greater than - * the given zxid + * get the snapshot logs which may contain transactions newer than the given zxid. + * This includes logs with starting zxid greater than given zxid, as well as the + * newest transaction log with starting zxid less than given zxid. The latter log + * file may contain transactions beyond given zxid. * @param zxid the zxid that contains logs greater than * zxid * @return @@ -380,6 +451,14 @@ public void commit() throws IOException { txnLog.commit(); } + /** + * + * @return elapsed sync time of transaction log commit in milliseconds + */ + public long getTxnLogElapsedSyncTime() { + return txnLog.getTxnLogSyncElapsedTime(); + } + /** * roll the transaction logs * @throws IOException @@ -406,4 +485,18 @@ public DatadirException(String msg, Exception e) { super(msg, e); } } + + @SuppressWarnings("serial") + public static class LogDirContentCheckException extends DatadirException { + public LogDirContentCheckException(String msg) { + super(msg); + } + } + + @SuppressWarnings("serial") + public static class SnapDirContentCheckException extends DatadirException { + public SnapDirContentCheckException(String msg) { + super(msg); + } + } } diff --git a/src/java/main/org/apache/zookeeper/server/persistence/TxnLog.java b/src/java/main/org/apache/zookeeper/server/persistence/TxnLog.java index 2b32d525cd9..90e0f6f2cfb 100644 --- a/src/java/main/org/apache/zookeeper/server/persistence/TxnLog.java +++ b/src/java/main/org/apache/zookeeper/server/persistence/TxnLog.java @@ -77,11 +77,17 @@ public interface TxnLog { long getDbId() throws IOException; /** - * commmit the trasaction and make sure + * commit the transaction and make sure * they are persisted * @throws IOException */ void commit() throws IOException; + + /** + * + * @return transaction log's elapsed sync time in milliseconds + */ + long getTxnLogSyncElapsedTime(); /** * close the transactions logs diff --git a/src/java/main/org/apache/zookeeper/server/persistence/TxnLogToolkit.java b/src/java/main/org/apache/zookeeper/server/persistence/TxnLogToolkit.java new file mode 100644 index 00000000000..887d98bad8f --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/persistence/TxnLogToolkit.java @@ -0,0 +1,315 @@ +/** + * 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.persistence; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.server.TraceFormatter; +import org.apache.zookeeper.server.util.SerializeUtils; +import org.apache.zookeeper.txn.TxnHeader; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.util.Date; +import java.util.Scanner; +import java.util.zip.Adler32; +import java.util.zip.Checksum; + +import static org.apache.zookeeper.server.persistence.FileTxnLog.TXNLOG_MAGIC; + +public class TxnLogToolkit implements Closeable { + + static class TxnLogToolkitException extends Exception { + private static final long serialVersionUID = 1L; + private int exitCode; + + TxnLogToolkitException(int exitCode, String message, Object... params) { + super(String.format(message, params)); + this.exitCode = exitCode; + } + + int getExitCode() { + return exitCode; + } + } + + static class TxnLogToolkitParseException extends TxnLogToolkitException { + private static final long serialVersionUID = 1L; + private Options options; + + TxnLogToolkitParseException(Options options, int exitCode, String message, Object... params) { + super(exitCode, message, params); + this.options = options; + } + + Options getOptions() { + return options; + } + } + + private File txnLogFile; + private boolean recoveryMode = false; + private boolean verbose = false; + private FileInputStream txnFis; + private BinaryInputArchive logStream; + + // Recovery mode + private int crcFixed = 0; + private FileOutputStream recoveryFos; + private BinaryOutputArchive recoveryOa; + private File recoveryLogFile; + private FilePadding filePadding = new FilePadding(); + private boolean force = false; + + /** + * @param args Command line arguments + */ + public static void main(String[] args) throws Exception { + try (final TxnLogToolkit lt = parseCommandLine(args)) { + lt.dump(new Scanner(System.in)); + lt.printStat(); + } catch (TxnLogToolkitParseException e) { + System.err.println(e.getMessage() + "\n"); + printHelpAndExit(e.getExitCode(), e.getOptions()); + } catch (TxnLogToolkitException e) { + System.err.println(e.getMessage()); + System.exit(e.getExitCode()); + } + } + + public TxnLogToolkit(boolean recoveryMode, boolean verbose, String txnLogFileName, boolean force) + throws FileNotFoundException, TxnLogToolkitException { + this.recoveryMode = recoveryMode; + this.verbose = verbose; + this.force = force; + txnLogFile = new File(txnLogFileName); + if (!txnLogFile.exists() || !txnLogFile.canRead()) { + throw new TxnLogToolkitException(1, "File doesn't exist or not readable: %s", txnLogFile); + } + if (recoveryMode) { + recoveryLogFile = new File(txnLogFile.toString() + ".fixed"); + if (recoveryLogFile.exists()) { + throw new TxnLogToolkitException(1, "Recovery file %s already exists or not writable", recoveryLogFile); + } + } + + openTxnLogFile(); + if (recoveryMode) { + openRecoveryFile(); + } + } + + public void dump(Scanner scanner) throws Exception { + crcFixed = 0; + + FileHeader fhdr = new FileHeader(); + fhdr.deserialize(logStream, "fileheader"); + if (fhdr.getMagic() != TXNLOG_MAGIC) { + throw new TxnLogToolkitException(2, "Invalid magic number for %s", txnLogFile.getName()); + } + System.out.println("ZooKeeper Transactional Log File with dbid " + + fhdr.getDbid() + " txnlog format version " + + fhdr.getVersion()); + + if (recoveryMode) { + fhdr.serialize(recoveryOa, "fileheader"); + recoveryFos.flush(); + filePadding.setCurrentSize(recoveryFos.getChannel().position()); + } + + int count = 0; + while (true) { + long crcValue; + byte[] bytes; + try { + crcValue = logStream.readLong("crcvalue"); + bytes = logStream.readBuffer("txnEntry"); + } catch (EOFException e) { + System.out.println("EOF reached after " + count + " txns."); + return; + } + if (bytes.length == 0) { + // Since we preallocate, we define EOF to be an + // empty transaction + System.out.println("EOF reached after " + count + " txns."); + return; + } + Checksum crc = new Adler32(); + crc.update(bytes, 0, bytes.length); + if (crcValue != crc.getValue()) { + if (recoveryMode) { + if (!force) { + printTxn(bytes, "CRC ERROR"); + if (askForFix(scanner)) { + crcValue = crc.getValue(); + ++crcFixed; + } + } else { + crcValue = crc.getValue(); + printTxn(bytes, "CRC FIXED"); + ++crcFixed; + } + } else { + printTxn(bytes, "CRC ERROR"); + } + } + if (!recoveryMode || verbose) { + printTxn(bytes); + } + if (logStream.readByte("EOR") != 'B') { + throw new TxnLogToolkitException(1, "Last transaction was partial."); + } + if (recoveryMode) { + filePadding.padFile(recoveryFos.getChannel()); + recoveryOa.writeLong(crcValue, "crcvalue"); + recoveryOa.writeBuffer(bytes, "txnEntry"); + recoveryOa.writeByte((byte)'B', "EOR"); + } + count++; + } + } + + private boolean askForFix(Scanner scanner) throws TxnLogToolkitException { + while (true) { + System.out.print("Would you like to fix it (Yes/No/Abort) ? "); + char answer = Character.toUpperCase(scanner.next().charAt(0)); + switch (answer) { + case 'Y': + return true; + case 'N': + return false; + case 'A': + throw new TxnLogToolkitException(0, "Recovery aborted."); + } + } + } + + private void printTxn(byte[] bytes) throws IOException { + printTxn(bytes, ""); + } + + private void printTxn(byte[] bytes, String prefix) throws IOException { + TxnHeader hdr = new TxnHeader(); + Record txn = SerializeUtils.deserializeTxn(bytes, hdr); + String txns = String.format("%s session 0x%s cxid 0x%s zxid 0x%s %s %s", + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG).format(new Date(hdr.getTime())), + Long.toHexString(hdr.getClientId()), + Long.toHexString(hdr.getCxid()), + Long.toHexString(hdr.getZxid()), + TraceFormatter.op2String(hdr.getType()), + txn); + if (prefix != null && !"".equals(prefix.trim())) { + System.out.print(prefix + " - "); + } + if (txns.endsWith("\n")) { + System.out.print(txns); + } else { + System.out.println(txns); + } + } + + private void openTxnLogFile() throws FileNotFoundException { + txnFis = new FileInputStream(txnLogFile); + logStream = BinaryInputArchive.getArchive(txnFis); + } + + private void closeTxnLogFile() throws IOException { + if (txnFis != null) { + txnFis.close(); + } + } + + private void openRecoveryFile() throws FileNotFoundException { + recoveryFos = new FileOutputStream(recoveryLogFile); + recoveryOa = BinaryOutputArchive.getArchive(recoveryFos); + } + + private void closeRecoveryFile() throws IOException { + if (recoveryFos != null) { + recoveryFos.close(); + } + } + + private static TxnLogToolkit parseCommandLine(String[] args) throws TxnLogToolkitException, FileNotFoundException { + CommandLineParser parser = new PosixParser(); + Options options = new Options(); + + Option helpOpt = new Option("h", "help", false, "Print help message"); + options.addOption(helpOpt); + + Option recoverOpt = new Option("r", "recover", false, "Recovery mode. Re-calculate CRC for broken entries."); + options.addOption(recoverOpt); + + Option quietOpt = new Option("v", "verbose", false, "Be verbose in recovery mode: print all entries, not just fixed ones."); + options.addOption(quietOpt); + + Option dumpOpt = new Option("d", "dump", false, "Dump mode. Dump all entries of the log file. (this is the default)"); + options.addOption(dumpOpt); + + Option forceOpt = new Option("y", "yes", false, "Non-interactive mode: repair all CRC errors without asking"); + options.addOption(forceOpt); + + try { + CommandLine cli = parser.parse(options, args); + if (cli.hasOption("help")) { + printHelpAndExit(0, options); + } + if (cli.getArgs().length < 1) { + printHelpAndExit(1, options); + } + return new TxnLogToolkit(cli.hasOption("recover"), cli.hasOption("verbose"), cli.getArgs()[0], cli.hasOption("yes")); + } catch (ParseException e) { + throw new TxnLogToolkitParseException(options, 1, e.getMessage()); + } + } + + private static void printHelpAndExit(int exitCode, Options options) { + HelpFormatter help = new HelpFormatter(); + help.printHelp(120,"TxnLogToolkit [-dhrv] ", "", options, ""); + System.exit(exitCode); + } + + private void printStat() { + if (recoveryMode) { + System.out.printf("Recovery file %s has been written with %d fixed CRC error(s)%n", recoveryLogFile, crcFixed); + } + } + + @Override + public void close() throws IOException { + if (recoveryMode) { + closeRecoveryFile(); + } + closeTxnLogFile(); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/persistence/Util.java b/src/java/main/org/apache/zookeeper/server/persistence/Util.java index 7ef7f9c4144..8efc7722df9 100644 --- a/src/java/main/org/apache/zookeeper/server/persistence/Util.java +++ b/src/java/main/org/apache/zookeeper/server/persistence/Util.java @@ -21,7 +21,6 @@ import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.io.Serializable; @@ -51,7 +50,6 @@ public class Util { private static final String SNAP_DIR="snapDir"; private static final String LOG_DIR="logDir"; private static final String DB_FORMAT_CONV="dbFormatConversion"; - private static final ByteBuffer fill = ByteBuffer.allocateDirect(1); public static String makeURIString(String dataDir, String dataLogDir, String convPolicy){ @@ -81,11 +79,11 @@ public static URI makeFileLoggerURL(File dataDir, File dataLogDir,String convPol /** * Creates a valid transaction log file name. * - * @param zxid used as a file name suffix (extention) + * @param zxid used as a file name suffix (extension) * @return file name */ public static String makeLogName(long zxid) { - return "log." + Long.toHexString(zxid); + return FileTxnLog.LOG_FILE_PREFIX + "." + Long.toHexString(zxid); } /** @@ -95,7 +93,7 @@ public static String makeLogName(long zxid) { * @return file name */ public static String makeSnapshotName(long zxid) { - return "snapshot." + Long.toHexString(zxid); + return FileSnap.SNAPSHOT_FILE_PREFIX + "." + Long.toHexString(zxid); } /** @@ -130,7 +128,7 @@ public static String getFormatConversionPolicy(Properties props){ /** * Extracts zxid from the file name. The file name should have been created - * using one of the {@link makeLogName} or {@link makeSnapshotName}. + * using one of the {@link #makeLogName(long)} or {@link #makeSnapshotName(long)}. * * @param name the file name to parse * @param prefix the file name prefix (snapshot or log) @@ -159,14 +157,13 @@ public static long getZxidFromName(String name, String prefix) { * @throws IOException */ public static boolean isValidSnapshot(File f) throws IOException { - if (f==null || Util.getZxidFromName(f.getName(), "snapshot") == -1) + if (f==null || Util.getZxidFromName(f.getName(), FileSnap.SNAPSHOT_FILE_PREFIX) == -1) return false; // Check for a valid snapshot - RandomAccessFile raf = new RandomAccessFile(f, "r"); - try { + try (RandomAccessFile raf = new RandomAccessFile(f, "r")) { // including the header and the last / bytes - // the snapshot should be atleast 10 bytes + // the snapshot should be at least 10 bytes if (raf.length() < 10) { return false; } @@ -174,8 +171,8 @@ public static boolean isValidSnapshot(File f) throws IOException { byte bytes[] = new byte[5]; int readlen = 0; int l; - while(readlen < 5 && - (l = raf.read(bytes, readlen, bytes.length - readlen)) >= 0) { + while (readlen < 5 && + (l = raf.read(bytes, readlen, bytes.length - readlen)) >= 0) { readlen += l; } if (readlen != bytes.length) { @@ -191,36 +188,11 @@ public static boolean isValidSnapshot(File f) throws IOException { + " byte = " + (b & 0xff)); return false; } - } finally { - raf.close(); } return true; } - /** - * Grows the file to the specified number of bytes. This only happenes if - * the current file position is sufficiently close (less than 4K) to end of - * file. - * - * @param f output stream to pad - * @param currentSize application keeps track of the cuurent file size - * @param preAllocSize how many bytes to pad - * @return the new file size. It can be the same as currentSize if no - * padding was done. - * @throws IOException - */ - public static long padLogFile(FileOutputStream f,long currentSize, - long preAllocSize) throws IOException{ - long position = f.getChannel().position(); - if (position + 4096 >= currentSize) { - currentSize = currentSize + preAllocSize; - fill.position(0); - f.getChannel().write(fill, currentSize-fill.remaining()); - } - return currentSize; - } - /** * Reads a transaction entry from the input archive. * @param ia archive to read from @@ -269,7 +241,7 @@ public static byte[] marshallTxnEntry(TxnHeader hdr, Record txn) * Write the serialized transaction record to the output archive. * * @param oa output archive - * @param bytes serialized trasnaction record + * @param bytes serialized transaction record * @throws IOException */ public static void writeTxnBytes(OutputArchive oa, byte[] bytes) @@ -322,5 +294,25 @@ public static List sortDataDir(File[] files, String prefix, boolean ascend Collections.sort(filelist, new DataDirFileComparator(prefix, ascending)); return filelist; } + + /** + * Returns true if fileName is a log file name. + * + * @param fileName + * @return + */ + public static boolean isLogFileName(String fileName) { + return fileName.startsWith(FileTxnLog.LOG_FILE_PREFIX + "."); + } + + /** + * Returns true if fileName is a snapshot file name. + * + * @param fileName + * @return + */ + public static boolean isSnapshotFileName(String fileName) { + return fileName.startsWith(FileSnap.SNAPSHOT_FILE_PREFIX + "."); + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/AuthFastLeaderElection.java b/src/java/main/org/apache/zookeeper/server/quorum/AuthFastLeaderElection.java index 6cd0af88292..0f8c9c1ec88 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/AuthFastLeaderElection.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/AuthFastLeaderElection.java @@ -36,6 +36,7 @@ import java.util.Random; import java.util.concurrent.atomic.AtomicLong; +import org.apache.zookeeper.common.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -417,7 +418,7 @@ class WorkerSender extends ZooKeeperThread { super("WorkerSender"); maxAttempts = attempts; rand = new Random(java.lang.Thread.currentThread().getId() - + System.currentTimeMillis()); + + Time.currentElapsedTime()); } long genChallenge() { diff --git a/src/java/main/org/apache/zookeeper/server/quorum/CommitProcessor.java b/src/java/main/org/apache/zookeeper/server/quorum/CommitProcessor.java index bf90d591986..e87f3591b76 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/CommitProcessor.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/CommitProcessor.java @@ -29,6 +29,7 @@ import org.apache.zookeeper.server.RequestProcessor; import org.apache.zookeeper.server.WorkerService; import org.apache.zookeeper.server.ZooKeeperCriticalThread; +import org.apache.zookeeper.server.ZooKeeperServerListener; /** * This RequestProcessor matches the incoming committed requests with the @@ -105,13 +106,13 @@ public class CommitProcessor extends ZooKeeperCriticalThread implements /** * This flag indicates whether we need to wait for a response to come back from the * leader or we just let the sync operation flow through like a read. The flag will - * be true if the CommitProcessor is in a Leader pipeline. + * be false if the CommitProcessor is in a Leader pipeline. */ boolean matchSyncs; public CommitProcessor(RequestProcessor nextProcessor, String id, - boolean matchSyncs) { - super("CommitProcessor:" + id); + boolean matchSyncs, ZooKeeperServerListener listener) { + super("CommitProcessor:" + id, listener); this.nextProcessor = nextProcessor; this.matchSyncs = matchSyncs; } @@ -132,7 +133,10 @@ protected boolean needCommit(Request request) { switch (request.type) { case OpCode.create: case OpCode.create2: + case OpCode.createTTL: + case OpCode.createContainer: case OpCode.delete: + case OpCode.deleteContainer: case OpCode.setData: case OpCode.reconfig: case OpCode.multi: @@ -184,10 +188,8 @@ public void run() { */ processCommitted(); } - } catch (InterruptedException e) { - LOG.warn("Interrupted exception while waiting", e); } catch (Throwable e) { - LOG.error("Unexpected exception causing CommitProcessor to exit", e); + handleException(this.getName(), e); } LOG.info("CommitProcessor exited loop!"); } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/FastLeaderElection.java b/src/java/main/org/apache/zookeeper/server/quorum/FastLeaderElection.java index dfe692f4889..e3cb0454cc5 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/FastLeaderElection.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/FastLeaderElection.java @@ -22,12 +22,12 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.jmx.MBeanRegistry; import org.apache.zookeeper.server.ZooKeeperThread; import org.apache.zookeeper.server.quorum.QuorumCnxManager.Message; @@ -293,14 +293,19 @@ public void run() { LOG.info("{} Received version: {} my version: {}", self.getId(), Long.toHexString(rqv.getVersion()), Long.toHexString(self.getQuorumVerifier().getVersion())); - self.processReconfig(rqv, null, null, false); - if (!rqv.equals(curQV)) { - LOG.info("restarting leader election"); - self.shuttingDownLE = true; - self.getElectionAlg().shutdown(); - - break; - } + if (self.getPeerState() == ServerState.LOOKING) { + LOG.debug("Invoking processReconfig(), state: {}", self.getServerState()); + self.processReconfig(rqv, null, null, false); + if (!rqv.equals(curQV)) { + LOG.info("restarting leader election"); + self.shuttingDownLE = true; + self.getElectionAlg().shutdown(); + + break; + } + } else { + LOG.debug("Skip processReconfig(), state: {}", self.getServerState()); + } } } catch (IOException e) { LOG.error("Something went wrong while processing config received from {}", response.sid); @@ -316,7 +321,7 @@ public void run() { * If it is from a non-voting server (such as an observer or * a non-voting follower), respond right away. */ - if(!self.getCurrentAndNextConfigVoters().contains(response.sid)) { + if(!validVoter(response.sid)) { Vote current = self.getCurrentVote(); QuorumVerifier qv = self.getQuorumVerifier(); ToSend notmsg = new ToSend(ToSend.mType.notification, @@ -871,7 +876,7 @@ public Vote lookForLeader() throws InterruptedException { self.jmxLeaderElectionBean = null; } if (self.start_fle == 0) { - self.start_fle = System.currentTimeMillis(); + self.start_fle = Time.currentElapsedTime(); } try { HashMap recvset = new HashMap(); @@ -921,10 +926,10 @@ public Vote lookForLeader() throws InterruptedException { tmpTimeOut : maxNotificationInterval); LOG.info("Notification time out: " + notTimeout); } - else if (self.getCurrentAndNextConfigVoters().contains(n.sid)) { + else if (validVoter(n.sid) && validVoter(n.leader)) { /* * Only proceed if the vote comes from a replica in the current or next - * voting view. + * voting view for a replica in the current or next voting view. */ switch (n.state) { case LOOKING: @@ -1050,7 +1055,12 @@ && checkLeader(outofelection, n.leader, IGNOREVALUE)) { break; } } else { - LOG.warn("Ignoring notification from non-cluster member " + n.sid); + if (!validVoter(n.leader)) { + LOG.warn("Ignoring notification for non-cluster member sid {} from sid {}", n.leader, n.sid); + } + if (!validVoter(n.sid)) { + LOG.warn("Ignoring notification for sid {} from non-quorum member sid {}", n.leader, n.sid); + } } } return null; @@ -1064,6 +1074,19 @@ && checkLeader(outofelection, n.leader, IGNOREVALUE)) { LOG.warn("Failed to unregister with JMX", e); } self.jmxLeaderElectionBean = null; + LOG.debug("Number of connection processing threads: {}", + manager.getConnectionThreadCount()); } } + + /** + * Check if a given sid is represented in either the current or + * the next voting view + * + * @param sid Server identifier + * @return boolean + */ + private boolean validVoter(long sid) { + return self.getCurrentAndNextConfigVoters().contains(sid); + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/Follower.java b/src/java/main/org/apache/zookeeper/server/quorum/Follower.java index 6dbb0b22a4e..65086017aac 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/Follower.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/Follower.java @@ -24,8 +24,10 @@ import org.apache.jute.Record; import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.server.Request; import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; +import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; import org.apache.zookeeper.server.util.SerializeUtils; import org.apache.zookeeper.server.util.ZxidUtils; import org.apache.zookeeper.txn.SetDataTxn; @@ -61,16 +63,18 @@ public String toString() { * @throws InterruptedException */ void followLeader() throws InterruptedException { - self.end_fle = System.currentTimeMillis(); - LOG.info("FOLLOWING - LEADER ELECTION TOOK - " + - (self.end_fle - self.start_fle)); + self.end_fle = Time.currentElapsedTime(); + long electionTimeTaken = self.end_fle - self.start_fle; + self.setElectionTimeTaken(electionTimeTaken); + LOG.info("FOLLOWING - LEADER ELECTION TOOK - {} {}", electionTimeTaken, + QuorumPeer.FLE_TIME_UNIT); self.start_fle = 0; self.end_fle = 0; fzk.registerJMX(new FollowerBean(this, zk), self.jmxLocalPeerBean); try { - InetSocketAddress addr = findLeader(); + QuorumServer leaderServer = findLeader(); try { - connectToLeader(addr); + connectToLeader(leaderServer.addr, leaderServer.hostname); long newEpochZxid = registerWithLeader(Leader.FOLLOWERINFO); if (self.isReconfigStateChange()) throw new Exception("learned about role change"); @@ -84,7 +88,7 @@ void followLeader() throws InterruptedException { } syncWithLeader(newEpochZxid); QuorumPacket qp = new QuorumPacket(); - while (self.isRunning()) { + while (this.isRunning()) { readPacket(qp); processPacket(qp); } @@ -164,7 +168,7 @@ protected void processPacket(QuorumPacket qp) throws Exception{ fzk.sync(); break; default: - LOG.warn("unknown type " + qp.getType()); + LOG.warn("Unknown packet type: {}", LearnerHandler.packetToString(qp)); break; } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/FollowerBean.java b/src/java/main/org/apache/zookeeper/server/quorum/FollowerBean.java index fd31fa2b6ef..8773ab85c90 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/FollowerBean.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/FollowerBean.java @@ -22,7 +22,7 @@ import org.apache.zookeeper.server.ZooKeeperServerBean; /** - * Follower MBean inteface implementation + * Follower MBean interface implementation */ public class FollowerBean extends ZooKeeperServerBean implements FollowerMXBean { private final Follower follower; @@ -47,4 +47,9 @@ public String getLastQueuedZxid() { public int getPendingRevalidationCount() { return follower.getPendingRevalidationsCount(); } + + @Override + public long getElectionTimeTaken() { + return follower.self.getElectionTimeTaken(); + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/FollowerMXBean.java b/src/java/main/org/apache/zookeeper/server/quorum/FollowerMXBean.java index ded0e1ca0e1..45c7fd8e2e7 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/FollowerMXBean.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/FollowerMXBean.java @@ -38,4 +38,9 @@ public interface FollowerMXBean extends ZooKeeperServerMXBean { * @return count of pending revalidations */ public int getPendingRevalidationCount(); + + /** + * @return time taken for leader election in milliseconds. + */ + public long getElectionTimeTaken(); } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/FollowerRequestProcessor.java b/src/java/main/org/apache/zookeeper/server/quorum/FollowerRequestProcessor.java index 8b16d3f78ff..c623eba888c 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/FollowerRequestProcessor.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/FollowerRequestProcessor.java @@ -49,7 +49,8 @@ public class FollowerRequestProcessor extends ZooKeeperCriticalThread implements public FollowerRequestProcessor(FollowerZooKeeperServer zks, RequestProcessor nextProcessor) { - super("FollowerRequestProcessor:" + zks.getServerId()); + super("FollowerRequestProcessor:" + zks.getServerId(), zks + .getZooKeeperServerListener()); this.zks = zks; this.nextProcessor = nextProcessor; } @@ -83,7 +84,10 @@ public void run() { break; case OpCode.create: case OpCode.create2: + case OpCode.createTTL: + case OpCode.createContainer: case OpCode.delete: + case OpCode.deleteContainer: case OpCode.setData: case OpCode.reconfig: case OpCode.setACL: @@ -101,7 +105,7 @@ public void run() { } } } catch (Exception e) { - LOG.error("Unexpected exception causing exit", e); + handleException(this.getName(), e); } LOG.info("FollowerRequestProcessor exited loop!"); } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/FollowerZooKeeperServer.java b/src/java/main/org/apache/zookeeper/server/quorum/FollowerZooKeeperServer.java index 8a7522afd5c..f1649f10427 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/FollowerZooKeeperServer.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/FollowerZooKeeperServer.java @@ -69,7 +69,7 @@ public Follower getFollower(){ protected void setupRequestProcessors() { RequestProcessor finalProcessor = new FinalRequestProcessor(this); commitProcessor = new CommitProcessor(finalProcessor, - Long.toString(getServerId()), true); + Long.toString(getServerId()), true, getZooKeeperServerListener()); commitProcessor.start(); firstProcessor = new FollowerRequestProcessor(this, commitProcessor); ((FollowerRequestProcessor) firstProcessor).start(); diff --git a/src/java/main/org/apache/zookeeper/server/quorum/Leader.java b/src/java/main/org/apache/zookeeper/server/quorum/Leader.java index 20589045752..8d48bf4f73d 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/Leader.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/Leader.java @@ -19,6 +19,7 @@ package org.apache.zookeeper.server.quorum; import java.io.ByteArrayOutputStream; +import java.io.BufferedInputStream; import java.io.IOException; import java.net.BindException; import java.net.ServerSocket; @@ -33,19 +34,23 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import javax.security.sasl.SaslException; import org.apache.jute.BinaryOutputArchive; import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.server.FinalRequestProcessor; import org.apache.zookeeper.server.Request; import org.apache.zookeeper.server.RequestProcessor; -import org.apache.zookeeper.server.ZooKeeperThread; +import org.apache.zookeeper.server.ZooKeeperCriticalThread; import org.apache.zookeeper.server.quorum.QuorumPeer.LearnerType; import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; +import org.apache.zookeeper.server.util.SerializeUtils; import org.apache.zookeeper.server.util.ZxidUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,7 +96,8 @@ public String toString() { final QuorumPeer self; - private boolean quorumFormed = false; + // VisibleForTesting + protected boolean quorumFormed = false; // the follower acceptor thread volatile LearnerCnxAcceptor cnxAcceptor = null; @@ -100,6 +106,12 @@ public String toString() { private final HashSet learners = new HashSet(); + private final ProposalStats proposalStats; + + public ProposalStats getProposalStats() { + return proposalStats; + } + /** * Returns a copy of the current learner snapshot */ @@ -217,6 +229,7 @@ public boolean isQuorumSynced(QuorumVerifier qv) { Leader(QuorumPeer self,LeaderZooKeeperServer zk) throws IOException { this.self = self; + this.proposalStats = new ProposalStats(); try { if (self.getQuorumListenOnAllIPs()) { ss = new ServerSocket(self.getQuorumAddress().getPort()); @@ -345,13 +358,15 @@ public boolean isQuorumSynced(QuorumVerifier qv) { private final ConcurrentLinkedQueue toBeApplied = new ConcurrentLinkedQueue(); - private final Proposal newLeaderProposal = new Proposal(); + // VisibleForTesting + protected final Proposal newLeaderProposal = new Proposal(); - class LearnerCnxAcceptor extends ZooKeeperThread { + class LearnerCnxAcceptor extends ZooKeeperCriticalThread { private volatile boolean stop = false; public LearnerCnxAcceptor() { - super("LearnerCnxAcceptor-" + ss.getLocalSocketAddress()); + super("LearnerCnxAcceptor-" + ss.getLocalSocketAddress(), zk + .getZooKeeperServerListener()); } @Override @@ -364,7 +379,10 @@ public void run() { // in LearnerHandler switch to the syncLimit s.setSoTimeout(self.tickTime * self.initLimit); s.setTcpNoDelay(nodelay); - LearnerHandler fh = new LearnerHandler(s, Leader.this); + + BufferedInputStream is = new BufferedInputStream( + s.getInputStream()); + LearnerHandler fh = new LearnerHandler(s, is, Leader.this); fh.start(); } catch (SocketException e) { if (stop) { @@ -378,10 +396,13 @@ public void run() { } else { throw e; } + } catch (SaslException e){ + LOG.error("Exception while connecting to quorum learner", e); } } } catch (Exception e) { - LOG.warn("Exception while accepting follower", e); + LOG.warn("Exception while accepting follower", e.getMessage()); + handleException(this.getName(), e); } } @@ -405,9 +426,11 @@ public void halt() { * @throws InterruptedException */ void lead() throws IOException, InterruptedException { - self.end_fle = System.currentTimeMillis(); - LOG.info("LEADING - LEADER ELECTION TOOK - " + - (self.end_fle - self.start_fle)); + self.end_fle = Time.currentElapsedTime(); + long electionTimeTaken = self.end_fle - self.start_fle; + self.setElectionTimeTaken(electionTimeTaken); + LOG.info("LEADING - LEADER ELECTION TOOK - {} {}", electionTimeTaken, + QuorumPeer.FLE_TIME_UNIT); self.start_fle = 0; self.end_fle = 0; @@ -485,7 +508,7 @@ void lead() throws IOException, InterruptedException { self.setCurrentEpoch(epoch); try { - waitForNewLeaderAck(self.getId(), zk.getZxid(), LearnerType.PARTICIPANT); + waitForNewLeaderAck(self.getId(), zk.getZxid()); } catch (InterruptedException e) { shutdown("Waiting for a quorum of followers, only synced with sids: [ " + newLeaderProposal.ackSetsToString() + " ]"); @@ -531,7 +554,7 @@ void lead() throws IOException, InterruptedException { } if (!System.getProperty("zookeeper.leaderServes", "yes").equals("no")) { - self.cnxnFactory.setZooKeeperServer(zk); + self.setZooKeeperServer(zk); } self.adminServer.setZooKeeperServer(zk); @@ -546,15 +569,17 @@ void lead() throws IOException, InterruptedException { // We ping twice a tick, so we only update the tick every other // iteration boolean tickSkip = true; + // If not null then shutdown this leader + String shutdownMessage = null; while (true) { synchronized (this) { - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); long cur = start; long end = start + self.tickTime / 2; while (cur < end) { wait(end - cur); - cur = System.currentTimeMillis(); + cur = Time.currentElapsedTime(); } if (!tickSkip) { @@ -581,14 +606,19 @@ void lead() throws IOException, InterruptedException { } } + // check leader running status + if (!this.isRunning()) { + // set shutdown flag + shutdownMessage = "Unexpected internal error"; + break; + } + if (!tickSkip && !syncedAckSet.hasAllQuorums()) { // Lost quorum of last committed and/or last proposed - // config, shutdown - shutdown("Not sufficient followers synced, only synced with sids: [ " - + syncedAckSet.ackSetsToString() + " ]"); - // make sure the order is the same! - // the leader goes to looking - return; + // config, set shutdown flag + shutdownMessage = "Not sufficient followers synced, only synced with sids: [ " + + syncedAckSet.ackSetsToString() + " ]"; + break; } tickSkip = !tickSkip; } @@ -596,6 +626,10 @@ void lead() throws IOException, InterruptedException { f.ping(); } } + if (shutdownMessage != null) { + shutdown(shutdownMessage); + // leader goes in looking state + } } finally { zk.unregisterJMX(this); } @@ -621,15 +655,14 @@ void shutdown(String reason) { } // NIO should not accept conenctions - self.cnxnFactory.setZooKeeperServer(null); + self.setZooKeeperServer(null); self.adminServer.setZooKeeperServer(null); try { ss.close(); } catch (IOException e) { LOG.warn("Ignoring unexpected exception during close",e); } - // clear all the connections - self.cnxnFactory.closeAll(); + self.closeAllConnections(); // shutdown the previous zk if (zk != null) { zk.shutdown(); @@ -697,19 +730,20 @@ private long getDesignatedLeader(Proposal reconfigProposal, long zxid) { /** * @return True if committed, otherwise false. - * @param a proposal p **/ synchronized public boolean tryToCommit(Proposal p, long zxid, SocketAddress followerAddr) { // make sure that ops are committed in order. With reconfigurations it is now possible // that different operations wait for different sets of acks, and we still want to enforce // that they are committed in order. Currently we only permit one outstanding reconfiguration // such that the reconfiguration and subsequent outstanding ops proposed while the reconfig is - // pending all wait for a quorum of old and new config, so its not possible to get enough acks + // pending all wait for a quorum of old and new config, so it's not possible to get enough acks // for an operation without getting enough acks for preceding ops. But in the future if multiple // concurrent reconfigs are allowed, this can happen. if (outstandingProposals.containsKey(zxid - 1)) return false; - // getting a quorum from all necessary configurations + // in order to be committed, a proposal must be accepted by a quorum. + // + // getting a quorum from all necessary configurations. if (!p.hasAllQuorums()) { return false; } @@ -722,8 +756,6 @@ synchronized public boolean tryToCommit(Proposal p, long zxid, SocketAddress fol + (lastCommitted+1)); } - // in order to be committed, a proposal must be accepted by a quorum - outstandingProposals.remove(zxid); if (p.request != null) { @@ -745,7 +777,7 @@ synchronized public boolean tryToCommit(Proposal p, long zxid, SocketAddress fol QuorumVerifier newQV = p.qvAcksetPairs.get(p.qvAcksetPairs.size()-1).getQuorumVerifier(); self.processReconfig(newQV, designatedLeader, zk.getZxid(), true); - + if (designatedLeader != self.getId()) { allowedToCommit = false; } @@ -968,8 +1000,6 @@ public void commitAndActivate(long zxid, long designatedLeader) { /** * Create an inform packet and send it to all observers. - * @param zxid - * @param proposal */ public void inform(Proposal proposal) { QuorumPacket qp = new QuorumPacket(Leader.INFORM, proposal.request.zxid, @@ -980,8 +1010,6 @@ public void inform(Proposal proposal) { /** * Create an inform&activate packet and send it to all observers. - * @param zxid - * @param proposal */ public void informAndActivate(Proposal proposal, long designatedLeader) { byte[] proposalData = proposal.packet.getData(); @@ -1031,19 +1059,9 @@ public Proposal propose(Request request) throws XidRolloverException { throw new XidRolloverException(msg); } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos); - try { - request.getHdr().serialize(boa, "hdr"); - if (request.getTxn() != null) { - request.getTxn().serialize(boa, "txn"); - } - baos.close(); - } catch (IOException e) { - LOG.warn("This really should be impossible", e); - } - QuorumPacket pp = new QuorumPacket(Leader.PROPOSAL, request.zxid, - baos.toByteArray(), null); + byte[] data = SerializeUtils.serializeRequest(request); + proposalStats.setLastProposalSize(data.length); + QuorumPacket pp = new QuorumPacket(Leader.PROPOSAL, request.zxid, data, null); Proposal p = new Proposal(); p.packet = pp; @@ -1096,11 +1114,7 @@ synchronized public void processSync(LearnerSyncRequest r){ /** * Sends a sync message to the appropriate server - * - * @param f - * @param r */ - public void sendSync(LearnerSyncRequest r){ QuorumPacket qp = new QuorumPacket(Leader.SYNC, 0, null, null); r.fh.queuePacket(qp); @@ -1150,7 +1164,8 @@ synchronized public long startForwarding(LearnerHandler handler, return lastProposed; } - private final HashSet connectingFollowers = new HashSet(); + // VisibleForTesting + protected final Set connectingFollowers = new HashSet(); public long getEpochToPropose(long sid, long lastAcceptedEpoch) throws InterruptedException, IOException { synchronized(connectingFollowers) { if (!waitingForNewEpoch) { @@ -1159,7 +1174,9 @@ public long getEpochToPropose(long sid, long lastAcceptedEpoch) throws Interrupt if (lastAcceptedEpoch >= epoch) { epoch = lastAcceptedEpoch+1; } - connectingFollowers.add(sid); + if (isParticipant(sid)) { + connectingFollowers.add(sid); + } QuorumVerifier verifier = self.getQuorumVerifier(); if (connectingFollowers.contains(self.getId()) && verifier.containsQuorum(connectingFollowers)) { @@ -1167,12 +1184,12 @@ public long getEpochToPropose(long sid, long lastAcceptedEpoch) throws Interrupt self.setAcceptedEpoch(epoch); connectingFollowers.notifyAll(); } else { - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); long cur = start; long end = start + self.getInitLimit()*self.getTickTime(); while(waitingForNewEpoch && cur < end) { connectingFollowers.wait(end - cur); - cur = System.currentTimeMillis(); + cur = Time.currentElapsedTime(); } if (waitingForNewEpoch) { throw new InterruptedException("Timeout while waiting for epoch from quorum"); @@ -1182,8 +1199,10 @@ public long getEpochToPropose(long sid, long lastAcceptedEpoch) throws Interrupt } } - private final HashSet electingFollowers = new HashSet(); - private boolean electionFinished = false; + // VisibleForTesting + protected final Set electingFollowers = new HashSet(); + // VisibleForTesting + protected boolean electionFinished = false; public void waitForEpochAck(long id, StateSummary ss) throws IOException, InterruptedException { synchronized(electingFollowers) { if (electionFinished) { @@ -1197,19 +1216,21 @@ public void waitForEpochAck(long id, StateSummary ss) throws IOException, Interr + leaderStateSummary.getLastZxid() + " (last zxid)"); } - electingFollowers.add(id); + if (isParticipant(id)) { + electingFollowers.add(id); + } } QuorumVerifier verifier = self.getQuorumVerifier(); if (electingFollowers.contains(self.getId()) && verifier.containsQuorum(electingFollowers)) { electionFinished = true; electingFollowers.notifyAll(); } else { - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); long cur = start; long end = start + self.getInitLimit()*self.getTickTime(); while(!electionFinished && cur < end) { electingFollowers.wait(end - cur); - cur = System.currentTimeMillis(); + cur = Time.currentElapsedTime(); } if (!electionFinished) { throw new InterruptedException("Timeout while waiting for epoch to be acked by quorum"); @@ -1255,7 +1276,7 @@ private synchronized void startZkServer() { QuorumVerifier newQV = self.getLastSeenQuorumVerifier(); Long designatedLeader = getDesignatedLeader(newLeaderProposal, zk.getZxid()); - + self.processReconfig(newQV, designatedLeader, zk.getZxid(), true); if (designatedLeader != self.getId()) { allowedToCommit = false; @@ -1279,10 +1300,9 @@ private synchronized void startZkServer() { * sufficient acks. * * @param sid - * @param learnerType * @throws InterruptedException */ - public void waitForNewLeaderAck(long sid, long zxid, LearnerType learnerType) + public void waitForNewLeaderAck(long sid, long zxid) throws InterruptedException { synchronized (newLeaderProposal.qvAcksetPairs) { @@ -1310,12 +1330,12 @@ public void waitForNewLeaderAck(long sid, long zxid, LearnerType learnerType) quorumFormed = true; newLeaderProposal.qvAcksetPairs.notifyAll(); } else { - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); long cur = start; long end = start + self.getInitLimit() * self.getTickTime(); while (!quorumFormed && cur < end) { newLeaderProposal.qvAcksetPairs.wait(end - cur); - cur = System.currentTimeMillis(); + cur = Time.currentElapsedTime(); } if (!quorumFormed) { throw new InterruptedException( @@ -1374,4 +1394,12 @@ public static String getPacketType(int packetType) { return "UNKNOWN"; } } + + private boolean isRunning() { + return self.isRunning() && zk.isRunning(); + } + + private boolean isParticipant(long sid) { + return self.getQuorumVerifier().getVotingMembers().containsKey(sid); + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LeaderBean.java b/src/java/main/org/apache/zookeeper/server/quorum/LeaderBean.java index b5a3a10ecd0..9f5eb2409a9 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LeaderBean.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LeaderBean.java @@ -20,8 +20,6 @@ import org.apache.zookeeper.server.ZooKeeperServerBean; import org.apache.zookeeper.server.ZooKeeperServer; -import org.apache.zookeeper.server.quorum.LearnerHandler; -import org.apache.zookeeper.server.quorum.Leader; /** * Leader MBean interface implementation. @@ -50,4 +48,28 @@ public String followerInfo() { return sb.toString(); } + @Override + public long getElectionTimeTaken() { + return leader.self.getElectionTimeTaken(); + } + + @Override + public int getLastProposalSize() { + return leader.getProposalStats().getLastProposalSize(); + } + + @Override + public int getMinProposalSize() { + return leader.getProposalStats().getMinProposalSize(); + } + + @Override + public int getMaxProposalSize() { + return leader.getProposalStats().getMaxProposalSize(); + } + + @Override + public void resetProposalStatistics() { + leader.getProposalStats().reset(); + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LeaderMXBean.java b/src/java/main/org/apache/zookeeper/server/quorum/LeaderMXBean.java index bf08104ec7d..7a1a439fa02 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LeaderMXBean.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LeaderMXBean.java @@ -33,4 +33,29 @@ public interface LeaderMXBean extends ZooKeeperServerMXBean { * @return information on current followers */ public String followerInfo(); + + /** + * @return time taken for leader election in milliseconds. + */ + public long getElectionTimeTaken(); + + /** + * @return size of latest generated proposal + */ + public int getLastProposalSize(); + + /** + * @return size of smallest generated proposal + */ + public int getMinProposalSize(); + + /** + * @return size of largest generated proposal + */ + public int getMaxProposalSize(); + + /** + * Resets statistics of proposal size (min/max/last) + */ + public void resetProposalStatistics(); } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LeaderSessionTracker.java b/src/java/main/org/apache/zookeeper/server/quorum/LeaderSessionTracker.java index ff715f1017c..38bbfe8f69b 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LeaderSessionTracker.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LeaderSessionTracker.java @@ -27,6 +27,7 @@ import org.apache.zookeeper.KeeperException.SessionMovedException; import org.apache.zookeeper.KeeperException.UnknownSessionException; import org.apache.zookeeper.server.SessionTrackerImpl; +import org.apache.zookeeper.server.ZooKeeperServerListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,14 +47,15 @@ public class LeaderSessionTracker extends UpgradeableSessionTracker { public LeaderSessionTracker(SessionExpirer expirer, ConcurrentMap sessionsWithTimeouts, - int tickTime, long id, boolean localSessionsEnabled) { + int tickTime, long id, boolean localSessionsEnabled, + ZooKeeperServerListener listener) { this.globalSessionTracker = new SessionTrackerImpl( - expirer, sessionsWithTimeouts, tickTime, id); + expirer, sessionsWithTimeouts, tickTime, id, listener); this.localSessionsEnabled = localSessionsEnabled; if (this.localSessionsEnabled) { - createLocalSessionTracker(expirer, tickTime, id); + createLocalSessionTracker(expirer, tickTime, id, listener); } serverId = id; } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java b/src/java/main/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java index fbec77add25..4f8c095fd08 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java @@ -18,10 +18,9 @@ package org.apache.zookeeper.server.quorum; -import java.io.IOException; - import org.apache.zookeeper.KeeperException.SessionExpiredException; import org.apache.zookeeper.jmx.MBeanRegistry; +import org.apache.zookeeper.server.ContainerManager; import org.apache.zookeeper.server.DataTreeBean; import org.apache.zookeeper.server.FinalRequestProcessor; import org.apache.zookeeper.server.PrepRequestProcessor; @@ -31,6 +30,9 @@ import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + /** * * Just like the standard ZooKeeperServer. We just replace the request @@ -39,6 +41,8 @@ * FinalRequestProcessor */ public class LeaderZooKeeperServer extends QuorumZooKeeperServer { + private ContainerManager containerManager; // guarded by sync + CommitProcessor commitProcessor; @@ -62,7 +66,8 @@ protected void setupRequestProcessors() { RequestProcessor finalProcessor = new FinalRequestProcessor(this); RequestProcessor toBeAppliedProcessor = new Leader.ToBeAppliedRequestProcessor(finalProcessor, getLeader()); commitProcessor = new CommitProcessor(toBeAppliedProcessor, - Long.toString(getServerId()), false); + Long.toString(getServerId()), false, + getZooKeeperServerListener()); commitProcessor.start(); ProposalRequestProcessor proposalProcessor = new ProposalRequestProcessor(this, commitProcessor); @@ -70,6 +75,31 @@ protected void setupRequestProcessors() { prepRequestProcessor = new PrepRequestProcessor(this, proposalProcessor); prepRequestProcessor.start(); firstProcessor = new LeaderRequestProcessor(this, prepRequestProcessor); + + setupContainerManager(); + } + + private synchronized void setupContainerManager() { + containerManager = new ContainerManager(getZKDatabase(), prepRequestProcessor, + Integer.getInteger("znode.container.checkIntervalMs", (int) TimeUnit.MINUTES.toMillis(1)), + Integer.getInteger("znode.container.maxPerMinute", 10000) + ); + } + + @Override + public synchronized void startup() { + super.startup(); + if (containerManager != null) { + containerManager.start(); + } + } + + @Override + public synchronized void shutdown() { + if (containerManager != null) { + containerManager.stop(); + } + super.shutdown(); } @Override @@ -82,7 +112,8 @@ public int getGlobalOutstandingLimit() { public void createSessionTracker() { sessionTracker = new LeaderSessionTracker( this, getZKDatabase().getSessionWithTimeOuts(), - tickTime, self.getId(), self.areLocalSessionsEnabled()); + tickTime, self.getId(), self.areLocalSessionsEnabled(), + getZooKeeperServerListener()); } public boolean touch(long sess, int to) { diff --git a/src/java/main/org/apache/zookeeper/server/quorum/Learner.java b/src/java/main/org/apache/zookeeper/server/quorum/Learner.java index 4dd1e947357..d4c458fab5b 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/Learner.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/Learner.java @@ -28,7 +28,6 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; -import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; @@ -41,20 +40,18 @@ import org.apache.jute.Record; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.ZooDefs.OpCode; -import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.Request; import org.apache.zookeeper.server.ServerCnxn; import org.apache.zookeeper.server.ZooTrace; -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.QuorumVerifier; import org.apache.zookeeper.server.util.SerializeUtils; import org.apache.zookeeper.server.util.ZxidUtils; import org.apache.zookeeper.txn.SetDataTxn; import org.apache.zookeeper.txn.TxnHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class is the superclass of two of the three main actors in a ZK @@ -198,46 +195,89 @@ void request(Request request) throws IOException { /** * Returns the address of the node we think is the leader. */ - protected InetSocketAddress findLeader() { - InetSocketAddress addr = null; + protected QuorumServer findLeader() { + QuorumServer leaderServer = null; // Find the leader by id Vote current = self.getCurrentVote(); for (QuorumServer s : self.getView().values()) { if (s.id == current.getId()) { - addr = s.addr; + // Ensure we have the leader's correct IP address before + // attempting to connect. + s.recreateSocketAddresses(); + leaderServer = s; break; } } - if (addr == null) { + if (leaderServer == null) { LOG.warn("Couldn't find the leader with id = " + current.getId()); } - return addr; + return leaderServer; } - + + /** + * Overridable helper method to return the System.nanoTime(). + * This method behaves identical to System.nanoTime(). + */ + protected long nanoTime() { + return System.nanoTime(); + } + + /** + * Overridable helper method to simply call sock.connect(). This can be + * overriden in tests to fake connection success/failure for connectToLeader. + */ + protected void sockConnect(Socket sock, InetSocketAddress addr, int timeout) + throws IOException { + sock.connect(addr, timeout); + } + /** * Establish a connection with the Leader found by findLeader. Retries - * 5 times before giving up. + * until either initLimit time has elapsed or 5 tries have happened. * @param addr - the address of the Leader to connect to. * @throws IOException - if the socket connection fails on the 5th attempt + *
  • if there is an authentication failure while connecting to leader
  • * @throws ConnectException * @throws InterruptedException */ - protected void connectToLeader(InetSocketAddress addr) + protected void connectToLeader(InetSocketAddress addr, String hostname) throws IOException, ConnectException, InterruptedException { sock = new Socket(); sock.setSoTimeout(self.tickTime * self.initLimit); + + int initLimitTime = self.tickTime * self.initLimit; + int remainingInitLimitTime = initLimitTime; + long startNanoTime = nanoTime(); + for (int tries = 0; tries < 5; tries++) { try { - sock.connect(addr, self.tickTime * self.syncLimit); + // recalculate the init limit time because retries sleep for 1000 milliseconds + remainingInitLimitTime = initLimitTime - (int)((nanoTime() - startNanoTime) / 1000000); + if (remainingInitLimitTime <= 0) { + LOG.error("initLimit exceeded on retries."); + throw new IOException("initLimit exceeded on retries."); + } + + sockConnect(sock, addr, Math.min(self.tickTime * self.syncLimit, remainingInitLimitTime)); sock.setTcpNoDelay(nodelay); break; } catch (IOException e) { - if (tries == 4) { - LOG.error("Unexpected exception",e); + remainingInitLimitTime = initLimitTime - (int)((nanoTime() - startNanoTime) / 1000000); + + if (remainingInitLimitTime <= 1000) { + LOG.error("Unexpected exception, initLimit exceeded. tries=" + tries + + ", remaining init limit=" + remainingInitLimitTime + + ", connecting to " + addr,e); + throw e; + } else if (tries >= 4) { + LOG.error("Unexpected exception, retries exceeded. tries=" + tries + + ", remaining init limit=" + remainingInitLimitTime + + ", connecting to " + addr,e); throw e; } else { - LOG.warn("Unexpected exception, tries="+tries+ + LOG.warn("Unexpected exception, tries=" + tries + + ", remaining init limit=" + remainingInitLimitTime + ", connecting to " + addr,e); sock = new Socket(); sock.setSoTimeout(self.tickTime * self.initLimit); @@ -245,6 +285,9 @@ protected void connectToLeader(InetSocketAddress addr) } Thread.sleep(1000); } + + self.authLearner.authenticate(sock, hostname); + leaderIs = BinaryInputArchive.getArchive(new BufferedInputStream( sock.getInputStream())); bufferedOutput = new BufferedOutputStream(sock.getOutputStream()); @@ -324,23 +367,35 @@ protected void syncWithLeader(long newLeaderZxid) throws Exception{ QuorumVerifier newLeaderQV = null; - readPacket(qp); + // In the DIFF case we don't need to do a snapshot because the transactions will sync on top of any existing snapshot + // For SNAP and TRUNC the snapshot is needed to save that history + boolean snapshotNeeded = true; + readPacket(qp); LinkedList packetsCommitted = new LinkedList(); LinkedList packetsNotCommitted = new LinkedList(); synchronized (zk) { if (qp.getType() == Leader.DIFF) { - LOG.info("Getting a diff from the leader 0x" + Long.toHexString(qp.getZxid())); + LOG.info("Getting a diff from the leader 0x{}", Long.toHexString(qp.getZxid())); + snapshotNeeded = false; } else if (qp.getType() == Leader.SNAP) { - LOG.info("Getting a snapshot from leader"); + LOG.info("Getting a snapshot from leader 0x" + Long.toHexString(qp.getZxid())); // The leader is going to dump the database // db is clear as part of deserializeSnapshot() zk.getZKDatabase().deserializeSnapshot(leaderIs); + // ZOOKEEPER-2819: overwrite config node content extracted + // from leader snapshot with local config, to avoid potential + // inconsistency of config node content during rolling restart. + if (!QuorumPeerConfig.isReconfigEnabled()) { + LOG.debug("Reset config node content from local config after deserialization of snapshot."); + zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier()); + } String signature = leaderIs.readString("signature"); if (!signature.equals("BenWasHere")) { LOG.error("Missing signature. Got " + signature); throw new IOException("Missing signature"); } + zk.getZKDatabase().setlastProcessedZxid(qp.getZxid()); } else if (qp.getType() == Leader.TRUNC) { //we need to truncate the log to the lastzxid of the leader LOG.warn("Truncating log to get in sync with the leader 0x" @@ -352,24 +407,27 @@ else if (qp.getType() == Leader.SNAP) { + Long.toHexString(qp.getZxid())); System.exit(13); } + zk.getZKDatabase().setlastProcessedZxid(qp.getZxid()); } else { - LOG.error("Got unexpected packet from leader " - + qp.getType() + " exiting ... " ); + LOG.error("Got unexpected packet from leader: {}, exiting ... ", + LearnerHandler.packetToString(qp)); System.exit(13); } zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier()); - zk.getZKDatabase().setlastProcessedZxid(qp.getZxid()); zk.createSessionTracker(); long lastQueued = 0; - // in V1.0 we take a snapshot when we get the NEWLEADER message, but in pre V1.0 - // we take the snapshot at the UPDATE, since V1.0 also gets the UPDATE (after the NEWLEADER) + // in Zab V1.0 (ZK 3.4+) we might take a snapshot when we get the NEWLEADER message, but in pre V1.0 + // we take the snapshot on the UPDATE message, since Zab V1.0 also gets the UPDATE (after the NEWLEADER) // we need to make sure that we don't take the snapshot twice. - boolean snapshotTaken = false; + boolean isPreZAB1_0 = true; + //If we are not going to take the snapshot be sure the transactions are not applied in memory + // but written out to the transaction log + boolean writeToTxnLog = !snapshotNeeded; // we are now going to start getting transactions to apply followed by an UPTODATE outerLoop: while (self.isRunning()) { @@ -397,19 +455,19 @@ else if (qp.getType() == Leader.SNAP) { break; case Leader.COMMIT: case Leader.COMMITANDACTIVATE: - if (!snapshotTaken) { - pif = packetsNotCommitted.peekFirst(); + pif = packetsNotCommitted.peekFirst(); + if (pif.hdr.getZxid() == qp.getZxid() && qp.getType() == Leader.COMMITANDACTIVATE) { + QuorumVerifier qv = self.configFromString(new String(((SetDataTxn) pif.rec).getData())); + boolean majorChange = self.processReconfig(qv, ByteBuffer.wrap(qp.getData()).getLong(), + qp.getZxid(), true); + if (majorChange) { + throw new Exception("changes proposed in reconfig"); + } + } + if (!writeToTxnLog) { if (pif.hdr.getZxid() != qp.getZxid()) { LOG.warn("Committing " + qp.getZxid() + ", but next proposal is " + pif.hdr.getZxid()); } else { - if (qp.getType() == Leader.COMMITANDACTIVATE) { - QuorumVerifier qv = self.configFromString(new String(((SetDataTxn)pif.rec).getData())); - boolean majorChange = - self.processReconfig(qv, ByteBuffer.wrap(qp.getData()).getLong(), qp.getZxid(), true); - if (majorChange) { - throw new Exception("changes proposed in reconfig"); - } - } zk.processTxn(pif.hdr, pif.rec); packetsNotCommitted.remove(); } @@ -422,7 +480,7 @@ else if (qp.getType() == Leader.SNAP) { PacketInFlight packet = new PacketInFlight(); packet.hdr = new TxnHeader(); - if (qp.getType() == Leader.COMMITANDACTIVATE) { + if (qp.getType() == Leader.INFORMANDACTIVATE) { ByteBuffer buffer = ByteBuffer.wrap(qp.getData()); long suggestedLeaderId = buffer.getLong(); byte[] remainingdata = new byte[buffer.remaining()]; @@ -445,8 +503,7 @@ else if (qp.getType() == Leader.SNAP) { } lastQueued = packet.hdr.getZxid(); } - - if (!snapshotTaken) { + if (!writeToTxnLog) { // Apply to db directly if we haven't taken the snapshot zk.processTxn(packet.hdr, packet.rec); } else { @@ -464,14 +521,15 @@ else if (qp.getType() == Leader.SNAP) { throw new Exception("changes proposed in reconfig"); } } - if (!snapshotTaken) { // true for the pre v1.0 case - zk.takeSnapshot(); + if (isPreZAB1_0) { + zk.takeSnapshot(); self.setCurrentEpoch(newEpoch); } - self.cnxnFactory.setZooKeeperServer(zk); + self.setZooKeeperServer(zk); self.adminServer.setZooKeeperServer(zk); break outerLoop; - case Leader.NEWLEADER: // it will be NEWLEADER in v1.0 + case Leader.NEWLEADER: // Getting NEWLEADER here instead of in discovery + // means this is Zab 1.0 LOG.info("Learner received NEWLEADER message"); if (qp.getData()!=null && qp.getData().length > 1) { try { @@ -482,10 +540,14 @@ else if (qp.getType() == Leader.SNAP) { e.printStackTrace(); } } + + if (snapshotNeeded) { + zk.takeSnapshot(); + } - zk.takeSnapshot(); self.setCurrentEpoch(newEpoch); - snapshotTaken = true; + writeToTxnLog = true; //Anything after this needs to go to the transaction log, not applied directly in memory + isPreZAB1_0 = false; writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true); break; } @@ -580,14 +642,16 @@ protected void ping(QuorumPacket qp) throws IOException { * Shutdown the Peer */ public void shutdown() { - // set the zookeeper server to null - self.cnxnFactory.setZooKeeperServer(null); - // clear all the connections - self.cnxnFactory.closeAll(); + self.setZooKeeperServer(null); + self.closeAllConnections(); self.adminServer.setZooKeeperServer(null); // shutdown previous zookeeper if (zk != null) { zk.shutdown(); } } + + boolean isRunning() { + return self.isRunning() && zk.isRunning(); + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LearnerHandler.java b/src/java/main/org/apache/zookeeper/server/quorum/LearnerHandler.java index b5d0f0dcae4..f6c68b01355 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LearnerHandler.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LearnerHandler.java @@ -33,6 +33,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import javax.security.sasl.SaslException; + import org.apache.jute.BinaryInputArchive; import org.apache.jute.BinaryOutputArchive; import org.apache.jute.Record; @@ -155,6 +157,7 @@ public synchronized boolean check(long time) { private BinaryOutputArchive oa; + private final BufferedInputStream bufferedInput; private BufferedOutputStream bufferedOutput; /** @@ -179,11 +182,27 @@ public synchronized boolean check(long time) { */ private long leaderLastZxid; - LearnerHandler(Socket sock, Leader leader) throws IOException { + LearnerHandler(Socket sock, BufferedInputStream bufferedInput,Leader leader) throws IOException { super("LearnerHandler-" + sock.getRemoteSocketAddress()); this.sock = sock; this.leader = leader; - leader.addLearnerHandler(this); + this.bufferedInput = bufferedInput; + + try { + if (leader.self != null) { + leader.self.authServer.authenticate(sock, + new DataInputStream(bufferedInput)); + } + } catch (IOException e) { + LOG.error("Server failed to authenticate quorum learner, addr: {}, closing connection", + sock.getRemoteSocketAddress(), e); + try { + sock.close(); + } catch (IOException ie) { + LOG.error("Exception while closing socket", ie); + } + throw new SaslException("Authentication failure: " + e.getMessage()); + } } @Override @@ -255,11 +274,8 @@ private void sendPackets() throws InterruptedException { } static public String packetToString(QuorumPacket p) { - if (true) - return null; - String type = null; + String type; String mess = null; - Record txn = null; switch (p.getType()) { case Leader.ACK: @@ -281,7 +297,7 @@ static public String packetToString(QuorumPacket p) { type = "PROPOSAL"; TxnHeader hdr = new TxnHeader(); try { - txn = SerializeUtils.deserializeTxn(p.getData(), hdr); + SerializeUtils.deserializeTxn(p.getData(), hdr); // mess = "transaction: " + txn.toString(); } catch (IOException e) { LOG.warn("Unexpected exception",e); @@ -305,6 +321,30 @@ static public String packetToString(QuorumPacket p) { case Leader.UPTODATE: type = "UPTODATE"; break; + case Leader.DIFF: + type = "DIFF"; + break; + case Leader.TRUNC: + type = "TRUNC"; + break; + case Leader.SNAP: + type = "SNAP"; + break; + case Leader.ACKEPOCH: + type = "ACKEPOCH"; + break; + case Leader.SYNC: + type = "SYNC"; + break; + case Leader.INFORM: + type = "INFORM"; + break; + case Leader.COMMITANDACTIVATE: + type = "COMMITANDACTIVATE"; + break; + case Leader.INFORMANDACTIVATE: + type = "INFORMANDACTIVATE"; + break; default: type = "UNKNOWN" + p.getType(); } @@ -322,35 +362,35 @@ static public String packetToString(QuorumPacket p) { @Override public void run() { try { + leader.addLearnerHandler(this); tickOfNextAckDeadline = leader.self.tick.get() + leader.self.initLimit + leader.self.syncLimit; - ia = BinaryInputArchive.getArchive(new BufferedInputStream(sock - .getInputStream())); + ia = BinaryInputArchive.getArchive(bufferedInput); bufferedOutput = new BufferedOutputStream(sock.getOutputStream()); oa = BinaryOutputArchive.getArchive(bufferedOutput); QuorumPacket qp = new QuorumPacket(); ia.readRecord(qp, "packet"); if(qp.getType() != Leader.FOLLOWERINFO && qp.getType() != Leader.OBSERVERINFO){ - LOG.error("First packet " + qp.toString() + LOG.error("First packet " + qp.toString() + " is not FOLLOWERINFO or OBSERVERINFO!"); return; } - + byte learnerInfoData[] = qp.getData(); if (learnerInfoData != null) { ByteBuffer bbsid = ByteBuffer.wrap(learnerInfoData); if (learnerInfoData.length >= 8) { this.sid = bbsid.getLong(); - } + } if (learnerInfoData.length >= 12) { this.version = bbsid.getInt(); // protocolVersion } if (learnerInfoData.length >= 20) { long configVersion = bbsid.getLong(); if (configVersion > leader.self.getQuorumVerifier().getVersion()) { - throw new IOException("Follower is ahead of the leader (has a later activated configuration)"); + throw new IOException("Follower is ahead of the leader (has a later activated configuration)"); } } } else { @@ -459,14 +499,15 @@ public void run() { qp = new QuorumPacket(); ia.readRecord(qp, "packet"); if(qp.getType() != Leader.ACK){ - LOG.error("Next packet was supposed to be an ACK"); + LOG.error("Next packet was supposed to be an ACK," + + " but received packet: {}", packetToString(qp)); return; } if(LOG.isDebugEnabled()){ LOG.debug("Received NEWLEADER-ACK message from " + sid); } - leader.waitForNewLeaderAck(getSid(), qp.getZxid(), getLearnerType()); + leader.waitForNewLeaderAck(getSid(), qp.getZxid()); syncLimitCheck.start(); @@ -544,7 +585,8 @@ public void run() { // owns the session leader.zk.setOwner(id, this); } catch (SessionExpiredException e) { - LOG.error("Somehow session " + Long.toHexString(id) + " expired right after being renewed! (impossible)", e); + LOG.error("Somehow session " + Long.toHexString(id) + + " expired right after being renewed! (impossible)", e); } } if (LOG.isTraceEnabled()) { @@ -573,6 +615,8 @@ public void run() { leader.zk.submitLearnerRequest(si); break; default: + LOG.warn("unexpected quorum packet, type: {}", packetToString(qp)); + break; } } } catch (IOException e) { @@ -647,7 +691,7 @@ public boolean syncFollower(long peerLastZxid, ZKDatabase db, Leader leader) { // Keep track of the latest zxid which already queued long currentZxid = peerLastZxid; boolean needSnap = true; - boolean txnLogSyncEnabled = (db.getSnapshotSizeFactor() >= 0); + boolean txnLogSyncEnabled = db.isTxnLogSyncEnabled(); ReentrantReadWriteLock lock = db.getLogLock(); ReadLock rl = lock.readLock(); try { @@ -666,7 +710,7 @@ public boolean syncFollower(long peerLastZxid, ZKDatabase db, Leader leader) { if (db.getCommittedLog().isEmpty()) { /* - * It is possible that commitedLog is empty. In that case + * It is possible that committedLog is empty. In that case * setting these value to the latest txn in leader db * will reduce the case that we need to handle * @@ -686,7 +730,7 @@ public boolean syncFollower(long peerLastZxid, ZKDatabase db, Leader leader) { * 2. Peer and leader is already sync, send empty diff * 3. Follower has txn that we haven't seen. This may be old leader * so we need to send TRUNC. However, if peer has newEpochZxid, - * we cannot send TRUC since the follower has no txnlog + * we cannot send TRUNC since the follower has no txnlog * 4. Follower is within committedLog range or already in-sync. * We may need to send DIFF or TRUNC depending on follower's zxid * We always send empty DIFF if follower is already in-sync @@ -706,7 +750,7 @@ public boolean syncFollower(long peerLastZxid, ZKDatabase db, Leader leader) { needOpPacket = false; needSnap = false; } else if (peerLastZxid > maxCommittedLog && !isPeerNewEpochZxid) { - // Newer than commitedLog, send trunc and done + // Newer than committedLog, send trunc and done LOG.debug("Sending TRUNC to follower zxidToSend=0x" + Long.toHexString(maxCommittedLog) + " for peer sid:" + getSid()); @@ -775,12 +819,12 @@ public boolean syncFollower(long peerLastZxid, ZKDatabase db, Leader leader) { * @param itr iterator point to the proposals * @param peerLastZxid last zxid seen by the follower * @param maxZxid max zxid of the proposal to queue, null if no limit - * @param lastCommitedZxid when sending diff, we need to send lastCommitedZxid + * @param lastCommittedZxid when sending diff, we need to send lastCommittedZxid * on the leader to follow Zab 1.0 protocol. * @return last zxid of the queued proposal */ protected long queueCommittedProposals(Iterator itr, - long peerLastZxid, Long maxZxid, Long lastCommitedZxid) { + long peerLastZxid, Long maxZxid, Long lastCommittedZxid) { boolean isPeerNewEpochZxid = (peerLastZxid & 0xffffffffL) == 0; long queuedZxid = peerLastZxid; // as we look through proposals, this variable keeps track of previous @@ -808,9 +852,9 @@ protected long queueCommittedProposals(Iterator itr, // Send diff when we see the follower's zxid in our history if (packetZxid == peerLastZxid) { LOG.info("Sending DIFF zxid=0x" + - Long.toHexString(lastCommitedZxid) + + Long.toHexString(lastCommittedZxid) + " for peer sid: " + getSid()); - queueOpPacket(Leader.DIFF, lastCommitedZxid); + queueOpPacket(Leader.DIFF, lastCommittedZxid); needOpPacket = false; continue; } @@ -818,9 +862,9 @@ protected long queueCommittedProposals(Iterator itr, if (isPeerNewEpochZxid) { // Send diff and fall through if zxid is of a new-epoch LOG.info("Sending DIFF zxid=0x" + - Long.toHexString(lastCommitedZxid) + + Long.toHexString(lastCommittedZxid) + " for peer sid: " + getSid()); - queueOpPacket(Leader.DIFF, lastCommitedZxid); + queueOpPacket(Leader.DIFF, lastCommittedZxid); needOpPacket = false; } else if (packetZxid > peerLastZxid ) { // Peer have some proposals that the leader hasn't seen yet @@ -862,9 +906,9 @@ protected long queueCommittedProposals(Iterator itr, // is the catch when our history older than learner and there is // no new txn since then. So we need an empty diff LOG.info("Sending DIFF zxid=0x" + - Long.toHexString(lastCommitedZxid) + + Long.toHexString(lastCommittedZxid) + " for peer sid: " + getSid()); - queueOpPacket(Leader.DIFF, lastCommitedZxid); + queueOpPacket(Leader.DIFF, lastCommittedZxid); needOpPacket = false; } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LearnerSessionTracker.java b/src/java/main/org/apache/zookeeper/server/quorum/LearnerSessionTracker.java index eb176815adb..1cc2ab12957 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LearnerSessionTracker.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LearnerSessionTracker.java @@ -32,6 +32,7 @@ import org.apache.zookeeper.KeeperException.SessionMovedException; import org.apache.zookeeper.KeeperException.UnknownSessionException; import org.apache.zookeeper.server.SessionTrackerImpl; +import org.apache.zookeeper.server.ZooKeeperServerListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,7 +63,8 @@ public class LearnerSessionTracker extends UpgradeableSessionTracker { public LearnerSessionTracker(SessionExpirer expirer, ConcurrentMap sessionsWithTimeouts, - int tickTime, long id, boolean localSessionsEnabled) { + int tickTime, long id, boolean localSessionsEnabled, + ZooKeeperServerListener listener) { this.expirer = expirer; this.touchTable.set(new ConcurrentHashMap()); this.globalSessionsWithTimeouts = sessionsWithTimeouts; @@ -71,7 +73,7 @@ public LearnerSessionTracker(SessionExpirer expirer, this.localSessionsEnabled = localSessionsEnabled; if (this.localSessionsEnabled) { - createLocalSessionTracker(expirer, tickTime, id); + createLocalSessionTracker(expirer, tickTime, id, listener); } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LearnerSnapshotThrottler.java b/src/java/main/org/apache/zookeeper/server/quorum/LearnerSnapshotThrottler.java index 97b48915321..3542234b719 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LearnerSnapshotThrottler.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LearnerSnapshotThrottler.java @@ -18,8 +18,7 @@ package org.apache.zookeeper.server.quorum; -import java.util.concurrent.atomic.AtomicInteger; - +import org.apache.zookeeper.common.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -97,11 +96,11 @@ public LearnerSnapshot beginSnapshot(boolean essential) if (!essential && timeoutMillis > 0 && snapsInProgress >= maxConcurrentSnapshots) { - long timestamp = System.currentTimeMillis(); + long timestamp = Time.currentElapsedTime(); do { snapCountSyncObject.wait(timeoutMillis); } while (snapsInProgress >= maxConcurrentSnapshots - && timestamp + timeoutMillis < System.currentTimeMillis()); + && timestamp + timeoutMillis < Time.currentElapsedTime()); } if (essential || snapsInProgress < maxConcurrentSnapshots) { diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java b/src/java/main/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java index 22f9da0e70c..1d4e8c8ab27 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LearnerZooKeeperServer.java @@ -22,7 +22,6 @@ import java.util.Map; import org.apache.zookeeper.jmx.MBeanRegistry; -import org.apache.zookeeper.KeeperException.SessionExpiredException; import org.apache.zookeeper.server.DataTreeBean; import org.apache.zookeeper.server.quorum.LearnerSessionTracker; import org.apache.zookeeper.server.ServerCnxn; @@ -84,7 +83,8 @@ public long getServerId() { public void createSessionTracker() { sessionTracker = new LearnerSessionTracker( this, getZKDatabase().getSessionWithTimeOuts(), - this.tickTime, self.getId(), self.areLocalSessionsEnabled()); + this.tickTime, self.getId(), self.areLocalSessionsEnabled(), + getZooKeeperServerListener()); } @Override @@ -157,7 +157,11 @@ protected void unregisterJMX(Learner peer) { } @Override - public void shutdown() { + public synchronized void shutdown() { + if (!canShutdown()) { + LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!"); + return; + } LOG.info("Shutting down"); try { super.shutdown(); diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LocalPeerBean.java b/src/java/main/org/apache/zookeeper/server/quorum/LocalPeerBean.java index d200c5034d3..361eb9473c7 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LocalPeerBean.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LocalPeerBean.java @@ -18,7 +18,7 @@ package org.apache.zookeeper.server.quorum; -import org.apache.zookeeper.common.HostNameUtils; + /** * Implementation of the local peer MBean interface. @@ -67,7 +67,7 @@ public int getTick() { } public String getState() { - return peer.getState().toString(); + return peer.getServerState(); } public String getQuorumAddress() { @@ -79,13 +79,17 @@ public int getElectionType() { } public String getElectionAddress() { - return HostNameUtils.getHostString(peer.getElectionAddress()) + ":" - + peer.getElectionAddress().getPort(); + return peer.getElectionAddress().getHostString() + ":" + + peer.getElectionAddress().getPort(); } public String getClientAddress() { - return HostNameUtils.getHostString(peer.getClientAddress()) + ":" - + peer.getClientAddress().getPort(); + if (null != peer.cnxnFactory) { + return String.format("%s:%d", peer.cnxnFactory.getLocalAddress() + .getHostString(), peer.getClientPort()); + } else { + return ""; + } } public String getLearnerType(){ diff --git a/src/java/main/org/apache/zookeeper/server/quorum/LocalSessionTracker.java b/src/java/main/org/apache/zookeeper/server/quorum/LocalSessionTracker.java index ae7793aa270..df6ccb28655 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/LocalSessionTracker.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/LocalSessionTracker.java @@ -20,6 +20,7 @@ import java.util.concurrent.ConcurrentMap; import org.apache.zookeeper.server.SessionTrackerImpl; +import org.apache.zookeeper.server.ZooKeeperServerListener; /** * Local session tracker. @@ -27,8 +28,8 @@ public class LocalSessionTracker extends SessionTrackerImpl { public LocalSessionTracker(SessionExpirer expirer, ConcurrentMap sessionsWithTimeouts, - int tickTime, long id) { - super(expirer, sessionsWithTimeouts, tickTime, id); + int tickTime, long id, ZooKeeperServerListener listener) { + super(expirer, sessionsWithTimeouts, tickTime, id, listener); } public boolean isLocalSession(long sessionId) { diff --git a/src/java/main/org/apache/zookeeper/server/quorum/Observer.java b/src/java/main/org/apache/zookeeper/server/quorum/Observer.java index 2ca8d205834..f0f724e5e6f 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/Observer.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/Observer.java @@ -19,17 +19,12 @@ package org.apache.zookeeper.server.quorum; import java.io.IOException; -import java.net.InetSocketAddress; import java.nio.ByteBuffer; import org.apache.jute.Record; -import org.apache.zookeeper.ZooDefs.OpCode; import org.apache.zookeeper.server.ObserverBean; import org.apache.zookeeper.server.Request; -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.QuorumPeerConfig.ConfigException; import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; import org.apache.zookeeper.server.util.SerializeUtils; import org.apache.zookeeper.txn.SetDataTxn; @@ -68,17 +63,17 @@ void observeLeader() throws Exception { zk.registerJMX(new ObserverBean(this, zk), self.jmxLocalPeerBean); try { - InetSocketAddress addr = findLeader(); - LOG.info("Observing " + addr); + QuorumServer leaderServer = findLeader(); + LOG.info("Observing " + leaderServer.addr); try { - connectToLeader(addr); + connectToLeader(leaderServer.addr, leaderServer.hostname); long newLeaderZxid = registerWithLeader(Leader.OBSERVERINFO); if (self.isReconfigStateChange()) throw new Exception("learned about role change"); syncWithLeader(newLeaderZxid); QuorumPacket qp = new QuorumPacket(); - while (self.isRunning()) { + while (this.isRunning()) { readPacket(qp); processPacket(qp); } @@ -154,6 +149,9 @@ protected void processPacket(QuorumPacket qp) throws Exception{ throw new Exception("changes proposed in reconfig"); } break; + default: + LOG.warn("Unknown packet type: {}", LearnerHandler.packetToString(qp)); + break; } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/ObserverRequestProcessor.java b/src/java/main/org/apache/zookeeper/server/quorum/ObserverRequestProcessor.java index 8297bce9c1e..85a5212e557 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/ObserverRequestProcessor.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/ObserverRequestProcessor.java @@ -58,7 +58,8 @@ public class ObserverRequestProcessor extends ZooKeeperCriticalThread implements */ public ObserverRequestProcessor(ObserverZooKeeperServer zks, RequestProcessor nextProcessor) { - super("ObserverRequestProcessor:" + zks.getServerId()); + super("ObserverRequestProcessor:" + zks.getServerId(), zks + .getZooKeeperServerListener()); this.zks = zks; this.nextProcessor = nextProcessor; } @@ -92,7 +93,10 @@ public void run() { break; case OpCode.create: case OpCode.create2: + case OpCode.createTTL: + case OpCode.createContainer: case OpCode.delete: + case OpCode.deleteContainer: case OpCode.setData: case OpCode.reconfig: case OpCode.setACL: @@ -110,7 +114,7 @@ public void run() { } } } catch (Exception e) { - LOG.error("Unexpected exception causing exit", e); + handleException(this.getName(), e); } LOG.info("ObserverRequestProcessor exited loop!"); } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java b/src/java/main/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java index a0883b0d446..bd2bcc0792d 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/ObserverZooKeeperServer.java @@ -92,7 +92,8 @@ protected void setupRequestProcessors() { // Currently, they behave almost exactly the same as followers. RequestProcessor finalProcessor = new FinalRequestProcessor(this); commitProcessor = new CommitProcessor(finalProcessor, - Long.toString(getServerId()), true); + Long.toString(getServerId()), true, + getZooKeeperServerListener()); commitProcessor.start(); firstProcessor = new ObserverRequestProcessor(this, commitProcessor); ((ObserverRequestProcessor) firstProcessor).start(); @@ -130,7 +131,11 @@ public String getState() { }; @Override - public void shutdown() { + public synchronized void shutdown() { + if (!canShutdown()) { + LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!"); + return; + } super.shutdown(); if (syncRequestProcessorEnabled && syncProcessor != null) { syncProcessor.shutdown(); diff --git a/src/java/main/org/apache/zookeeper/server/quorum/ProposalStats.java b/src/java/main/org/apache/zookeeper/server/quorum/ProposalStats.java new file mode 100644 index 00000000000..2f3a9c72c19 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/quorum/ProposalStats.java @@ -0,0 +1,71 @@ +/** + * 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; + +/** + * Provides live statistics about a running Leader. + */ +public class ProposalStats { + /** + * Size of the last generated proposal. This should fit into server's jute.maxbuffer setting. + */ + private int lastProposalSize = -1; + + /** + * Size of the smallest proposal which has been generated since the server was started. + */ + private int minProposalSize = -1; + + /** + * Size of the largest proposal which has been generated since the server was started. + */ + private int maxProposalSize = -1; + + public synchronized int getLastProposalSize() { + return lastProposalSize; + } + + synchronized void setLastProposalSize(int value) { + lastProposalSize = value; + if (minProposalSize == -1 || value < minProposalSize) { + minProposalSize = value; + } + if (value > maxProposalSize) { + maxProposalSize = value; + } + } + + public synchronized int getMinProposalSize() { + return minProposalSize; + } + + public synchronized int getMaxProposalSize() { + return maxProposalSize; + } + + public synchronized void reset() { + lastProposalSize = -1; + minProposalSize = -1; + maxProposalSize = -1; + } + + public synchronized String toString() { + return String.format("%d/%d/%d", lastProposalSize, minProposalSize, maxProposalSize); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumCnxManager.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumCnxManager.java index 9f2a0326a0f..ffc1fd12bbd 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumCnxManager.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumCnxManager.java @@ -18,6 +18,8 @@ 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.IOException; @@ -28,14 +30,25 @@ import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.channels.UnresolvedAddressException; +import java.util.Collections; import java.util.Enumeration; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.apache.zookeeper.server.ZooKeeperThread; +import org.apache.zookeeper.server.quorum.auth.QuorumAuthLearner; +import org.apache.zookeeper.server.quorum.auth.QuorumAuthServer; +import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,28 +84,48 @@ public class QuorumCnxManager { static final int SEND_CAPACITY = 1; static final int PACKETMAXSIZE = 1024 * 512; + /* - * Maximum number of attempts to connect to a peer + * Negative counter for observer server ids. */ - static final int MAX_CONNECTION_ATTEMPTS = 2; - + private AtomicLong observerCounter = new AtomicLong(-1); + /* - * Negative counter for observer server ids. + * Protocol identifier used among peers */ - - private long observerCounter = -1; - + public static final long PROTOCOL_VERSION = -65536L; + + /* + * Max buffer size to be read from the network. + */ + static public final int maxBuffer = 2048; + /* * Connection time out value in milliseconds */ private int cnxTO = 5000; - + + final QuorumPeer self; + /* * Local IP address */ - final QuorumPeer self; + final long mySid; + final int socketTimeout; + final Map view; + final boolean listenOnAllIPs; + private ThreadPoolExecutor connectionExecutor; + private final Set inprogressConnections = Collections + .synchronizedSet(new HashSet()); + private QuorumAuthServer authServer; + private QuorumAuthLearner authLearner; + private boolean quorumSaslAuthEnabled; + /* + * Counter to count connection processing threads. + */ + private AtomicInteger connectionThreadCnt = new AtomicInteger(0); /* * Mapping from Peer to Thread number @@ -126,6 +159,12 @@ public class QuorumCnxManager { */ private AtomicInteger threadCnt = new AtomicInteger(0); + /* + * Socket options for TCP keepalive + */ + private final boolean tcpKeepAlive = Boolean.getBoolean("zookeeper.tcpKeepAlive"); + + static public class Message { Message(ByteBuffer buffer, long sid) { this.buffer = buffer; @@ -136,7 +175,81 @@ static public class Message { long sid; } - public QuorumCnxManager(QuorumPeer self) { + /* + * This class parses the initial identification sent out by peers with their + * sid & hostname. + */ + static public class InitialMessage { + public Long sid; + public InetSocketAddress electionAddr; + + InitialMessage(Long sid, InetSocketAddress address) { + this.sid = sid; + this.electionAddr = address; + } + + @SuppressWarnings("serial") + public static class InitialMessageException extends Exception { + InitialMessageException(String message, Object... args) { + super(String.format(message, args)); + } + } + + static public InitialMessage parse(Long protocolVersion, DataInputStream din) + throws InitialMessageException, IOException { + Long sid; + + if (protocolVersion != PROTOCOL_VERSION) { + throw new InitialMessageException( + "Got unrecognized protocol version %s", protocolVersion); + } + + sid = din.readLong(); + + int remaining = din.readInt(); + if (remaining <= 0 || remaining > maxBuffer) { + throw new InitialMessageException( + "Unreasonable buffer length: %s", remaining); + } + + byte[] b = new byte[remaining]; + int num_read = din.read(b); + + if (num_read != remaining) { + throw new InitialMessageException( + "Read only %s bytes out of %s sent by server %s", + num_read, remaining, sid); + } + + // FIXME: IPv6 is not supported. Using something like Guava's HostAndPort + // parser would be good. + String addr = new String(b); + String[] host_port = addr.split(":"); + + if (host_port.length != 2) { + throw new InitialMessageException("Badly formed address: %s", addr); + } + + int port; + try { + port = Integer.parseInt(host_port[1]); + } catch (NumberFormatException e) { + throw new InitialMessageException("Bad port number: %s", host_port[1]); + } + + return new InitialMessage(sid, new InetSocketAddress(host_port[0], port)); + } + } + + public QuorumCnxManager(QuorumPeer self, + final long mySid, + Map view, + QuorumAuthServer authServer, + QuorumAuthLearner authLearner, + int socketTimeout, + boolean listenOnAllIPs, + int quorumCnxnThreadsSize, + boolean quorumSaslAuthEnabled) { this.recvQueue = new ArrayBlockingQueue(RECV_CAPACITY); this.queueSendMap = new ConcurrentHashMap>(); this.senderWorkerMap = new ConcurrentHashMap(); @@ -146,129 +259,289 @@ public QuorumCnxManager(QuorumPeer self) { if(cnxToValue != null){ this.cnxTO = Integer.parseInt(cnxToValue); } - + this.self = self; + this.mySid = mySid; + this.socketTimeout = socketTimeout; + this.view = view; + this.listenOnAllIPs = listenOnAllIPs; + + initializeAuth(mySid, authServer, authLearner, quorumCnxnThreadsSize, + quorumSaslAuthEnabled); + // Starts listener thread that waits for connection requests listener = new Listener(); listener.setName("QuorumPeerListener"); } + private void initializeAuth(final long mySid, + final QuorumAuthServer authServer, + final QuorumAuthLearner authLearner, + final int quorumCnxnThreadsSize, + final boolean quorumSaslAuthEnabled) { + this.authServer = authServer; + this.authLearner = authLearner; + this.quorumSaslAuthEnabled = quorumSaslAuthEnabled; + if (!this.quorumSaslAuthEnabled) { + LOG.debug("Not initializing connection executor as quorum sasl auth is disabled"); + return; + } + + // init connection executors + final AtomicInteger threadIndex = new AtomicInteger(1); + SecurityManager s = System.getSecurityManager(); + final ThreadGroup group = (s != null) ? s.getThreadGroup() + : Thread.currentThread().getThreadGroup(); + ThreadFactory daemonThFactory = new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, "QuorumConnectionThread-" + + "[myid=" + mySid + "]-" + + threadIndex.getAndIncrement()); + return t; + } + }; + this.connectionExecutor = new ThreadPoolExecutor(3, + quorumCnxnThreadsSize, 60, TimeUnit.SECONDS, + new SynchronousQueue(), daemonThFactory); + this.connectionExecutor.allowCoreThreadTimeOut(true); + } + + /** * Invokes initiateConnection for testing purposes * * @param sid */ public void testInitiateConnection(long sid) throws Exception { - if (LOG.isDebugEnabled()) { - LOG.debug("Opening channel to server " + sid); - } + LOG.debug("Opening channel to server " + sid); Socket sock = new Socket(); setSockOpts(sock); sock.connect(self.getVotingView().get(sid).electionAddr, cnxTO); initiateConnection(sock, sid); } - + /** * If this server has initiated the connection, then it gives up on the * connection if it loses challenge. Otherwise, it keeps the connection. */ - public boolean initiateConnection(Socket sock, Long sid) { + public void initiateConnection(final Socket sock, final Long sid) { + try { + startConnection(sock, sid); + } catch (IOException e) { + LOG.error("Exception while connecting, id: {}, addr: {}, closing learner connection", + new Object[] { sid, sock.getRemoteSocketAddress() }, e); + closeSocket(sock); + return; + } + } + + /** + * Server will initiate the connection request to its peer server + * asynchronously via separate connection thread. + */ + public void initiateConnectionAsync(final Socket sock, final Long sid) { + if(!inprogressConnections.add(sid)){ + // simply return as there is a connection request to + // server 'sid' already in progress. + LOG.debug("Connection request to server id: {} is already in progress, so skipping this request", + sid); + closeSocket(sock); + return; + } + try { + connectionExecutor.execute( + new QuorumConnectionReqThread(sock, sid)); + connectionThreadCnt.incrementAndGet(); + } catch (Throwable e) { + // Imp: Safer side catching all type of exceptions and remove 'sid' + // from inprogress connections. This is to avoid blocking further + // connection requests from this 'sid' in case of errors. + inprogressConnections.remove(sid); + LOG.error("Exception while submitting quorum connection request", e); + closeSocket(sock); + } + } + + /** + * Thread to send connection request to peer server. + */ + private class QuorumConnectionReqThread extends ZooKeeperThread { + final Socket sock; + final Long sid; + QuorumConnectionReqThread(final Socket sock, final Long sid) { + super("QuorumConnectionReqThread-" + sid); + this.sock = sock; + this.sid = sid; + } + + @Override + public void run() { + try{ + initiateConnection(sock, sid); + } finally { + inprogressConnections.remove(sid); + } + } + } + + private boolean startConnection(Socket sock, Long sid) + throws IOException { DataOutputStream dout = null; + DataInputStream din = null; try { + // Use BufferedOutputStream to reduce the number of IP packets. This is + // important for x-DC scenarios. + BufferedOutputStream buf = new BufferedOutputStream(sock.getOutputStream()); + dout = new DataOutputStream(buf); + // Sending id and challenge - dout = new DataOutputStream(sock.getOutputStream()); // represents protocol version (in other words - message type) - dout.writeLong(0xffff0000); + dout.writeLong(PROTOCOL_VERSION); dout.writeLong(self.getId()); - String addr = self.getElectionAddress().getHostName() + ":" + self.getElectionAddress().getPort(); + String addr = self.getElectionAddress().getHostString() + ":" + self.getElectionAddress().getPort(); byte[] addr_bytes = addr.getBytes(); dout.writeInt(addr_bytes.length); dout.write(addr_bytes); dout.flush(); + + din = new DataInputStream( + new BufferedInputStream(sock.getInputStream())); } catch (IOException e) { LOG.warn("Ignoring exception reading or writing challenge: ", e); closeSocket(sock); return false; } - + + // authenticate learner + QuorumPeer.QuorumServer qps = self.getVotingView().get(sid); + if (qps != null) { + // TODO - investigate why reconfig makes qps null. + authLearner.authenticate(sock, qps.hostname); + } + // If lost the challenge, then drop the new connection if (sid > self.getId()) { LOG.info("Have smaller server identifier, so dropping the " + - "connection: (" + sid + ", " + self.getId() + ")"); + "connection: (" + sid + ", " + self.getId() + ")"); closeSocket(sock); // Otherwise proceed with the connection } else { SendWorker sw = new SendWorker(sock, sid); - RecvWorker rw = new RecvWorker(sock, sid, sw); + RecvWorker rw = new RecvWorker(sock, din, sid, sw); sw.setRecv(rw); SendWorker vsw = senderWorkerMap.get(sid); - + if(vsw != null) vsw.finish(); - + senderWorkerMap.put(sid, sw); queueSendMap.putIfAbsent(sid, new ArrayBlockingQueue( - SEND_CAPACITY)); - + SEND_CAPACITY)); + sw.start(); rw.start(); - - return true; - + + return true; + } return false; } - - + /** * If this server receives a connection request, then it gives up on the new * connection if it wins. Notice that it checks whether it has a connection * to this server already or not. If it does, then it sends the smallest * possible long value to lose the challenge. - * + * */ - public boolean receiveConnection(Socket sock) { + public void receiveConnection(final Socket sock) { + DataInputStream din = null; + try { + din = new DataInputStream( + new BufferedInputStream(sock.getInputStream())); + + handleConnection(sock, din); + } catch (IOException e) { + LOG.error("Exception handling connection, addr: {}, closing server connection", + sock.getRemoteSocketAddress()); + closeSocket(sock); + } + } + + /** + * Server receives a connection request and handles it asynchronously via + * separate thread. + */ + public void receiveConnectionAsync(final Socket sock) { + try { + connectionExecutor.execute( + new QuorumConnectionReceiverThread(sock)); + connectionThreadCnt.incrementAndGet(); + } catch (Throwable e) { + LOG.error("Exception handling connection, addr: {}, closing server connection", + sock.getRemoteSocketAddress()); + closeSocket(sock); + } + } + + /** + * Thread to receive connection request from peer server. + */ + private class QuorumConnectionReceiverThread extends ZooKeeperThread { + private final Socket sock; + QuorumConnectionReceiverThread(final Socket sock) { + super("QuorumConnectionReceiverThread-" + sock.getRemoteSocketAddress()); + this.sock = sock; + } + + @Override + public void run() { + receiveConnection(sock); + } + } + + private void handleConnection(Socket sock, DataInputStream din) + throws IOException { Long sid = null, protocolVersion = null; InetSocketAddress electionAddr = null; + try { - DataInputStream din = new DataInputStream(sock.getInputStream()); protocolVersion = din.readLong(); if (protocolVersion >= 0) { // this is a server id and not a protocol version sid = protocolVersion; } else { - sid = din.readLong(); - int num_remaining_bytes = din.readInt(); - byte[] b = new byte[num_remaining_bytes]; - int num_read = din.read(b); - if (num_read == num_remaining_bytes) { - if (protocolVersion == 0xffff0000) { - String addr = new String(b); - String[] host_port = addr.split(":"); - electionAddr = new InetSocketAddress(host_port[0], Integer.parseInt(host_port[1])); - } else { - LOG.error("Got urecognized protocol version " + protocolVersion + " from " + sid); - } - } else { - LOG.error("Read only " + num_read + " bytes out of " + num_remaining_bytes + " sent by server " + sid); + try { + InitialMessage init = InitialMessage.parse(protocolVersion, din); + sid = init.sid; + electionAddr = init.electionAddr; + } catch (InitialMessage.InitialMessageException ex) { + LOG.error(ex.toString()); + closeSocket(sock); + return; } - } + } + if (sid == QuorumPeer.OBSERVER_ID) { /* * Choose identifier at random. We need a value to identify * the connection. */ - - sid = observerCounter--; + sid = observerCounter.getAndDecrement(); LOG.info("Setting arbitrary identifier to observer: " + sid); } } catch (IOException e) { closeSocket(sock); - LOG.warn("Exception reading or writing challenge: " + e.toString()); - return false; + LOG.warn("Exception reading or writing challenge: {}", e.toString()); + return; } - + + // do authenticating learner + authServer.authenticate(sock, din); + //If wins the challenge, then close the new connection. if (sid < self.getId()) { /* @@ -284,7 +557,7 @@ public boolean receiveConnection(Socket sock) { /* * Now we start a new connection */ - LOG.debug("Create new connection to server: " + sid); + LOG.debug("Create new connection to server: {}", sid); closeSocket(sock); if (electionAddr != null) { @@ -293,28 +566,25 @@ public boolean receiveConnection(Socket sock) { connectOne(sid); } - // Otherwise start worker threads to receive data. - } else { + } else { // Otherwise start worker threads to receive data. SendWorker sw = new SendWorker(sock, sid); - RecvWorker rw = new RecvWorker(sock, sid, sw); + RecvWorker rw = new RecvWorker(sock, din, sid, sw); sw.setRecv(rw); SendWorker vsw = senderWorkerMap.get(sid); - - if(vsw != null) + + if (vsw != null) { vsw.finish(); - + } + senderWorkerMap.put(sid, sw); - - queueSendMap.putIfAbsent(sid, new ArrayBlockingQueue( - SEND_CAPACITY)); - + + queueSendMap.putIfAbsent(sid, + new ArrayBlockingQueue(SEND_CAPACITY)); + sw.start(); rw.start(); - - return true; } - return false; } /** @@ -325,7 +595,7 @@ public void toSend(Long sid, ByteBuffer b) { /* * If sending message to myself, then simply enqueue it (loopback). */ - if (self.getId() == sid) { + if (this.mySid == sid) { b.position(0); addToRecvQueue(new Message(b.duplicate(), sid)); /* @@ -354,39 +624,45 @@ public void toSend(Long sid, ByteBuffer b) { * @param sid server id * @return boolean success indication */ - synchronized boolean connectOne(long sid, InetSocketAddress electionAddr){ + synchronized private boolean connectOne(long sid, InetSocketAddress electionAddr){ if (senderWorkerMap.get(sid) != null) { LOG.debug("There is a connection already for server " + sid); return true; } - try { - if (LOG.isDebugEnabled()) { - LOG.debug("Opening channel to server " + sid); - } - Socket sock = new Socket(); - setSockOpts(sock); - sock.connect(electionAddr, cnxTO); - if (LOG.isDebugEnabled()) { - LOG.debug("Connected to server " + sid); - } - initiateConnection(sock, sid); - return true; - } catch (UnresolvedAddressException e) { - // Sun doesn't include the address that causes this - // exception to be thrown, also UAE cannot be wrapped cleanly - // so we log the exception in order to capture this critical - // detail. - LOG.warn("Cannot open channel to " + sid - + " at election address " + electionAddr, e); - throw e; - } catch (IOException e) { - LOG.warn("Cannot open channel to " + sid - + " at election address " + electionAddr, - e); - return false; - } - + Socket sock = null; + try { + LOG.debug("Opening channel to server " + sid); + sock = new Socket(); + setSockOpts(sock); + sock.connect(electionAddr, cnxTO); + LOG.debug("Connected to server " + sid); + // Sends connection request asynchronously if the quorum + // sasl authentication is enabled. This is required because + // sasl server authentication process may take few seconds to + // finish, this may delay next peer connection requests. + if (quorumSaslAuthEnabled) { + initiateConnectionAsync(sock, sid); + } else { + initiateConnection(sock, sid); + } + return true; + } catch (UnresolvedAddressException e) { + // Sun doesn't include the address that causes this + // exception to be thrown, also UAE cannot be wrapped cleanly + // so we log the exception in order to capture this critical + // detail. + LOG.warn("Cannot open channel to " + sid + + " at election address " + electionAddr, e); + closeSocket(sock); + throw e; + } catch (IOException e) { + LOG.warn("Cannot open channel to " + sid + + " at election address " + electionAddr, + e); + closeSocket(sock); + return false; + } } /** @@ -394,26 +670,31 @@ synchronized boolean connectOne(long sid, InetSocketAddress electionAddr){ * * @param sid server id */ - synchronized void connectOne(long sid){ if (senderWorkerMap.get(sid) != null) { - LOG.debug("There is a connection already for server " + sid); - return; - } - synchronized(self) { - boolean knownId = false; - if (self.getView().containsKey(sid)) { - knownId = true; - if (connectOne(sid, self.getView().get(sid).electionAddr)) - return; - } - if (self.getLastSeenQuorumVerifier()!=null && self.getLastSeenQuorumVerifier().getAllMembers().containsKey(sid) - && (!knownId || (self.getLastSeenQuorumVerifier().getAllMembers().get(sid).electionAddr != - self.getView().get(sid).electionAddr))) { - knownId = true; - if (connectOne(sid, self.getLastSeenQuorumVerifier().getAllMembers().get(sid).electionAddr)) - return; - } + LOG.debug("There is a connection already for server " + sid); + return; + } + synchronized (self.QV_LOCK) { + boolean knownId = false; + // Resolve hostname for the remote server before attempting to + // connect in case the underlying ip address has changed. + self.recreateSocketAddresses(sid); + Map lastCommittedView = self.getView(); + QuorumVerifier lastSeenQV = self.getLastSeenQuorumVerifier(); + Map lastProposedView = lastSeenQV.getAllMembers(); + if (lastCommittedView.containsKey(sid)) { + knownId = true; + if (connectOne(sid, lastCommittedView.get(sid).electionAddr)) + return; + } + if (lastSeenQV != null && lastProposedView.containsKey(sid) + && (!knownId || (lastProposedView.get(sid).electionAddr != + lastCommittedView.get(sid).electionAddr))) { + knownId = true; + if (connectOne(sid, lastProposedView.get(sid).electionAddr)) + return; + } if (!knownId) { LOG.warn("Invalid server id: " + sid); return; @@ -466,6 +747,13 @@ public void halt() { LOG.warn("Got interrupted before joining the listener", ex); } softHalt(); + + // clear data structures used for auth + if (connectionExecutor != null) { + connectionExecutor.shutdown(); + } + inprogressConnections.clear(); + resetConnectionThreadCount(); } /** @@ -486,6 +774,7 @@ public void softHalt() { */ private void setSockOpts(Socket sock) throws SocketException { sock.setTcpNoDelay(true); + sock.setKeepAlive(tcpKeepAlive); sock.setSoTimeout(self.tickTime * self.syncLimit); } @@ -496,6 +785,10 @@ private void setSockOpts(Socket sock) throws SocketException { * Reference to socket */ private void closeSocket(Socket sock) { + if (sock == null) { + return; + } + try { sock.close(); } catch (IOException ie) { @@ -509,11 +802,19 @@ private void closeSocket(Socket sock) { public long getThreadCount() { return threadCnt.get(); } + /** - * Return reference to QuorumPeer + * Return number of connection processing threads. */ - public QuorumPeer getQuorumPeer() { - return self; + public long getConnectionThreadCount() { + return connectionThreadCnt.get(); + } + + /** + * Reset the value of connection processing threads count to zero. + */ + private void resetConnectionThreadCount() { + connectionThreadCnt.set(0); } /** @@ -536,7 +837,7 @@ public Listener() { public void run() { int numRetries = 0; InetSocketAddress addr; - + Socket client = null; while((!shutdown) && (numRetries < 3)){ try { ss = new ServerSocket(); @@ -545,17 +846,29 @@ public void run() { int port = self.getElectionAddress().getPort(); addr = new InetSocketAddress(port); } else { + // Resolve hostname for this server in case the + // underlying ip address has changed. + self.recreateSocketAddresses(self.getId()); addr = self.getElectionAddress(); } LOG.info("My election bind port: " + addr.toString()); setName(addr.toString()); ss.bind(addr); while (!shutdown) { - Socket client = ss.accept(); + client = ss.accept(); setSockOpts(client); LOG.info("Received connection request " + client.getRemoteSocketAddress()); - receiveConnection(client); + // Receive and handle the connection request + // asynchronously if the quorum sasl authentication is + // enabled. This is required because sasl server + // authentication process may take few seconds to finish, + // this may delay next peer connection requests. + if (quorumSaslAuthEnabled) { + receiveConnectionAsync(client); + } else { + receiveConnection(client); + } numRetries = 0; } } catch (IOException e) { @@ -573,6 +886,7 @@ public void run() { LOG.error("Interrupted while sleeping. " + "Ignoring exception", ie); } + closeSocket(client); } } LOG.info("Leaving listener"); @@ -599,7 +913,8 @@ void halt(){ try{ LOG.debug("Trying to close listener: " + ss); if(ss != null) { - LOG.debug("Closing listener: " + self.getId()); + LOG.debug("Closing listener: " + + QuorumCnxManager.this.mySid); ss.close(); } } catch (IOException e){ @@ -658,9 +973,7 @@ synchronized RecvWorker getRecvWorker(){ } synchronized boolean finish() { - if (LOG.isDebugEnabled()) { - LOG.debug("Calling finish for " + sid); - } + LOG.debug("Calling finish for " + sid); if(!running){ /* @@ -671,16 +984,14 @@ synchronized boolean finish() { running = false; closeSocket(sock); - // channel = null; this.interrupt(); if (recvWorker != null) { recvWorker.finish(); } - if (LOG.isDebugEnabled()) { - LOG.debug("Removing entry from senderWorkerMap sid=" + sid); - } + LOG.debug("Removing entry from senderWorkerMap sid=" + sid); + senderWorkerMap.remove(sid, this); threadCnt.decrementAndGet(); return running; @@ -755,8 +1066,9 @@ public void run() { } } } catch (Exception e) { - LOG.warn("Exception when using channel: for id " + sid + " my id = " + - self.getId() + " error = " + e); + LOG.warn("Exception when using channel: for id " + sid + + " my id = " + QuorumCnxManager.this.mySid + + " error = " + e); } this.finish(); LOG.warn("Send worker leaving thread " + " id " + sid + " my id = " + self.getId()); @@ -771,16 +1083,16 @@ class RecvWorker extends ZooKeeperThread { Long sid; Socket sock; volatile boolean running = true; - DataInputStream din; + final DataInputStream din; final SendWorker sw; - RecvWorker(Socket sock, Long sid, SendWorker sw) { + RecvWorker(Socket sock, DataInputStream din, Long sid, SendWorker sw) { super("RecvWorker:" + sid); this.sid = sid; this.sock = sock; this.sw = sw; + this.din = din; try { - din = new DataInputStream(sock.getInputStream()); // OK to wait until socket disconnects while reading. sock.setSoTimeout(0); } catch (IOException e) { @@ -833,14 +1145,12 @@ public void run() { addToRecvQueue(new Message(message.duplicate(), sid)); } } catch (Exception e) { - LOG.warn("Connection broken for id " + sid + ", my id = " + - self.getId() + ", error = " , e); + LOG.warn("Connection broken for id " + sid + ", my id = " + + QuorumCnxManager.this.mySid + ", error = " , e); } finally { LOG.warn("Interrupting SendWorker"); sw.finish(); - if (sock != null) { - closeSocket(sock); - } + closeSocket(sock); } } } @@ -956,4 +1266,8 @@ public Message pollRecvQueue(long timeout, TimeUnit unit) throws InterruptedException { return recvQueue.poll(timeout, unit); } + + public boolean connectedToPeer(long peerSid) { + return senderWorkerMap.get(peerSid) != null; + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java index 76f0afcbcbe..0ad92359800 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java @@ -18,21 +18,19 @@ package org.apache.zookeeper.server.quorum; import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; -import java.io.OutputStreamWriter; -import java.io.StringReader; -import java.io.StringWriter; import java.io.Writer; import java.net.DatagramPacket; import java.net.DatagramSocket; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketException; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; @@ -45,19 +43,25 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import org.apache.zookeeper.KeeperException.NoNodeException; -import org.apache.zookeeper.ZooDefs; +import javax.security.sasl.SaslException; + +import org.apache.zookeeper.KeeperException.BadArgumentsException; import org.apache.zookeeper.common.AtomicFileWritingIdiom; import org.apache.zookeeper.common.AtomicFileWritingIdiom.WriterStatement; -import org.apache.zookeeper.common.HostNameUtils; -import org.apache.zookeeper.common.PathUtils; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.jmx.MBeanRegistry; import org.apache.zookeeper.jmx.ZKMBeanInfo; -import org.apache.zookeeper.server.DataNode; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.ZooKeeperServer; import org.apache.zookeeper.server.ZooKeeperThread; +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.auth.NullQuorumAuthLearner; +import org.apache.zookeeper.server.quorum.auth.NullQuorumAuthServer; import org.apache.zookeeper.server.admin.AdminServer; import org.apache.zookeeper.server.admin.AdminServer.AdminServerException; import org.apache.zookeeper.server.admin.AdminServerFactory; @@ -104,8 +108,11 @@ public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider private Map jmxRemotePeerBean; LeaderElectionBean jmxLeaderElectionBean; private QuorumCnxManager qcm; + QuorumAuthServer authServer; + QuorumAuthLearner authLearner; - /* ZKDatabase is a top level member of quorumpeer + /** + * ZKDatabase is a top level member of quorumpeer * which will be used in all the zookeeperservers * instantiated later. Also, it is created once on * bootup and only thrown away in case of a truncate @@ -121,35 +128,58 @@ public static class QuorumServer { public InetSocketAddress clientAddr = null; public long id; + + public String hostname; public LearnerType type = LearnerType.PARTICIPANT; - + private List myAddrs; + public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr, InetSocketAddress clientAddr) { - this.id = id; - this.addr = addr; - this.electionAddr = electionAddr; - this.clientAddr = clientAddr; + this(id, addr, electionAddr, clientAddr, LearnerType.PARTICIPANT); } - public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr) { - this.id = id; - this.addr = addr; - this.electionAddr = electionAddr; - this.clientAddr = null; + this(id, addr, electionAddr, (InetSocketAddress)null, LearnerType.PARTICIPANT); } + // VisibleForTesting public QuorumServer(long id, InetSocketAddress addr) { - this.id = id; - this.addr = addr; - this.electionAddr = null; - this.clientAddr = null; + this(id, addr, (InetSocketAddress)null, (InetSocketAddress)null, LearnerType.PARTICIPANT); } - - + + /** + * Performs a DNS lookup for server address and election address. + * + * If the DNS lookup fails, this.addr and electionAddr remain + * unmodified. + */ + public void recreateSocketAddresses() { + if (this.addr == null) { + LOG.warn("Server address has not been initialized"); + return; + } + if (this.electionAddr == null) { + LOG.warn("Election address has not been initialized"); + return; + } + String host = this.addr.getHostString(); + InetAddress address = null; + try { + address = InetAddress.getByName(host); + } catch (UnknownHostException ex) { + LOG.warn("Failed to resolve address: {}", host, ex); + return; + } + LOG.debug("Resolved address for {}: {}", host, address); + int port = this.addr.getPort(); + this.addr = new InetSocketAddress(address, port); + port = this.electionAddr.getPort(); + this.electionAddr = new InetSocketAddress(address, port); + } + private void setType(String s) throws ConfigException { if (s.toLowerCase().equals("observer")) { type = LearnerType.OBSERVER; @@ -157,17 +187,38 @@ private void setType(String s) throws ConfigException { type = LearnerType.PARTICIPANT; } else { throw new ConfigException("Unrecognised peertype: " + s); - } - } + } + } + + private static String[] splitWithLeadingHostname(String s) + throws ConfigException + { + /* Does it start with an IPv6 literal? */ + if (s.startsWith("[")) { + int i = s.indexOf("]:"); + if (i < 0) { + throw new ConfigException(s + " starts with '[' but has no matching ']:'"); + } - private static final String wrongFormat = " does not have the form server_cofig or server_config;client_config"+ + String[] sa = s.substring(i + 2).split(":"); + String[] nsa = new String[sa.length + 1]; + nsa[0] = s.substring(1, i); + System.arraycopy(sa, 0, nsa, 1, sa.length); + + return nsa; + } else { + return s.split(":"); + } + } + + private static final String wrongFormat = " does not have the form server_config or server_config;client_config"+ " where server_config is host:port:port or host:port:port:type and client_config is port or host:port"; - + public QuorumServer(long sid, String addressStr) throws ConfigException { // LOG.warn("sid = " + sid + " addressStr = " + addressStr); this.id = sid; String serverClientParts[] = addressStr.split(";"); - String serverParts[] = serverClientParts[0].split(":"); + String serverParts[] = splitWithLeadingHostname(serverClientParts[0]); if ((serverClientParts.length > 2) || (serverParts.length < 3) || (serverParts.length > 4)) { throw new ConfigException(addressStr + wrongFormat); @@ -175,13 +226,13 @@ public QuorumServer(long sid, String addressStr) throws ConfigException { if (serverClientParts.length == 2) { //LOG.warn("ClientParts: " + serverClientParts[1]); - String clientParts[] = serverClientParts[1].split(":"); + String clientParts[] = splitWithLeadingHostname(serverClientParts[1]); if (clientParts.length > 2) { throw new ConfigException(addressStr + wrongFormat); } // is client_config a host:port or just a port - String hostname = (clientParts.length == 2) ? clientParts[0] : "0.0.0.0"; + hostname = (clientParts.length == 2) ? clientParts[0] : "0.0.0.0"; try { clientAddr = new InetSocketAddress(hostname, Integer.parseInt(clientParts[clientParts.length - 1])); @@ -204,32 +255,55 @@ public QuorumServer(long sid, String addressStr) throws ConfigException { } catch (NumberFormatException e) { throw new ConfigException("Address unresolved: " + serverParts[0] + ":" + serverParts[2]); } - if (serverParts.length == 4) setType(serverParts[3]); + + if (serverParts.length == 4) { + setType(serverParts[3]); + } + + this.hostname = serverParts[0]; + + setMyAddrs(); } public QuorumServer(long id, InetSocketAddress addr, InetSocketAddress electionAddr, LearnerType type) { + this(id, addr, electionAddr, (InetSocketAddress)null, type); + } + + public QuorumServer(long id, InetSocketAddress addr, + InetSocketAddress electionAddr, InetSocketAddress clientAddr, LearnerType type) { this.id = id; this.addr = addr; this.electionAddr = electionAddr; this.type = type; - this.clientAddr = null; + this.clientAddr = clientAddr; + + setMyAddrs(); } - public QuorumServer(long id, InetSocketAddress addr, - InetSocketAddress electionAddr, InetSocketAddress clientAddr, LearnerType type) { - this.id = id; - this.addr = addr; - this.electionAddr = electionAddr; - this.type = type; - this.clientAddr = clientAddr; - } + private void setMyAddrs() { + this.myAddrs = new ArrayList(); + this.myAddrs.add(this.addr); + this.myAddrs.add(this.clientAddr); + this.myAddrs.add(this.electionAddr); + this.myAddrs = excludedSpecialAddresses(this.myAddrs); + } + + private static String delimitedHostString(InetSocketAddress addr) + { + String host = addr.getHostString(); + if (host.contains(":")) { + return "[" + host + "]"; + } else { + return host; + } + } public String toString(){ - StringWriter sw = new StringWriter(); + StringWriter sw = new StringWriter(); //addr should never be null, but just to make sure - if (addr !=null) { - sw.append(HostNameUtils.getHostString(addr)); + if (addr !=null) { + sw.append(delimitedHostString(addr)); sw.append(":"); sw.append(String.valueOf(addr.getPort())); } @@ -241,7 +315,7 @@ public String toString(){ else if (type == LearnerType.PARTICIPANT) sw.append(":participant"); if (clientAddr!=null){ sw.append(";"); - sw.append(HostNameUtils.getHostString(clientAddr)); + sw.append(delimitedHostString(clientAddr)); sw.append(":"); sw.append(String.valueOf(clientAddr.getPort())); } @@ -269,6 +343,44 @@ public boolean equals(Object o){ if (!checkAddressesEqual(clientAddr, qs.clientAddr)) return false; return true; } + + public void checkAddressDuplicate(QuorumServer s) throws BadArgumentsException { + List otherAddrs = new ArrayList(); + otherAddrs.add(s.addr); + otherAddrs.add(s.clientAddr); + otherAddrs.add(s.electionAddr); + otherAddrs = excludedSpecialAddresses(otherAddrs); + + for (InetSocketAddress my: this.myAddrs) { + + for (InetSocketAddress other: otherAddrs) { + if (my.equals(other)) { + String error = String.format("%s of server.%d conflicts %s of server.%d", my, this.id, other, s.id); + throw new BadArgumentsException(error); + } + } + } + } + + private List excludedSpecialAddresses(List addrs) { + List included = new ArrayList(); + InetAddress wcAddr = new InetSocketAddress(0).getAddress(); + + for (InetSocketAddress addr : addrs) { + if (addr == null) { + continue; + } + InetAddress inetaddr = addr.getAddress(); + + if (inetaddr == null || + inetaddr.equals(wcAddr) || // wildCard address(0.0.0.0) + inetaddr.isLoopbackAddress()) { // loopback address(localhost/127.0.0.1) + continue; + } + included.add(addr); + } + return included; + } } @@ -299,7 +411,8 @@ public enum LearnerType { /* * Record leader election time */ - public long start_fle, end_fle; + public long start_fle, end_fle; // fle = fast leader election + public static final String FLE_TIME_UNIT = "MS"; /* * Default value of peer is participant @@ -317,32 +430,11 @@ public void setLearnerType(LearnerType p) { learnerType = p; } - - protected synchronized void setDynamicConfigFilename(String s) { - dynamicConfigFilename = PathUtils.normalizeFileSystemPath(s); - } - - protected synchronized String getDynamicConfigFilename() { - return dynamicConfigFilename; - } - protected synchronized void setConfigFileName(String s) { configFilename = s; } - protected synchronized void setConfigBackwardCompatibility(boolean bc) { - configBackwardCompatibility = bc; - } - - protected synchronized boolean getConfigBackwardCompatibility() { - return configBackwardCompatibility; - } - - private String dynamicConfigFilename = null; - private String configFilename = null; - - private boolean configBackwardCompatibility = false; public int getQuorumSize(){ return getVotingView().size(); @@ -358,6 +450,10 @@ public int getQuorumSize(){ //last proposed quorum verifier public QuorumVerifier lastSeenQuorumVerifier = null; + // Lock object that guard access to quorumVerifier and lastSeenQuorumVerifier. + final Object QV_LOCK = new Object(); + + /** * My id */ @@ -384,7 +480,7 @@ public synchronized void setCurrentVote(Vote v){ currentVote = v; } - volatile boolean running = true; + private volatile boolean running = true; /** * The number of milliseconds of each tick @@ -443,6 +539,56 @@ public synchronized void setCurrentVote(Vote v){ */ protected boolean quorumListenOnAllIPs = false; + /** + * Keeps time taken for leader election in milliseconds. Sets the value to + * this variable only after the completion of leader election. + */ + private long electionTimeTaken = -1; + + /** + * Enable/Disables quorum authentication using sasl. Defaulting to false. + */ + protected boolean quorumSaslEnableAuth; + + /** + * If this is false, quorum peer server will accept another quorum peer client + * connection even if the authentication did not succeed. This can be used while + * upgrading ZooKeeper server. Defaulting to false (required). + */ + protected boolean quorumServerSaslAuthRequired; + + /** + * If this is false, quorum peer learner will talk to quorum peer server + * without authentication. This can be used while upgrading ZooKeeper + * server. Defaulting to false (required). + */ + protected boolean quorumLearnerSaslAuthRequired; + + /** + * Kerberos quorum service principal. Defaulting to 'zkquorum/localhost'. + */ + protected String quorumServicePrincipal; + + /** + * Quorum learner login context name in jaas-conf file to read the kerberos + * security details. Defaulting to 'QuorumLearner'. + */ + protected String quorumLearnerLoginContext; + + /** + * Quorum server login context name in jaas-conf file to read the kerberos + * security details. Defaulting to 'QuorumServer'. + */ + protected String quorumServerLoginContext; + + // TODO: need to tune the default value of thread size + private static final int QUORUM_CNXN_THREADS_SIZE_DEFAULT_VALUE = 20; + /** + * The maximum number of threads to allow in the connectionExecutors thread + * pool which will be used to initiate quorum server connections. + */ + protected int quorumCnxnThreadsSize = QUORUM_CNXN_THREADS_SIZE_DEFAULT_VALUE; + /** * @deprecated As of release 3.4.0, this class has been deprecated, since * it is used with one of the udp-based versions of leader election, which @@ -555,28 +701,68 @@ public synchronized ServerState getPeerState(){ private InetSocketAddress myElectionAddr = null; private InetSocketAddress myClientAddr = null; - public synchronized InetSocketAddress getQuorumAddress(){ - return myQuorumAddr; + /** + * Resolves hostname for a given server ID. + * + * This method resolves hostname for a given server ID in both quorumVerifer + * and lastSeenQuorumVerifier. If the server ID matches the local server ID, + * it also updates myQuorumAddr and myElectionAddr. + */ + public void recreateSocketAddresses(long id) { + QuorumVerifier qv = getQuorumVerifier(); + if (qv != null) { + QuorumServer qs = qv.getAllMembers().get(id); + if (qs != null) { + qs.recreateSocketAddresses(); + if (id == getId()) { + setQuorumAddress(qs.addr); + setElectionAddress(qs.electionAddr); + } + } + } + qv = getLastSeenQuorumVerifier(); + if (qv != null) { + QuorumServer qs = qv.getAllMembers().get(id); + if (qs != null) { + qs.recreateSocketAddresses(); + } + } } - - public synchronized void setQuorumAddress(InetSocketAddress addr){ - myQuorumAddr = addr; + + public InetSocketAddress getQuorumAddress(){ + synchronized (QV_LOCK) { + return myQuorumAddr; + } } + public void setQuorumAddress(InetSocketAddress addr){ + synchronized (QV_LOCK) { + myQuorumAddr = addr; + } + } + public InetSocketAddress getElectionAddress(){ - return myElectionAddr; + synchronized (QV_LOCK) { + return myElectionAddr; + } } public void setElectionAddress(InetSocketAddress addr){ - myElectionAddr = addr; + synchronized (QV_LOCK) { + myElectionAddr = addr; + } } public InetSocketAddress getClientAddress(){ - return myClientAddr; + synchronized (QV_LOCK) { + return myClientAddr; + } } public void setClientAddress(InetSocketAddress addr){ - myClientAddr = addr; + synchronized (QV_LOCK) { + myClientAddr = addr; + } } private int electionType; @@ -584,20 +770,26 @@ public void setClientAddress(InetSocketAddress addr){ Election electionAlg; ServerCnxnFactory cnxnFactory; + ServerCnxnFactory secureCnxnFactory; + private FileTxnSnapLog logFactory = null; private final QuorumStats quorumStats; AdminServer adminServer; - public QuorumPeer() { + public static QuorumPeer testingQuorumPeer() throws SaslException { + return new QuorumPeer(); + } + + public QuorumPeer() throws SaslException { super("QuorumPeer"); quorumStats = new QuorumStats(this); jmxRemotePeerBean = new HashMap(); adminServer = AdminServerFactory.createAdminServer(); + initialize(); } - /** * For backward compatibility purposes, we instantiate QuorumMaj by default. */ @@ -606,9 +798,9 @@ public QuorumPeer(Map quorumPeers, File dataDir, File dataLogDir, int electionType, long myid, int tickTime, int initLimit, int syncLimit, ServerCnxnFactory cnxnFactory) throws IOException { - this(quorumPeers, dataDir, dataLogDir, electionType, myid, tickTime, - initLimit, syncLimit, false, cnxnFactory, - new QuorumMaj(quorumPeers), null); + this(quorumPeers, dataDir, dataLogDir, electionType, myid, tickTime, + initLimit, syncLimit, false, cnxnFactory, + new QuorumMaj(quorumPeers)); } public QuorumPeer(Map quorumPeers, File dataDir, @@ -616,7 +808,7 @@ public QuorumPeer(Map quorumPeers, File dataDir, long myid, int tickTime, int initLimit, int syncLimit, boolean quorumListenOnAllIPs, ServerCnxnFactory cnxnFactory, - QuorumVerifier quorumConfig, String memFilename) throws IOException { + QuorumVerifier quorumConfig) throws IOException { this(); this.cnxnFactory = cnxnFactory; this.electionType = electionType; @@ -627,12 +819,28 @@ public QuorumPeer(Map quorumPeers, File dataDir, this.quorumListenOnAllIPs = quorumListenOnAllIPs; this.logFactory = new FileTxnSnapLog(dataLogDir, dataDir); this.zkDb = new ZKDatabase(this.logFactory); - this.dynamicConfigFilename = (memFilename != null) ? memFilename : "zoo_replicated" + myid + ".dynamic"; if(quorumConfig == null) quorumConfig = new QuorumMaj(quorumPeers); setQuorumVerifier(quorumConfig, false); adminServer = AdminServerFactory.createAdminServer(); } + public void initialize() throws SaslException { + // init quorum auth server & learner + if (isQuorumSaslAuthEnabled()) { + Set authzHosts = new HashSet(); + for (QuorumServer qs : getView().values()) { + authzHosts.add(qs.hostname); + } + authServer = new SaslQuorumAuthServer(isQuorumServerSaslAuthRequired(), + quorumServerLoginContext, authzHosts); + authLearner = new SaslQuorumAuthLearner(isQuorumLearnerSaslAuthRequired(), + quorumServicePrincipal, quorumLearnerLoginContext); + } else { + authServer = new NullQuorumAuthServer(); + authLearner = new NullQuorumAuthLearner(); + } + } + QuorumStats quorumStats() { return quorumStats; } @@ -643,7 +851,7 @@ public synchronized void start() { throw new RuntimeException("My id " + myid + " not in the peer list"); } loadDataBase(); - cnxnFactory.start(); + startServerCnxnFactory(); try { adminServer.start(); } catch (AdminServerException e) { @@ -689,7 +897,7 @@ private void loadDataBase() { writeLongToFile(ACCEPTED_EPOCH_FILENAME, acceptedEpoch); } if (acceptedEpoch < currentEpoch) { - throw new IOException("The current epoch, " + ZxidUtils.zxidToString(currentEpoch) + " is less than the accepted epoch, " + ZxidUtils.zxidToString(acceptedEpoch)); + throw new IOException("The accepted epoch, " + ZxidUtils.zxidToString(acceptedEpoch) + " is less than the current epoch, " + ZxidUtils.zxidToString(currentEpoch)); } } catch(IOException ie) { LOG.error("Unable to load database on disk", ie); @@ -754,10 +962,9 @@ public QuorumPeer(Map quorumPeers, File snapDir, long myid, int tickTime, int initLimit, int syncLimit) throws IOException { - this(quorumPeers, snapDir, logDir, electionAlg, - myid,tickTime, initLimit,syncLimit, false, - ServerCnxnFactory.createFactory(new InetSocketAddress(clientPort), -1), - new QuorumMaj(quorumPeers), null); + this(quorumPeers, snapDir, logDir, electionAlg, myid, tickTime, initLimit, syncLimit, false, + ServerCnxnFactory.createFactory(getClientAddress(quorumPeers, myid, clientPort), -1), + new QuorumMaj(quorumPeers)); } /** @@ -772,8 +979,24 @@ public QuorumPeer(Map quorumPeers, File snapDir, { this(quorumPeers, snapDir, logDir, electionAlg, myid,tickTime, initLimit,syncLimit, false, - ServerCnxnFactory.createFactory(new InetSocketAddress(clientPort), -1), - quorumConfig, null); + ServerCnxnFactory.createFactory(getClientAddress(quorumPeers, myid, clientPort), -1), + quorumConfig); + } + + private static InetSocketAddress getClientAddress(Map quorumPeers, long myid, int clientPort) + throws IOException { + QuorumServer quorumServer = quorumPeers.get(myid); + if (null == quorumServer) { + throw new IOException("No QuorumServer correspoding to myid " + myid); + } + if (null == quorumServer.clientAddr) { + return new InetSocketAddress(clientPort); + } + if (quorumServer.clientAddr.getPort() != clientPort) { + throw new IOException("QuorumServer port " + quorumServer.clientAddr.getPort() + + " does not match with given port " + clientPort); + } + return quorumServer.clientAddr; } /** @@ -820,7 +1043,7 @@ protected Election createElectionAlgorithm(int electionAlgorithm){ le = new AuthFastLeaderElection(this, true); break; case 3: - qcm = new QuorumCnxManager(this); + qcm = createCnxnManager(); QuorumCnxManager.Listener listener = qcm.listener; if(listener != null){ listener.start(); @@ -1014,7 +1237,7 @@ public void run() { } break; } - start_fle = System.currentTimeMillis(); + start_fle = Time.currentElapsedTime(); } } finally { LOG.warn("QuorumPeer main thread exited"); @@ -1063,7 +1286,7 @@ public void shutdown() { if (follower != null) { follower.shutdown(); } - cnxnFactory.shutdown(); + shutdownServerCnxnFactory(); if(udpSocket != null) { udpSocket.close(); } @@ -1188,11 +1411,13 @@ public void setTickTime(int tickTime) { /** Maximum number of connections allowed from particular host (ip) */ public int getMaxClientCnxnsPerHost() { - ServerCnxnFactory fac = getCnxnFactory(); - if (fac == null) { - return -1; + if (cnxnFactory != null) { + return cnxnFactory.getMaxClientCnxnsPerHost(); } - return fac.getMaxClientCnxnsPerHost(); + if (secureCnxnFactory != null) { + return secureCnxnFactory.getMaxClientCnxnsPerHost(); + } + return -1; } /** Whether local sessions are enabled */ @@ -1268,28 +1493,35 @@ public QuorumVerifier configFromString(String s) throws IOException, ConfigExcep } /** - * Return QuorumVerifier object for the last committed configuration + * Return QuorumVerifier object for the last committed configuration. */ - - public synchronized QuorumVerifier getQuorumVerifier(){ - return quorumVerifier; - + public QuorumVerifier getQuorumVerifier(){ + synchronized (QV_LOCK) { + return quorumVerifier; + } } - public synchronized QuorumVerifier getLastSeenQuorumVerifier(){ - return lastSeenQuorumVerifier; + /** + * Return QuorumVerifier object for the last proposed configuration. + */ + public QuorumVerifier getLastSeenQuorumVerifier(){ + synchronized (QV_LOCK) { + return lastSeenQuorumVerifier; + } } - public synchronized void connectNewPeers(){ - if (qcm!=null && getQuorumVerifier()!=null && getLastSeenQuorumVerifier()!=null) { - Map committedView = getQuorumVerifier().getAllMembers(); - for (Entry e: getLastSeenQuorumVerifier().getAllMembers().entrySet()){ - if (e.getKey() != getId() && !committedView.containsKey(e.getKey())) - qcm.connectOne(e.getKey(), e.getValue().electionAddr); - } + private void connectNewPeers(){ + synchronized (QV_LOCK) { + if (qcm != null && quorumVerifier != null && lastSeenQuorumVerifier != null) { + Map committedView = quorumVerifier.getAllMembers(); + for (Entry e : lastSeenQuorumVerifier.getAllMembers().entrySet()) { + if (e.getKey() != getId() && !committedView.containsKey(e.getKey())) + qcm.connectOne(e.getKey()); + } + } } } - + public synchronized void restartLeaderElection(QuorumVerifier qvOLD, QuorumVerifier qvNEW){ if (qvOLD == null || !qvOLD.equals(qvNEW)) { LOG.warn("Restarting Leader Election"); @@ -1298,84 +1530,96 @@ public synchronized void restartLeaderElection(QuorumVerifier qvOLD, QuorumVerif startLeaderElection(); } } + + public String getNextDynamicConfigFilename() { + if (configFilename == null) { + LOG.warn("configFilename is null! This should only happen in tests."); + return null; + } + return configFilename + QuorumPeerConfig.nextDynamicConfigFileSuffix; + } - public synchronized void setLastSeenQuorumVerifier(QuorumVerifier qv, boolean writeToDisk){ - if (lastSeenQuorumVerifier!=null && lastSeenQuorumVerifier.getVersion() > qv.getVersion()) { - LOG.error("setLastSeenQuorumVerifier called with stale config " + qv.getVersion() + - ". Current version: " + quorumVerifier.getVersion()); - - } - // assuming that a version uniquely identifies a configuration, so if - // version is the same, nothing to do here. - if (lastSeenQuorumVerifier != null && - lastSeenQuorumVerifier.getVersion() == qv.getVersion()) { - return; - } - lastSeenQuorumVerifier = qv; - connectNewPeers(); - if (writeToDisk) { - try { - QuorumPeerConfig.writeDynamicConfig(dynamicConfigFilename + ".next", null, false, qv, false); - } catch(IOException e){ - LOG.error("Error closing file: ", e.getMessage()); - } - } + public void setLastSeenQuorumVerifier(QuorumVerifier qv, boolean writeToDisk){ + synchronized (QV_LOCK) { + if (lastSeenQuorumVerifier != null && lastSeenQuorumVerifier.getVersion() > qv.getVersion()) { + LOG.error("setLastSeenQuorumVerifier called with stale config " + qv.getVersion() + + ". Current version: " + quorumVerifier.getVersion()); + } + // assuming that a version uniquely identifies a configuration, so if + // version is the same, nothing to do here. + if (lastSeenQuorumVerifier != null && + lastSeenQuorumVerifier.getVersion() == qv.getVersion()) { + return; + } + lastSeenQuorumVerifier = qv; + connectNewPeers(); + if (writeToDisk) { + try { + String fileName = getNextDynamicConfigFilename(); + if (fileName != null) { + QuorumPeerConfig.writeDynamicConfig(fileName, qv, true); + } + } catch (IOException e) { + LOG.error("Error writing next dynamic config file to disk: ", e.getMessage()); + } + } + } } - public synchronized QuorumVerifier setQuorumVerifier(QuorumVerifier qv, boolean writeToDisk){ - if ((quorumVerifier != null) && (quorumVerifier.getVersion() >= qv.getVersion())) { - // this is normal. For example - server found out about new config through FastLeaderElection gossiping - // and then got the same config in UPTODATE message so its already known - LOG.debug(getId() + " setQuorumVerifier called with known or old config " + qv.getVersion() + - ". Current version: " + quorumVerifier.getVersion()); - return quorumVerifier; - } - QuorumVerifier prevQV = quorumVerifier; - quorumVerifier = qv; - if (lastSeenQuorumVerifier == null || (qv.getVersion() > lastSeenQuorumVerifier.getVersion())) - lastSeenQuorumVerifier = qv; - if (writeToDisk) { - // we need to write the dynamic config file. Either it already exists - // or we have the old-style config file and we're in the backward compatibility mode, - // so we'll create the dynamic config file for the first time now - if (dynamicConfigFilename !=null || (configFilename !=null && configBackwardCompatibility)) { - try { - if (configBackwardCompatibility) { - setDynamicConfigFilename(configFilename + ".dynamic"); + public QuorumVerifier setQuorumVerifier(QuorumVerifier qv, boolean writeToDisk){ + synchronized (QV_LOCK) { + if ((quorumVerifier != null) && (quorumVerifier.getVersion() >= qv.getVersion())) { + // this is normal. For example - server found out about new config through FastLeaderElection gossiping + // and then got the same config in UPTODATE message so its already known + LOG.debug(getId() + " setQuorumVerifier called with known or old config " + qv.getVersion() + + ". Current version: " + quorumVerifier.getVersion()); + return quorumVerifier; + } + QuorumVerifier prevQV = quorumVerifier; + quorumVerifier = qv; + if (lastSeenQuorumVerifier == null || (qv.getVersion() > lastSeenQuorumVerifier.getVersion())) + lastSeenQuorumVerifier = qv; + + if (writeToDisk) { + // some tests initialize QuorumPeer without a static config file + if (configFilename != null) { + try { + String dynamicConfigFilename = makeDynamicConfigFilename( + qv.getVersion()); + QuorumPeerConfig.writeDynamicConfig( + dynamicConfigFilename, qv, false); + QuorumPeerConfig.editStaticConfig(configFilename, + dynamicConfigFilename, + needEraseClientInfoFromStaticConfig()); + } catch (IOException e) { + LOG.error("Error closing file: ", e.getMessage()); } - QuorumPeerConfig.writeDynamicConfig(dynamicConfigFilename, configFilename, - configBackwardCompatibility, qv, - needEraseClientInfoFromStaticConfig(prevQV, qv)); - configBackwardCompatibility = false; - } catch(IOException e){ - LOG.error("Error closing file: ", e.getMessage()); + } else { + LOG.info("writeToDisk == true but configFilename == null"); } - } else { - LOG.error("writeToDisk == true but dynamicConfigFilename == null, configFilename " - + (configFilename == null ? "== null": "!=null") - + " and configBackwardCompatibility == " + configBackwardCompatibility); } + + if (qv.getVersion() == lastSeenQuorumVerifier.getVersion()) { + QuorumPeerConfig.deleteFile(getNextDynamicConfigFilename()); + } + QuorumServer qs = qv.getAllMembers().get(getId()); + if (qs != null) { + setQuorumAddress(qs.addr); + setElectionAddress(qs.electionAddr); + setClientAddress(qs.clientAddr); + } + return prevQV; } + } - if (qv.getVersion() == lastSeenQuorumVerifier.getVersion()){ - QuorumPeerConfig.deleteFile(dynamicConfigFilename + ".next"); - } - QuorumServer qs = qv.getAllMembers().get(getId()); - if (qs!=null){ - setQuorumAddress(qs.addr); - setElectionAddress(qs.electionAddr); - setClientAddress(qs.clientAddr); - } - return prevQV; + private String makeDynamicConfigFilename(long version) { + return configFilename + ".dynamic." + Long.toHexString(version); } - private boolean needEraseClientInfoFromStaticConfig(QuorumVerifier oldQV, - QuorumVerifier newQV) { - QuorumServer myOldSpec = oldQV.getAllMembers().get(getId()); - QuorumServer myNewSpec = newQV.getAllMembers().get(getId()); - return (myNewSpec != null && myNewSpec.clientAddr != null - && (myOldSpec == null || myOldSpec.clientAddr == null)); + private boolean needEraseClientInfoFromStaticConfig() { + QuorumServer server = quorumVerifier.getAllMembers().get(getId()); + return (server != null && server.clientAddr != null); } /** @@ -1450,16 +1694,56 @@ public void setQuorumListenOnAllIPs(boolean quorumListenOnAllIPs) { this.quorumListenOnAllIPs = quorumListenOnAllIPs; } - public ServerCnxnFactory getCnxnFactory() { - return cnxnFactory; - } - public void setCnxnFactory(ServerCnxnFactory cnxnFactory) { this.cnxnFactory = cnxnFactory; } + public void setSecureCnxnFactory(ServerCnxnFactory secureCnxnFactory) { + this.secureCnxnFactory = secureCnxnFactory; + } + + private void startServerCnxnFactory() { + if (cnxnFactory != null) { + cnxnFactory.start(); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.start(); + } + } + + private void shutdownServerCnxnFactory() { + if (cnxnFactory != null) { + cnxnFactory.shutdown(); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.shutdown(); + } + } + + // Leader and learner will control the zookeeper server and pass it into QuorumPeer. + public void setZooKeeperServer(ZooKeeperServer zks) { + if (cnxnFactory != null) { + cnxnFactory.setZooKeeperServer(zks); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.setZooKeeperServer(zks); + } + } + + public void closeAllConnections() { + if (cnxnFactory != null) { + cnxnFactory.closeAll(); + } + if (secureCnxnFactory != null) { + secureCnxnFactory.closeAll(); + } + } + public int getClientPort() { - return cnxnFactory.getLocalPort(); + if (cnxnFactory != null) { + return cnxnFactory.getLocalPort(); + } + return -1; } public void setTxnFactory(FileTxnSnapLog factory) { @@ -1477,14 +1761,14 @@ public FileTxnSnapLog getTxnFactory() { public void setZKDatabase(ZKDatabase database) { this.zkDb = database; } - + + protected ZKDatabase getZkDb() { + return zkDb; + } + public synchronized void initConfigInZKDatabase() { if (zkDb != null) zkDb.initConfigInZKDatabase(getQuorumVerifier()); } - - public void setRunning(boolean running) { - this.running = running; - } public boolean isRunning() { return running; @@ -1559,11 +1843,16 @@ public void setAcceptedEpoch(long e) throws IOException { writeLongToFile(ACCEPTED_EPOCH_FILENAME, e); } - public boolean processReconfig(QuorumVerifier qv, Long suggestedLeaderId, Long zxid, boolean restartLE){ + public boolean processReconfig(QuorumVerifier qv, Long suggestedLeaderId, Long zxid, boolean restartLE) { + if (!QuorumPeerConfig.isReconfigEnabled()) { + LOG.debug("Reconfig feature is disabled, skip reconfig processing."); + return false; + } + InetSocketAddress oldClientAddr = getClientAddress(); // update last committed quorum verifier, write the new config to disk - // and restart leader election if config changed + // and restart leader election if config changed. QuorumVerifier prevQV = setQuorumVerifier(qv, true); // There is no log record for the initial config, thus after syncing @@ -1587,8 +1876,8 @@ public boolean processReconfig(QuorumVerifier qv, Long suggestedLeaderId, Long z cnxnFactory.reconfigure(myNewQS.clientAddr); updateThreadName(); } - - boolean roleChange = updateLearnerType(qv); + + boolean roleChange = updateLearnerType(qv); boolean leaderChange = false; if (suggestedLeaderId != null) { // zxid should be non-null too @@ -1706,7 +1995,97 @@ protected void updateElectionVote(long newEpoch) { } private void updateThreadName() { - setName("QuorumPeer" + "[myid=" + getId() + "]" + - cnxnFactory.getLocalAddress()); + String plain = cnxnFactory != null ? + cnxnFactory.getLocalAddress() != null ? + cnxnFactory.getLocalAddress().toString() : "disabled" : "disabled"; + String secure = secureCnxnFactory != null ? secureCnxnFactory.getLocalAddress().toString() : "disabled"; + setName(String.format("QuorumPeer[myid=%d](plain=%s)(secure=%s)", getId(), plain, secure)); + } + + /** + * Sets the time taken for leader election in milliseconds. + * + * @param electionTimeTaken time taken for leader election + */ + void setElectionTimeTaken(long electionTimeTaken) { + this.electionTimeTaken = electionTimeTaken; + } + + /** + * @return the time taken for leader election in milliseconds. + */ + long getElectionTimeTaken() { + return electionTimeTaken; + } + + void setQuorumServerSaslRequired(boolean serverSaslRequired) { + quorumServerSaslAuthRequired = serverSaslRequired; + LOG.info("{} set to {}", QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, + serverSaslRequired); + } + + void setQuorumLearnerSaslRequired(boolean learnerSaslRequired) { + quorumLearnerSaslAuthRequired = learnerSaslRequired; + LOG.info("{} set to {}", QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, + learnerSaslRequired); + } + + void setQuorumSaslEnabled(boolean enableAuth) { + quorumSaslEnableAuth = enableAuth; + if (!quorumSaslEnableAuth) { + LOG.info("QuorumPeer communication is not secured!"); + } else { + LOG.info("{} set to {}", + QuorumAuth.QUORUM_SASL_AUTH_ENABLED, enableAuth); + } + } + + void setQuorumServicePrincipal(String servicePrincipal) { + quorumServicePrincipal = servicePrincipal; + LOG.info("{} set to {}", QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL, + quorumServicePrincipal); + } + + void setQuorumLearnerLoginContext(String learnerContext) { + quorumLearnerLoginContext = learnerContext; + LOG.info("{} set to {}", QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT, + quorumLearnerLoginContext); + } + + void setQuorumServerLoginContext(String serverContext) { + quorumServerLoginContext = serverContext; + LOG.info("{} set to {}", QuorumAuth.QUORUM_SERVER_SASL_LOGIN_CONTEXT, + quorumServerLoginContext); + } + + void setQuorumCnxnThreadsSize(int qCnxnThreadsSize) { + if (qCnxnThreadsSize > QUORUM_CNXN_THREADS_SIZE_DEFAULT_VALUE) { + quorumCnxnThreadsSize = qCnxnThreadsSize; + } + LOG.info("quorum.cnxn.threads.size set to {}", quorumCnxnThreadsSize); + } + + boolean isQuorumSaslAuthEnabled() { + return quorumSaslEnableAuth; + } + + private boolean isQuorumServerSaslAuthRequired() { + return quorumServerSaslAuthRequired; + } + + private boolean isQuorumLearnerSaslAuthRequired() { + return quorumLearnerSaslAuthRequired; + } + + public QuorumCnxManager createCnxnManager() { + return new QuorumCnxManager(this, + this.getId(), + this.getView(), + this.authServer, + this.authLearner, + this.tickTime * this.syncLimit, + this.getQuorumListenOnAllIPs(), + this.quorumCnxnThreadsSize, + this.isQuorumSaslAuthEnabled()); } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java index c4397a1f80d..0f868c34c60 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java @@ -19,45 +19,55 @@ package org.apache.zookeeper.server.quorum; import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.FileReader; -import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.io.StringReader; import java.io.Writer; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Map.Entry; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.zookeeper.common.StringUtils; +import org.apache.zookeeper.common.ZKConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.apache.zookeeper.common.AtomicFileWritingIdiom; import org.apache.zookeeper.common.AtomicFileWritingIdiom.OutputStreamStatement; import org.apache.zookeeper.common.AtomicFileWritingIdiom.WriterStatement; +import org.apache.zookeeper.common.PathUtils; import org.apache.zookeeper.server.ZooKeeperServer; import org.apache.zookeeper.server.quorum.QuorumPeer.LearnerType; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.server.quorum.auth.QuorumAuth; import org.apache.zookeeper.server.quorum.flexible.QuorumHierarchical; import org.apache.zookeeper.server.quorum.flexible.QuorumMaj; import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; import org.apache.zookeeper.server.util.VerifyingFileFactory; - +@InterfaceAudience.Public public class QuorumPeerConfig { private static final Logger LOG = LoggerFactory.getLogger(QuorumPeerConfig.class); + private static final int UNSET_SERVERID = -1; + public static final String nextDynamicConfigFileSuffix = ".dynamic.next"; + private static boolean standaloneEnabled = true; + private static boolean reconfigEnabled = false; protected InetSocketAddress clientPortAddress; + protected InetSocketAddress secureClientPortAddress; protected File dataDir; protected File dataLogDir; - protected boolean configBackwardCompatibilityMode = false; protected String dynamicConfigFileStr = null; protected String configFileStr = null; protected int tickTime = ZooKeeperServer.DEFAULT_TICK_TIME; @@ -75,7 +85,7 @@ public class QuorumPeerConfig { protected int electionPort = 2182; protected boolean quorumListenOnAllIPs = false; - protected long serverId; + protected long serverId = UNSET_SERVERID; protected QuorumVerifier quorumVerifier = null, lastSeenQuorumVerifier = null; protected int snapRetainCount = 3; @@ -84,6 +94,17 @@ public class QuorumPeerConfig { protected LearnerType peerType = LearnerType.PARTICIPANT; + /** + * Configurations for the quorumpeer-to-quorumpeer sasl authentication + */ + protected boolean quorumServerRequireSasl = false; + protected boolean quorumLearnerRequireSasl = false; + protected boolean quorumEnableSasl = false; + protected String quorumServicePrincipal = QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE; + protected String quorumLearnerLoginContext = QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT_DFAULT_VALUE; + protected String quorumServerLoginContext = QuorumAuth.QUORUM_SERVER_SASL_LOGIN_CONTEXT_DFAULT_VALUE; + protected int quorumCnxnThreadsSize; + /** * Minimum snapshot retain count. * @see org.apache.zookeeper.server.PurgeTxnLog#purge(File, File, int) @@ -136,18 +157,27 @@ public void parse(String path) throws ConfigException { FileInputStream inConfig = new FileInputStream(dynamicConfigFileStr); try { dynamicCfg.load(inConfig); + if (dynamicCfg.getProperty("version") != null) { + throw new ConfigException("dynamic file shouldn't have version inside"); + } + + String version = getVersionFromFilename(dynamicConfigFileStr); + // If there isn't any version associated with the filename, + // the default version is 0. + if (version != null) { + dynamicCfg.setProperty("version", version); + } } finally { inConfig.close(); } - quorumVerifier = parseDynamicConfig(dynamicCfg, electionAlg, true, configBackwardCompatibilityMode); - checkValidity(); - + setupQuorumPeerConfig(dynamicCfg, false); + } catch (IOException e) { throw new ConfigException("Error processing " + dynamicConfigFileStr, e); } catch (IllegalArgumentException e) { throw new ConfigException("Error processing " + dynamicConfigFileStr, e); } - File nextDynamicConfigFile = new File(dynamicConfigFileStr + ".next"); + File nextDynamicConfigFile = new File(configFileStr + nextDynamicConfigFileSuffix); if (nextDynamicConfigFile.exists()) { try { Properties dynamicConfigNextCfg = new Properties(); @@ -165,7 +195,7 @@ public void parse(String path) throws ConfigException { break; } } - lastSeenQuorumVerifier = createQuorumVerifier(dynamicConfigNextCfg, isHierarchical); + lastSeenQuorumVerifier = createQuorumVerifier(dynamicConfigNextCfg, isHierarchical); } catch (IOException e) { LOG.warn("NextQuorumVerifier is initiated to null"); } @@ -173,6 +203,25 @@ public void parse(String path) throws ConfigException { } } + // This method gets the version from the end of dynamic file name. + // For example, "zoo.cfg.dynamic.0" returns initial version "0". + // "zoo.cfg.dynamic.1001" returns version of hex number "0x1001". + // If a dynamic file name doesn't have any version at the end of file, + // e.g. "zoo.cfg.dynamic", it returns null. + public static String getVersionFromFilename(String filename) { + int i = filename.lastIndexOf('.'); + if(i < 0 || i >= filename.length()) + return null; + + String hexVersion = filename.substring(i + 1); + try { + long version = Long.parseLong(hexVersion, 16); + return Long.toHexString(version); + } catch (NumberFormatException e) { + return null; + } + } + /** * Parse config from a Properties. * @param zkProp Properties to parse from. @@ -182,7 +231,9 @@ public void parse(String path) throws ConfigException { public void parseProperties(Properties zkProp) throws IOException, ConfigException { int clientPort = 0; + int secureClientPort = 0; String clientPortAddress = null; + String secureClientPortAddress = null; VerifyingFileFactory vff = new VerifyingFileFactory.Builder(LOG).warnForRelativePath().build(); for (Entry entry : zkProp.entrySet()) { String key = entry.getKey().toString().trim(); @@ -199,6 +250,10 @@ public void parseProperties(Properties zkProp) localSessionsUpgradingEnabled = Boolean.parseBoolean(value); } else if (key.equals("clientPortAddress")) { clientPortAddress = value.trim(); + } else if (key.equals("secureClientPort")) { + secureClientPort = Integer.parseInt(value); + } else if (key.equals("secureClientPortAddress")){ + secureClientPortAddress = value.trim(); } else if (key.equals("tickTime")) { tickTime = Integer.parseInt(value); } else if (key.equals("maxClientCnxns")) { @@ -238,15 +293,59 @@ public void parseProperties(Properties zkProp) } else if (value.toLowerCase().equals("false")) { setStandaloneEnabled(false); } else { - throw new ConfigException("Invalid option for standalone mode. Choose 'true' or 'false.'"); + throw new ConfigException("Invalid option " + value + " for standalone mode. Choose 'true' or 'false.'"); + } + } else if (key.equals("reconfigEnabled")) { + if (value.toLowerCase().equals("true")) { + setReconfigEnabled(true); + } else if (value.toLowerCase().equals("false")) { + setReconfigEnabled(false); + } else { + throw new ConfigException("Invalid option " + value + " for reconfigEnabled flag. Choose 'true' or 'false.'"); } } else if ((key.startsWith("server.") || key.startsWith("group") || key.startsWith("weight")) && zkProp.containsKey("dynamicConfigFile")) { throw new ConfigException("parameter: " + key + " must be in a separate dynamic config file"); + } else if (key.equals(QuorumAuth.QUORUM_SASL_AUTH_ENABLED)) { + quorumEnableSasl = Boolean.parseBoolean(value); + } else if (key.equals(QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED)) { + quorumServerRequireSasl = Boolean.parseBoolean(value); + } else if (key.equals(QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED)) { + quorumLearnerRequireSasl = Boolean.parseBoolean(value); + } else if (key.equals(QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT)) { + quorumLearnerLoginContext = value; + } else if (key.equals(QuorumAuth.QUORUM_SERVER_SASL_LOGIN_CONTEXT)) { + quorumServerLoginContext = value; + } else if (key.equals(QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL)) { + quorumServicePrincipal = value; + } else if (key.equals("quorum.cnxn.threads.size")) { + quorumCnxnThreadsSize = Integer.parseInt(value); } else { System.setProperty("zookeeper." + key, value); } } + if (!quorumEnableSasl && quorumServerRequireSasl) { + throw new IllegalArgumentException( + QuorumAuth.QUORUM_SASL_AUTH_ENABLED + + " is disabled, so cannot enable " + + QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED); + } + if (!quorumEnableSasl && quorumLearnerRequireSasl) { + throw new IllegalArgumentException( + QuorumAuth.QUORUM_SASL_AUTH_ENABLED + + " is disabled, so cannot enable " + + QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED); + } + // If quorumpeer learner is not auth enabled then self won't be able to + // join quorum. So this condition is ensuring that the quorumpeer learner + // is also auth enabled while enabling quorum server require sasl. + if (!quorumLearnerRequireSasl && quorumServerRequireSasl) { + throw new IllegalArgumentException( + QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED + + " is disabled, so cannot enable " + + QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED); + } + // Reset to MIN_SNAP_RETAIN_COUNT if invalid (less than 3) // PurgeTxnLog.purge(File, File, int) will not allow to purge less // than 3. @@ -262,15 +361,38 @@ public void parseProperties(Properties zkProp) if (dataLogDir == null) { dataLogDir = dataDir; } - if (clientPortAddress != null) { - if (clientPort == 0) { - throw new IllegalArgumentException("clientPortAddress is set but clientPort is not set"); + + if (clientPort == 0) { + LOG.info("clientPort is not set"); + if (clientPortAddress != null) { + throw new IllegalArgumentException("clientPortAddress is set but clientPort is not set"); + } + } else if (clientPortAddress != null) { + this.clientPortAddress = new InetSocketAddress( + InetAddress.getByName(clientPortAddress), clientPort); + LOG.info("clientPortAddress is {}", this.clientPortAddress.toString()); + } else { + this.clientPortAddress = new InetSocketAddress(clientPort); + LOG.info("clientPortAddress is {}", this.clientPortAddress.toString()); + } + + if (secureClientPort == 0) { + LOG.info("secureClientPort is not set"); + if (secureClientPortAddress != null) { + throw new IllegalArgumentException("secureClientPortAddress is set but secureClientPort is not set"); + } + } else if (secureClientPortAddress != null) { + this.secureClientPortAddress = new InetSocketAddress( + InetAddress.getByName(secureClientPortAddress), secureClientPort); + LOG.info("secureClientPortAddress is {}", this.secureClientPortAddress.toString()); + } else { + this.secureClientPortAddress = new InetSocketAddress(secureClientPort); + LOG.info("secureClientPortAddress is {}", this.secureClientPortAddress.toString()); + } + if (this.secureClientPortAddress != null) { + configureSSLAuth(); } - this.clientPortAddress = new InetSocketAddress( - InetAddress.getByName(clientPortAddress), clientPort); - } else if (clientPort!=0){ - this.clientPortAddress = new InetSocketAddress(clientPort); - } + if (tickTime == 0) { throw new IllegalArgumentException("tickTime is not set"); } @@ -284,53 +406,106 @@ public void parseProperties(Properties zkProp) } // backward compatibility - dynamic configuration in the same file as - // static configuration params see writeDynamicConfig() - we change the - // config file to new format if reconfig happens + // static configuration params see writeDynamicConfig() if (dynamicConfigFileStr == null) { - configBackwardCompatibilityMode = true; - quorumVerifier = parseDynamicConfig(zkProp, electionAlg, true, - configBackwardCompatibilityMode); - checkValidity(); + setupQuorumPeerConfig(zkProp, true); + if (isDistributed() && isReconfigEnabled()) { + // we don't backup static config for standalone mode. + // we also don't backup if reconfig feature is disabled. + backupOldConfig(); + } } } - + /** - * Writes dynamic configuration file, updates static config file if needed. - * @param dynamicConfigFilename - * @param configFileStr - * @param configBackwardCompatibilityMode - * @param qv - * @param needEraseStaticClientInfo indicates whether we need to erase the clientPort - * and clientPortAddress from static config file. + * Configure SSL authentication only if it is not configured. + * + * @throws ConfigException + * If authentication scheme is configured but authentication + * provider is not configured. */ - public static void writeDynamicConfig(String dynamicConfigFilename, String configFileStr, - final boolean configBackwardCompatibilityMode, final QuorumVerifier qv, - final boolean needEraseStaticClientInfo) throws IOException { + private void configureSSLAuth() throws ConfigException { + String sslAuthProp = "zookeeper.authProvider." + System.getProperty(ZKConfig.SSL_AUTHPROVIDER, "x509"); + if (System.getProperty(sslAuthProp) == null) { + if ("zookeeper.authProvider.x509".equals(sslAuthProp)) { + System.setProperty("zookeeper.authProvider.x509", + "org.apache.zookeeper.server.auth.X509AuthenticationProvider"); + } else { + throw new ConfigException("No auth provider configured for the SSL authentication scheme '" + + System.getProperty(ZKConfig.SSL_AUTHPROVIDER) + "'."); + } + } + } - final String actualDynamicConfigFilename = dynamicConfigFilename; - new AtomicFileWritingIdiom(new File(actualDynamicConfigFilename), new OutputStreamStatement() { + /** + * Backward compatibility -- It would backup static config file on bootup + * if users write dynamic configuration in "zoo.cfg". + */ + private void backupOldConfig() throws IOException { + new AtomicFileWritingIdiom(new File(configFileStr + ".bak"), new OutputStreamStatement() { @Override - public void write(OutputStream outConfig) throws IOException { - byte b[] = qv.toString().getBytes(); - outConfig.write(b); + public void write(OutputStream output) throws IOException { + InputStream input = null; + try { + input = new FileInputStream(new File(configFileStr)); + byte[] buf = new byte[1024]; + int bytesRead; + while ((bytesRead = input.read(buf)) > 0) { + output.write(buf, 0, bytesRead); + } + } finally { + if( input != null) { + input.close(); + } + } } }); - // the following is for users who run without a dynamic config file (old config file) - // we create a dynamic config file, remove all the dynamic definitions from the config file and add a pointer - // to the config file. The dynamic config file's name will be the same as the config file's - // with ".dynamic" appended to it + } - if (!configBackwardCompatibilityMode && !needEraseStaticClientInfo) - return; + /** + * Writes dynamic configuration file + */ + public static void writeDynamicConfig(final String dynamicConfigFilename, + final QuorumVerifier qv, + final boolean needKeepVersion) + throws IOException { + + new AtomicFileWritingIdiom(new File(dynamicConfigFilename), new WriterStatement() { + @Override + public void write(Writer out) throws IOException { + Properties cfg = new Properties(); + cfg.load( new StringReader( + qv.toString())); + + List servers = new ArrayList(); + for (Entry entry : cfg.entrySet()) { + String key = entry.getKey().toString().trim(); + if ( !needKeepVersion && key.startsWith("version")) + continue; - editStaticConfig(configFileStr, actualDynamicConfigFilename, - configBackwardCompatibilityMode, needEraseStaticClientInfo); + String value = entry.getValue().toString().trim(); + servers.add(key + .concat("=") + .concat(value)); + } + + Collections.sort(servers); + out.write(StringUtils.joinStrings(servers, "\n")); + } + }); } - private static void editStaticConfig(final String configFileStr, - final String dynamicFileStr, - final boolean backwardCompatible, - final boolean eraseClientPortAddress) + /** + * Edit static config file. + * If there are quorum information in static file, e.g. "server.X", "group", + * it will remove them. + * If it needs to erase client port information left by the old config, + * "eraseClientPortAddress" should be set true. + * It should also updates dynamic file pointer on reconfig. + */ + public static void editStaticConfig(final String configFileStr, + final String dynamicFileStr, + final boolean eraseClientPortAddress) throws IOException { // Some tests may not have a static config file. if (configFileStr == null) @@ -341,6 +516,11 @@ private static void editStaticConfig(final String configFileStr, .failForNonExistingPath() .build()).create(configFileStr); + final File dynamicFile = (new VerifyingFileFactory.Builder(LOG) + .warnForRelativePath() + .failForNonExistingPath() + .build()).create(dynamicFileStr); + final Properties cfg = new Properties(); FileInputStream in = new FileInputStream(configFile); try { @@ -358,6 +538,7 @@ public void write(Writer out) throws IOException { if (key.startsWith("server.") || key.startsWith("group") || key.startsWith("weight") + || key.startsWith("dynamicConfigFile") || (eraseClientPortAddress && (key.startsWith("clientPort") || key.startsWith("clientPortAddress")))) { @@ -369,16 +550,18 @@ public void write(Writer out) throws IOException { out.write(key.concat("=").concat(value).concat("\n")); } - if ( ! backwardCompatible ) - return; - - out.write("dynamicConfigFile=".concat(dynamicFileStr).concat("\n")); + // updates the dynamic file pointer + String dynamicConfigFilePath = PathUtils.normalizeFileSystemPath(dynamicFile.getCanonicalPath()); + out.write("dynamicConfigFile=" + .concat(dynamicConfigFilePath) + .concat("\n")); } }); } - public static void deleteFile(String filename){ + public static void deleteFile(String filename){ + if (filename == null) return; File f = new File(filename); if (f.exists()) { try{ @@ -401,7 +584,16 @@ private static QuorumVerifier createQuorumVerifier(Properties dynamicConfigProp, return new QuorumMaj(dynamicConfigProp); } } - + + void setupQuorumPeerConfig(Properties prop, boolean configBackwardCompatibilityMode) + throws IOException, ConfigException { + quorumVerifier = parseDynamicConfig(prop, electionAlg, true, configBackwardCompatibilityMode); + setupMyId(); + setupClientPort(); + setupPeerType(); + checkValidity(); + } + /** * Parse dynamic configuration file and return * quorumVerifier for new configuration. @@ -427,6 +619,10 @@ public static QuorumVerifier parseDynamicConfig(Properties dynamicConfigProp, in int numParticipators = qv.getVotingMembers().size(); int numObservers = qv.getObservingMembers().size(); if (numParticipators == 0) { + if (!standaloneEnabled) { + throw new IllegalArgumentException("standaloneEnabled = false then " + + "number of participants should be >0"); + } if (numObservers > 0) { throw new IllegalArgumentException("Observers w/o participants is an invalid configuration"); } @@ -462,68 +658,75 @@ public static QuorumVerifier parseDynamicConfig(Properties dynamicConfigProp, in } return qv; } - + + private void setupMyId() throws IOException { + File myIdFile = new File(dataDir, "myid"); + // standalone server doesn't need myid file. + if (!myIdFile.isFile()) { + return; + } + BufferedReader br = new BufferedReader(new FileReader(myIdFile)); + String myIdString; + try { + myIdString = br.readLine(); + } finally { + br.close(); + } + try { + serverId = Long.parseLong(myIdString); + MDC.put("myid", myIdString); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("serverid " + myIdString + + " is not a number"); + } + } + + private void setupClientPort() throws ConfigException { + if (serverId == UNSET_SERVERID) { + return; + } + QuorumServer qs = quorumVerifier.getAllMembers().get(serverId); + if (clientPortAddress != null && qs != null && qs.clientAddr != null) { + if ((!clientPortAddress.getAddress().isAnyLocalAddress() + && !clientPortAddress.equals(qs.clientAddr)) || + (clientPortAddress.getAddress().isAnyLocalAddress() + && clientPortAddress.getPort() != qs.clientAddr.getPort())) + throw new ConfigException("client address for this server (id = " + serverId + + ") in static config file is " + clientPortAddress + + " is different from client address found in dynamic file: " + qs.clientAddr); + } + if (qs != null && qs.clientAddr != null) clientPortAddress = qs.clientAddr; + } + + private void setupPeerType() { + // Warn about inconsistent peer type + LearnerType roleByServersList = quorumVerifier.getObservingMembers().containsKey(serverId) ? LearnerType.OBSERVER + : LearnerType.PARTICIPANT; + if (roleByServersList != peerType) { + LOG.warn("Peer type from servers list (" + roleByServersList + + ") doesn't match peerType (" + peerType + + "). Defaulting to servers list."); + + peerType = roleByServersList; + } + } public void checkValidity() throws IOException, ConfigException{ - int numMembers = quorumVerifier.getVotingMembers().size(); - if (numMembers > 1 || (!standaloneEnabled && numMembers > 0)) { - if (initLimit == 0) { - throw new IllegalArgumentException("initLimit is not set"); - } - if (syncLimit == 0) { - throw new IllegalArgumentException("syncLimit is not set"); - } - - - File myIdFile = new File(dataDir, "myid"); - if (!myIdFile.exists()) { - throw new IllegalArgumentException(myIdFile.toString() - + " file is missing"); + if (isDistributed()) { + if (initLimit == 0) { + throw new IllegalArgumentException("initLimit is not set"); } - BufferedReader br = new BufferedReader(new FileReader(myIdFile)); - String myIdString; - try { - myIdString = br.readLine(); - } finally { - br.close(); + if (syncLimit == 0) { + throw new IllegalArgumentException("syncLimit is not set"); } - try { - serverId = Long.parseLong(myIdString); - MDC.put("myid", myIdString); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("serverid " + myIdString - + " is not a number"); - } - - QuorumServer qs = quorumVerifier.getAllMembers().get(serverId); - if (clientPortAddress!=null && qs!=null && qs.clientAddr!=null){ - if ((!clientPortAddress.getAddress().isAnyLocalAddress() - && !clientPortAddress.equals(qs.clientAddr)) || - (clientPortAddress.getAddress().isAnyLocalAddress() - && clientPortAddress.getPort()!=qs.clientAddr.getPort())) - throw new ConfigException("client address for this server (id = " + serverId + ") in static config file is " + clientPortAddress + " is different from client address found in dynamic file: " + qs.clientAddr); - else { - editStaticConfig(configFileStr, null, false, true); - } + if (serverId == UNSET_SERVERID) { + throw new IllegalArgumentException("myid file is missing"); } - if (qs!=null && qs.clientAddr != null) clientPortAddress = qs.clientAddr; - - // Warn about inconsistent peer type - LearnerType roleByServersList = quorumVerifier.getObservingMembers().containsKey(serverId) ? LearnerType.OBSERVER - : LearnerType.PARTICIPANT; - if (roleByServersList != peerType) { - LOG.warn("Peer type from servers list (" + roleByServersList - + ") doesn't match peerType (" + peerType - + "). Defaulting to servers list."); - - peerType = roleByServersList; - } - } - } - + public InetSocketAddress getClientPortAddress() { return clientPortAddress; } + public InetSocketAddress getSecureClientPortAddress() { return secureClientPortAddress; } public File getDataDir() { return dataDir; } public File getDataLogDir() { return dataLogDir; } public int getTickTime() { return tickTime; } @@ -574,19 +777,11 @@ public boolean isDistributed() { public LearnerType getPeerType() { return peerType; } - - public String getDynamicConfigFilename() { - return dynamicConfigFileStr; - } - + public String getConfigFilename(){ return configFileStr; } - public boolean getConfigBackwardCompatibility(){ - return configBackwardCompatibilityMode; - } - public Boolean getQuorumListenOnAllIPs() { return quorumListenOnAllIPs; } @@ -596,7 +791,13 @@ public static boolean isStandaloneEnabled() { } public static void setStandaloneEnabled(boolean enabled) { - standaloneEnabled = enabled; + standaloneEnabled = enabled; + } + + public static boolean isReconfigEnabled() { return reconfigEnabled; } + + public static void setReconfigEnabled(boolean enabled) { + reconfigEnabled = enabled; } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java index 0a8a45af266..3da6e20edd4 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java @@ -20,7 +20,9 @@ import java.io.IOException; import javax.management.JMException; +import javax.security.sasl.SaslException; +import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.jmx.ManagedUtil; @@ -61,6 +63,7 @@ * "myid" that contains the server id as an ASCII decimal value. * */ +@InterfaceAudience.Public public class QuorumPeerMain { private static final Logger LOG = LoggerFactory.getLogger(QuorumPeerMain.class); @@ -126,7 +129,9 @@ protected void initializeAndRun(String[] args) } } - public void runFromConfig(QuorumPeerConfig config) throws IOException, AdminServerException { + public void runFromConfig(QuorumPeerConfig config) + throws IOException, AdminServerException + { try { ManagedUtil.registerLog4jMBeans(); } catch (JMException e) { @@ -135,11 +140,24 @@ public void runFromConfig(QuorumPeerConfig config) throws IOException, AdminServ LOG.info("Starting quorum peer"); try { - ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory(); - cnxnFactory.configure(config.getClientPortAddress(), - config.getMaxClientCnxns()); + ServerCnxnFactory cnxnFactory = null; + ServerCnxnFactory secureCnxnFactory = null; + + if (config.getClientPortAddress() != null) { + cnxnFactory = ServerCnxnFactory.createFactory(); + cnxnFactory.configure(config.getClientPortAddress(), + config.getMaxClientCnxns(), + false); + } + + if (config.getSecureClientPortAddress() != null) { + secureCnxnFactory = ServerCnxnFactory.createFactory(); + secureCnxnFactory.configure(config.getSecureClientPortAddress(), + config.getMaxClientCnxns(), + true); + } - quorumPeer = new QuorumPeer(); + quorumPeer = getQuorumPeer(); quorumPeer.setTxnFactory(new FileTxnSnapLog( config.getDataLogDir(), config.getDataDir())); @@ -154,9 +172,7 @@ public void runFromConfig(QuorumPeerConfig config) throws IOException, AdminServ quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout()); quorumPeer.setInitLimit(config.getInitLimit()); quorumPeer.setSyncLimit(config.getSyncLimit()); - quorumPeer.setDynamicConfigFilename(config.getDynamicConfigFilename()); quorumPeer.setConfigFileName(config.getConfigFilename()); - quorumPeer.setConfigBackwardCompatibility(config.getConfigBackwardCompatibility()); quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory())); quorumPeer.setQuorumVerifier(config.getQuorumVerifier(), false); if (config.getLastSeenQuorumVerifier()!=null) { @@ -164,9 +180,22 @@ public void runFromConfig(QuorumPeerConfig config) throws IOException, AdminServ } quorumPeer.initConfigInZKDatabase(); quorumPeer.setCnxnFactory(cnxnFactory); + quorumPeer.setSecureCnxnFactory(secureCnxnFactory); quorumPeer.setLearnerType(config.getPeerType()); quorumPeer.setSyncEnabled(config.getSyncEnabled()); quorumPeer.setQuorumListenOnAllIPs(config.getQuorumListenOnAllIPs()); + + // sets quorum sasl authentication configurations + quorumPeer.setQuorumSaslEnabled(config.quorumEnableSasl); + if(quorumPeer.isQuorumSaslAuthEnabled()){ + quorumPeer.setQuorumServerSaslRequired(config.quorumServerRequireSasl); + quorumPeer.setQuorumLearnerSaslRequired(config.quorumLearnerRequireSasl); + quorumPeer.setQuorumServicePrincipal(config.quorumServicePrincipal); + quorumPeer.setQuorumServerLoginContext(config.quorumServerLoginContext); + quorumPeer.setQuorumLearnerLoginContext(config.quorumLearnerLoginContext); + } + quorumPeer.setQuorumCnxnThreadsSize(config.quorumCnxnThreadsSize); + quorumPeer.initialize(); quorumPeer.start(); quorumPeer.join(); @@ -175,4 +204,9 @@ public void runFromConfig(QuorumPeerConfig config) throws IOException, AdminServ LOG.warn("Quorum Peer interrupted", e); } } + + // @VisibleForTesting + protected QuorumPeer getQuorumPeer() throws SaslException { + return new QuorumPeer(); + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumZooKeeperServer.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumZooKeeperServer.java index 52d8765eb24..f6e4f1135b5 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumZooKeeperServer.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumZooKeeperServer.java @@ -23,6 +23,8 @@ import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.MultiTransactionRecord; +import org.apache.zookeeper.Op; import org.apache.zookeeper.ZooDefs.OpCode; import org.apache.zookeeper.proto.CreateRequest; import org.apache.zookeeper.server.ByteBufferInputStream; @@ -62,18 +64,41 @@ public Request checkUpgradeSession(Request request) // This is called by the request processor thread (either follower // or observer request processor), which is unique to a learner. // So will not be called concurrently by two threads. - if (request.type != OpCode.create || + if ((request.type != OpCode.create && request.type != OpCode.create2 && request.type != OpCode.multi) || !upgradeableSessionTracker.isLocalSession(request.sessionId)) { return null; } - CreateRequest createRequest = new CreateRequest(); - request.request.rewind(); - ByteBufferInputStream.byteBuffer2Record(request.request, createRequest); - request.request.rewind(); - CreateMode createMode = CreateMode.fromFlag(createRequest.getFlags()); - if (!createMode.isEphemeral()) { - return null; + + if (OpCode.multi == request.type) { + MultiTransactionRecord multiTransactionRecord = new MultiTransactionRecord(); + request.request.rewind(); + ByteBufferInputStream.byteBuffer2Record(request.request, multiTransactionRecord); + request.request.rewind(); + boolean containsEphemeralCreate = false; + for (Op op : multiTransactionRecord) { + if (op.getType() == OpCode.create || op.getType() == OpCode.create2) { + CreateRequest createRequest = (CreateRequest)op.toRequestRecord(); + CreateMode createMode = CreateMode.fromFlag(createRequest.getFlags()); + if (createMode.isEphemeral()) { + containsEphemeralCreate = true; + break; + } + } + } + if (!containsEphemeralCreate) { + return null; + } + } else { + CreateRequest createRequest = new CreateRequest(); + request.request.rewind(); + ByteBufferInputStream.byteBuffer2Record(request.request, createRequest); + request.request.rewind(); + CreateMode createMode = CreateMode.fromFlag(createRequest.getFlags()); + if (!createMode.isEphemeral()) { + return null; + } } + // Uh oh. We need to upgrade before we can proceed. if (!self.isLocalSessionsUpgradingEnabled()) { throw new KeeperException.EphemeralOnLocalSessionException(); @@ -156,4 +181,9 @@ public void dumpConf(PrintWriter pwriter) { pwriter.println("membership: "); pwriter.print(new String(self.getQuorumVerifier().toString().getBytes())); } + + @Override + protected void setState(State state) { + this.state = state; + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyRequestProcessor.java b/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyRequestProcessor.java index 18617b82730..c62eb4200b7 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyRequestProcessor.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyRequestProcessor.java @@ -52,8 +52,10 @@ public class ReadOnlyRequestProcessor extends ZooKeeperCriticalThread implements private final ZooKeeperServer zks; - public ReadOnlyRequestProcessor(ZooKeeperServer zks, RequestProcessor nextProcessor) { - super("ReadOnlyRequestProcessor:" + zks.getServerId()); + public ReadOnlyRequestProcessor(ZooKeeperServer zks, + RequestProcessor nextProcessor) { + super("ReadOnlyRequestProcessor:" + zks.getServerId(), zks + .getZooKeeperServerListener()); this.zks = zks; this.nextProcessor = nextProcessor; } @@ -80,7 +82,10 @@ public void run() { case OpCode.sync: case OpCode.create: case OpCode.create2: + case OpCode.createTTL: + case OpCode.createContainer: case OpCode.delete: + case OpCode.deleteContainer: case OpCode.setData: case OpCode.reconfig: case OpCode.setACL: @@ -101,15 +106,13 @@ public void run() { nextProcessor.processRequest(request); } } - } catch (InterruptedException e) { - LOG.error("Unexpected interruption", e); } catch (RequestProcessorException e) { if (e.getCause() instanceof XidRolloverException) { LOG.info(e.getCause().getMessage()); } - LOG.error("Unexpected exception", e); + handleException(this.getName(), e); } catch (Exception e) { - LOG.error("Unexpected exception", e); + handleException(this.getName(), e); } LOG.info("ReadOnlyRequestProcessor exited loop!"); } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java b/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java index 2aab6d09f9b..2ef8a5e3ed0 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java @@ -68,7 +68,7 @@ public synchronized void startup() { } registerJMX(new ReadOnlyBean(this), self.jmxLocalPeerBean); super.startup(); - self.cnxnFactory.setZooKeeperServer(this); + self.setZooKeeperServer(this); self.adminServer.setZooKeeperServer(this); LOG.info("Read-only server started"); } @@ -137,13 +137,17 @@ public long getServerId() { @Override public synchronized void shutdown() { + if (!canShutdown()) { + LOG.debug("ZooKeeper server is not running, so not proceeding to shutdown!"); + return; + } shutdown = true; unregisterJMX(this); // set peer's server to null - self.cnxnFactory.setZooKeeperServer(null); + self.setZooKeeperServer(null); // clear all the connections - self.cnxnFactory.closeAll(); + self.closeAllConnections(); self.adminServer.setZooKeeperServer(null); @@ -168,4 +172,9 @@ public void dumpConf(PrintWriter pwriter) { pwriter.print("peerType="); pwriter.println(self.getLearnerType().ordinal()); } + + @Override + protected void setState(State state) { + this.state = state; + } } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/RemotePeerBean.java b/src/java/main/org/apache/zookeeper/server/quorum/RemotePeerBean.java index a5af465f2eb..dcf56848bf6 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/RemotePeerBean.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/RemotePeerBean.java @@ -18,7 +18,6 @@ package org.apache.zookeeper.server.quorum; -import org.apache.zookeeper.common.HostNameUtils; import org.apache.zookeeper.jmx.ZKMBeanInfo; /** @@ -44,16 +43,18 @@ public boolean isHidden() { } public String getQuorumAddress() { - return peer.addr.getHostName()+":"+peer.addr.getPort(); + return peer.addr.getHostString()+":"+peer.addr.getPort(); } public String getElectionAddress() { - return HostNameUtils.getHostString(peer.electionAddr) + ":" - + peer.electionAddr.getPort(); + return peer.electionAddr.getHostString() + ":" + peer.electionAddr.getPort(); } public String getClientAddress() { - return HostNameUtils.getHostString(peer.clientAddr) + ":" + if (null == peer.clientAddr) { + return ""; + } + return peer.clientAddr.getHostString() + ":" + peer.clientAddr.getPort(); } diff --git a/src/java/main/org/apache/zookeeper/server/quorum/UpgradeableSessionTracker.java b/src/java/main/org/apache/zookeeper/server/quorum/UpgradeableSessionTracker.java index 45a515a61af..2e58ff590c0 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/UpgradeableSessionTracker.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/UpgradeableSessionTracker.java @@ -22,6 +22,7 @@ import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.server.SessionTracker; +import org.apache.zookeeper.server.ZooKeeperServerListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,11 +38,11 @@ public abstract class UpgradeableSessionTracker implements SessionTracker { public void start() {} public void createLocalSessionTracker(SessionExpirer expirer, - int tickTime, long id) { + int tickTime, long id, ZooKeeperServerListener listener) { this.localSessionsWithTimeouts = new ConcurrentHashMap(); this.localSessionTracker = new LocalSessionTracker( - expirer, this.localSessionsWithTimeouts, tickTime, id); + expirer, this.localSessionsWithTimeouts, tickTime, id, listener); } public boolean isTrackingSession(long sessionId) { diff --git a/src/java/main/org/apache/zookeeper/server/quorum/auth/NullQuorumAuthLearner.java b/src/java/main/org/apache/zookeeper/server/quorum/auth/NullQuorumAuthLearner.java new file mode 100644 index 00000000000..0af891c55e1 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/quorum/auth/NullQuorumAuthLearner.java @@ -0,0 +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.server.quorum.auth; + +import java.net.Socket; + +/** + * This class represents no authentication learner, it just return + * without performing any authentication. + */ +public class NullQuorumAuthLearner implements QuorumAuthLearner { + + @Override + public void authenticate(Socket sock, String hostname) { + return; // simply return don't require auth + } +} diff --git a/src/java/main/org/apache/zookeeper/server/quorum/auth/NullQuorumAuthServer.java b/src/java/main/org/apache/zookeeper/server/quorum/auth/NullQuorumAuthServer.java new file mode 100644 index 00000000000..b26a54a81ea --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/quorum/auth/NullQuorumAuthServer.java @@ -0,0 +1,34 @@ +/** + * 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.DataInputStream; +import java.net.Socket; + +/** + * This class represents no authentication server, it just return + * without performing any authentication. + */ +public class NullQuorumAuthServer implements QuorumAuthServer { + + @Override + public void authenticate(final Socket sock, final DataInputStream din) { + return; // simply return don't require auth + } +} diff --git a/src/java/main/org/apache/zookeeper/server/quorum/auth/QuorumAuth.java b/src/java/main/org/apache/zookeeper/server/quorum/auth/QuorumAuth.java new file mode 100644 index 00000000000..8bfa394decc --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/quorum/auth/QuorumAuth.java @@ -0,0 +1,96 @@ +/** + * 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.DataInputStream; +import java.io.IOException; +import org.apache.jute.BinaryInputArchive; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.zookeeper.server.quorum.QuorumAuthPacket; + +public class QuorumAuth { + private static final Logger LOG = LoggerFactory.getLogger(QuorumAuth.class); + + public static final String QUORUM_SASL_AUTH_ENABLED = "quorum.auth.enableSasl"; + public static final String QUORUM_SERVER_SASL_AUTH_REQUIRED = "quorum.auth.serverRequireSasl"; + public static final String QUORUM_LEARNER_SASL_AUTH_REQUIRED = "quorum.auth.learnerRequireSasl"; + + public static final String QUORUM_KERBEROS_SERVICE_PRINCIPAL = "quorum.auth.kerberos.servicePrincipal"; + public static final String QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE = "zkquorum/localhost"; + + public static final String QUORUM_LEARNER_SASL_LOGIN_CONTEXT = "quorum.auth.learner.saslLoginContext"; + public static final String QUORUM_LEARNER_SASL_LOGIN_CONTEXT_DFAULT_VALUE = "QuorumLearner"; + + public static final String QUORUM_SERVER_SASL_LOGIN_CONTEXT = "quorum.auth.server.saslLoginContext"; + public static final String QUORUM_SERVER_SASL_LOGIN_CONTEXT_DFAULT_VALUE = "QuorumServer"; + + static final String QUORUM_SERVER_PROTOCOL_NAME = "zookeeper-quorum"; + static final String QUORUM_SERVER_SASL_DIGEST = "zk-quorum-sasl-md5"; + static final String QUORUM_AUTH_MESSAGE_TAG = "qpconnect"; + + // this is negative, so that if a learner that does auth, connects to a + // server, it'll think the received packet is an authentication packet + public static final long QUORUM_AUTH_MAGIC_NUMBER = -0xa0dbcafecafe1234L; + + public enum Status { + IN_PROGRESS(0), SUCCESS(1), ERROR(-1); + private int status; + + Status(int status) { + this.status = status; + } + + static Status getStatus(int status) { + switch (status) { + case 0: + return IN_PROGRESS; + case 1: + return SUCCESS; + case -1: + return ERROR; + default: + LOG.error("Unknown status:{}!", status); + assert false : "Unknown status!"; + return ERROR; + } + } + + int status() { + return status; + } + } + + public static QuorumAuthPacket createPacket(Status status, byte[] response) { + return new QuorumAuthPacket(QUORUM_AUTH_MAGIC_NUMBER, + status.status(), response); + } + + public static boolean nextPacketIsAuth(DataInputStream din) + throws IOException { + din.mark(32); + BinaryInputArchive bia = new BinaryInputArchive(din); + boolean firstIsAuth = (bia.readLong("NO_TAG") + == QuorumAuth.QUORUM_AUTH_MAGIC_NUMBER); + din.reset(); + return firstIsAuth; + } +} diff --git a/src/java/main/org/apache/zookeeper/server/quorum/auth/QuorumAuthLearner.java b/src/java/main/org/apache/zookeeper/server/quorum/auth/QuorumAuthLearner.java new file mode 100644 index 00000000000..af712574802 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/quorum/auth/QuorumAuthLearner.java @@ -0,0 +1,40 @@ +/** + * 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.net.Socket; + +/** + * Interface for quorum learner authentication mechanisms. + */ +public interface QuorumAuthLearner { + + /** + * Performs an authentication step for the given socket connection. + * + * @param sock + * socket connection to other quorum peer server + * @param hostname + * host name of other quorum peer server + * @throws IOException + * if there is an authentication failure + */ + public void authenticate(Socket sock, String hostname) throws IOException; +} diff --git a/src/java/main/org/apache/zookeeper/server/quorum/auth/QuorumAuthServer.java b/src/java/main/org/apache/zookeeper/server/quorum/auth/QuorumAuthServer.java new file mode 100644 index 00000000000..e9de8f00a5a --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/quorum/auth/QuorumAuthServer.java @@ -0,0 +1,41 @@ +/** + * 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.DataInputStream; +import java.io.IOException; +import java.net.Socket; + +/** + * Interface for quorum server authentication mechanisms. + */ +public interface QuorumAuthServer { + + /** + * Performs an authentication step for the given socket connection. + * + * @param sock + * socket connection to other quorum peer + * @param din + * stream used to read auth data send by the quorum learner + * @throws IOException if the server fails to authenticate connecting quorum learner + */ + public void authenticate(Socket sock, DataInputStream din) + throws IOException; +} diff --git a/src/java/main/org/apache/zookeeper/server/quorum/auth/SaslQuorumAuthLearner.java b/src/java/main/org/apache/zookeeper/server/quorum/auth/SaslQuorumAuthLearner.java new file mode 100644 index 00000000000..31f4f55c81f --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/quorum/auth/SaslQuorumAuthLearner.java @@ -0,0 +1,223 @@ +/** + * 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.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; + +import javax.security.auth.Subject; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginException; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; + +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.zookeeper.Login; +import org.apache.zookeeper.SaslClientCallbackHandler; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.quorum.QuorumAuthPacket; +import org.apache.zookeeper.util.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SaslQuorumAuthLearner implements QuorumAuthLearner { + private static final Logger LOG = LoggerFactory + .getLogger(SaslQuorumAuthLearner.class); + + private final Login learnerLogin; + private final boolean quorumRequireSasl; + private final String quorumServicePrincipal; + + public SaslQuorumAuthLearner(boolean quorumRequireSasl, + String quorumServicePrincipal, String loginContext) + throws SaslException { + this.quorumRequireSasl = quorumRequireSasl; + this.quorumServicePrincipal = quorumServicePrincipal; + try { + AppConfigurationEntry entries[] = Configuration + .getConfiguration() + .getAppConfigurationEntry(loginContext); + if (entries == null || entries.length == 0) { + throw new LoginException("SASL-authentication failed because" + + " the specified JAAS configuration " + + "section '" + loginContext + + "' could not be found."); + } + this.learnerLogin = new Login(loginContext, + new SaslClientCallbackHandler(null, "QuorumLearner"), new ZKConfig()); + this.learnerLogin.startThreadIfNeeded(); + } catch (LoginException e) { + throw new SaslException("Failed to initialize authentication mechanism using SASL", e); + } + } + + @Override + public void authenticate(Socket sock, String hostName) throws IOException { + if (!quorumRequireSasl) { // let it through, we don't require auth + LOG.info("Skipping SASL authentication as {}={}", + QuorumAuth.QUORUM_LEARNER_SASL_AUTH_REQUIRED, + quorumRequireSasl); + return; + } + SaslClient sc = null; + String principalConfig = SecurityUtils + .getServerPrincipal(quorumServicePrincipal, hostName); + try { + DataOutputStream dout = new DataOutputStream( + sock.getOutputStream()); + DataInputStream din = new DataInputStream(sock.getInputStream()); + byte[] responseToken = new byte[0]; + sc = SecurityUtils.createSaslClient(learnerLogin.getSubject(), + principalConfig, + QuorumAuth.QUORUM_SERVER_PROTOCOL_NAME, + QuorumAuth.QUORUM_SERVER_SASL_DIGEST, LOG, "QuorumLearner"); + + if (sc.hasInitialResponse()) { + responseToken = createSaslToken(new byte[0], sc, learnerLogin); + } + send(dout, responseToken); + QuorumAuthPacket authPacket = receive(din); + QuorumAuth.Status qpStatus = QuorumAuth.Status + .getStatus(authPacket.getStatus()); + while (!sc.isComplete()) { + switch (qpStatus) { + case SUCCESS: + responseToken = createSaslToken(authPacket.getToken(), sc, + learnerLogin); + // we're done; don't expect to send another BIND + if (responseToken != null) { + throw new SaslException("Protocol error: attempting to send response after completion"); + } + break; + case IN_PROGRESS: + responseToken = createSaslToken(authPacket.getToken(), sc, + learnerLogin); + send(dout, responseToken); + authPacket = receive(din); + qpStatus = QuorumAuth.Status + .getStatus(authPacket.getStatus()); + break; + case ERROR: + throw new SaslException( + "Authentication failed against server addr: " + + sock.getRemoteSocketAddress()); + default: + LOG.warn("Unknown status:{}!", qpStatus); + throw new SaslException( + "Authentication failed against server addr: " + + sock.getRemoteSocketAddress()); + } + } + + // Validate status code at the end of authentication exchange. + checkAuthStatus(sock, qpStatus); + } finally { + if (sc != null) { + try { + sc.dispose(); + } catch (SaslException e) { + LOG.error("SaslClient dispose() failed", e); + } + } + } + return; + } + + private void checkAuthStatus(Socket sock, QuorumAuth.Status qpStatus) + throws SaslException { + if (qpStatus == QuorumAuth.Status.SUCCESS) { + LOG.info("Successfully completed the authentication using SASL. server addr: {}, status: {}", + sock.getRemoteSocketAddress(), qpStatus); + } else { + throw new SaslException("Authentication failed against server addr: " + + sock.getRemoteSocketAddress() + ", qpStatus: " + + qpStatus); + } + } + + private QuorumAuthPacket receive(DataInputStream din) throws IOException { + QuorumAuthPacket authPacket = new QuorumAuthPacket(); + BinaryInputArchive bia = BinaryInputArchive.getArchive(din); + authPacket.deserialize(bia, QuorumAuth.QUORUM_AUTH_MESSAGE_TAG); + return authPacket; + } + + private void send(DataOutputStream dout, byte[] response) + throws IOException { + QuorumAuthPacket authPacket; + BufferedOutputStream bufferedOutput = new BufferedOutputStream(dout); + BinaryOutputArchive boa = BinaryOutputArchive + .getArchive(bufferedOutput); + authPacket = QuorumAuth.createPacket( + QuorumAuth.Status.IN_PROGRESS, response); + boa.writeRecord(authPacket, QuorumAuth.QUORUM_AUTH_MESSAGE_TAG); + bufferedOutput.flush(); + } + + // TODO: need to consolidate the #createSaslToken() implementation between ZooKeeperSaslClient#createSaslToken(). + private byte[] createSaslToken(final byte[] saslToken, + final SaslClient saslClient, final Login login) + throws SaslException { + if (saslToken == null) { + throw new SaslException( + "Error in authenticating with a Zookeeper Quorum member: the quorum member's saslToken is null."); + } + if (login.getSubject() != null) { + synchronized (login) { + try { + final byte[] retval = Subject.doAs(login.getSubject(), + new PrivilegedExceptionAction() { + public byte[] run() throws SaslException { + LOG.debug("saslClient.evaluateChallenge(len=" + + saslToken.length + ")"); + return saslClient.evaluateChallenge(saslToken); + } + }); + return retval; + } catch (PrivilegedActionException e) { + String error = "An error: (" + e + + ") occurred when evaluating Zookeeper Quorum Member's " + + " received SASL token."; + // Try to provide hints to use about what went wrong so they + // can fix their configuration. + // TODO: introspect about e: look for GSS information. + final String UNKNOWN_SERVER_ERROR_TEXT = "(Mechanism level: Server not found in Kerberos database (7) - UNKNOWN_SERVER)"; + if (e.toString().indexOf(UNKNOWN_SERVER_ERROR_TEXT) > -1) { + error += " This may be caused by Java's being unable to resolve the Zookeeper Quorum Member's" + + " hostname correctly. You may want to try to adding" + + " '-Dsun.net.spi.nameservice.provider.1=dns,sun' to your server's JVMFLAGS environment."; + } + LOG.error(error); + throw new SaslException(error); + } + } + } else { + throw new SaslException( + "Cannot make SASL token without subject defined. " + + "For diagnosis, please look for WARNs and ERRORs in your log related to the Login class."); + } + } +} diff --git a/src/java/main/org/apache/zookeeper/server/quorum/auth/SaslQuorumAuthServer.java b/src/java/main/org/apache/zookeeper/server/quorum/auth/SaslQuorumAuthServer.java new file mode 100644 index 00000000000..fc5e3b6b736 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/quorum/auth/SaslQuorumAuthServer.java @@ -0,0 +1,179 @@ +/** + * 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.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.util.Set; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.zookeeper.Login; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.quorum.QuorumAuthPacket; +import org.apache.zookeeper.util.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SaslQuorumAuthServer implements QuorumAuthServer { + + private static final Logger LOG = LoggerFactory + .getLogger(SaslQuorumAuthServer.class); + + private final static int MAX_RETRIES = 5; + private final Login serverLogin; + private final boolean quorumRequireSasl; + + public SaslQuorumAuthServer(boolean quorumRequireSasl, String loginContext, Set authzHosts) + throws SaslException { + this.quorumRequireSasl = quorumRequireSasl; + try { + AppConfigurationEntry entries[] = Configuration.getConfiguration() + .getAppConfigurationEntry(loginContext); + if (entries == null || entries.length == 0) { + throw new LoginException("SASL-authentication failed" + + " because the specified JAAS configuration " + + "section '" + loginContext + "' could not be found."); + } + SaslQuorumServerCallbackHandler saslServerCallbackHandler = new SaslQuorumServerCallbackHandler( + Configuration.getConfiguration(), loginContext, authzHosts); + serverLogin = new Login(loginContext, saslServerCallbackHandler, new ZKConfig()); + serverLogin.startThreadIfNeeded(); + } catch (Throwable e) { + throw new SaslException( + "Failed to initialize authentication mechanism using SASL", + e); + } + } + + @Override + public void authenticate(Socket sock, DataInputStream din) + throws SaslException { + DataOutputStream dout = null; + SaslServer ss = null; + try { + if (!QuorumAuth.nextPacketIsAuth(din)) { + if (quorumRequireSasl) { + throw new SaslException("Learner not trying to authenticate" + + " and authentication is required"); + } else { + // let it through, we don't require auth + return; + } + } + + byte[] token = receive(din); + int tries = 0; + dout = new DataOutputStream(sock.getOutputStream()); + byte[] challenge = null; + ss = SecurityUtils.createSaslServer(serverLogin.getSubject(), + QuorumAuth.QUORUM_SERVER_PROTOCOL_NAME, + QuorumAuth.QUORUM_SERVER_SASL_DIGEST, serverLogin.callbackHandler, + LOG); + while (!ss.isComplete()) { + challenge = ss.evaluateResponse(token); + if (!ss.isComplete()) { + // limited number of retries. + if (++tries > MAX_RETRIES) { + send(dout, challenge, QuorumAuth.Status.ERROR); + LOG.warn("Failed to authenticate using SASL, server addr: {}, retries={} exceeded.", + sock.getRemoteSocketAddress(), tries); + break; + } + send(dout, challenge, QuorumAuth.Status.IN_PROGRESS); + token = receive(din); + } + } + // Authentication exchange has completed + if (ss.isComplete()) { + send(dout, challenge, QuorumAuth.Status.SUCCESS); + LOG.info("Successfully completed the authentication using SASL. learner addr: {}", + sock.getRemoteSocketAddress()); + } + } catch (Exception e) { + try { + if (dout != null) { + // send error message to the learner + send(dout, new byte[0], QuorumAuth.Status.ERROR); + } + } catch (IOException ioe) { + LOG.warn("Exception while sending failed status", ioe); + } + // If sasl is not required, when a server initializes a + // connection it will try to log in, but it will also + // accept connections that do not start with a sasl + // handshake. + if (quorumRequireSasl) { + LOG.error("Failed to authenticate using SASL", e); + throw new SaslException( + "Failed to authenticate using SASL: " + e.getMessage()); + } else { + LOG.warn("Failed to authenticate using SASL", e); + LOG.warn("Maintaining learner connection despite SASL authentication failure." + + " server addr: {}, {}: {}", + new Object[] { sock.getRemoteSocketAddress(), + QuorumAuth.QUORUM_SERVER_SASL_AUTH_REQUIRED, + quorumRequireSasl }); + return; // let it through, we don't require auth + } + } finally { + if (ss != null) { + try { + ss.dispose(); + } catch (SaslException e) { + LOG.error("SaslServer dispose() failed", e); + } + } + } + return; + } + + private byte[] receive(DataInputStream din) throws IOException { + QuorumAuthPacket authPacket = new QuorumAuthPacket(); + BinaryInputArchive bia = BinaryInputArchive.getArchive(din); + authPacket.deserialize(bia, QuorumAuth.QUORUM_AUTH_MESSAGE_TAG); + return authPacket.getToken(); + } + + private void send(DataOutputStream dout, byte[] challenge, + QuorumAuth.Status s) throws IOException { + BufferedOutputStream bufferedOutput = new BufferedOutputStream(dout); + BinaryOutputArchive boa = BinaryOutputArchive + .getArchive(bufferedOutput); + QuorumAuthPacket authPacket; + if (challenge == null && s != QuorumAuth.Status.SUCCESS) { + authPacket = QuorumAuth.createPacket( + QuorumAuth.Status.IN_PROGRESS, null); + } else { + authPacket = QuorumAuth.createPacket(s, challenge); + } + + boa.writeRecord(authPacket, QuorumAuth.QUORUM_AUTH_MESSAGE_TAG); + bufferedOutput.flush(); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/quorum/auth/SaslQuorumServerCallbackHandler.java b/src/java/main/org/apache/zookeeper/server/quorum/auth/SaslQuorumServerCallbackHandler.java new file mode 100644 index 00000000000..3e71bb1774b --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/quorum/auth/SaslQuorumServerCallbackHandler.java @@ -0,0 +1,148 @@ +/** + * 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.Set; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.RealmCallback; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is used by the SASL mechanisms to get further information to complete + * the authentication. For example, a SASL mechanism might use this callback + * handler to do verification operation. This is used by the QuorumServer to + * perform the mutual quorum peer authentication. + */ +public class SaslQuorumServerCallbackHandler implements CallbackHandler { + private static final String USER_PREFIX = "user_"; + private static final Logger LOG = LoggerFactory.getLogger(SaslQuorumServerCallbackHandler.class); + + private String userName; + private final Map credentials = new HashMap(); + private final Set authzHosts; + + public SaslQuorumServerCallbackHandler(Configuration configuration, + String serverSection, Set authzHosts) throws IOException { + AppConfigurationEntry configurationEntries[] = configuration.getAppConfigurationEntry(serverSection); + + if (configurationEntries == null) { + String errorMessage = "Could not find a '" + serverSection + "' entry in this configuration: Server cannot start."; + LOG.error(errorMessage); + throw new IOException(errorMessage); + } + credentials.clear(); + for(AppConfigurationEntry entry: configurationEntries) { + Map options = entry.getOptions(); + // Populate DIGEST-MD5 user -> password map with JAAS configuration entries from the "QuorumServer" section. + // Usernames are distinguished from other options by prefixing the username with a "user_" prefix. + for(Map.Entry pair : options.entrySet()) { + String key = pair.getKey(); + if (key.startsWith(USER_PREFIX)) { + String userName = key.substring(USER_PREFIX.length()); + credentials.put(userName,(String)pair.getValue()); + } + } + } + + // authorized host lists + this.authzHosts = authzHosts; + } + + public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) { + handleNameCallback((NameCallback) callback); + } else if (callback instanceof PasswordCallback) { + handlePasswordCallback((PasswordCallback) callback); + } else if (callback instanceof RealmCallback) { + handleRealmCallback((RealmCallback) callback); + } else if (callback instanceof AuthorizeCallback) { + handleAuthorizeCallback((AuthorizeCallback) callback); + } + } + } + + private void handleNameCallback(NameCallback nc) { + // check to see if this user is in the user password database. + if (credentials.get(nc.getDefaultName()) == null) { + LOG.warn("User '{}' not found in list of DIGEST-MD5 authenticateable users.", + nc.getDefaultName()); + return; + } + nc.setName(nc.getDefaultName()); + userName = nc.getDefaultName(); + } + + private void handlePasswordCallback(PasswordCallback pc) { + if (credentials.containsKey(userName) ) { + pc.setPassword(credentials.get(userName).toCharArray()); + } else { + LOG.warn("No password found for user: {}", userName); + } + } + + private void handleRealmCallback(RealmCallback rc) { + LOG.debug("QuorumLearner supplied realm: {}", rc.getDefaultText()); + rc.setText(rc.getDefaultText()); + } + + private void handleAuthorizeCallback(AuthorizeCallback ac) { + String authenticationID = ac.getAuthenticationID(); + String authorizationID = ac.getAuthorizationID(); + + boolean authzFlag = false; + // 1. Matches authenticationID and authorizationID + authzFlag = authenticationID.equals(authorizationID); + + // 2. Verify whether the connecting host is present in authorized hosts. + // If not exists, then connecting peer is not authorized to join the + // ensemble and will reject it. + if (authzFlag) { + String[] components = authorizationID.split("[/@]"); + if (components.length == 3) { + authzFlag = authzHosts.contains(components[1]); + } + if (!authzFlag) { + LOG.error("SASL authorization completed, {} is not authorized to connect", + components[1]); + } + } + + // Sets authorization flag + ac.setAuthorized(authzFlag); + if (ac.isAuthorized()) { + ac.setAuthorizedID(authorizationID); + LOG.info("Successfully authenticated learner: authenticationID={}; authorizationID={}.", + authenticationID, authorizationID); + } + LOG.debug("SASL authorization completed, authorized flag set to {}", ac.isAuthorized()); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/quorum/flexible/QuorumHierarchical.java b/src/java/main/org/apache/zookeeper/server/quorum/flexible/QuorumHierarchical.java index b03081a2023..404aa8ceadf 100644 --- a/src/java/main/org/apache/zookeeper/server/quorum/flexible/QuorumHierarchical.java +++ b/src/java/main/org/apache/zookeeper/server/quorum/flexible/QuorumHierarchical.java @@ -102,16 +102,16 @@ public boolean equals(Object o){ QuorumServer qso = qm.getAllMembers().get(qs.id); if (qso == null || !qs.equals(qso)) return false; } - for (Long sid: serverWeight.keySet()){ - if (!serverWeight.get(sid).equals(qm.serverWeight.get(sid))) + for (Entry entry : serverWeight.entrySet()) { + if (!entry.getValue().equals(qm.serverWeight.get(entry.getKey()))) return false; } - for (Long sid: groupWeight.keySet()){ - if (!groupWeight.get(sid).equals(qm.groupWeight.get(sid))) + for (Entry entry : groupWeight.entrySet()) { + if (!entry.getValue().equals(qm.groupWeight.get(entry.getKey()))) return false; } - for (Long sid: serverGroup.keySet()){ - if (!serverGroup.get(sid).equals(qm.serverGroup.get(sid))) + for (Entry entry : serverGroup.entrySet()) { + if (!entry.getValue().equals(qm.serverGroup.get(entry.getKey()))) return false; } return true; @@ -295,15 +295,16 @@ public String toString(){ * different places, so we have a separate method. */ private void computeGroupWeight(){ - for(long sid : serverGroup.keySet()){ - Long gid = serverGroup.get(sid); + for (Entry entry : serverGroup.entrySet()) { + Long sid = entry.getKey(); + Long gid = entry.getValue(); if(!groupWeight.containsKey(gid)) groupWeight.put(gid, serverWeight.get(sid)); else { long totalWeight = serverWeight.get(sid) + groupWeight.get(gid); groupWeight.put(gid, totalWeight); - } - } + } + } /* * Do not consider groups with weight zero @@ -344,27 +345,27 @@ public boolean containsQuorum(Set set){ * Check if all groups have majority */ int majGroupCounter = 0; - for(long gid : expansion.keySet()) { - LOG.debug("Group info: " + expansion.get(gid) + ", " + gid + ", " + groupWeight.get(gid)); - if(expansion.get(gid) > (groupWeight.get(gid) / 2) ) + for (Entry entry : expansion.entrySet()) { + Long gid = entry.getKey(); + LOG.debug("Group info: {}, {}, {}", entry.getValue(), gid, groupWeight.get(gid)); + if (entry.getValue() > (groupWeight.get(gid) / 2)) majGroupCounter++; } - - LOG.debug("Majority group counter: " + majGroupCounter + ", " + numGroups); - if((majGroupCounter > (numGroups / 2))){ - LOG.debug("Positive set size: " + set.size()); + + LOG.debug("Majority group counter: {}, {}", majGroupCounter, numGroups); + if ((majGroupCounter > (numGroups / 2))){ + LOG.debug("Positive set size: {}", set.size()); return true; - } - else { - LOG.debug("Negative set size: " + set.size()); + } else { + LOG.debug("Negative set size: {}", set.size()); return false; } - } - public Map getVotingMembers() { + } + public Map getVotingMembers() { return participatingMembers; } - public Map getObservingMembers() { + public Map getObservingMembers() { return observingMembers; } diff --git a/src/java/main/org/apache/zookeeper/server/util/ConfigUtils.java b/src/java/main/org/apache/zookeeper/server/util/ConfigUtils.java index 216a3987b16..1ca37d1f864 100644 --- a/src/java/main/org/apache/zookeeper/server/util/ConfigUtils.java +++ b/src/java/main/org/apache/zookeeper/server/util/ConfigUtils.java @@ -28,11 +28,11 @@ public class ConfigUtils { - static public String getClientConfigStr(String configData) { - Properties props = new Properties(); + static public String getClientConfigStr(String configData) { + Properties props = new Properties(); try { props.load(new StringReader(configData)); - } catch (IOException e) { + } catch (IOException e) { e.printStackTrace(); return ""; } @@ -43,17 +43,20 @@ static public String getClientConfigStr(String configData) { String key = entry.getKey().toString().trim(); String value = entry.getValue().toString().trim(); if (key.equals("version")) version = value; - if (!key.startsWith("server.")) continue; + if (!key.startsWith("server.")) continue; QuorumPeer.QuorumServer qs; try { qs = new QuorumPeer.QuorumServer(-1, value); - } catch (ConfigException e) { + } catch (ConfigException e) { e.printStackTrace(); continue; } if (!first) sb.append(","); else first = false; - sb.append(qs.clientAddr.getHostName() + ":" + qs.clientAddr.getPort()); + if (null != qs.clientAddr) { + sb.append(qs.clientAddr.getHostString() + + ":" + qs.clientAddr.getPort()); + } } return version + " " + sb.toString(); } diff --git a/src/java/main/org/apache/zookeeper/server/util/KerberosUtil.java b/src/java/main/org/apache/zookeeper/server/util/KerberosUtil.java index 1434ff0ea67..f5f4e26b6af 100644 --- a/src/java/main/org/apache/zookeeper/server/util/KerberosUtil.java +++ b/src/java/main/org/apache/zookeeper/server/util/KerberosUtil.java @@ -36,10 +36,10 @@ public static String getDefaultRealm() } else { classRef = Class.forName("sun.security.krb5.Config"); } - getInstanceMethod = classRef.getMethod("getInstance", new Class[0]); + getInstanceMethod = classRef.getMethod("getInstance", new Class[0]); kerbConf = getInstanceMethod.invoke(classRef, new Object[0]); getDefaultRealmMethod = classRef.getDeclaredMethod("getDefaultRealm", - new Class[0]); + new Class[0]); return (String)getDefaultRealmMethod.invoke(kerbConf, new Object[0]); } } diff --git a/src/java/main/org/apache/zookeeper/server/util/OSMXBean.java b/src/java/main/org/apache/zookeeper/server/util/OSMXBean.java index a75af02e49f..972afe7d08e 100644 --- a/src/java/main/org/apache/zookeeper/server/util/OSMXBean.java +++ b/src/java/main/org/apache/zookeeper/server/util/OSMXBean.java @@ -86,7 +86,7 @@ private Long getOSUnixMXBeanMethod (String mBeanMethodName) classRef = Class.forName("com.sun.management.UnixOperatingSystemMXBean"); if (classRef.isInstance(osMbean)) { mBeanMethod = classRef.getDeclaredMethod(mBeanMethodName, - new Class[0]); + new Class[0]); unixos = classRef.cast(osMbean); return (Long)mBeanMethod.invoke(unixos); } diff --git a/src/java/main/org/apache/zookeeper/server/util/SerializeUtils.java b/src/java/main/org/apache/zookeeper/server/util/SerializeUtils.java index 1a45c5ef0d9..eccf5270bd0 100644 --- a/src/java/main/org/apache/zookeeper/server/util/SerializeUtils.java +++ b/src/java/main/org/apache/zookeeper/server/util/SerializeUtils.java @@ -18,32 +18,37 @@ package org.apache.zookeeper.server.util; -import java.io.ByteArrayInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; import org.apache.jute.InputArchive; import org.apache.jute.OutputArchive; import org.apache.jute.Record; import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.common.IOUtils; import org.apache.zookeeper.server.DataTree; +import org.apache.zookeeper.server.Request; import org.apache.zookeeper.server.ZooTrace; +import org.apache.zookeeper.txn.CreateContainerTxn; import org.apache.zookeeper.txn.CreateSessionTxn; +import org.apache.zookeeper.txn.CreateTTLTxn; import org.apache.zookeeper.txn.CreateTxn; import org.apache.zookeeper.txn.CreateTxnV0; import org.apache.zookeeper.txn.DeleteTxn; import org.apache.zookeeper.txn.ErrorTxn; +import org.apache.zookeeper.txn.MultiTxn; import org.apache.zookeeper.txn.SetACLTxn; import org.apache.zookeeper.txn.SetDataTxn; import org.apache.zookeeper.txn.TxnHeader; -import org.apache.zookeeper.txn.MultiTxn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; public class SerializeUtils { private static final Logger LOG = LoggerFactory.getLogger(SerializeUtils.class); @@ -68,7 +73,14 @@ public static Record deserializeTxn(byte txnBytes[], TxnHeader hdr) case OpCode.create2: txn = new CreateTxn(); break; + case OpCode.createTTL: + txn = new CreateTTLTxn(); + break; + case OpCode.createContainer: + txn = new CreateContainerTxn(); + break; case OpCode.delete: + case OpCode.deleteContainer: txn = new DeleteTxn(); break; case OpCode.reconfig: @@ -140,4 +152,20 @@ public static void serializeSnapshot(DataTree dt,OutputArchive oa, dt.serialize(oa, "tree"); } + public static byte[] serializeRequest(Request request) { + if (request == null || request.getHdr() == null) return null; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos); + try { + request.getHdr().serialize(boa, "hdr"); + if (request.getTxn() != null) { + request.getTxn().serialize(boa, "txn"); + } + } catch (IOException e) { + LOG.error("This really should be impossible", e); + } finally { + IOUtils.cleanup(LOG, baos); + } + return baos.toByteArray(); + } } diff --git a/src/java/main/org/apache/zookeeper/server/util/VerifyingFileFactory.java b/src/java/main/org/apache/zookeeper/server/util/VerifyingFileFactory.java index 74c7408d849..0e931597b2e 100644 --- a/src/java/main/org/apache/zookeeper/server/util/VerifyingFileFactory.java +++ b/src/java/main/org/apache/zookeeper/server/util/VerifyingFileFactory.java @@ -37,6 +37,10 @@ public VerifyingFileFactory(Builder builder){ public File create(String path) { File file = new File(path); + return validate(file); + } + + public File validate(File file) { if(warnForRelativePath) doWarnForRelativePath(file); if(failForNonExistingPath) doFailForNonExistingPath(file); return file; diff --git a/src/java/main/org/apache/zookeeper/util/SecurityUtils.java b/src/java/main/org/apache/zookeeper/util/SecurityUtils.java new file mode 100644 index 00000000000..67484e4e03f --- /dev/null +++ b/src/java/main/org/apache/zookeeper/util/SecurityUtils.java @@ -0,0 +1,298 @@ +/** + * 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.util; + +import java.security.Principal; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.zookeeper.SaslClientCallbackHandler; +import org.apache.zookeeper.server.auth.KerberosName; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; +import org.slf4j.Logger; + +public final class SecurityUtils { + + public static final String QUORUM_HOSTNAME_PATTERN = "_HOST"; + + /** + * Create an instance of a SaslClient. It will return null if there is an exception. + * + * @param subject subject + * @param servicePrincipal principal + * @param protocol name of the protocol for which the authentication is being performed + * @param serverName name of the server to authenticate to + * @param LOG logger + * @param entity can be either zookeeper client or quorum learner + * + * @return saslclient object + * @throws SaslException + */ + public static SaslClient createSaslClient(final Subject subject, + final String servicePrincipal, final String protocol, + final String serverName, final Logger LOG, final String entity) throws SaslException { + SaslClient saslClient; + // Use subject.getPrincipals().isEmpty() as an indication of which SASL + // mechanism to use: if empty, use DIGEST-MD5; otherwise, use GSSAPI. + if (subject.getPrincipals().isEmpty()) { + // no principals: must not be GSSAPI: use DIGEST-MD5 mechanism + // instead. + LOG.info("{} will use DIGEST-MD5 as SASL mechanism.", entity); + String[] mechs = { "DIGEST-MD5" }; + String username = (String) (subject.getPublicCredentials() + .toArray()[0]); + String password = (String) (subject.getPrivateCredentials() + .toArray()[0]); + // 'domain' parameter is hard-wired between the server and client + saslClient = Sasl.createSaslClient(mechs, username, protocol, + serverName, null, new SaslClientCallbackHandler(password, entity)); + return saslClient; + } else { // GSSAPI. + final Object[] principals = subject.getPrincipals().toArray(); + // determine client principal from subject. + final Principal clientPrincipal = (Principal) principals[0]; + boolean usingNativeJgss = Boolean + .getBoolean("sun.security.jgss.native"); + if (usingNativeJgss) { + // http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html + // """ + // In addition, when performing operations as a particular + // Subject, e.g. Subject.doAs(...) or + // Subject.doAsPrivileged(...), + // the to-be-used GSSCredential should be added to Subject's + // private credential set. Otherwise, the GSS operations will + // fail since no credential is found. + // """ + try { + GSSManager manager = GSSManager.getInstance(); + Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2"); + GSSCredential cred = manager.createCredential(null, + GSSContext.DEFAULT_LIFETIME, krb5Mechanism, + GSSCredential.INITIATE_ONLY); + subject.getPrivateCredentials().add(cred); + LOG.debug("Added private credential to {} principal name: '{}'", + entity, clientPrincipal); + } catch (GSSException ex) { + LOG.warn("Cannot add private credential to subject; " + + "authentication at the server may fail", ex); + } + } + final KerberosName clientKerberosName = new KerberosName( + clientPrincipal.getName()); + // assume that server and client are in the same realm (by default; + // unless the system property + // "zookeeper.server.realm" is set). + String serverRealm = System.getProperty("zookeeper.server.realm", + clientKerberosName.getRealm()); + KerberosName serviceKerberosName = new KerberosName( + servicePrincipal + "@" + serverRealm); + final String serviceName = serviceKerberosName.getServiceName(); + final String serviceHostname = serviceKerberosName.getHostName(); + final String clientPrincipalName = clientKerberosName.toString(); + try { + saslClient = Subject.doAs(subject, + new PrivilegedExceptionAction() { + public SaslClient run() throws SaslException { + LOG.info("{} will use GSSAPI as SASL mechanism.", entity); + String[] mechs = { "GSSAPI" }; + LOG.debug("creating sasl client: {}={};service={};serviceHostname={}", + new Object[] { entity, clientPrincipalName, serviceName, serviceHostname }); + SaslClient saslClient = Sasl.createSaslClient( + mechs, clientPrincipalName, serviceName, + serviceHostname, null, + new SaslClientCallbackHandler(null, entity)); + return saslClient; + } + }); + return saslClient; + } catch (Exception e) { + LOG.error("Exception while trying to create SASL client", e); + return null; + } + } + } + + /** + * Create an instance of a SaslServer. It will return null if there is an exception. + * + * @param subject subject + * @param protocol protocol + * @param serverName server name + * @param callbackHandler login callback handler + * @param LOG logger + * @return sasl server object + */ + public static SaslServer createSaslServer(final Subject subject, + final String protocol, final String serverName, + final CallbackHandler callbackHandler, final Logger LOG) { + if (subject != null) { + // server is using a JAAS-authenticated subject: determine service + // principal name and hostname from zk server's subject. + if (subject.getPrincipals().size() > 0) { + try { + final Object[] principals = subject.getPrincipals() + .toArray(); + final Principal servicePrincipal = (Principal) principals[0]; + + // e.g. servicePrincipalNameAndHostname := + // "zookeeper/myhost.foo.com@FOO.COM" + final String servicePrincipalNameAndHostname = servicePrincipal + .getName(); + + int indexOf = servicePrincipalNameAndHostname.indexOf("/"); + + // e.g. servicePrincipalName := "zookeeper" + final String servicePrincipalName = servicePrincipalNameAndHostname + .substring(0, indexOf); + + // e.g. serviceHostnameAndKerbDomain := + // "myhost.foo.com@FOO.COM" + final String serviceHostnameAndKerbDomain = servicePrincipalNameAndHostname + .substring(indexOf + 1, + servicePrincipalNameAndHostname.length()); + + indexOf = serviceHostnameAndKerbDomain.indexOf("@"); + // e.g. serviceHostname := "myhost.foo.com" + final String serviceHostname = serviceHostnameAndKerbDomain + .substring(0, indexOf); + + // TODO: should depend on zoo.cfg specified mechs, but if + // subject is non-null, it can be assumed to be GSSAPI. + final String mech = "GSSAPI"; + + LOG.debug("serviceHostname is '" + serviceHostname + "'"); + LOG.debug("servicePrincipalName is '" + servicePrincipalName + + "'"); + LOG.debug("SASL mechanism(mech) is '" + mech + "'"); + + boolean usingNativeJgss = Boolean + .getBoolean("sun.security.jgss.native"); + if (usingNativeJgss) { + // http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html + // """ + // In addition, when performing operations as a + // particular + // Subject, e.g. Subject.doAs(...) or + // Subject.doAsPrivileged(...), the to-be-used + // GSSCredential should be added to Subject's + // private credential set. Otherwise, the GSS operations + // will fail since no credential is found. + // """ + try { + GSSManager manager = GSSManager.getInstance(); + Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2"); + GSSName gssName = manager.createName( + servicePrincipalName + "@" + + serviceHostname, + GSSName.NT_HOSTBASED_SERVICE); + GSSCredential cred = manager.createCredential( + gssName, GSSContext.DEFAULT_LIFETIME, + krb5Mechanism, GSSCredential.ACCEPT_ONLY); + subject.getPrivateCredentials().add(cred); + LOG.debug("Added private credential to service principal name: '{}'," + + " GSSCredential name: {}", servicePrincipalName, cred.getName()); + } catch (GSSException ex) { + LOG.warn("Cannot add private credential to subject; " + + "clients authentication may fail", ex); + } + } + try { + return Subject.doAs(subject, + new PrivilegedExceptionAction() { + public SaslServer run() { + try { + SaslServer saslServer; + saslServer = Sasl.createSaslServer( + mech, servicePrincipalName, + serviceHostname, null, + callbackHandler); + return saslServer; + } catch (SaslException e) { + LOG.error("Zookeeper Server failed to create a SaslServer to interact with a client during session initiation: ", e); + return null; + } + } + }); + } catch (PrivilegedActionException e) { + // TODO: exit server at this point(?) + LOG.error("Zookeeper Quorum member experienced a PrivilegedActionException exception while creating a SaslServer using a JAAS principal context:", e); + } + } catch (IndexOutOfBoundsException e) { + LOG.error("server principal name/hostname determination error: ", e); + } + } else { + // JAAS non-GSSAPI authentication: assuming and supporting only + // DIGEST-MD5 mechanism for now. + // TODO: use 'authMech=' value in zoo.cfg. + try { + SaslServer saslServer = Sasl.createSaslServer("DIGEST-MD5", + protocol, serverName, null, callbackHandler); + return saslServer; + } catch (SaslException e) { + LOG.error("Zookeeper Quorum member failed to create a SaslServer to interact with a client during session initiation", e); + } + } + } + return null; + } + + /** + * Convert Kerberos principal name pattern to valid Kerberos principal name. + * If the principal name contains hostname pattern "_HOST" then it replaces + * with the given hostname, which should be fully-qualified domain name. + * + * @param principalConfig + * the Kerberos principal name conf value to convert + * @param hostname + * the fully-qualified domain name used for substitution + * @return converted Kerberos principal name + */ + public static String getServerPrincipal(String principalConfig, + String hostname) { + String[] components = getComponents(principalConfig); + if (components == null || components.length != 2 + || !components[1].equals(QUORUM_HOSTNAME_PATTERN)) { + return principalConfig; + } else { + return replacePattern(components, hostname); + } + } + + private static String[] getComponents(String principalConfig) { + if (principalConfig == null) + return null; + return principalConfig.split("[/]"); + } + + private static String replacePattern(String[] components, String hostname) { + return components[0] + "/" + hostname.toLowerCase(); + } +} diff --git a/src/java/main/org/apache/zookeeper/version/util/VerGen.java b/src/java/main/org/apache/zookeeper/version/util/VerGen.java index d4cbeb64eaa..014f01d90ba 100644 --- a/src/java/main/org/apache/zookeeper/version/util/VerGen.java +++ b/src/java/main/org/apache/zookeeper/version/util/VerGen.java @@ -34,7 +34,7 @@ static void printUsage() { System.exit(1); } - public static void generateFile(File outputDir, Version version, int rev, String buildDate) + public static void generateFile(File outputDir, Version version, String rev, String buildDate) { String path = PACKAGE_NAME.replaceAll("\\.", "/"); File pkgdir = new File(outputDir, path); @@ -50,10 +50,8 @@ public static void generateFile(File outputDir, Version version, int rev, String System.out.println(path + " is not a directory."); System.exit(1); } - File file = new File(pkgdir, TYPE_NAME + ".java"); - FileWriter w = null; - try { - w = new FileWriter(file); + + try (FileWriter w = new FileWriter(new File(pkgdir, TYPE_NAME + ".java"))) { w.write("// Do not edit!\n// File generated by org.apache.zookeeper" + ".version.util.VerGen.\n"); w.write("/**\n"); @@ -76,33 +74,25 @@ public static void generateFile(File outputDir, Version version, int rev, String w.write("\n"); w.write("package " + PACKAGE_NAME + ";\n\n"); w.write("public interface " + TYPE_NAME + " {\n"); - w.write(" public static final int MAJOR=" + version.maj + ";\n"); - w.write(" public static final int MINOR=" + version.min + ";\n"); - w.write(" public static final int MICRO=" + version.micro + ";\n"); - w.write(" public static final String QUALIFIER=" + w.write(" int MAJOR=" + version.maj + ";\n"); + w.write(" int MINOR=" + version.min + ";\n"); + w.write(" int MICRO=" + version.micro + ";\n"); + w.write(" String QUALIFIER=" + (version.qualifier == null ? null : "\"" + version.qualifier + "\"") + ";\n"); - if (rev < 0) { + if (rev.equals("-1")) { System.out.println("Unknown REVISION number, using " + rev); } - w.write(" public static final int REVISION=" + rev + ";\n"); - w.write(" public static final String BUILD_DATE=\"" + buildDate + w.write(" int REVISION=-1; //TODO: remove as related to SVN VCS\n"); + w.write(" String REVISION_HASH=\"" + rev + "\";\n"); + w.write(" String BUILD_DATE=\"" + buildDate + "\";\n"); w.write("}\n"); } catch (IOException e) { System.out.println("Unable to generate version.Info file: " + e.getMessage()); System.exit(1); - } finally { - if (w != null) { - try { - w.close(); - } catch (IOException e) { - System.out.println("Unable to close file writer" - + e.getMessage()); - } - } } } @@ -146,7 +136,7 @@ public static Version parseVersionString(String input) { *
  • min - minor version number *
  • micro - minor minor version number *
  • qualifier - optional qualifier (dash followed by qualifier text) - *
  • rev - current SVN revision number + *
  • rev - current Git revision number *
  • buildDate - date the build * */ @@ -160,11 +150,11 @@ public static void main(String[] args) { "Invalid version number format, must be \"x.y.z(-.*)?\""); System.exit(1); } - int rev; - try { - rev = Integer.parseInt(args[1]); - } catch (NumberFormatException e) { - rev = -1; + String rev = args[1]; + if (rev == null || rev.trim().isEmpty()) { + rev = "-1"; + } else { + rev = rev.trim(); } generateFile(new File("."), version, rev, args[2]); } catch (NumberFormatException e) { diff --git a/src/java/systest/org/apache/zookeeper/test/system/BaseSysTest.java b/src/java/systest/org/apache/zookeeper/test/system/BaseSysTest.java index 93e149470cd..2ed516c3ac6 100644 --- a/src/java/systest/org/apache/zookeeper/test/system/BaseSysTest.java +++ b/src/java/systest/org/apache/zookeeper/test/system/BaseSysTest.java @@ -25,18 +25,18 @@ import java.net.UnknownHostException; import java.util.HashMap; -import junit.framework.TestCase; - import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.junit.After; +import org.junit.Before; import org.junit.Ignore; import org.junit.runner.JUnitCore; @Ignore("No tests in this class.") -public class BaseSysTest extends TestCase { +public class BaseSysTest { private static final File testData = new File( System.getProperty("test.data.dir", "build/test/data")); private static int fakeBasePort = 33222; @@ -51,16 +51,18 @@ public class BaseSysTest extends TestCase { } } InstanceManager im; - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { if (!fakeMachines) { zk = new ZooKeeper(zkHostPort, 15000, new Watcher() {public void process(WatchedEvent e){}}); im = new InstanceManager(zk, prefix); } } - @Override - protected void tearDown() throws Exception { - im.close(); + @After + public void tearDown() throws Exception { + if (null != im) { + im.close(); + } } int serverCount = defaultServerCount; @@ -126,8 +128,9 @@ private void distributedConfigureServers(int count) throws IOException { sbClient.append(','); sbServer.append(','); } - sbClient.append(r[0]); - sbServer.append(r[1]); + sbClient.append(r[0]); // r[0] == "host:clientPort" + sbServer.append(r[1]); // r[1] == "host:leaderPort:leaderElectionPort" + sbServer.append(";"+(r[0].split(":"))[1]); // Appending ";clientPort" } serverHostPort = sbClient.toString(); quorumHostPort = sbServer.toString(); @@ -146,10 +149,17 @@ private void fakeConfigureServers(int count) throws IOException { qps = new QuorumPeer[count]; qpsDirs = new File[count]; for(int i = 1; i <= count; i++) { - peers.put(Long.valueOf(i), new QuorumServer(i, new InetSocketAddress("127.0.0.1", fakeBasePort + i))); + InetSocketAddress peerAddress = new InetSocketAddress("127.0.0.1", + fakeBasePort + i); + InetSocketAddress electionAddr = new InetSocketAddress("127.0.0.1", + serverCount + fakeBasePort + i); + peers.put(Long.valueOf(i), new QuorumServer(i, peerAddress, + electionAddr)); } StringBuilder sb = new StringBuilder(); for(int i = 0; i < count; i++) { + //make that testData exists otherwise it fails on windows + testData.mkdirs(); qpsDirs[i] = File.createTempFile("sysTest", ".tmp", testData); qpsDirs[i].delete(); qpsDirs[i].mkdir(); diff --git a/src/java/systest/org/apache/zookeeper/test/system/GenerateLoad.java b/src/java/systest/org/apache/zookeeper/test/system/GenerateLoad.java index 57d0dcbd67d..768d23603e5 100644 --- a/src/java/systest/org/apache/zookeeper/test/system/GenerateLoad.java +++ b/src/java/systest/org/apache/zookeeper/test/system/GenerateLoad.java @@ -53,6 +53,8 @@ import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.common.Time; + public class GenerateLoad { protected static final Logger LOG = LoggerFactory.getLogger(GenerateLoad.class); @@ -194,7 +196,7 @@ static class ReporterThread extends Thread { public void run() { try { - currentInterval = System.currentTimeMillis() / INTERVAL; + currentInterval = Time.currentElapsedTime() / INTERVAL; // Give things time to report; Thread.sleep(INTERVAL * 2); long min = 99999; @@ -202,7 +204,7 @@ public void run() { long total = 0; int number = 0; while (true) { - long now = System.currentTimeMillis(); + long now = Time.currentElapsedTime(); long lastInterval = currentInterval; currentInterval += 1; long count = remove(lastInterval); @@ -249,13 +251,13 @@ public void run() { } synchronized static void sendChange(int percentage) { - long now = System.currentTimeMillis(); + long now = Time.currentElapsedTime(); long start = now; ReporterThread.percentage = percentage; for (SlaveThread st : slaves.toArray(new SlaveThread[0])) { st.send(percentage); } - now = System.currentTimeMillis(); + now = Time.currentElapsedTime(); long delay = now - start; if (delay > 1000) { System.out.println("Delay of " + delay + " to send new percentage"); @@ -387,7 +389,7 @@ public void processResult(int rc, String path, Object ctx, byte[] data, errors++; } else { finished++; - rlatency += System.currentTimeMillis() - (Long) ctx; + rlatency += Time.currentElapsedTime() - (Long) ctx; reads++; } } @@ -401,7 +403,7 @@ public void processResult(int rc, String path, Object ctx, Stat stat) { errors++; } else { finished++; - wlatency += System.currentTimeMillis() - (Long) ctx; + wlatency += Time.currentElapsedTime() - (Long) ctx; writes++; } } @@ -427,7 +429,7 @@ public void run() { if (percentage == -1 || (finished == 0 && errors == 0)) { continue; } - String report = System.currentTimeMillis() + " " + String report = Time.currentElapsedTime() + " " + percentage + " " + finished + " " + errors + " " + outstanding + "\n"; /* String subreport = reads + " " @@ -543,9 +545,9 @@ public void process(WatchedEvent event) { synchronized public boolean waitConnected(long timeout) throws InterruptedException { - long endTime = System.currentTimeMillis() + timeout; - while (!connected && System.currentTimeMillis() < endTime) { - wait(endTime - System.currentTimeMillis()); + long endTime = Time.currentElapsedTime() + timeout; + while (!connected && Time.currentElapsedTime() < endTime) { + wait(endTime - Time.currentElapsedTime()); } return connected; } @@ -603,8 +605,9 @@ public static void main(String[] args) throws InterruptedException, quorumHostPort.append(','); zkHostPort.append(','); } - zkHostPort.append(r[0]); - quorumHostPort.append(r[1]); + zkHostPort.append(r[0]); // r[0] == "host:clientPort" + quorumHostPort.append(r[1]); // r[1] == "host:leaderPort:leaderElectionPort" + quorumHostPort.append(";"+(r[0].split(":"))[1]); // Appending ";clientPort" } for (int i = 0; i < serverCount; i++) { QuorumPeerInstance.startInstance(im, quorumHostPort @@ -695,12 +698,16 @@ private static String getMode(String hostPort) throws NumberFormatException, Unk s.getOutputStream().write("stat".getBytes()); BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String line; - while((line = br.readLine()) != null) { + try { + while((line = br.readLine()) != null) { if (line.startsWith("Mode: ")) { - return line.substring(6); + return line.substring(6); } + } + return "unknown"; + } finally { + s.close(); } - return "unknown"; } private static void doUsage() { diff --git a/src/java/systest/org/apache/zookeeper/test/system/InstanceManager.java b/src/java/systest/org/apache/zookeeper/test/system/InstanceManager.java index 809fa4819ee..fed0a9021f5 100644 --- a/src/java/systest/org/apache/zookeeper/test/system/InstanceManager.java +++ b/src/java/systest/org/apache/zookeeper/test/system/InstanceManager.java @@ -38,6 +38,7 @@ import org.apache.zookeeper.KeeperException.NodeExistsException; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.common.Time; /** * This class doles out assignments to InstanceContainers that are registered to @@ -294,9 +295,9 @@ public void resetStatus(String name) throws InterruptedException, KeeperExceptio public String getStatus(String name, long timeout) throws KeeperException, InterruptedException { Stat stat = new Stat(); byte data[] = null; - long endTime = System.currentTimeMillis() + timeout; + long endTime = Time.currentElapsedTime() + timeout; KeeperException lastException = null; - for(int i = 0; i < maxTries && endTime > System.currentTimeMillis(); i++) { + for(int i = 0; i < maxTries && endTime > Time.currentElapsedTime(); i++) { try { data = zk.getData(reportsNode + '/' + name, false, stat); if (LOG.isDebugEnabled()) { @@ -317,7 +318,7 @@ public void process(WatchedEvent event) { } }}); if (eStat == null) { - eventObj.wait(endTime - System.currentTimeMillis()); + eventObj.wait(endTime - Time.currentElapsedTime()); } } lastException = e; diff --git a/src/java/systest/org/apache/zookeeper/test/system/QuorumPeerInstance.java b/src/java/systest/org/apache/zookeeper/test/system/QuorumPeerInstance.java index 1417cacde88..2231d01ae32 100644 --- a/src/java/systest/org/apache/zookeeper/test/system/QuorumPeerInstance.java +++ b/src/java/systest/org/apache/zookeeper/test/system/QuorumPeerInstance.java @@ -33,6 +33,7 @@ import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.test.TestUtils; class QuorumPeerInstance implements Instance { final private static Logger LOG = LoggerFactory.getLogger(QuorumPeerInstance.class); @@ -52,7 +53,8 @@ public void setReporter(Reporter r) { } InetSocketAddress clientAddr; - InetSocketAddress quorumAddr; + InetSocketAddress quorumLeaderAddr; + InetSocketAddress quorumLeaderElectionAddr; HashMap peers; File snapDir, logDir; @@ -67,7 +69,12 @@ public QuorumPeerInstance() { Properties p; if (zkDirs.exists()) { p = new Properties(); - p.load(new FileInputStream(zkDirs)); + FileInputStream input = new FileInputStream(zkDirs); + try { + p.load(input); + } finally { + input.close(); + } } else { p = System.getProperties(); } @@ -108,13 +115,20 @@ public void configure(String params) { } try { ServerSocket ss = new ServerSocket(0, 1, InetAddress.getLocalHost()); - quorumAddr = (InetSocketAddress) ss.getLocalSocketAddress(); + quorumLeaderAddr = (InetSocketAddress) ss.getLocalSocketAddress(); ss.close(); } catch(IOException e) { e.printStackTrace(); } - String report = clientAddr.getHostName() + ':' + clientAddr.getPort() + - ',' + quorumAddr.getHostName() + ':' + quorumAddr.getPort(); + try { + ServerSocket ss = new ServerSocket(0, 1, InetAddress.getLocalHost()); + quorumLeaderElectionAddr = (InetSocketAddress) ss.getLocalSocketAddress(); + ss.close(); + } catch(IOException e) { + e.printStackTrace(); + } + String report = clientAddr.getHostString() + ':' + clientAddr.getPort() + + ',' + quorumLeaderAddr.getHostString() + ':' + quorumLeaderAddr.getPort() + ':' + quorumLeaderElectionAddr.getPort(); try { if (LOG.isDebugEnabled()) { LOG.debug("Reporting " + report); @@ -157,8 +171,15 @@ public void configure(String params) { String parts[] = quorumSpecs.split(","); peers = new HashMap(); for(int i = 0; i < parts.length; i++) { - String subparts[] = parts[i].split(":"); - peers.put(Long.valueOf(i), new QuorumServer(i, new InetSocketAddress(subparts[0], Integer.parseInt(subparts[1])))); + // parts[i] == "host:leaderPort:leaderElectionPort;clientPort" + String subparts[] = ((parts[i].split(";"))[0]).split(":"); + String clientPort = (parts[i].split(";"))[1]; + peers.put(Long.valueOf(i), + new QuorumServer( + i, + new InetSocketAddress(subparts[0], Integer.parseInt(subparts[1])), + new InetSocketAddress(subparts[0], Integer.parseInt(subparts[2])), + new InetSocketAddress(subparts[0], Integer.parseInt(clientPort)))); } try { if (LOG.isDebugEnabled()) { @@ -188,17 +209,6 @@ public void configure(String params) { public void start() { } - - static private void recursiveDelete(File dir) { - if (!dir.isDirectory()) { - dir.delete(); - return; - } - for(File f: dir.listFiles()) { - recursiveDelete(f); - } - dir.delete(); - } public void stop() { if (LOG.isDebugEnabled()) { @@ -208,10 +218,10 @@ public void stop() { peer.shutdown(); } if (logDir != null) { - recursiveDelete(logDir); + TestUtils.deleteFileRecursively(logDir); } if (snapDir != null) { - recursiveDelete(snapDir); + TestUtils.deleteFileRecursively(snapDir); } } diff --git a/src/java/systest/org/apache/zookeeper/test/system/SimpleClient.java b/src/java/systest/org/apache/zookeeper/test/system/SimpleClient.java index 96c357c61d5..6ca5cc39c51 100644 --- a/src/java/systest/org/apache/zookeeper/test/system/SimpleClient.java +++ b/src/java/systest/org/apache/zookeeper/test/system/SimpleClient.java @@ -51,7 +51,9 @@ public void start() { try { zk = new ZooKeeper(hostPort, 15000, this); zk.getData("/simpleCase", true, this, null); - r.report("Client " + index + " connecting to " + hostPort); + if (null != r) { + r.report("Client " + index + " connecting to " + hostPort); + } } catch (Exception e) { e.printStackTrace(); } diff --git a/src/java/systest/org/apache/zookeeper/test/system/SimpleSysTest.java b/src/java/systest/org/apache/zookeeper/test/system/SimpleSysTest.java index 9cdf4d912a2..cf42d39f3c1 100644 --- a/src/java/systest/org/apache/zookeeper/test/system/SimpleSysTest.java +++ b/src/java/systest/org/apache/zookeeper/test/system/SimpleSysTest.java @@ -30,7 +30,9 @@ import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper.States; import org.apache.zookeeper.data.Stat; +import org.junit.Assert; import org.junit.Test; +import org.apache.zookeeper.common.Time; /** * This does a basic system test. It starts up an ensemble of servers and a set of clients. @@ -46,8 +48,8 @@ public class SimpleSysTest extends BaseSysTest implements Watcher { synchronized private boolean waitForConnect(ZooKeeper zk, long timeout) throws InterruptedException { connected = (zk.getState() == States.CONNECTED); - long end = System.currentTimeMillis() + timeout; - while(!connected && end > System.currentTimeMillis()) { + long end = Time.currentElapsedTime() + timeout; + while(!connected && end > Time.currentElapsedTime()) { wait(timeout); connected = (zk.getState() == States.CONNECTED); } @@ -81,10 +83,10 @@ public void testSimpleCase() throws Exception { for(int j = 0; j < maxTries; j++) { try { byte b[] = zk.getData("/simpleCase/" + i, false, stat); - assertEquals("orig", new String(b)); + Assert.assertEquals("orig", new String(b)); } catch(NoNodeException e) { if (j+1 == maxTries) { - fail("Max tries exceeded on client " + i); + Assert.fail("Max tries exceeded on client " + i); } Thread.sleep(1000); } @@ -98,11 +100,11 @@ public void testSimpleCase() throws Exception { if (i+1 > getServerCount()/2) { startServer(i); } else if (i+1 == getServerCount()/2) { - assertTrue("Connection didn't recover", waitForConnect(zk, 10000)); + Assert.assertTrue("Connection didn't recover", waitForConnect(zk, 10000)); try { zk.setData("/simpleCase", "new".getBytes(), -1); } catch(ConnectionLossException e) { - assertTrue("Connection didn't recover", waitForConnect(zk, 10000)); + Assert.assertTrue("Connection didn't recover", waitForConnect(zk, 10000)); zk.setData("/simpleCase", "new".getBytes(), -1); } for(int j = 0; j < i; j++) { @@ -112,11 +114,11 @@ public void testSimpleCase() throws Exception { } } Thread.sleep(100); // wait for things to stabilize - assertTrue("Servers didn't bounce", waitForConnect(zk, 15000)); + Assert.assertTrue("Servers didn't bounce", waitForConnect(zk, 15000)); try { zk.getData("/simpleCase", false, stat); } catch(ConnectionLossException e) { - assertTrue("Servers didn't bounce", waitForConnect(zk, 15000)); + Assert.assertTrue("Servers didn't bounce", waitForConnect(zk, 15000)); } // check that the change has propagated to everyone @@ -127,7 +129,7 @@ public void testSimpleCase() throws Exception { break; } if (j+1 == maxTries) { - fail("max tries exceeded for " + i); + Assert.fail("max tries exceeded for " + i); } Thread.sleep(1000); } @@ -142,7 +144,7 @@ public void testSimpleCase() throws Exception { for(int j = 0; j < maxTries; j++) { zk.getData("/simpleCase/" + i, false, stat); if (j+1 == maxTries) { - fail("max tries exceeded waiting for child " + i + " to die"); + Assert.fail("max tries exceeded waiting for child " + i + " to die"); } Thread.sleep(200); } diff --git a/src/java/test/bin/check_compatibility.py b/src/java/test/bin/check_compatibility.py new file mode 100644 index 00000000000..cad8195942f --- /dev/null +++ b/src/java/test/bin/check_compatibility.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +# +# 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. + +# Script which checks Java API compatibility between two revisions of the +# Java client. +# +# Based on the compatibility checker from the HBase project, but ported to +# Python for better readability. + +# Lifted from Kudu: https://github.com/apache/kudu/blob/master/build-support/check_compatibility.py + +import logging +import optparse +import os +import shutil +import subprocess +import sys + +JAVA_ACC_GIT_URL = "https://github.com/lvc/japi-compliance-checker.git" + +# The annotations for what we consider our public API. +PUBLIC_ANNOTATIONS = ["org.apache.yetus.audience.InterfaceAudience.LimitedPrivate", + "org.apache.yetus.audience.InterfaceAudience.Public"] + +# Various relative paths +PATH_TO_REPO_DIR = "../../../../" +PATH_TO_BUILD_DIR = PATH_TO_REPO_DIR + "build/compat-check" +PATH_TO_JACC_DIR = PATH_TO_REPO_DIR + "build/jacc" + +def check_output(*popenargs, **kwargs): + # r"""Run command with arguments and return its output as a byte string. + # Backported from Python 2.7 as it's implemented as pure python on stdlib. + # >>> check_output(['/usr/bin/python', '--version']) + # Python 2.6.2 + # """ + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + error = subprocess.CalledProcessError(retcode, cmd) + error.output = output + raise error + return output + +def get_repo_dir(): + """ Return the path to the top of the repo. """ + dirname, _ = os.path.split(os.path.abspath(__file__)) + return os.path.abspath(os.path.join(dirname, PATH_TO_REPO_DIR)) + + +def get_scratch_dir(): + """ Return the path to the scratch dir that we build within. """ + dirname, _ = os.path.split(os.path.abspath(__file__)) + return os.path.abspath(os.path.join(dirname, PATH_TO_BUILD_DIR)) + + +def get_java_acc_dir(): + dirname, _ = os.path.split(os.path.abspath(__file__)) + return os.path.abspath(os.path.join(dirname, PATH_TO_JACC_DIR)) + + +def clean_scratch_dir(scratch_dir): + """ Clean up and re-create the scratch directory. """ + if os.path.exists(scratch_dir): + logging.info("Removing scratch dir %s...", scratch_dir) + shutil.rmtree(scratch_dir) + logging.info("Creating empty scratch dir %s...", scratch_dir) + os.makedirs(scratch_dir) + + +def checkout_tree(rev, path): + """ Check out the Java source tree for the given revision into the given path. """ + logging.info("Checking out %s in %s", rev, path) + os.makedirs(path) + # Extract java source + subprocess.check_call(["bash", '-o', 'pipefail', "-c", + ("git archive --format=tar %s | " + + "tar -C \"%s\" -xf -") % (rev, path)], + cwd=get_repo_dir()) + + +def get_git_hash(revname): + """ Convert 'revname' to its SHA-1 hash. """ + return check_output(["git", "rev-parse", revname], + cwd=get_repo_dir()).strip() + + +def build_tree(path): + """ Run the Java build within 'path'. """ + logging.info("Building in %s...", path) + subprocess.check_call(["ant", "jar"], cwd=path) + + +def checkout_java_acc(force): + """ + Check out the Java API Compliance Checker. If 'force' is true, will re-download even if the + directory exists. + """ + acc_dir = get_java_acc_dir() + if os.path.exists(acc_dir): + logging.info("Java JAVA_ACC is already downloaded.") + if not force: + return + logging.info("Forcing re-download.") + shutil.rmtree(acc_dir) + logging.info("Checking out Java JAVA_ACC...") + subprocess.check_call(["git", "clone", "-b", "2.1", "--single-branch", "--depth=1", JAVA_ACC_GIT_URL, acc_dir]) + + +def find_client_jars(path): + """ Return a list of jars within 'path' to be checked for compatibility. """ + return check_output(["find", path, "-name", "zookeeper*.jar"]).rstrip('\n') + + +def run_java_acc(src_name, src, dst_name, dst): + """ Run the compliance checker to compare 'src' and 'dst'. """ + src_jar = find_client_jars(src) + dst_jar = find_client_jars(dst) + logging.info("Will check compatibility between original jars:\n%s\n" + + "and new jars:\n%s", + src_jar, dst_jar) + + annotations_path = os.path.join(get_scratch_dir(), "annotations.txt") + with file(annotations_path, "w") as f: + for ann in PUBLIC_ANNOTATIONS: + print >>f, ann + + java_acc_path = os.path.join(get_java_acc_dir(), "japi-compliance-checker.pl") + + out_path = os.path.join(get_scratch_dir(), "report.html") + subprocess.check_call(["perl", java_acc_path, + "-lib", "ZooKeeper", + "-v1", src_name, + "-v2", dst_name, + "-d1", src_jar, + "-d2", dst_jar, + "-annotations-list", annotations_path, + "-report-path", out_path]) + + +def main(argv): + logging.basicConfig(level=logging.INFO) + parser = optparse.OptionParser( + usage="usage: %prog SRC..[DST]") + parser.add_option("-f", "--force-download", dest="force_download_deps", + help=("Download dependencies (i.e. Java JAVA_ACC) even if they are " + + "already present")) + opts, args = parser.parse_args() + + if len(args) != 1: + parser.error("no src/dst revision specified") + sys.exit(1) + + src_rev, dst_rev = args[0].split("..", 1) + if dst_rev == "": + dst_rev = "HEAD" + src_rev = get_git_hash(src_rev) + dst_rev = get_git_hash(dst_rev) + + logging.info("Source revision: %s", src_rev) + logging.info("Destination revision: %s", dst_rev) + + # Download deps. + checkout_java_acc(opts.force_download_deps) + + # Set up the build. + scratch_dir = get_scratch_dir() + clean_scratch_dir(scratch_dir) + + # Check out the src and dst source trees. + src_dir = os.path.join(scratch_dir, "src") + dst_dir = os.path.join(scratch_dir, "dst") + checkout_tree(src_rev, src_dir) + checkout_tree(dst_rev, dst_dir) + + # Run the build in each. + build_tree(src_dir) + build_tree(dst_dir) + + run_java_acc(src_rev, src_dir + "/build", + dst_rev, dst_dir + "/build") + + +if __name__ == "__main__": + main(sys.argv) \ No newline at end of file diff --git a/src/java/test/bin/test-github-pr.sh b/src/java/test/bin/test-github-pr.sh new file mode 100755 index 00000000000..e155769f268 --- /dev/null +++ b/src/java/test/bin/test-github-pr.sh @@ -0,0 +1,616 @@ +#!/usr/bin/env bash +# 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. + + +#set -x + +### Setup some variables. +### GIT_COMMIT and BUILD_URL are set by Hudson if it is run by patch process +### Read variables from properties file +. `dirname $0`/test-patch.properties + +############################################################################### +parseArgs() { + case "$1" in + QABUILD) + ### Set QABUILD to true to indicate that this script is being run by Hudson + QABUILD=true + if [[ $# != 14 ]] ; then + echo "ERROR: usage $0 QABUILD " + cleanupAndExit 0 + fi + PATCH_DIR=$2 + PS=$3 + WGET=$4 + JIRACLI=$5 + GIT=$6 + GREP=$7 + PATCH=$8 + FINDBUGS_HOME=$9 + FORREST_HOME=${10} + BASEDIR=${11} + JIRA_PASSWD=${12} + JAVA5_HOME=${13} + CURL=${14} + if [ ! -e "$PATCH_DIR" ] ; then + mkdir -p $PATCH_DIR + fi + + ## Obtain PR number and title + PULLREQUEST_ID=${GIT_PR_NUMBER} + PULLREQUEST_TITLE="${GIT_PR_TITLE}" + + ## Extract jira number from PR title + local prefix=${PULLREQUEST_TITLE%ZOOKEEPER\-[0-9]*} + local noprefix=${PULLREQUEST_TITLE#$prefix} + local regex='\(ZOOKEEPER-.[0-9]*\)' + defect=$(expr "$noprefix" : ${regex}) + + echo "Pull request id: ${PULLREQUEST_ID}" + echo "Pull request title: ${PULLREQUEST_TITLE}" + echo "Defect number: ${defect}" + + JIRA_COMMENT="GitHub Pull Request ${PULLREQUEST_NUMBER} Build + " + ;; + DEVELOPER) + ### Set QABUILD to false to indicate that this script is being run by a developer + QABUILD=false + if [[ $# != 10 ]] ; then + echo "ERROR: usage $0 DEVELOPER " + cleanupAndExit 0 + fi + PATCH_DIR=$3 + PATCH_FILE=${PATCH_DIR}/patch + curl -L $2.diff > ${PATCH_FILE} + ### PATCH_FILE contains the location of the patchfile + if [[ ! -e "$PATCH_FILE" ]] ; then + echo "Unable to locate the patch file $PATCH_FILE" + cleanupAndExit 0 + fi + ### Check if $PATCH_DIR exists. If it does not exist, create a new directory + if [[ ! -e "$PATCH_DIR" ]] ; then + mkdir "$PATCH_DIR" + if [[ $? == 0 ]] ; then + echo "$PATCH_DIR has been created" + else + echo "Unable to create $PATCH_DIR" + cleanupAndExit 0 + fi + fi + GIT=$4 + GREP=$5 + PATCH=$6 + FINDBUGS_HOME=$7 + FORREST_HOME=$8 + BASEDIR=$9 + JAVA5_HOME=${10} + ### Obtain the patch filename to append it to the version number + local subject=`grep "Subject:" ${PATCH_FILE}` + local length=`expr match ${subject} ZOOKEEPER-[0-9]*` + local position=`expr index ${subject} ZOOKEEPER-` + defect=${${subject:$position:$length}#ZOOKEEPER-} + ;; + *) + echo "ERROR: usage $0 QABUILD [args] | DEVELOPER [args]" + cleanupAndExit 0 + ;; + esac +} + +############################################################################### +checkout () { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Testing patch for pull request ${PULLREQUEST_ID}." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + ### When run by a developer, if the workspace contains modifications, do not continue + # Ref http://stackoverflow.com/a/2659808 for details on checking dirty status + ${GIT} diff-index --quiet HEAD + if [[ $? -ne 0 ]] ; then + uncommitted=`${GIT} diff --name-only HEAD` + uncommitted="You have the following files with uncommitted changes:${NEWLINE}${uncommitted}" + fi + untracked="$(${GIT} ls-files --exclude-standard --others)" && test -z "${untracked}" + if [[ $? -ne 0 ]] ; then + untracked="You have untracked and unignored files:${NEWLINE}${untracked}" + fi + + if [[ $QABUILD == "false" ]] ; then + if [[ $uncommitted || $untracked ]] ; then + echo "ERROR: can't run in a workspace that contains the following modifications" + echo "" + echo "${uncommitted}" + echo "" + echo "${untracked}" + cleanupAndExit 1 + fi + else + # I don't believe we need to do anything here - the jenkins job will + # cleanup the environment for us ("cleanup before checkout" action) + # on the precommit jenkins job + echo + fi + return $? +} + +############################################################################### +setup () { + ### exit if warnings are NOT defined in the properties file + if [ -z "$OK_FINDBUGS_WARNINGS" ] || [[ -z "$OK_JAVADOC_WARNINGS" ]] || [[ -z $OK_RELEASEAUDIT_WARNINGS ]]; then + echo "Please define the following properties in test-patch.properties file" + echo "OK_FINDBUGS_WARNINGS" + echo "OK_RELEASEAUDIT_WARNINGS" + echo "OK_JAVADOC_WARNINGS" + cleanupAndExit 1 + fi + ### get pull request diff + ${CURL} -L ${GIT_PR_URL}.diff > $PATCH_DIR/patch + + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Pre-build trunk to verify trunk stability and javac warnings" + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + echo "$ANT_HOME/bin/ant -Djavac.args="-Xlint -Xmaxwarns 1000" -Djava5.home=${JAVA5_HOME} -Dforrest.home=${FORREST_HOME} -DZookeeperPatchProcess= clean tar > $PATCH_DIR/trunkJavacWarnings.txt 2>&1" + $ANT_HOME/bin/ant -Djavac.args="-Xlint -Xmaxwarns 1000" -Djava5.home=${JAVA5_HOME} -Dforrest.home=${FORREST_HOME} -DZookeeperPatchProcess= clean tar > $PATCH_DIR/trunkJavacWarnings.txt 2>&1 + if [[ $? != 0 ]] ; then + echo "Trunk compilation is broken?" + cleanupAndExit 1 + fi +} + +############################################################################### +### Check for @author tags in the patch +checkAuthor () { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Checking there are no @author tags in the patch." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + authorTags=`$GREP -c -i '@author' $PATCH_DIR/patch` + echo "There appear to be $authorTags @author tags in the patch." + if [[ $authorTags != 0 ]] ; then + JIRA_COMMENT="$JIRA_COMMENT + + -1 @author. The patch appears to contain $authorTags @author tags which the Zookeeper community has agreed to not allow in code contributions." + return 1 + fi + JIRA_COMMENT="$JIRA_COMMENT + + +1 @author. The patch does not contain any @author tags." + return 0 +} + +############################################################################### +### Check for tests in the patch +checkTests () { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Checking there are new or changed tests in the patch." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + testReferences=`$GREP -c -i '/test' $PATCH_DIR/patch` + echo "There appear to be $testReferences test files referenced in the patch." + if [[ $testReferences == 0 ]] ; then + if [[ $QABUILD == "true" ]] ; then + patchIsDoc=`$GREP -c -i 'title="documentation' $PATCH_DIR/jira` + if [[ $patchIsDoc != 0 ]] ; then + echo "The patch appears to be a documentation patch that doesn't require tests." + JIRA_COMMENT="$JIRA_COMMENT + + +0 tests included. The patch appears to be a documentation patch that doesn't require tests." + return 0 + fi + fi + JIRA_COMMENT="$JIRA_COMMENT + + -1 tests included. The patch doesn't appear to include any new or modified tests. + Please justify why no new tests are needed for this patch. + Also please list what manual steps were performed to verify this patch." + return 1 + fi + JIRA_COMMENT="$JIRA_COMMENT + + +1 tests included. The patch appears to include $testReferences new or modified tests." + return 0 +} + +############################################################################### +### Check there are no javadoc warnings +checkJavadocWarnings () { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Determining number of patched javadoc warnings." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + echo "$ANT_HOME/bin/ant -DZookeeperPatchProcess= clean javadoc | tee $PATCH_DIR/patchJavadocWarnings.txt" + $ANT_HOME/bin/ant -DZookeeperPatchProcess= clean javadoc | tee $PATCH_DIR/patchJavadocWarnings.txt + javadocWarnings=`$GREP -o '\[javadoc\] [0-9]* warning' $PATCH_DIR/patchJavadocWarnings.txt | awk '{total += $2} END {print total}'` + echo "" + echo "" + echo "There appear to be $javadocWarnings javadoc warnings generated by the patched build." + + ### if current warnings greater than OK_JAVADOC_WARNINGS + if [[ $javadocWarnings > $OK_JAVADOC_WARNINGS ]] ; then + JIRA_COMMENT="$JIRA_COMMENT + + -1 javadoc. The javadoc tool appears to have generated `expr $(($javadocWarnings-$OK_JAVADOC_WARNINGS))` warning messages." + return 1 + fi + JIRA_COMMENT="$JIRA_COMMENT + + +1 javadoc. The javadoc tool did not generate any warning messages." + return 0 +} + +############################################################################### +### Check there are no changes in the number of Javac warnings +checkJavacWarnings () { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Determining number of patched javac warnings." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + echo "$ANT_HOME/bin/ant -Djavac.args="-Xlint -Xmaxwarns 1000" -Djava5.home=${JAVA5_HOME} -Dforrest.home=${FORREST_HOME} -DZookeeperPatchProcess= clean tar > $PATCH_DIR/patchJavacWarnings.txt 2>&1" + $ANT_HOME/bin/ant -Djavac.args="-Xlint -Xmaxwarns 1000" -Djava5.home=${JAVA5_HOME} -Dforrest.home=${FORREST_HOME} -DZookeeperPatchProcess= clean tar > $PATCH_DIR/patchJavacWarnings.txt 2>&1 + if [[ $? != 0 ]] ; then + JIRA_COMMENT="$JIRA_COMMENT + + -1 javac. The patch appears to cause tar ant target to fail." + return 1 + fi + ### Compare trunk and patch javac warning numbers + if [[ -f $PATCH_DIR/patchJavacWarnings.txt ]] ; then + trunkJavacWarnings=`$GREP -o '\[javac\] [0-9]* warning' $PATCH_DIR/trunkJavacWarnings.txt | awk '{total += $2} END {print total}'` + patchJavacWarnings=`$GREP -o '\[javac\] [0-9]* warning' $PATCH_DIR/patchJavacWarnings.txt | awk '{total += $2} END {print total}'` + echo "There appear to be $trunkJavacWarnings javac compiler warnings before the patch and $patchJavacWarnings javac compiler warnings after applying the patch." + if [[ $patchJavacWarnings != "" && $trunkJavacWarnings != "" ]] ; then + if [[ $patchJavacWarnings -gt $trunkJavacWarnings ]] ; then + JIRA_COMMENT="$JIRA_COMMENT + + -1 javac. The applied patch generated $patchJavacWarnings javac compiler warnings (more than the trunk's current $trunkJavacWarnings warnings)." + return 1 + fi + fi + fi + JIRA_COMMENT="$JIRA_COMMENT + + +1 javac. The applied patch does not increase the total number of javac compiler warnings." + return 0 +} + +############################################################################### +### Check there are no changes in the number of release audit (RAT) warnings +checkReleaseAuditWarnings () { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Determining number of patched release audit warnings." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + echo "$ANT_HOME/bin/ant -Djava5.home=${JAVA5_HOME} -Dforrest.home=${FORREST_HOME} -DZookeeperPatchProcess= releaseaudit > $PATCH_DIR/patchReleaseAuditWarnings.txt 2>&1" + $ANT_HOME/bin/ant -Djava5.home=${JAVA5_HOME} -Dforrest.home=${FORREST_HOME} -DZookeeperPatchProcess= releaseaudit > $PATCH_DIR/patchReleaseAuditWarnings.txt 2>&1 + + ### Compare trunk and patch release audit warning numbers + if [[ -f $PATCH_DIR/patchReleaseAuditWarnings.txt ]] ; then + patchReleaseAuditWarnings=`$GREP -c '\!?????' $PATCH_DIR/patchReleaseAuditWarnings.txt` + echo "" + echo "" + echo "There appear to be $OK_RELEASEAUDIT_WARNINGS release audit warnings before the patch and $patchReleaseAuditWarnings release audit warnings after applying the patch." + if [[ $patchReleaseAuditWarnings != "" && $OK_RELEASEAUDIT_WARNINGS != "" ]] ; then + if [[ $patchReleaseAuditWarnings -gt $OK_RELEASEAUDIT_WARNINGS ]] ; then + JIRA_COMMENT="$JIRA_COMMENT + + -1 release audit. The applied patch generated $patchReleaseAuditWarnings release audit warnings (more than the trunk's current $OK_RELEASEAUDIT_WARNINGS warnings)." + $GREP '\!?????' $PATCH_DIR/patchReleaseAuditWarnings.txt > $PATCH_DIR/patchReleaseAuditProblems.txt + echo "Lines that start with ????? in the release audit report indicate files that do not have an Apache license header." >> $PATCH_DIR/patchReleaseAuditProblems.txt + JIRA_COMMENT_FOOTER="Release audit warnings: $BUILD_URL/artifact/trunk/patchprocess/patchReleaseAuditProblems.txt +$JIRA_COMMENT_FOOTER" + return 1 + fi + fi + fi + JIRA_COMMENT="$JIRA_COMMENT + + +1 release audit. The applied patch does not increase the total number of release audit warnings." + return 0 +} + +############################################################################### +### Check there are no changes in the number of Checkstyle warnings +checkStyle () { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Determining number of patched checkstyle warnings." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + echo "THIS IS NOT IMPLEMENTED YET" + echo "" + echo "" + echo "$ANT_HOME/bin/ant -DZookeeperPatchProcess= checkstyle" + $ANT_HOME/bin/ant -DZookeeperPatchProcess= checkstyle + JIRA_COMMENT_FOOTER="Checkstyle results: $BUILD_URL/artifact/trunk/build/test/checkstyle-errors.html +$JIRA_COMMENT_FOOTER" + ### TODO: calculate actual patchStyleErrors +# patchStyleErrors=0 +# if [[ $patchStyleErrors != 0 ]] ; then +# JIRA_COMMENT="$JIRA_COMMENT +# +# -1 checkstyle. The patch generated $patchStyleErrors code style errors." +# return 1 +# fi +# JIRA_COMMENT="$JIRA_COMMENT +# +# +1 checkstyle. The patch generated 0 code style errors." + return 0 +} + +############################################################################### +### Check there are no changes in the number of Findbugs warnings +checkFindbugsWarnings () { + findbugs_version=`${FINDBUGS_HOME}/bin/findbugs -version` + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Determining number of patched Findbugs warnings." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + echo "$ANT_HOME/bin/ant -Dfindbugs.home=$FINDBUGS_HOME -Djava5.home=${JAVA5_HOME} -Dforrest.home=${FORREST_HOME} -DZookeeperPatchProcess= findbugs" + $ANT_HOME/bin/ant -Dfindbugs.home=$FINDBUGS_HOME -Djava5.home=${JAVA5_HOME} -Dforrest.home=${FORREST_HOME} -DZookeeperPatchProcess= findbugs + if [ $? != 0 ] ; then + JIRA_COMMENT="$JIRA_COMMENT + + -1 findbugs. The patch appears to cause Findbugs (version ${findbugs_version}) to fail." + return 1 + fi +JIRA_COMMENT_FOOTER="Findbugs warnings: $BUILD_URL/artifact/trunk/build/test/findbugs/newPatchFindbugsWarnings.html +$JIRA_COMMENT_FOOTER" + cp $BASEDIR/build/test/findbugs/*.xml $PATCH_DIR/patchFindbugsWarnings.xml + $FINDBUGS_HOME/bin/setBugDatabaseInfo -timestamp "01/01/2000" \ + $PATCH_DIR/patchFindbugsWarnings.xml \ + $PATCH_DIR/patchFindbugsWarnings.xml + findbugsWarnings=`$FINDBUGS_HOME/bin/filterBugs -first "01/01/2000" $PATCH_DIR/patchFindbugsWarnings.xml \ + $BASEDIR/build/test/findbugs/newPatchFindbugsWarnings.xml | /usr/bin/awk '{print $1}'` + $FINDBUGS_HOME/bin/convertXmlToText -html \ + $BASEDIR/build/test/findbugs/newPatchFindbugsWarnings.xml \ + $BASEDIR/build/test/findbugs/newPatchFindbugsWarnings.html + cp $BASEDIR/build/test/findbugs/newPatchFindbugsWarnings.html $PATCH_DIR/newPatchFindbugsWarnings.html + cp $BASEDIR/build/test/findbugs/newPatchFindbugsWarnings.xml $PATCH_DIR/newPatchFindbugsWarnings.xml + + ### if current warnings greater than OK_FINDBUGS_WARNINGS + if [[ $findbugsWarnings > $OK_FINDBUGS_WARNINGS ]] ; then + JIRA_COMMENT="$JIRA_COMMENT + + -1 findbugs. The patch appears to introduce `expr $(($findbugsWarnings-$OK_FINDBUGS_WARNINGS))` new Findbugs (version ${findbugs_version}) warnings." + return 1 + fi + JIRA_COMMENT="$JIRA_COMMENT + + +1 findbugs. The patch does not introduce any new Findbugs (version ${findbugs_version}) warnings." + return 0 +} + +############################################################################### +### Run the test-core target +runCoreTests () { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Running core tests." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + + ### Kill any rogue build processes from the last attempt + $PS auxwww | $GREP ZookeeperPatchProcess | /usr/bin/nawk '{print $2}' | /usr/bin/xargs -t -I {} /bin/kill -9 {} > /dev/null + + echo "$ANT_HOME/bin/ant -DZookeeperPatchProcess= -Dtest.junit.output.format=xml -Dtest.output=yes -Dtest.junit.threads=8 -Dcompile.c++=yes -Dforrest.home=$FORREST_HOME -Djava5.home=$JAVA5_HOME test-core" + $ANT_HOME/bin/ant -DZookeeperPatchProcess= -Dtest.junit.output.format=xml -Dtest.output=yes -Dtest.junit.threads=8 -Dcompile.c++=yes -Dforrest.home=$FORREST_HOME -Djava5.home=$JAVA5_HOME test-core + if [[ $? != 0 ]] ; then + JIRA_COMMENT="$JIRA_COMMENT + + -1 core tests. The patch failed core unit tests." + return 1 + fi + JIRA_COMMENT="$JIRA_COMMENT + + +1 core tests. The patch passed core unit tests." + return 0 +} + +############################################################################### +### Run the test-contrib target +runContribTests () { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Running contrib tests." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + + ### Kill any rogue build processes from the last attempt + $PS auxwww | $GREP ZookeeperPatchProcess | /usr/bin/nawk '{print $2}' | /usr/bin/xargs -t -I {} /bin/kill -9 {} > /dev/null + + echo "$ANT_HOME/bin/ant -DZookeeperPatchProcess= -Dtest.junit.output.format=xml -Dtest.output=yes test-contrib" + $ANT_HOME/bin/ant -DZookeeperPatchProcess= -Dtest.junit.output.format=xml -Dtest.output=yes test-contrib + if [[ $? != 0 ]] ; then + JIRA_COMMENT="$JIRA_COMMENT + + -1 contrib tests. The patch failed contrib unit tests." + return 1 + fi + JIRA_COMMENT="$JIRA_COMMENT + + +1 contrib tests. The patch passed contrib unit tests." + return 0 +} + +############################################################################### +### Submit a comment to the defect's Jira +submitJiraComment () { + local result=$1 + ### Do not output the value of JIRA_COMMENT_FOOTER when run by a developer + if [[ $QABUILD == "false" ]] ; then + JIRA_COMMENT_FOOTER="" + fi + if [[ $result == 0 ]] ; then + comment="+1 overall. $JIRA_COMMENT + +$JIRA_COMMENT_FOOTER" + else + comment="-1 overall. $JIRA_COMMENT + +$JIRA_COMMENT_FOOTER" + fi + ### Output the test result to the console + echo " + + + +$comment" + + if [[ $QABUILD == "true" ]] ; then + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Adding comment to Jira." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + ### Update Jira with a comment + export USER=jenkins + $JIRACLI -s https://issues.apache.org/jira -a addcomment -u hadoopqa -p $JIRA_PASSWD --comment "$comment" --issue $defect + $JIRACLI -s https://issues.apache.org/jira -a logout -u hadoopqa -p $JIRA_PASSWD + fi +} + +############################################################################### +### Cleanup files +cleanupAndExit () { + local result=$1 + if [[ $QABUILD == "true" ]] ; then + if [ -e "$PATCH_DIR" ] ; then + mv $PATCH_DIR $BASEDIR + fi + fi + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Finished build." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + exit $result +} + +############################################################################### +############################################################################### +############################################################################### + +JIRA_COMMENT="" +JIRA_COMMENT_FOOTER="Console output: $BUILD_URL/console + +This message is automatically generated." + +### Check if arguments to the script have been specified properly or not +echo "----- Going to parser args -----" +parseArgs $@ +cd $BASEDIR + +echo "----- Parsed args, going to checkout -----" +checkout +RESULT=$? +if [[ $QABUILD == "true" ]] ; then + if [[ $RESULT != 0 ]] ; then + exit 100 + fi +fi +setup +checkAuthor +(( RESULT = RESULT + $? )) + +checkTests +checkTestsResult=$? +(( RESULT = RESULT + $checkTestsResult )) +if [[ $checkTestsResult != 0 ]] ; then + submitJiraComment 1 + cleanupAndExit 1 +fi +checkJavadocWarnings +(( RESULT = RESULT + $? )) +checkJavacWarnings +(( RESULT = RESULT + $? )) +### Checkstyle not implemented yet +#checkStyle +#(( RESULT = RESULT + $? )) +checkFindbugsWarnings +(( RESULT = RESULT + $? )) +checkReleaseAuditWarnings +(( RESULT = RESULT + $? )) +### Do not call these when run by a developer +if [[ $QABUILD == "true" ]] ; then + runCoreTests + (( RESULT = RESULT + $? )) + runContribTests + (( RESULT = RESULT + $? )) +fi +JIRA_COMMENT_FOOTER="Test results: $BUILD_URL/testReport/ +$JIRA_COMMENT_FOOTER" + +submitJiraComment $RESULT +cleanupAndExit $RESULT diff --git a/src/java/test/bin/test-patch.sh b/src/java/test/bin/test-patch.sh index d42789b171d..fbc6037d542 100755 --- a/src/java/test/bin/test-patch.sh +++ b/src/java/test/bin/test-patch.sh @@ -120,6 +120,7 @@ checkout () { cd $BASEDIR $SVN revert -R . rm -rf `$SVN status --no-ignore` + $SVN upgrade $SVN update fi return $? @@ -475,8 +476,8 @@ runCoreTests () { ### Kill any rogue build processes from the last attempt $PS auxwww | $GREP ZookeeperPatchProcess | /usr/bin/nawk '{print $2}' | /usr/bin/xargs -t -I {} /bin/kill -9 {} > /dev/null - echo "$ANT_HOME/bin/ant -DZookeeperPatchProcess= -Dtest.junit.output.format=xml -Dtest.output=yes -Dcompile.c++=yes -Dforrest.home=$FORREST_HOME -Djava5.home=$JAVA5_HOME test-core" - $ANT_HOME/bin/ant -DZookeeperPatchProcess= -Dtest.junit.output.format=xml -Dtest.output=yes -Dcompile.c++=yes -Dforrest.home=$FORREST_HOME -Djava5.home=$JAVA5_HOME test-core + echo "$ANT_HOME/bin/ant -DZookeeperPatchProcess= -Dtest.junit.output.format=xml -Dtest.output=yes -Dtest.junit.threads=8 -Dcompile.c++=yes -Dforrest.home=$FORREST_HOME -Djava5.home=$JAVA5_HOME test-core" + $ANT_HOME/bin/ant -DZookeeperPatchProcess= -Dtest.junit.output.format=xml -Dtest.output=yes -Dtest.junit.threads=8 -Dcompile.c++=yes -Dforrest.home=$FORREST_HOME -Djava5.home=$JAVA5_HOME test-core if [[ $? != 0 ]] ; then JIRA_COMMENT="$JIRA_COMMENT diff --git a/src/java/test/checkstyle.xml b/src/java/test/checkstyle.xml index 541573132b2..a5d51821aa8 100644 --- a/src/java/test/checkstyle.xml +++ b/src/java/test/checkstyle.xml @@ -4,6 +4,23 @@ "http://www.puppycrawl.com/dtds/configuration_1_2.dtd"> + - + @@ -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 e88e5f20f9e..71715e9ad7f 100644 --- a/src/java/test/config/findbugsExcludeFile.xml +++ b/src/java/test/config/findbugsExcludeFile.xml @@ -74,6 +74,11 @@ + + + + + @@ -144,4 +149,18 @@ + + + + + + + + + + + diff --git a/src/java/test/data/invalidsnap/version-2/log.42 b/src/java/test/data/invalidsnap/version-2/log.42 new file mode 100644 index 00000000000..5385be516e2 Binary files /dev/null and b/src/java/test/data/invalidsnap/version-2/log.42 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/data/ssl/README.md b/src/java/test/data/ssl/README.md new file mode 100644 index 00000000000..b8823d8a3de --- /dev/null +++ b/src/java/test/data/ssl/README.md @@ -0,0 +1,10 @@ +SSL test data +=================== + +testKeyStore.jks +--- +Testing keystore, password is "testpass". + +testTrustStore.jks +--- +Testing truststore, password is "testpass". diff --git a/src/java/test/data/ssl/testKeyStore.jks b/src/java/test/data/ssl/testKeyStore.jks new file mode 100644 index 00000000000..40a7d0b7eae Binary files /dev/null and b/src/java/test/data/ssl/testKeyStore.jks differ diff --git a/src/java/test/data/ssl/testTrustStore.jks b/src/java/test/data/ssl/testTrustStore.jks new file mode 100644 index 00000000000..33f09c11dfa Binary files /dev/null and b/src/java/test/data/ssl/testTrustStore.jks differ 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..52fdae91d26 --- /dev/null +++ b/src/java/test/org/apache/jute/BinaryInputArchiveTest.java @@ -0,0 +1,44 @@ +/** + * 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 org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + + +// TODO: introduce JuteTestCase as in ZKTestCase +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/ClientCnxnSocketTest.java b/src/java/test/org/apache/zookeeper/ClientCnxnSocketTest.java new file mode 100644 index 00000000000..054e1ed4803 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/ClientCnxnSocketTest.java @@ -0,0 +1,53 @@ +/** + * 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.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.common.ZKConfig; +import org.junit.Test; + +public class ClientCnxnSocketTest { + + @Test + public void testWhenInvalidJuteMaxBufferIsConfiguredIOExceptionIsThrown() { + ZKClientConfig clientConfig = new ZKClientConfig(); + String value = "SomeInvalidInt"; + clientConfig.setProperty(ZKConfig.JUTE_MAXBUFFER, value); + // verify ClientCnxnSocketNIO creation + try { + new ClientCnxnSocketNIO(clientConfig); + fail("IOException is expected."); + } catch (IOException e) { + assertTrue(e.getMessage().contains(value)); + } + // verify ClientCnxnSocketNetty creation + try { + new ClientCnxnSocketNetty(clientConfig); + fail("IOException is expected."); + } catch (IOException e) { + assertTrue(e.getMessage().contains(value)); + } + + } + +} diff --git a/src/java/test/org/apache/zookeeper/ClientReconnectTest.java b/src/java/test/org/apache/zookeeper/ClientReconnectTest.java index 33d1524bab7..566b915c1dd 100644 --- a/src/java/test/org/apache/zookeeper/ClientReconnectTest.java +++ b/src/java/test/org/apache/zookeeper/ClientReconnectTest.java @@ -28,19 +28,18 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import junit.framework.Assert; -import junit.framework.TestCase; - +import org.apache.zookeeper.client.ZKClientConfig; import org.apache.zookeeper.client.HostProvider; +import org.junit.Assert; import org.junit.Test; -public class ClientReconnectTest extends TestCase { +public class ClientReconnectTest extends ZKTestCase { private SocketChannel sc; private CountDownLatch countDownLatch = new CountDownLatch(3); class MockCnxn extends ClientCnxnSocketNIO { MockCnxn() throws IOException { - super(); + super(new ZKClientConfig()); } @Override @@ -63,6 +62,7 @@ public void testClientReconnect() throws IOException, InterruptedException { InetSocketAddress inaddr = new InetSocketAddress("127.0.0.1", 1111); when(hostProvider.next(anyLong())).thenReturn(inaddr); ZooKeeper zk = mock(ZooKeeper.class); + when(zk.getClientConfig()).thenReturn(new ZKClientConfig()); sc = SocketChannel.open(); ClientCnxnSocketNIO nioCnxn = new MockCnxn(); diff --git a/src/java/test/org/apache/zookeeper/CustomHostProviderTest.java b/src/java/test/org/apache/zookeeper/CustomHostProviderTest.java new file mode 100644 index 00000000000..f9762d24996 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/CustomHostProviderTest.java @@ -0,0 +1,79 @@ +/** + * 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.client.HostProvider; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Assert; +import org.junit.Test; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; + +public class CustomHostProviderTest extends ZKTestCase implements Watcher { + private AtomicInteger counter = new AtomicInteger(3); + + private class SpecialHostProvider implements HostProvider { + // ignores its connectstring, and next() always returns localhost:2181 + // it will count down when updateServerList() is called + @Override + public int size() { + return 1; + } + @Override + public InetSocketAddress next(long spinDelay) { + return new InetSocketAddress("127.0.0.1", 2181); + } + @Override + public void onConnected() { + } + @Override + public boolean updateServerList(Collection + serverAddresses, InetSocketAddress currentHost) { + counter.decrementAndGet(); + return false; + } + } + @Override + public void process(WatchedEvent event) { + } + + @Test + public void testZooKeeperWithCustomHostProvider() throws IOException, + InterruptedException { + final int CLIENT_PORT = PortAssignment.unique(); + final HostProvider specialHostProvider = new SpecialHostProvider(); + int expectedCounter = 3; + counter.set(expectedCounter); + + ZooKeeper zkDefaults = new ZooKeeper("127.0.0.1:" + CLIENT_PORT, + ClientBase.CONNECTION_TIMEOUT, this, false); + + ZooKeeper zkSpecial = new ZooKeeper("127.0.0.1:" + CLIENT_PORT, + ClientBase.CONNECTION_TIMEOUT, this, false, specialHostProvider); + + Assert.assertTrue(counter.get() == expectedCounter); + zkDefaults.updateServerList("127.0.0.1:" + PortAssignment.unique()); + Assert.assertTrue(counter.get() == expectedCounter); + + zkSpecial.updateServerList("127.0.0.1:" + PortAssignment.unique()); + expectedCounter--; + Assert.assertTrue(counter.get() == expectedCounter); + } +} diff --git a/src/java/test/org/apache/zookeeper/JUnit4ZKTestRunner.java b/src/java/test/org/apache/zookeeper/JUnit4ZKTestRunner.java index 1d0fd6c88f4..3456a155f9c 100644 --- a/src/java/test/org/apache/zookeeper/JUnit4ZKTestRunner.java +++ b/src/java/test/org/apache/zookeeper/JUnit4ZKTestRunner.java @@ -20,11 +20,14 @@ 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; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; +import java.util.Arrays; +import java.util.List; /** * The sole responsibility of this class is to print to the log when a test @@ -37,17 +40,41 @@ public JUnit4ZKTestRunner(Class klass) throws InitializationError { super(klass); } + public static List computeTestMethodsForClass(final Class klass, final List defaultMethods) { + List list = defaultMethods; + String methodName = System.getProperty("test.method"); + if (methodName == null) { + LOG.info("No test.method specified. using default methods."); + } else { + LOG.info("Picked up test.method={}", methodName); + try { + list = Arrays.asList(new FrameworkMethod(klass.getMethod(methodName))); + } catch (NoSuchMethodException nsme) { + LOG.warn("{} does not have test.method={}. failing to default methods.", klass.getName(), methodName); + } + } + return list; + } + + + @Override + protected List computeTestMethods() { + return computeTestMethodsForClass(getTestClass().getJavaClass(), super.computeTestMethods()); + } + public static 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(); @@ -59,10 +86,20 @@ public void evaluate() throws Throwable { } 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/MultiResponseTest.java b/src/java/test/org/apache/zookeeper/MultiResponseTest.java index a03feb3dfb3..75d9a1207a1 100644 --- a/src/java/test/org/apache/zookeeper/MultiResponseTest.java +++ b/src/java/test/org/apache/zookeeper/MultiResponseTest.java @@ -17,18 +17,18 @@ package org.apache.zookeeper; -import junit.framework.TestCase; import org.apache.jute.BinaryInputArchive; import org.apache.jute.BinaryOutputArchive; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.ByteBufferInputStream; +import org.junit.Assert; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; -public class MultiResponseTest extends TestCase { +public class MultiResponseTest extends ZKTestCase { public void testRoundTrip() throws IOException { MultiResponse response = new MultiResponse(); @@ -42,8 +42,8 @@ public void testRoundTrip() throws IOException { MultiResponse decodedResponse = codeDecode(response); - assertEquals(response, decodedResponse); - assertEquals(response.hashCode(), decodedResponse.hashCode()); + Assert.assertEquals(response, decodedResponse); + Assert.assertEquals(response.hashCode(), decodedResponse.hashCode()); } @Test @@ -51,8 +51,8 @@ public void testEmptyRoundTrip() throws IOException { MultiResponse result = new MultiResponse(); MultiResponse decodedResult = codeDecode(result); - assertEquals(result, decodedResult); - assertEquals(result.hashCode(), decodedResult.hashCode()); + Assert.assertEquals(result, decodedResult); + Assert.assertEquals(result.hashCode(), decodedResult.hashCode()); } private MultiResponse codeDecode(MultiResponse request) throws IOException { diff --git a/src/java/test/org/apache/zookeeper/MultiTransactionRecordTest.java b/src/java/test/org/apache/zookeeper/MultiTransactionRecordTest.java index 374956456ba..d33a3d73e71 100644 --- a/src/java/test/org/apache/zookeeper/MultiTransactionRecordTest.java +++ b/src/java/test/org/apache/zookeeper/MultiTransactionRecordTest.java @@ -18,17 +18,17 @@ package org.apache.zookeeper; -import junit.framework.TestCase; import org.apache.jute.BinaryInputArchive; import org.apache.jute.BinaryOutputArchive; import org.apache.zookeeper.server.ByteBufferInputStream; +import org.junit.Assert; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; -public class MultiTransactionRecordTest extends TestCase { +public class MultiTransactionRecordTest extends ZKTestCase { @Test public void testRoundTrip() throws IOException { MultiTransactionRecord request = new MultiTransactionRecord(); @@ -39,8 +39,8 @@ public void testRoundTrip() throws IOException { MultiTransactionRecord decodedRequest = codeDecode(request); - assertEquals(request, decodedRequest); - assertEquals(request.hashCode(), decodedRequest.hashCode()); + Assert.assertEquals(request, decodedRequest); + Assert.assertEquals(request.hashCode(), decodedRequest.hashCode()); } @Test @@ -48,8 +48,8 @@ public void testEmptyRoundTrip() throws IOException { MultiTransactionRecord request = new MultiTransactionRecord(); MultiTransactionRecord decodedRequest = codeDecode(request); - assertEquals(request, decodedRequest); - assertEquals(request.hashCode(), decodedRequest.hashCode()); + Assert.assertEquals(request, decodedRequest); + Assert.assertEquals(request.hashCode(), decodedRequest.hashCode()); } private MultiTransactionRecord codeDecode(MultiTransactionRecord request) throws IOException { diff --git a/src/java/test/org/apache/zookeeper/PortAssignment.java b/src/java/test/org/apache/zookeeper/PortAssignment.java index 06f9361047b..5c5b0930038 100644 --- a/src/java/test/org/apache/zookeeper/PortAssignment.java +++ b/src/java/test/org/apache/zookeeper/PortAssignment.java @@ -18,18 +18,193 @@ package org.apache.zookeeper; +import java.io.IOException; +import java.net.ServerSocket; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Assign ports to tests */ -public class PortAssignment { +public final class PortAssignment { private static final Logger LOG = LoggerFactory.getLogger(PortAssignment.class); - private static int nextPort = 11221; + // The available port range that we use stays away from the ephemeral port + // range, which the OS will assign to client socket connections. We can't + // coordinate with the OS on the assignment of those ports, so it's best to + // stay out of that range to avoid conflicts. Typical ranges for ephemeral + // ports are: + // - IANA suggests 49152 - 65535 + // - Linux typically uses 32768 - 61000 + // - FreeBSD modern versions typically use the IANA suggested range + // - Windows modern versions typically use the IANA suggested range + private static final int GLOBAL_BASE_PORT = 11221; + private static final int GLOBAL_MAX_PORT = 32767; + + private static PortRange portRange = null; + private static int nextPort; - /** Assign a new, unique port to the test */ + /** + * Assign a new, unique port to the test. This method works by assigning + * ports from a valid port range as identified by the total number of + * concurrent test processes and the ID of this test process. Each + * concurrent test process uses an isolated range, so it's not possible for + * multiple test processes to collide on the same port. Within the port + * range, ports are assigned in monotonic increasing order, wrapping around + * to the beginning of the range if needed. As an extra precaution, the + * method attempts to bind to the port and immediately close it before + * returning it to the caller. If the port cannot be bound, then it tries + * the next one in the range. This provides some resiliency in case the port + * is otherwise occupied, such as a developer running other servers on the + * machine running the tests. + * + * @return port + */ public synchronized static int unique() { - LOG.info("assigning port " + nextPort); - return nextPort++; + if (portRange == null) { + portRange = setupPortRange(System.getProperty("test.junit.threads"), + System.getProperty("sun.java.command")); + nextPort = portRange.getMinimum(); + } + int candidatePort = nextPort; + for (;;) { + ++candidatePort; + if (candidatePort > portRange.getMaximum()) { + candidatePort = portRange.getMinimum(); + } + if (candidatePort == nextPort) { + throw new IllegalStateException(String.format( + "Could not assign port from range %s. The entire " + + "range has been exhausted.", portRange)); + } + try { + ServerSocket s = new ServerSocket(candidatePort); + s.close(); + nextPort = candidatePort; + LOG.info("Assigned port {} from range {}.", nextPort, portRange); + return nextPort; + } catch (IOException e) { + LOG.debug("Could not bind to port {} from range {}. " + + "Attempting next port.", candidatePort, portRange, e); + } + } + } + + /** + * Sets up the port range to be used. In typical usage, Ant invokes JUnit, + * possibly using multiple JUnit processes to execute multiple test suites + * concurrently. The count of JUnit processes is passed from Ant as a system + * property named "test.junit.threads". Ant's JUnit runner receives the + * thread ID as a command line argument of the form threadid=N, where N is an + * integer in the range [1, ${test.junit.threads}]. It's not otherwise + * accessible, so we need to parse it from the command line. This method + * uses these 2 pieces of information to split the available ports into + * disjoint ranges. Each JUnit process only assigns ports from its own range + * in order to prevent bind errors during concurrent test runs. If any of + * this information is unavailable or unparseable, then the default behavior + * is for this process to use the entire available port range. This is + * expected when running tests outside of Ant. + * + * @param strProcessCount string representation of integer process count, + * typically taken from system property test.junit.threads + * @param cmdLine command line containing threadid=N argument, typically + * taken from system property sun.java.command + * @return port range to use + */ + static PortRange setupPortRange(String strProcessCount, String cmdLine) { + Integer processCount = null; + if (strProcessCount != null && !strProcessCount.isEmpty()) { + try { + processCount = Integer.valueOf(strProcessCount); + } catch (NumberFormatException e) { + LOG.warn("Error parsing test.junit.threads = {}.", + strProcessCount, e); + } + } + + Integer threadId = null; + if (processCount != null) { + if (cmdLine != null && !cmdLine.isEmpty()) { + Matcher m = Pattern.compile("threadid=(\\d+)").matcher(cmdLine); + if (m.find()) { + try { + threadId = Integer.valueOf(m.group(1)); + } catch (NumberFormatException e) { + LOG.warn("Error parsing threadid from {}.", cmdLine, e); + } + } + } + } + + final PortRange newPortRange; + if (processCount != null && processCount > 1 && threadId != null) { + // We know the total JUnit process count and this test process's ID. + // Use these values to calculate the valid range for port assignments + // within this test process. We lose a few possible ports to the + // remainder, but that's acceptable. + int portRangeSize = (GLOBAL_MAX_PORT - GLOBAL_BASE_PORT) / + processCount; + int minPort = GLOBAL_BASE_PORT + ((threadId - 1) * portRangeSize); + int maxPort = minPort + portRangeSize - 1; + newPortRange = new PortRange(minPort, maxPort); + LOG.info("Test process {}/{} using ports from {}.", threadId, + processCount, newPortRange); + } else { + // If running outside the context of Ant or Ant is using a single + // test process, then use all valid ports. + newPortRange = new PortRange(GLOBAL_BASE_PORT, GLOBAL_MAX_PORT); + LOG.info("Single test process using ports from {}.", newPortRange); + } + + return newPortRange; + } + + /** + * Contains the minimum and maximum (both inclusive) in a range of ports. + */ + static final class PortRange { + private final int minimum; + private final int maximum; + + /** + * Creates a new PortRange. + * + * @param minimum lower bound port number + * @param maximum upper bound port number + */ + PortRange(int minimum, int maximum) { + this.minimum = minimum; + this.maximum = maximum; + } + + /** + * Returns maximum port in the range. + * + * @return maximum + */ + int getMaximum() { + return maximum; + } + + /** + * Returns minimum port in the range. + * + * @return minimum + */ + int getMinimum() { + return minimum; + } + + @Override + public String toString() { + return String.format("%d - %d", minimum, maximum); + } + } + + /** + * There is no reason to instantiate this class. + */ + private PortAssignment() { } } diff --git a/src/java/test/org/apache/zookeeper/PortAssignmentTest.java b/src/java/test/org/apache/zookeeper/PortAssignmentTest.java new file mode 100644 index 00000000000..28691c67339 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/PortAssignmentTest.java @@ -0,0 +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; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.junit.Test; + +@RunWith(Parameterized.class) +@Parameterized.UseParametersRunnerFactory(ZKParameterized.RunnerFactory.class) +public class PortAssignmentTest { + + private final String strProcessCount; + private final String cmdLine; + private final int expectedMinimumPort; + private final int expectedMaximumPort; + + @Parameters + public static Collection data() { + return Arrays.asList( + new Object[] { "8", "threadid=1", 11221, 13913 }, + new Object[] { "8", "threadid=2", 13914, 16606 }, + new Object[] { "8", "threadid=3", 16607, 19299 }, + new Object[] { "8", "threadid=4", 19300, 21992 }, + new Object[] { "8", "threadid=5", 21993, 24685 }, + new Object[] { "8", "threadid=6", 24686, 27378 }, + new Object[] { "8", "threadid=7", 27379, 30071 }, + new Object[] { "8", "threadid=8", 30072, 32764 }, + new Object[] { "1", "threadid=1", 11221, 32767 }, + new Object[] { "2", "threadid=1", 11221, 21993 }, + new Object[] { "2", "threadid=2", 21994, 32766 }, + new Object[] { null, null, 11221, 32767 }, + new Object[] { "", "", 11221, 32767 }); + } + + public PortAssignmentTest(String strProcessCount, String cmdLine, + int expectedMinimumPort, int expectedMaximumPort) { + this.strProcessCount = strProcessCount; + this.cmdLine = cmdLine; + this.expectedMinimumPort = expectedMinimumPort; + this.expectedMaximumPort = expectedMaximumPort; + } + + @Test + public void testSetupPortRange() { + PortAssignment.PortRange portRange = PortAssignment.setupPortRange( + strProcessCount, cmdLine); + assertEquals(buildAssertionMessage("minimum"), expectedMinimumPort, + portRange.getMinimum()); + assertEquals(buildAssertionMessage("maximum"), expectedMaximumPort, + portRange.getMaximum()); + } + + private String buildAssertionMessage(String checkType) { + return String.format("strProcessCount = %s, cmdLine = %s, checking %s", + strProcessCount, cmdLine, checkType); + } +} diff --git a/src/java/test/org/apache/zookeeper/RemoveWatchesTest.java b/src/java/test/org/apache/zookeeper/RemoveWatchesTest.java index 36735849a3c..931bb6f15f9 100644 --- a/src/java/test/org/apache/zookeeper/RemoveWatchesTest.java +++ b/src/java/test/org/apache/zookeeper/RemoveWatchesTest.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -29,11 +30,14 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; +import org.apache.commons.collections.CollectionUtils; import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.KeeperException.NoWatcherException; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.WatcherType; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.server.ServerCnxn; import org.apache.zookeeper.test.ClientBase; import org.junit.Assert; import org.junit.Test; @@ -47,6 +51,7 @@ * Verifies removing watches using ZooKeeper client apis */ @RunWith(Parameterized.class) +@Parameterized.UseParametersRunnerFactory(ZKParameterized.RunnerFactory.class) public class RemoveWatchesTest extends ClientBase { private static final Logger LOG = LoggerFactory .getLogger(RemoveWatchesTest.class); @@ -139,14 +144,18 @@ public void testRemoveSingleWatcher() throws Exception { Assert.assertNotNull("Didn't set data watches", zk2.exists("/node2", w2)); removeWatches(zk2, "/node1", w1, WatcherType.Data, false, Code.OK); + Assert.assertEquals("Didn't find data watcher", 1, + zk2.getDataWatches().size()); + Assert.assertEquals("Didn't find data watcher", "/node2", + zk2.getDataWatches().get(0)); + removeWatches(zk2, "/node2", w2, WatcherType.Any, false, Code.OK); + Assert.assertTrue("Didn't remove data watcher", w2.matches()); // closing session should remove ephemeral nodes and trigger data // watches if any if (zk1 != null) { zk1.close(); zk1 = null; } - Assert.assertTrue("Didn't remove data watcher", w1.matches()); - Assert.assertFalse("Should have removed data watcher", w2.matches()); List events = w1.getEventsAfterWatchRemoval(); Assert.assertFalse( @@ -176,14 +185,18 @@ public void testMultipleDataWatchers() throws IOException, Assert.assertNotNull("Didn't set data watches", zk2.exists("/node1", w2)); removeWatches(zk2, "/node1", w2, WatcherType.Data, false, Code.OK); + Assert.assertEquals("Didn't find data watcher", 1, + zk2.getDataWatches().size()); + Assert.assertEquals("Didn't find data watcher", "/node1", + zk2.getDataWatches().get(0)); + removeWatches(zk2, "/node1", w1, WatcherType.Any, false, Code.OK); + Assert.assertTrue("Didn't remove data watcher", w2.matches()); // closing session should remove ephemeral nodes and trigger data // watches if any if (zk1 != null) { zk1.close(); zk1 = null; } - Assert.assertTrue("Didn't remove data watcher", w2.matches()); - Assert.assertFalse("Should have removed data watcher", w1.matches()); List events = w2.getEventsAfterWatchRemoval(); Assert.assertEquals( @@ -209,7 +222,10 @@ public void testMultipleChildWatchers() throws IOException, zk2.getChildren("/node1", w2); removeWatches(zk2, "/node1", w2, WatcherType.Children, false, Code.OK); Assert.assertTrue("Didn't remove child watcher", w2.matches()); - Assert.assertFalse("Should have removed child watcher", w1.matches()); + Assert.assertEquals("Didn't find child watcher", 1, zk2 + .getChildWatches().size()); + removeWatches(zk2, "/node1", w1, WatcherType.Any, false, Code.OK); + Assert.assertTrue("Didn't remove child watcher", w1.matches()); // create child to see NodeChildren notification zk1.create("/node1/node2", null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); @@ -447,7 +463,12 @@ public void testRemoveAnyDataWatcher() throws Exception { zk2.getChildren("/node1", w2); removeWatches(zk2, "/node1", w1, WatcherType.Any, false, Code.OK); Assert.assertTrue("Didn't remove data watcher", w1.matches()); - Assert.assertFalse("Shouldn't remove child watcher", w2.matches()); + Assert.assertEquals("Didn't find child watcher", 1, zk2 + .getChildWatches().size()); + Assert.assertEquals("Didn't find data watcher", 1, zk2 + .getDataWatches().size()); + removeWatches(zk2, "/node1", w2, WatcherType.Any, false, Code.OK); + Assert.assertTrue("Didn't remove child watcher", w2.matches()); } /** @@ -472,7 +493,12 @@ public void testRemoveAnyChildWatcher() throws Exception { zk2.getChildren("/node1", w1); removeWatches(zk2, "/node1", w2, WatcherType.Any, false, Code.OK); Assert.assertTrue("Didn't remove child watcher", w2.matches()); - Assert.assertFalse("Shouldn't remove data watcher", w1.matches()); + Assert.assertEquals("Didn't find child watcher", 1, zk2 + .getChildWatches().size()); + Assert.assertEquals("Didn't find data watcher", 1, zk2 + .getDataWatches().size()); + removeWatches(zk2, "/node1", w1, WatcherType.Any, false, Code.OK); + Assert.assertTrue("Didn't remove watchers", w1.matches()); } /** @@ -708,7 +734,10 @@ public void testChRootRemoveWatcher() throws Exception { zk2.getChildren("/node1", w1); removeWatches(zk2, "/node1", w1, WatcherType.Any, false, Code.OK); Assert.assertTrue("Didn't remove child watcher", w1.matches()); - Assert.assertFalse("Shouldn't remove data watcher", w2.matches()); + Assert.assertEquals("Didn't find child watcher", 1, zk2 + .getChildWatches().size()); + removeWatches(zk2, "/node1", w2, WatcherType.Any, false, Code.OK); + Assert.assertTrue("Didn't remove child watcher", w2.matches()); } /** @@ -930,16 +959,16 @@ public void process(WatchedEvent event) { Assert.assertNotNull("Didn't set data watches", zk2.exists("/node1", w2)); + Assert.assertTrue("Server session is not a watcher", + isServerSessionWatcher(zk2.getSessionId(), "/node1", + WatcherType.Data)); removeAllWatches(zk2, "/node1", WatcherType.Data, false, Code.OK); Assert.assertTrue("Didn't remove data watcher", rmWatchCount.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)); - zk1.setData("/node1", "test".getBytes(), -1); - LOG.info("Waiting for data watchers notification after watch removal"); - Assert.assertFalse("Received data watch notification!", - dWatchCount.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)); - Assert.assertEquals("Received watch notification after removal!", 2, - dWatchCount.getCount()); + Assert.assertFalse("Server session is still a watcher after removal", + isServerSessionWatcher(zk2.getSessionId(), "/node1", + WatcherType.Data)); } /** @@ -991,17 +1020,16 @@ public void process(WatchedEvent event) { Assert.assertEquals("Didn't set child watches", 0, zk2.getChildren("/node1", w2).size()); + Assert.assertTrue("Server session is not a watcher", + isServerSessionWatcher(zk2.getSessionId(), "/node1", + WatcherType.Children)); removeAllWatches(zk2, "/node1", WatcherType.Children, false, Code.OK); Assert.assertTrue("Didn't remove child watcher", rmWatchCount.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)); - zk1.create("/node1/node2", null, Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT); - LOG.info("Waiting for child watchers to be notified"); - Assert.assertFalse("Didn't get child watch notification!", - cWatchCount.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)); - Assert.assertEquals("Received watch notification after removal!", 2, - cWatchCount.getCount()); + Assert.assertFalse("Server session is still a watcher after removal", + isServerSessionWatcher(zk2.getSessionId(), "/node1", + WatcherType.Children)); } /** @@ -1067,17 +1095,15 @@ public void process(WatchedEvent event) { Assert.assertNotNull("Didn't set data watches", zk2.exists("/node1", w2)); + Assert.assertTrue("Server session is not a watcher", + isServerSessionWatcher(zk2.getSessionId(), "/node1", + WatcherType.Data)); removeAllWatches(zk2, "/node1", WatcherType.Any, false, Code.OK); Assert.assertTrue("Didn't remove data watcher", rmWatchCount.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)); - - zk1.create("/node1/node2", null, Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT); - zk1.setData("/node1", "test".getBytes(), -1); - - LOG.info("Waiting for child/data watchers notification after watch removal"); - Assert.assertFalse("Received watch notification after removal!", - watchCount.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)); + Assert.assertFalse("Server session is still a watcher after removal", + isServerSessionWatcher(zk2.getSessionId(), "/node1", + WatcherType.Data)); Assert.assertEquals("Received watch notification after removal!", 2, watchCount.getCount()); } @@ -1086,6 +1112,10 @@ public void process(WatchedEvent event) { * before/after calling removeWatches */ private class MyZooKeeper extends ZooKeeper { class MyWatchManager extends ZKWatchManager { + public MyWatchManager(boolean disableAutoWatchReset) { + super(disableAutoWatchReset); + } + public int lastrc; /* Pretend that any watcher exists */ @@ -1111,7 +1141,7 @@ public MyZooKeeper(String hp, int timeout, Watcher watcher) private MyWatchManager myWatchManager; protected ZKWatchManager defaultWatchManager() { - myWatchManager = new MyWatchManager(); + myWatchManager = new MyWatchManager(getClientConfig().getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET)); return myWatchManager; } @@ -1125,7 +1155,7 @@ private class MyWatcher implements Watcher { private String eventPath; private CountDownLatch latch; private List eventsAfterWatchRemoval = new ArrayList(); - public MyWatcher(String path, int count) { + MyWatcher(String path, int count) { this.path = path; latch = new CountDownLatch(count); } @@ -1146,6 +1176,14 @@ public void process(WatchedEvent event) { } } + /** + * Returns true if the watcher was triggered. Try to avoid using this + * method with assertFalse statements. A false return depends on a timed + * out wait on a latch, which makes tests run long. + * + * @return true if the watcher was triggered, false otherwise + * @throws InterruptedException if interrupted while waiting on latch + */ public boolean matches() throws InterruptedException { if (!latch.await(CONNECTION_TIMEOUT/5, TimeUnit.MILLISECONDS)) { LOG.error("Failed waiting to remove the watches"); @@ -1181,6 +1219,14 @@ public void processResult(int rc, String eventPath, Object ctx) { this.latch.countDown(); } + /** + * Returns true if the callback was triggered. Try to avoid using this + * method with assertFalse statements. A false return depends on a timed + * out wait on a latch, which makes tests run long. + * + * @return true if the watcher was triggered, false otherwise + * @throws InterruptedException if interrupted while waiting on latch + */ public boolean matches() throws InterruptedException { if (!latch.await(CONNECTION_TIMEOUT/5, TimeUnit.MILLISECONDS)) { return false; @@ -1188,4 +1234,25 @@ public boolean matches() throws InterruptedException { return path.equals(eventPath) && rc == eventRc; } } + + /** + * Checks if a session is registered with the server as a watcher. + * + * @param long sessionId the session ID to check + * @param path the path to check for watchers + * @param type the type of watcher + * @return true if the client session is a watcher on path for the type + */ + private boolean isServerSessionWatcher(long sessionId, String path, + WatcherType type) { + Set cnxns = new HashSet<>(); + CollectionUtils.addAll(cnxns, serverFactory.getConnections().iterator()); + for (ServerCnxn cnxn : cnxns) { + if (cnxn.getSessionId() == sessionId) { + return getServer(serverFactory).getZKDatabase().getDataTree() + .containsWatcher(path, type, cnxn); + } + } + return false; + } } 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 6e75998b1c8..eac070335ec 100644 --- a/src/java/test/org/apache/zookeeper/test/SaslAuthTest.java +++ b/src/java/test/org/apache/zookeeper/SaslAuthTest.java @@ -16,54 +16,78 @@ * 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 @@ -142,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 4d46fdf738a..bb1bd12e162 100644 --- a/src/java/test/org/apache/zookeeper/TestableZooKeeper.java +++ b/src/java/test/org/apache/zookeeper/TestableZooKeeper.java @@ -25,10 +25,11 @@ import java.util.concurrent.TimeUnit; import org.apache.jute.Record; +import org.apache.zookeeper.admin.ZooKeeperAdmin; import org.apache.zookeeper.proto.ReplyHeader; import org.apache.zookeeper.proto.RequestHeader; -public class TestableZooKeeper extends ZooKeeper { +public class TestableZooKeeper extends ZooKeeperAdmin { public TestableZooKeeper(String host, int sessionTimeout, Watcher watcher) throws IOException { @@ -95,12 +96,6 @@ public void run() { return false; } } - - public boolean testableWaitForShutdown(int wait) - throws InterruptedException - { - return super.testableWaitForShutdown(wait); - } public SocketAddress testableLocalSocketAddress() { return super.testableLocalSocketAddress(); @@ -121,4 +116,13 @@ public ReplyHeader submitRequest(RequestHeader h, Record request, Record response, WatchRegistration watchRegistration) throws InterruptedException { return cnxn.submitRequest(h, request, response, watchRegistration); } + + /** Testing only!!! Really!!!! This is only here to test when the client + * disconnects from the server w/o sending a session disconnect (ie + * ending the session cleanly). The server will eventually notice the + * client is no longer pinging and will timeout the session. + */ + public void disconnect() { + cnxn.disconnect(); + } } diff --git a/src/java/test/org/apache/zookeeper/VerGenTest.java b/src/java/test/org/apache/zookeeper/VerGenTest.java index 13d3abdd806..1d99e454691 100644 --- a/src/java/test/org/apache/zookeeper/VerGenTest.java +++ b/src/java/test/org/apache/zookeeper/VerGenTest.java @@ -36,6 +36,7 @@ * */ @RunWith(Parameterized.class) +@Parameterized.UseParametersRunnerFactory(ZKParameterized.RunnerFactory.class) public class VerGenTest extends ZKTestCase { @Parameters public static Collection data() { @@ -72,7 +73,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/ZKParameterized.java b/src/java/test/org/apache/zookeeper/ZKParameterized.java new file mode 100644 index 00000000000..1a049fbbcb0 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/ZKParameterized.java @@ -0,0 +1,58 @@ +/** + * 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.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; +import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters; +import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory; +import org.junit.runners.parameterized.TestWithParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class ZKParameterized { + private static final Logger LOG = LoggerFactory.getLogger(ZKParameterized.class); + public static class RunnerFactory extends BlockJUnit4ClassRunnerWithParametersFactory { + @Override + public org.junit.runner.Runner createRunnerForTestWithParameters(TestWithParameters test) throws InitializationError { + return new ZKParameterized.Runner(test); + } + } + + public static class Runner extends BlockJUnit4ClassRunnerWithParameters { + public Runner(TestWithParameters test) throws InitializationError { + super(test); + } + + + @Override + protected List computeTestMethods() { + return JUnit4ZKTestRunner.computeTestMethodsForClass(getTestClass().getJavaClass(), super.computeTestMethods()); + } + + + @Override + protected Statement methodInvoker(FrameworkMethod method, Object test) { + return new JUnit4ZKTestRunner.LoggedInvokeMethod(method, test); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/ZKTestCase.java b/src/java/test/org/apache/zookeeper/ZKTestCase.java index 4776f8b6af2..54a6be50927 100644 --- a/src/java/test/org/apache/zookeeper/ZKTestCase.java +++ b/src/java/test/org/apache/zookeeper/ZKTestCase.java @@ -51,6 +51,9 @@ public void starting(FrameworkMethod method) { // accidentally attempting to start multiple admin servers on the // same port. System.setProperty("zookeeper.admin.enableServer", "false"); + // ZOOKEEPER-2693 disables all 4lw by default. + // Here we enable the 4lw which ZooKeeper tests depends. + System.setProperty("zookeeper.4lw.commands.whitelist", "*"); testName = method.getName(); LOG.info("STARTING " + testName); } diff --git a/src/java/test/org/apache/zookeeper/ZooKeeperTest.java b/src/java/test/org/apache/zookeeper/ZooKeeperTest.java index 54d68fee03a..b0ac07fc2d5 100644 --- a/src/java/test/org/apache/zookeeper/ZooKeeperTest.java +++ b/src/java/test/org/apache/zookeeper/ZooKeeperTest.java @@ -22,27 +22,30 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.zookeeper.AsyncCallback.VoidCallback; import org.apache.zookeeper.ZooDefs.Ids; -import org.apache.zookeeper.cli.LsCommand; +import org.apache.zookeeper.cli.*; +import org.apache.zookeeper.common.StringUtils; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.test.ClientBase; import org.junit.Assert; import org.junit.Test; /** - * - * Testing Zookeeper public methods + * + * Testing ZooKeeper public methods * */ public class ZooKeeperTest extends ClientBase { + private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n"); + @Test - public void testDeleteRecursive() throws IOException, InterruptedException, - KeeperException { + public void testDeleteRecursive() throws IOException, InterruptedException, CliException, KeeperException { final ZooKeeper zk = createClient(); // making sure setdata works on / zk.setData("/", "some".getBytes(), -1); @@ -137,10 +140,10 @@ public void processResult(int rc, String path, Object ctx) { } Assert.assertEquals(4, ((AtomicInteger) ctx).get()); } - + @Test public void testStatWhenPathDoesNotExist() throws IOException, - InterruptedException { + InterruptedException, MalformedCommandException { final ZooKeeper zk = createClient(); ZooKeeperMain main = new ZooKeeperMain(zk); String cmdstring = "stat /invalidPath"; @@ -148,8 +151,8 @@ public void testStatWhenPathDoesNotExist() throws IOException, try { main.processZKCmd(main.cl); Assert.fail("As Node does not exist, command should fail by throwing No Node Exception."); - } catch (KeeperException e) { - Assert.assertEquals("KeeperErrorCode = NoNode for /invalidPath", e.getMessage()); + } catch (CliException e) { + Assert.assertEquals("Node does not exist: /invalidPath", e.getMessage()); } } @@ -165,38 +168,102 @@ public void testParseWithExtraSpaces() throws Exception { } @Test - public void testInvalidCommand() throws Exception { + public void testParseWithQuotes() throws Exception { final ZooKeeper zk = createClient(); ZooKeeperMain zkMain = new ZooKeeperMain(zk); - String cmdstring = "cret -s /node1"; - zkMain.cl.parseCommand(cmdstring); - Assert.assertFalse("Doesn't validate the command", zkMain - .processZKCmd(zkMain.cl)); + 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 testCreateCommandWithoutPath() throws Exception { + public void testParseWithMixedQuotes() throws Exception { final ZooKeeper zk = createClient(); ZooKeeperMain zkMain = new ZooKeeperMain(zk); - String cmdstring = "create "; - zkMain.cl.parseCommand(cmdstring); - Assert.assertFalse("Path is not validated.", zkMain - .processZKCmd(zkMain.cl)); - // create ephemeral - cmdstring = "create -e "; - zkMain.cl.parseCommand(cmdstring); - Assert.assertFalse("Path is not validated.", zkMain - .processZKCmd(zkMain.cl)); - // create sequential - cmdstring = "create -s "; + 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.assertFalse("Path is not validated.", zkMain - .processZKCmd(zkMain.cl)); - // create ephemeral sequential - cmdstring = "create -s -e "; + 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.assertFalse("Path is not validated.", zkMain - .processZKCmd(zkMain.cl)); + 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), ""); + } + + @Test + public void testNonexistantCommand() throws Exception { + testInvalidCommand("cret -s /node1", 127); + } + + @Test + public void testCreateCommandWithoutPath() throws Exception { + testInvalidCommand("create", 1); + } + + @Test + public void testCreateEphemeralCommandWithoutPath() throws Exception { + testInvalidCommand("create -e ", 1); + } + + @Test + public void testCreateSequentialCommandWithoutPath() throws Exception { + testInvalidCommand("create -s ", 1); + } + + @Test + public void testCreateEphemeralSequentialCommandWithoutPath() throws Exception { + testInvalidCommand("create -s -e ", 1); + } + + private void testInvalidCommand(String cmdString, int exitCode) throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + zkMain.cl.parseCommand(cmdString); + + // Verify that the exit code is set properly + zkMain.processCmd(zkMain.cl); + Assert.assertEquals(exitCode, zkMain.exitCode); + + // Verify that the correct exception is thrown + try { + zkMain.processZKCmd(zkMain.cl); + Assert.fail(); + } catch (CliException e) { + return; + } + Assert.fail("invalid command should throw CliException"); } @Test @@ -226,7 +293,7 @@ public void testCreateNodeWithoutData() throws Exception { .processZKCmd(zkMain.cl)); Assert.fail("Created the node with wrong option should " + "throw Exception."); - } catch (IllegalArgumentException e) { + } catch (MalformedPathException e) { Assert.assertEquals("Path must start with / character", e .getMessage()); } @@ -269,6 +336,11 @@ public void testDelete() throws Exception { Assert.assertFalse("", zkMain.processCmd(zkMain.cl)); } + @Test + public void testDeleteNonexistantNode() throws Exception { + testInvalidCommand("delete /blahblahblah", 1); + } + @Test public void testStatCommand() throws Exception { final ZooKeeper zk = createClient(); @@ -294,8 +366,8 @@ public void testInvalidStatCommand() throws Exception { try { Assert.assertFalse(zkMain.processZKCmd(zkMain.cl)); Assert.fail("Path doesn't exists so, command should fail."); - } catch (KeeperException e) { - Assert.assertEquals(KeeperException.Code.NONODE, e.code()); + } catch (CliWrapperException e) { + Assert.assertEquals(KeeperException.Code.NONODE, ((KeeperException)e.getCause()).code()); } } @@ -325,26 +397,21 @@ 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"); - } + + // For Invalid ACls should not throw exception + zkMain.executeLine(cmdstring); } @Test public void testDeleteWithInvalidVersionNo() throws Exception { final ZooKeeper zk = createClient(); ZooKeeperMain zkMain = new ZooKeeperMain(zk); - String cmdstring = "create -s -e /node1 data "; + 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"); - } + + // For Invalid dataversion number should not throw exception + zkMain.executeLine(cmdstring1); } @Test @@ -368,6 +435,41 @@ public void testCliCommandsNotEchoingUsage() throws Exception { } } + // 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 systemErr = System.err; // get current err + final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + System.setErr(new PrintStream(errContent)); + try { + zkMain.executeLine(cmd1); + Assert.assertEquals("Command index out of range", errContent + .toString().trim()); + } finally { + // revert redirect of out/err streams - important step! + System.setErr(systemErr); + } + } + + private static void runCommandExpect(CliCommand command, List expectedResults) + throws Exception { + // call command and put result in byteStream + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(byteStream); + command.setOut(out); + command.exec(); + + String result = byteStream.toString(); + assertTrue(result, result.contains( + StringUtils.joinStrings(expectedResults, LINE_SEPARATOR))); + } + @Test public void testSortedLs() throws Exception { final ZooKeeper zk = createClient(); @@ -379,17 +481,89 @@ public void testSortedLs() throws Exception { zkMain.executeLine("create /test1"); zkMain.executeLine("create /zk1"); - // call ls and put result in byteStream - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(byteStream); - String lsCmd = "ls /"; - LsCommand entity = new LsCommand(); - entity.setZk(zk); - entity.setOut(out); - entity.parse(lsCmd.split(" ")).exec(); + LsCommand cmd = new LsCommand(); + cmd.setZk(zk); + cmd.parse("ls /".split(" ")); + List expected = new ArrayList(); + expected.add("[aa1, aa2, aa3, test1, zk1, zookeeper]"); + runCommandExpect(cmd, expected); + } - String result = byteStream.toString(); - assertTrue(result, result.contains("[aa1, aa2, aa3, test1, zk1, zookeeper]")); + @Test + public void testLsrCommand() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + + zkMain.executeLine("create /a"); + zkMain.executeLine("create /a/b"); + zkMain.executeLine("create /a/c"); + zkMain.executeLine("create /a/b/d"); + zkMain.executeLine("create /a/c/e"); + zkMain.executeLine("create /a/f"); + + LsCommand cmd = new LsCommand(); + cmd.setZk(zk); + cmd.parse("ls -R /a".split(" ")); + + List expected = new ArrayList(); + expected.add("/a"); + expected.add("/a/b"); + expected.add("/a/c"); + expected.add("/a/f"); + expected.add("/a/b/d"); + expected.add("/a/c/e"); + runCommandExpect(cmd, expected); + } + + @Test + public void testLsrRootCommand() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + + LsCommand cmd = new LsCommand(); + cmd.setZk(zk); + cmd.parse("ls -R /".split(" ")); + + List expected = new ArrayList(); + expected.add("/"); + expected.add("/zookeeper"); + runCommandExpect(cmd, expected); + } + + @Test + public void testLsrLeafCommand() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + + zkMain.executeLine("create /b"); + zkMain.executeLine("create /b/c"); + + LsCommand cmd = new LsCommand(); + cmd.setZk(zk); + cmd.parse("ls -R /b/c".split(" ")); + + List expected = new ArrayList(); + expected.add("/b/c"); + runCommandExpect(cmd, expected); } + @Test + public void testLsrNonexistantZnodeCommand() throws Exception { + final ZooKeeper zk = createClient(); + ZooKeeperMain zkMain = new ZooKeeperMain(zk); + + zkMain.executeLine("create /b"); + zkMain.executeLine("create /b/c"); + + LsCommand cmd = new LsCommand(); + cmd.setZk(zk); + cmd.parse("ls -R /b/c/d".split(" ")); + + try { + runCommandExpect(cmd, new ArrayList()); + Assert.fail("Path doesn't exists so, command should fail."); + } catch (CliWrapperException e) { + Assert.assertEquals(KeeperException.Code.NONODE, ((KeeperException)e.getCause()).code()); + } + } } diff --git a/src/java/test/org/apache/zookeeper/ZooKeeperTestableTest.java b/src/java/test/org/apache/zookeeper/ZooKeeperTestableTest.java deleted file mode 100644 index 01675df8ff2..00000000000 --- a/src/java/test/org/apache/zookeeper/ZooKeeperTestableTest.java +++ /dev/null @@ -1,59 +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; - -import org.apache.zookeeper.test.ClientBase; -import org.junit.Assert; -import org.junit.Test; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public class ZooKeeperTestableTest extends ClientBase { - @Test - public void testSessionExpiration() throws IOException, InterruptedException, - KeeperException { - ZooKeeper zk = createClient(); - - final CountDownLatch expirationLatch = new CountDownLatch(1); - Watcher watcher = new Watcher() { - @Override - public void process(WatchedEvent event) { - if ( event.getState() == Event.KeeperState.Expired ) { - expirationLatch.countDown(); - } - } - }; - zk.exists("/foo", watcher); - - zk.getTestable().injectSessionExpiration(); - Assert.assertTrue(expirationLatch.await(5, TimeUnit.SECONDS)); - - boolean gotException = false; - try { - zk.exists("/foo", false); - Assert.fail("Should have thrown a SessionExpiredException"); - } catch (KeeperException.SessionExpiredException e) { - // correct - gotException = true; - } - Assert.assertTrue(gotException); - } -} diff --git a/src/java/test/org/apache/zookeeper/client/ZKClientConfigTest.java b/src/java/test/org/apache/zookeeper/client/ZKClientConfigTest.java new file mode 100644 index 00000000000..98f7c51bc96 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/client/ZKClientConfigTest.java @@ -0,0 +1,191 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.client; + +import static org.apache.zookeeper.client.ZKClientConfig.DISABLE_AUTO_WATCH_RESET; +import static org.apache.zookeeper.client.ZKClientConfig.ENABLE_CLIENT_SASL_KEY; +import static org.apache.zookeeper.client.ZKClientConfig.LOGIN_CONTEXT_NAME_KEY; +import static org.apache.zookeeper.client.ZKClientConfig.SECURE_CLIENT; +import static org.apache.zookeeper.client.ZKClientConfig.ZK_SASL_CLIENT_USERNAME; +import static org.apache.zookeeper.client.ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET; +import static org.apache.zookeeper.client.ZKClientConfig.ZOOKEEPER_SERVER_REALM; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +public class ZKClientConfigTest { + private static final File testData = new File(System.getProperty("test.data.dir", "build/test/data")); + @Rule + public Timeout timeout = new Timeout(10, TimeUnit.SECONDS); + + @BeforeClass + public static void init() { + if (!testData.exists()) { + testData.mkdirs(); + } + } + + @Test + public void testDefaultConfiguration() { + Map properties = new HashMap<>(); + properties.put(ZK_SASL_CLIENT_USERNAME, "zookeeper1"); + properties.put(LOGIN_CONTEXT_NAME_KEY, "Client1"); + properties.put(ENABLE_CLIENT_SASL_KEY, "true"); + properties.put(ZOOKEEPER_SERVER_REALM, "zookeeper/hadoop.hadoop.com"); + properties.put(DISABLE_AUTO_WATCH_RESET, "true"); + properties.put(ZOOKEEPER_CLIENT_CNXN_SOCKET, "ClientCnxnSocketNetty"); + properties.put(SECURE_CLIENT, "true"); + + for (Map.Entry e : properties.entrySet()) { + System.setProperty(e.getKey(), e.getValue()); + } + /** + * ZKClientConfig should get initialized with system properties + */ + ZKClientConfig conf = new ZKClientConfig(); + for (Map.Entry e : properties.entrySet()) { + assertEquals(e.getValue(), conf.getProperty(e.getKey())); + } + /** + * clear properties + */ + for (Map.Entry e : properties.entrySet()) { + System.clearProperty(e.getKey()); + } + + conf = new ZKClientConfig(); + /** + * test that all the properties are null + */ + for (Map.Entry e : properties.entrySet()) { + String result = conf.getProperty(e.getKey()); + assertNull(result); + } + } + + @Test + public void testSystemPropertyValue() { + String clientName = "zookeeper1"; + System.setProperty(ZK_SASL_CLIENT_USERNAME, clientName); + + ZKClientConfig conf = new ZKClientConfig(); + assertEquals(conf.getProperty(ZK_SASL_CLIENT_USERNAME), clientName); + + String newClientName = "zookeeper2"; + conf.setProperty(ZK_SASL_CLIENT_USERNAME, newClientName); + + assertEquals(conf.getProperty(ZK_SASL_CLIENT_USERNAME), newClientName); + } + + @Test + public void testReadConfigurationFile() throws IOException, ConfigException { + File file = File.createTempFile("clientConfig", ".conf", testData); + file.deleteOnExit(); + Properties clientConfProp = new Properties(); + clientConfProp.setProperty(ENABLE_CLIENT_SASL_KEY, "true"); + clientConfProp.setProperty(ZK_SASL_CLIENT_USERNAME, "ZK"); + clientConfProp.setProperty(LOGIN_CONTEXT_NAME_KEY, "MyClient"); + clientConfProp.setProperty(ZOOKEEPER_SERVER_REALM, "HADOOP.COM"); + clientConfProp.setProperty("dummyProperty", "dummyValue"); + OutputStream io = new FileOutputStream(file); + try { + clientConfProp.store(io, "Client Configurations"); + } finally { + io.close(); + } + + ZKClientConfig conf = new ZKClientConfig(); + conf.addConfiguration(file.getAbsolutePath()); + assertEquals(conf.getProperty(ENABLE_CLIENT_SASL_KEY), "true"); + assertEquals(conf.getProperty(ZK_SASL_CLIENT_USERNAME), "ZK"); + assertEquals(conf.getProperty(LOGIN_CONTEXT_NAME_KEY), "MyClient"); + assertEquals(conf.getProperty(ZOOKEEPER_SERVER_REALM), "HADOOP.COM"); + assertEquals(conf.getProperty("dummyProperty"), "dummyValue"); + + // try to delete it now as we have done with the created file, why to + // wait for deleteOnExit() deletion + file.delete(); + + } + + @Test + public void testSetConfiguration() { + ZKClientConfig conf = new ZKClientConfig(); + String defaultValue = conf.getProperty(ZKClientConfig.ENABLE_CLIENT_SASL_KEY, + ZKClientConfig.ENABLE_CLIENT_SASL_DEFAULT); + if (defaultValue.equals("true")) { + conf.setProperty(ENABLE_CLIENT_SASL_KEY, "false"); + } else { + conf.setProperty(ENABLE_CLIENT_SASL_KEY, "true"); + } + assertTrue(conf.getProperty(ENABLE_CLIENT_SASL_KEY) != defaultValue); + } + + @Test + public void testIntegerRetrievalFromProperty() { + ZKClientConfig conf = new ZKClientConfig(); + String prop = "UnSetProperty" + System.currentTimeMillis(); + int defaultValue = 100; + // property is not set we should get the default value + int result = conf.getInt(prop, defaultValue); + assertEquals(defaultValue, result); + + // property is set but can not be parsed to int, we should get the + // NumberFormatException + conf.setProperty(ZKConfig.JUTE_MAXBUFFER, "InvlaidIntValue123"); + try { + result = conf.getInt(ZKConfig.JUTE_MAXBUFFER, defaultValue); + fail("NumberFormatException is expected"); + } catch (NumberFormatException exception) { + // do nothing + } + assertEquals(defaultValue, result); + + // property is set to an valid int, we should get the set value + int value = ZKClientConfig.CLIENT_MAX_PACKET_LENGTH_DEFAULT; + conf.setProperty(ZKConfig.JUTE_MAXBUFFER, Integer.toString(value)); + result = conf.getInt(ZKConfig.JUTE_MAXBUFFER, defaultValue); + assertEquals(value, result); + + // property is set but with white spaces + value = 12345; + conf.setProperty(ZKConfig.JUTE_MAXBUFFER, + " " + Integer.toString(value) + " "); + result = conf.getInt(ZKConfig.JUTE_MAXBUFFER, defaultValue); + assertEquals(value, result); + } + +} diff --git a/src/java/test/org/apache/zookeeper/common/HostNameUtilsTest.java b/src/java/test/org/apache/zookeeper/common/HostNameUtilsTest.java deleted file mode 100644 index 8647d625493..00000000000 --- a/src/java/test/org/apache/zookeeper/common/HostNameUtilsTest.java +++ /dev/null @@ -1,97 +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.common; - -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import org.junit.Assert; -import org.junit.Test; - -public class HostNameUtilsTest { - - private String validName = "example.com"; - private String invalidName = "example.com_invalid"; - private int port = 123; - - @Test - public void testWildcard() { - InetSocketAddress socketAddress = new InetSocketAddress(port); - Assert.assertEquals("InetSocketAddress with no host. " + - "Expecting 0.0.0.0.", - socketAddress.getAddress().getHostAddress(), - HostNameUtils.getHostString(socketAddress)); - } - - @Test - public void testHostName() { - InetSocketAddress socketAddress = - new InetSocketAddress(validName, port); - Assert.assertEquals("InetSocketAddress with a valid hostname", - validName, - HostNameUtils.getHostString(socketAddress)); - - socketAddress = new InetSocketAddress(invalidName, port); - Assert.assertEquals("InetSocketAddress with an invalid hostname", - invalidName, - HostNameUtils.getHostString(socketAddress)); - } - - @Test - public void testGetByAddress() { - try { - byte[] byteAddress = new byte[]{1, 2, 3, 4}; - InetAddress address = InetAddress.getByAddress(byteAddress); - InetSocketAddress socketAddress = - new InetSocketAddress(address, port); - Assert.assertEquals("getByAddress with byte address only.", - address.getHostAddress(), - HostNameUtils.getHostString(socketAddress)); - - address = InetAddress.getByAddress(validName, byteAddress); - socketAddress = new InetSocketAddress(address, port); - Assert.assertEquals("getByAddress with a valid hostname and byte " + - "address.", - validName, - HostNameUtils.getHostString(socketAddress)); - - address = InetAddress.getByAddress(invalidName, byteAddress); - socketAddress = new InetSocketAddress(address, port); - Assert.assertEquals("getByAddress with an invalid hostname and " + - "byte address.", - invalidName, - HostNameUtils.getHostString(socketAddress)); - } catch (UnknownHostException ex) { - Assert.fail(ex.toString()); - } - } - - @Test - public void testGetByName() { - try { - InetAddress address = InetAddress.getByName(validName); - InetSocketAddress socketAddress = - new InetSocketAddress(address, port); - Assert.assertEquals("getByName with a valid hostname.", - validName, - HostNameUtils.getHostString(socketAddress)); - } catch (UnknownHostException ex) { - Assert.fail(ex.toString()); - } - } -} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/common/PathUtilsTest.java b/src/java/test/org/apache/zookeeper/common/PathUtilsTest.java index 9b224ee28af..58044568320 100644 --- a/src/java/test/org/apache/zookeeper/common/PathUtilsTest.java +++ b/src/java/test/org/apache/zookeeper/common/PathUtilsTest.java @@ -18,9 +18,10 @@ package org.apache.zookeeper.common; +import org.apache.zookeeper.ZKTestCase; import org.junit.Test; -public class PathUtilsTest { +public class PathUtilsTest extends ZKTestCase { @Test public void testValidatePath_ValidPath() { 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 2b7fb4697a1..da4ebaffccc 100644 --- a/src/java/test/org/apache/zookeeper/server/CRCTest.java +++ b/src/java/test/org/apache/zookeeper/server/CRCTest.java @@ -29,36 +29,30 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; import java.util.zip.Adler32; import java.util.zip.CheckedInputStream; import org.apache.jute.BinaryInputArchive; import org.apache.jute.InputArchive; -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.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.server.persistence.FileSnap; import org.apache.zookeeper.server.persistence.FileTxnLog; import org.apache.zookeeper.server.persistence.TxnLog.TxnIterator; import org.apache.zookeeper.test.ClientBase; import org.junit.Assert; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public class CRCTest extends ZKTestCase implements Watcher { +public class CRCTest extends ZKTestCase{ private static final Logger LOG = LoggerFactory.getLogger(CRCTest.class); private static final String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); - private volatile CountDownLatch startSignal; - /** * corrupt a file by writing m at 500 b * offset @@ -121,7 +115,7 @@ public void testChecksums() throws Exception { LOG.info("starting up the zookeeper server .. waiting"); Assert.assertTrue("waiting for server being up", ClientBase.waitForServerUp(HOSTPORT,CONNECTION_TIMEOUT)); - ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); + ZooKeeper zk = ClientBase.createZKClient(HOSTPORT); try { for (int i =0; i < 2000; i++) { zk.create("/crctest- " + i , ("/crctest- " + i).getBytes(), @@ -178,13 +172,4 @@ public void testChecksums() throws Exception { } Assert.assertTrue(cfile); } - - public void process(WatchedEvent event) { - LOG.info("Event:" + event.getState() + " " + event.getType() + " " + event.getPath()); - if (event.getState() == KeeperState.SyncConnected - && startSignal != null && startSignal.getCount() > 0) - { - startSignal.countDown(); - } - } } diff --git a/src/java/test/org/apache/zookeeper/server/CreateContainerTest.java b/src/java/test/org/apache/zookeeper/server/CreateContainerTest.java new file mode 100644 index 00000000000..4889684f79c --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/CreateContainerTest.java @@ -0,0 +1,281 @@ +/** + * 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.zookeeper.*; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.*; + +public class CreateContainerTest extends ClientBase { + private ZooKeeper zk; + + @Override + public void setUp() throws Exception { + super.setUp(); + zk = createClient(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + zk.close(); + } + + @Test(timeout = 30000) + public void testCreate() + throws KeeperException, InterruptedException { + createNoStatVerifyResult("/foo"); + createNoStatVerifyResult("/foo/child"); + } + + @Test(timeout = 30000) + public void testCreateWithStat() + throws KeeperException, InterruptedException { + Stat stat = createWithStatVerifyResult("/foo"); + Stat childStat = createWithStatVerifyResult("/foo/child"); + // Don't expect to get the same stats for different creates. + Assert.assertFalse(stat.equals(childStat)); + } + + @SuppressWarnings("ConstantConditions") + @Test(timeout = 30000) + public void testCreateWithNullStat() + throws KeeperException, InterruptedException { + final String name = "/foo"; + Assert.assertNull(zk.exists(name, false)); + + Stat stat = null; + // If a null Stat object is passed the create should still + // succeed, but no Stat info will be returned. + zk.create(name, name.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER, stat); + Assert.assertNull(stat); + Assert.assertNotNull(zk.exists(name, false)); + } + + @Test(timeout = 30000) + public void testSimpleDeletion() + throws KeeperException, InterruptedException { + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER); + zk.create("/foo/bar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.delete("/foo/bar", -1); // should cause "/foo" to get deleted when checkContainers() is called + + ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer() + .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100); + containerManager.checkContainers(); + + Thread.sleep(1000); + + Assert.assertNull("Container should have been deleted", zk.exists("/foo", false)); + } + + @Test(timeout = 30000) + public void testMultiWithContainerSimple() + throws KeeperException, InterruptedException { + Op createContainer = Op.create("/foo", new byte[0], + ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER); + zk.multi(Collections.singletonList(createContainer)); + + DataTree dataTree = serverFactory.getZooKeeperServer().getZKDatabase().getDataTree(); + Assert.assertEquals(dataTree.getContainers().size(), 1); + } + + @Test(timeout = 30000) + public void testMultiWithContainer() + throws KeeperException, InterruptedException { + Op createContainer = Op.create("/foo", new byte[0], + ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER); + Op createChild = Op.create("/foo/bar", new byte[0], + ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.multi(Arrays.asList(createContainer, createChild)); + + DataTree dataTree = serverFactory.getZooKeeperServer().getZKDatabase().getDataTree(); + Assert.assertEquals(dataTree.getContainers().size(), 1); + + zk.delete("/foo/bar", -1); // should cause "/foo" to get deleted when checkContainers() is called + + ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer() + .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100); + containerManager.checkContainers(); + + Thread.sleep(1000); + + Assert.assertNull("Container should have been deleted", zk.exists("/foo", false)); + + createContainer = Op.create("/foo", new byte[0], + ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER); + createChild = Op.create("/foo/bar", new byte[0], + ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + Op deleteChild = Op.delete("/foo/bar", -1); + zk.multi(Arrays.asList(createContainer, createChild, deleteChild)); + + containerManager.checkContainers(); + + Thread.sleep(1000); + + Assert.assertNull("Container should have been deleted", zk.exists("/foo", false)); + } + + @Test(timeout = 30000) + public void testSimpleDeletionAsync() + throws KeeperException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + AsyncCallback.Create2Callback cb = new AsyncCallback.Create2Callback() { + @Override + public void processResult(int rc, String path, Object ctx, String name, Stat stat) { + Assert.assertEquals(ctx, "context"); + latch.countDown(); + } + }; + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER, cb, "context"); + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + zk.create("/foo/bar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.delete("/foo/bar", -1); // should cause "/foo" to get deleted when checkContainers() is called + + ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer() + .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100); + containerManager.checkContainers(); + + Thread.sleep(1000); + + Assert.assertNull("Container should have been deleted", zk.exists("/foo", false)); + } + + @Test(timeout = 30000) + public void testCascadingDeletion() + throws KeeperException, InterruptedException { + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER); + zk.create("/foo/bar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER); + zk.create("/foo/bar/one", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.delete("/foo/bar/one", -1); // should cause "/foo/bar" and "/foo" to get deleted when checkContainers() is called + + ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer() + .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100); + containerManager.checkContainers(); + Thread.sleep(1000); + containerManager + .checkContainers(); + Thread.sleep(1000); + + Assert.assertNull("Container should have been deleted", zk.exists("/foo/bar", false)); + Assert.assertNull("Container should have been deleted", zk.exists("/foo", false)); + } + + @Test(timeout = 30000) + public void testFalseEmpty() + throws KeeperException, InterruptedException { + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER); + zk.create("/foo/bar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + + ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer() + .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100) { + @Override + protected Collection getCandidates() { + return Collections.singletonList("/foo"); + } + }; + containerManager.checkContainers(); + Thread.sleep(1000); + + Assert.assertNotNull("Container should have not been deleted", zk.exists("/foo", false)); + } + + @Test(timeout = 30000) + public void testMaxPerMinute() + throws InterruptedException { + final BlockingQueue queue = new LinkedBlockingQueue(); + RequestProcessor processor = new RequestProcessor() { + @Override + public void processRequest(Request request) { + queue.add(new String(request.request.array())); + } + + @Override + public void shutdown() { + } + }; + final ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer() + .getZKDatabase(), processor, 1, 2) { + @Override + protected long getMinIntervalMs() { + return 1000; + } + + @Override + protected Collection getCandidates() { + return Arrays.asList("/one", "/two", "/three", "/four"); + } + }; + Executors.newSingleThreadExecutor().submit(new Callable() { + @Override + public Void call() throws Exception { + containerManager.checkContainers(); + return null; + } + }); + Assert.assertEquals(queue.poll(5, TimeUnit.SECONDS), "/one"); + Assert.assertEquals(queue.poll(5, TimeUnit.SECONDS), "/two"); + Assert.assertEquals(queue.size(), 0); + Thread.sleep(500); + Assert.assertEquals(queue.size(), 0); + + Assert.assertEquals(queue.poll(5, TimeUnit.SECONDS), "/three"); + Assert.assertEquals(queue.poll(5, TimeUnit.SECONDS), "/four"); + } + + private void createNoStatVerifyResult(String newName) + throws KeeperException, InterruptedException { + Assert.assertNull("Node existed before created", zk.exists(newName, false)); + zk.create(newName, newName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER); + Assert.assertNotNull("Node was not created as expected", + zk.exists(newName, false)); + } + + private Stat createWithStatVerifyResult(String newName) + throws KeeperException, InterruptedException { + Assert.assertNull("Node existed before created", zk.exists(newName, false)); + Stat stat = new Stat(); + zk.create(newName, newName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER, stat); + validateCreateStat(stat, newName); + + Stat referenceStat = zk.exists(newName, false); + Assert.assertNotNull("Node was not created as expected", referenceStat); + Assert.assertEquals(referenceStat, stat); + + return stat; + } + + private void validateCreateStat(Stat stat, String name) { + Assert.assertEquals(stat.getCzxid(), stat.getMzxid()); + Assert.assertEquals(stat.getCzxid(), stat.getPzxid()); + Assert.assertEquals(stat.getCtime(), stat.getMtime()); + Assert.assertEquals(0, stat.getCversion()); + Assert.assertEquals(0, stat.getVersion()); + Assert.assertEquals(0, stat.getAversion()); + Assert.assertEquals(0, stat.getEphemeralOwner()); + Assert.assertEquals(name.length(), stat.getDataLength()); + Assert.assertEquals(0, stat.getNumChildren()); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/CreateTTLTest.java b/src/java/test/org/apache/zookeeper/server/CreateTTLTest.java new file mode 100644 index 00000000000..2440030cc99 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/CreateTTLTest.java @@ -0,0 +1,267 @@ +/** + * 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.zookeeper.AsyncCallback; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.KeeperException.Code; +import org.apache.zookeeper.Op; +import org.apache.zookeeper.OpResult; +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.proto.CreateResponse; +import org.apache.zookeeper.proto.CreateTTLRequest; +import org.apache.zookeeper.proto.ReplyHeader; +import org.apache.zookeeper.proto.RequestHeader; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +public class CreateTTLTest extends ClientBase { + private TestableZooKeeper zk; + + private static final Collection disabledTests = Collections.singleton("testDisabled"); + + @Override + public void setUp() throws Exception { + System.setProperty(EphemeralType.EXTENDED_TYPES_ENABLED_PROPERTY, disabledTests.contains(getTestName()) ? "false" : "true"); + super.setUpWithServerId(254); + zk = createClient(); + } + + @Override + public void tearDown() throws Exception { + System.clearProperty(EphemeralType.EXTENDED_TYPES_ENABLED_PROPERTY); + super.tearDown(); + zk.close(); + } + + @Test + public void testCreate() + throws KeeperException, InterruptedException { + Stat stat = new Stat(); + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, stat, 100); + Assert.assertEquals(0, stat.getEphemeralOwner()); + + final AtomicLong fakeElapsed = new AtomicLong(0); + ContainerManager containerManager = newContainerManager(fakeElapsed); + containerManager.checkContainers(); + Assert.assertNotNull("Ttl node should not have been deleted yet", zk.exists("/foo", false)); + + fakeElapsed.set(1000); + containerManager.checkContainers(); + Assert.assertNull("Ttl node should have been deleted", zk.exists("/foo", false)); + } + + @Test + public void testBadTTLs() throws InterruptedException, KeeperException { + RequestHeader h = new RequestHeader(1, ZooDefs.OpCode.createTTL); + + String path = "/bad_ttl"; + CreateTTLRequest request = new CreateTTLRequest(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_WITH_TTL.toFlag(), -100); + CreateResponse response = new CreateResponse(); + ReplyHeader r = zk.submitRequest(h, request, response, null); + Assert.assertEquals("An invalid CreateTTLRequest should throw BadArguments", + r.getErr(), Code.BADARGUMENTS.intValue()); + Assert.assertNull("An invalid CreateTTLRequest should not result in znode creation", + zk.exists(path, false)); + + request = new CreateTTLRequest(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_WITH_TTL.toFlag(), EphemeralType.TTL.maxValue() + 1); + response = new CreateResponse(); + r = zk.submitRequest(h, request, response, null); + Assert.assertEquals("An invalid CreateTTLRequest should throw BadArguments", + r.getErr(), Code.BADARGUMENTS.intValue()); + Assert.assertNull("An invalid CreateTTLRequest should not result in znode creation", + zk.exists(path, false)); + } + + @Test + public void testMaxTTLs() throws InterruptedException, KeeperException { + RequestHeader h = new RequestHeader(1, ZooDefs.OpCode.createTTL); + + String path = "/bad_ttl"; + CreateTTLRequest request = new CreateTTLRequest(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_WITH_TTL.toFlag(), EphemeralType.TTL.maxValue()); + CreateResponse response = new CreateResponse(); + ReplyHeader r = zk.submitRequest(h, request, response, null); + Assert.assertEquals("EphemeralType.getMaxTTL() should succeed", + r.getErr(), Code.OK.intValue()); + Assert.assertNotNull("Node should exist", + zk.exists(path, false)); + } + + @Test + public void testCreateSequential() + throws KeeperException, InterruptedException { + Stat stat = new Stat(); + String path = zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL, stat, 100); + Assert.assertEquals(0, stat.getEphemeralOwner()); + + final AtomicLong fakeElapsed = new AtomicLong(0); + ContainerManager containerManager = newContainerManager(fakeElapsed); + containerManager.checkContainers(); + Assert.assertNotNull("Ttl node should not have been deleted yet", zk.exists(path, false)); + + fakeElapsed.set(1000); + containerManager.checkContainers(); + Assert.assertNull("Ttl node should have been deleted", zk.exists(path, false)); + } + + @Test + public void testCreateAsync() + throws KeeperException, InterruptedException { + AsyncCallback.Create2Callback callback = new AsyncCallback.Create2Callback() { + @Override + public void processResult(int rc, String path, Object ctx, String name, Stat stat) { + // NOP + } + }; + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, callback, null, 100); + + final AtomicLong fakeElapsed = new AtomicLong(0); + ContainerManager containerManager = newContainerManager(fakeElapsed); + containerManager.checkContainers(); + Assert.assertNotNull("Ttl node should not have been deleted yet", zk.exists("/foo", false)); + + fakeElapsed.set(1000); + containerManager.checkContainers(); + Assert.assertNull("Ttl node should have been deleted", zk.exists("/foo", false)); + } + + @Test + public void testModifying() + throws KeeperException, InterruptedException { + Stat stat = new Stat(); + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, stat, 100); + Assert.assertEquals(0, stat.getEphemeralOwner()); + + final AtomicLong fakeElapsed = new AtomicLong(0); + ContainerManager containerManager = newContainerManager(fakeElapsed); + containerManager.checkContainers(); + Assert.assertNotNull("Ttl node should not have been deleted yet", zk.exists("/foo", false)); + + for ( int i = 0; i < 10; ++i ) { + fakeElapsed.set(50); + zk.setData("/foo", new byte[i + 1], -1); + containerManager.checkContainers(); + Assert.assertNotNull("Ttl node should not have been deleted yet", zk.exists("/foo", false)); + } + + fakeElapsed.set(200); + containerManager.checkContainers(); + Assert.assertNull("Ttl node should have been deleted", zk.exists("/foo", false)); + } + + @Test + public void testMulti() + throws KeeperException, InterruptedException { + Op createTtl = Op.create("/a", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, 100); + Op createTtlSequential = Op.create("/b", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL, 200); + Op createNonTtl = Op.create("/c", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + List results = zk.multi(Arrays.asList(createTtl, createTtlSequential, createNonTtl)); + String sequentialPath = ((OpResult.CreateResult)results.get(1)).getPath(); + + final AtomicLong fakeElapsed = new AtomicLong(0); + ContainerManager containerManager = newContainerManager(fakeElapsed); + containerManager.checkContainers(); + Assert.assertNotNull("node should not have been deleted yet", zk.exists("/a", false)); + Assert.assertNotNull("node should not have been deleted yet", zk.exists(sequentialPath, false)); + Assert.assertNotNull("node should never be deleted", zk.exists("/c", false)); + + fakeElapsed.set(110); + containerManager.checkContainers(); + Assert.assertNull("node should have been deleted", zk.exists("/a", false)); + Assert.assertNotNull("node should not have been deleted yet", zk.exists(sequentialPath, false)); + Assert.assertNotNull("node should never be deleted", zk.exists("/c", false)); + + fakeElapsed.set(210); + containerManager.checkContainers(); + Assert.assertNull("node should have been deleted", zk.exists("/a", false)); + Assert.assertNull("node should have been deleted", zk.exists(sequentialPath, false)); + Assert.assertNotNull("node should never be deleted", zk.exists("/c", false)); + } + + @Test + public void testBadUsage() throws KeeperException, InterruptedException { + for ( CreateMode createMode : CreateMode.values() ) { + try { + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, createMode, new Stat(), createMode.isTTL() ? 0 : 100); + Assert.fail("should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException dummy) { + // correct + } + } + + for ( CreateMode createMode : CreateMode.values() ) { + AsyncCallback.Create2Callback callback = new AsyncCallback.Create2Callback() { + @Override + public void processResult(int rc, String path, Object ctx, String name, Stat stat) { + // NOP + } + }; + try { + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, createMode, callback, null, createMode.isTTL() ? 0 : 100); + Assert.fail("should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException dummy) { + // correct + } + } + + try { + Op op = Op.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, 0); + zk.multi(Collections.singleton(op)); + Assert.fail("should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException dummy) { + // correct + } + try { + Op op = Op.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL, 0); + zk.multi(Collections.singleton(op)); + Assert.fail("should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException dummy) { + // correct + } + } + + @Test(expected = KeeperException.UnimplementedException.class) + public void testDisabled() throws KeeperException, InterruptedException { + // note, setUp() enables this test based on the test name + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, new Stat(), 100); + } + + private ContainerManager newContainerManager(final AtomicLong fakeElapsed) { + return new ContainerManager(serverFactory.getZooKeeperServer() + .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100) { + @Override + protected long getElapsed(DataNode node) { + return fakeElapsed.get(); + } + }; + } +} 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 index ebc2fca0cd5..57497cefc9c 100644 --- a/src/java/test/org/apache/zookeeper/server/DataTreeTest.java +++ b/src/java/test/org/apache/zookeeper/server/DataTreeTest.java @@ -25,24 +25,31 @@ import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; 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; +import java.util.List; +import java.util.ArrayList; public class DataTreeTest extends ZKTestCase { protected static final Logger LOG = LoggerFactory.getLogger(DataTreeTest.class); @@ -110,7 +117,7 @@ private void createEphemeralNode(long session, final DataTree dataTree, dataTree.getNode("/").stat.getCversion() + 1, 1, 1); } } - + @Test(timeout = 60000) public void testRootWatchTriggered() throws Exception { class MyWatcher implements Watcher{ @@ -145,18 +152,18 @@ public void testIncrementCversion() throws Exception { 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(); @@ -174,6 +181,104 @@ public void testPathTrieClearOnDeserialize() throws Exception { 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")); + 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 { + // Need check if the record is a DataNode instance because of changes in ZOOKEEPER-2014 + // which adds default ACL to config node. + if (r instanceof DataNode) { + 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()); + } + + @Test(timeout = 60000) + public void testReconfigACLClearOnDeserialize() throws Exception { + + DataTree tree = new DataTree(); + // simulate the upgrading scenario, where the reconfig znode + // doesn't exist and the acl cache is empty + tree.deleteNode(ZooDefs.CONFIG_NODE, 1); + tree.getReferenceCountedAclCache().aclIndex = 0; + + Assert.assertEquals( + "expected to have 1 acl in acl cache map", 0, tree.aclCacheSize()); + + // serialize the data with one znode with acl + tree.createNode( + "/bug", new byte[20], ZooDefs.Ids.OPEN_ACL_UNSAFE, -1, 1, 1, 1); + + 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); + tree.deserialize(ia, "test"); + + Assert.assertEquals( + "expected to have 1 acl in acl cache map", 1, tree.aclCacheSize()); + Assert.assertEquals( + "expected to have the same acl", ZooDefs.Ids.OPEN_ACL_UNSAFE, + tree.getACL("/bug", new Stat())); + + // simulate the upgrading case where the config node will be created + // again after leader election + tree.addConfigNode(); + + Assert.assertEquals( + "expected to have 2 acl in acl cache map", 2, tree.aclCacheSize()); + Assert.assertEquals( + "expected to have the same acl", ZooDefs.Ids.OPEN_ACL_UNSAFE, + tree.getACL("/bug", new Stat())); } } diff --git a/src/java/test/org/apache/zookeeper/server/DatadirCleanupManagerTest.java b/src/java/test/org/apache/zookeeper/server/DatadirCleanupManagerTest.java index afb36ddaa35..9d93013e10c 100644 --- a/src/java/test/org/apache/zookeeper/server/DatadirCleanupManagerTest.java +++ b/src/java/test/org/apache/zookeeper/server/DatadirCleanupManagerTest.java @@ -24,14 +24,15 @@ import java.io.File; -import junit.framework.Assert; +import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.test.ClientBase; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; -public class DatadirCleanupManagerTest { +public class DatadirCleanupManagerTest extends ZKTestCase { private DatadirCleanupManager purgeMgr; private File snapDir; diff --git a/src/java/test/org/apache/zookeeper/server/Emulate353TTLTest.java b/src/java/test/org/apache/zookeeper/server/Emulate353TTLTest.java new file mode 100644 index 00000000000..c601a27d677 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/Emulate353TTLTest.java @@ -0,0 +1,95 @@ +/** + * 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.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicLong; + +public class Emulate353TTLTest extends ClientBase { + private TestableZooKeeper zk; + + @Override + public void setUp() throws Exception { + System.setProperty(EphemeralType.EXTENDED_TYPES_ENABLED_PROPERTY, "true"); + System.setProperty(EphemeralType.TTL_3_5_3_EMULATION_PROPERTY, "true"); + super.setUp(); + zk = createClient(); + } + + @Override + public void tearDown() throws Exception { + System.clearProperty(EphemeralType.EXTENDED_TYPES_ENABLED_PROPERTY); + System.clearProperty(EphemeralType.TTL_3_5_3_EMULATION_PROPERTY); + super.tearDown(); + zk.close(); + } + + @Test + public void testCreate() + throws KeeperException, InterruptedException { + Stat stat = new Stat(); + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, stat, 100); + Assert.assertEquals(0, stat.getEphemeralOwner()); + + final AtomicLong fakeElapsed = new AtomicLong(0); + ContainerManager containerManager = newContainerManager(fakeElapsed); + containerManager.checkContainers(); + Assert.assertNotNull("Ttl node should not have been deleted yet", zk.exists("/foo", false)); + + fakeElapsed.set(1000); + containerManager.checkContainers(); + Assert.assertNull("Ttl node should have been deleted", zk.exists("/foo", false)); + } + + @Test + public void test353TTL() + throws KeeperException, InterruptedException { + DataTree dataTree = serverFactory.zkServer.getZKDatabase().dataTree; + long ephemeralOwner = EphemeralTypeEmulate353.ttlToEphemeralOwner(100); + dataTree.createNode("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, ephemeralOwner, + dataTree.getNode("/").stat.getCversion()+1, 1, 1); + + final AtomicLong fakeElapsed = new AtomicLong(0); + ContainerManager containerManager = newContainerManager(fakeElapsed); + containerManager.checkContainers(); + Assert.assertNotNull("Ttl node should not have been deleted yet", zk.exists("/foo", false)); + + fakeElapsed.set(1000); + containerManager.checkContainers(); + Assert.assertNull("Ttl node should have been deleted", zk.exists("/foo", false)); + } + + private ContainerManager newContainerManager(final AtomicLong fakeElapsed) { + return new ContainerManager(serverFactory.getZooKeeperServer() + .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100) { + @Override + protected long getElapsed(DataNode node) { + return fakeElapsed.get(); + } + }; + } +} diff --git a/src/java/test/org/apache/zookeeper/server/EphemeralTypeTest.java b/src/java/test/org/apache/zookeeper/server/EphemeralTypeTest.java new file mode 100644 index 00000000000..40863f5eefe --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/EphemeralTypeTest.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 org.apache.zookeeper.CreateMode; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class EphemeralTypeTest { + @Before + public void setUp() { + System.setProperty(EphemeralType.EXTENDED_TYPES_ENABLED_PROPERTY, "true"); + } + + @After + public void tearDown() { + System.clearProperty(EphemeralType.EXTENDED_TYPES_ENABLED_PROPERTY); + } + + @Test + public void testTtls() { + long ttls[] = {100, 1, EphemeralType.TTL.maxValue()}; + for (long ttl : ttls) { + long ephemeralOwner = EphemeralType.TTL.toEphemeralOwner(ttl); + Assert.assertEquals(EphemeralType.TTL, EphemeralType.get(ephemeralOwner)); + Assert.assertEquals(ttl, EphemeralType.TTL.getValue(ephemeralOwner)); + } + + EphemeralType.validateTTL(CreateMode.PERSISTENT_WITH_TTL, 100); + EphemeralType.validateTTL(CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL, 100); + + try { + EphemeralType.validateTTL(CreateMode.EPHEMERAL, 100); + Assert.fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException dummy) { + // expected + } + } + + @Test + public void testContainerValue() { + Assert.assertEquals(Long.MIN_VALUE, EphemeralType.CONTAINER_EPHEMERAL_OWNER); + Assert.assertEquals(EphemeralType.CONTAINER, EphemeralType.get(EphemeralType.CONTAINER_EPHEMERAL_OWNER)); + } + + @Test + public void testNonSpecial() { + Assert.assertEquals(EphemeralType.VOID, EphemeralType.get(0)); + Assert.assertEquals(EphemeralType.NORMAL, EphemeralType.get(1)); + Assert.assertEquals(EphemeralType.NORMAL, EphemeralType.get(Long.MAX_VALUE)); + } + + @Test + public void testServerIds() { + for ( int i = 0; i < 255; ++i ) { + Assert.assertEquals(EphemeralType.NORMAL, EphemeralType.get(SessionTrackerImpl.initializeNextSession(i))); + } + try { + Assert.assertEquals(EphemeralType.TTL, EphemeralType.get(SessionTrackerImpl.initializeNextSession(255))); + Assert.fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + Assert.assertEquals(EphemeralType.NORMAL, EphemeralType.get(SessionTrackerImpl.initializeNextSession(EphemeralType.MAX_EXTENDED_SERVER_ID))); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/MockServerCnxn.java b/src/java/test/org/apache/zookeeper/server/MockServerCnxn.java new file mode 100644 index 00000000000..2e37272decb --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/MockServerCnxn.java @@ -0,0 +1,113 @@ +/** + * 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.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.security.cert.Certificate; +import org.apache.jute.Record; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.proto.ReplyHeader; + +public class MockServerCnxn extends ServerCnxn { + public Certificate[] clientChain; + public boolean secure; + + @Override + int getSessionTimeout() { + return 0; + } + + @Override + void close() { + } + + @Override + public void sendResponse(ReplyHeader h, Record r, String tag) + throws IOException { + } + + @Override + void sendCloseSession() { + } + + @Override + public void process(WatchedEvent event) { + } + + @Override + public long getSessionId() { + return 0; + } + + @Override + void setSessionId(long sessionId) { + } + + @Override + public boolean isSecure() { + return secure; + } + + @Override + public Certificate[] getClientCertificateChain() { + return clientChain; + } + + @Override + public void setClientCertificateChain(Certificate[] chain) { + clientChain = chain; + } + + @Override + void sendBuffer(ByteBuffer closeConn) { + } + + @Override + void enableRecv() { + } + + @Override + void disableRecv() { + } + + @Override + void setSessionTimeout(int sessionTimeout) { + } + + @Override + protected ServerStats serverStats() { + return null; + } + + @Override + public long getOutstandingRequests() { + return 0; + } + + @Override + public InetSocketAddress getRemoteSocketAddress() { + return null; + } + + @Override + public int getInterestOps() { + return 0; + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/server/MultiOpSessionUpgradeTest.java b/src/java/test/org/apache/zookeeper/server/MultiOpSessionUpgradeTest.java new file mode 100644 index 00000000000..0426e2293ef --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/MultiOpSessionUpgradeTest.java @@ -0,0 +1,136 @@ +/** + * 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.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.Op; +import org.apache.zookeeper.OpResult; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.proto.CreateRequest; +import org.apache.zookeeper.proto.GetDataRequest; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.QuorumZooKeeperServer; +import org.apache.zookeeper.server.quorum.UpgradeableSessionTracker; +import org.apache.zookeeper.test.QuorumBase; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MultiOpSessionUpgradeTest extends QuorumBase { + protected static final Logger LOG = LoggerFactory.getLogger(MultiOpSessionUpgradeTest.class); + + @Override + public void setUp() throws Exception { + localSessionsEnabled = true; + localSessionsUpgradingEnabled = true; + super.setUp(); + } + + @Test + public void ephemeralCreateMultiOpTest() throws KeeperException, InterruptedException, IOException { + final ZooKeeper zk = createClient(); + + String data = "test"; + String path = "/ephemeralcreatemultiop"; + zk.create(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + + QuorumZooKeeperServer server = getConnectedServer(zk.getSessionId()); + Assert.assertNotNull("unable to find server interlocutor", server); + UpgradeableSessionTracker sessionTracker = (UpgradeableSessionTracker)server.getSessionTracker(); + Assert.assertFalse("session already global", sessionTracker.isGlobalSession(zk.getSessionId())); + + List multi = null; + try { + multi = zk.multi(Arrays.asList( + Op.setData(path, data.getBytes(), 0), + Op.create(path + "/e", data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL), + Op.create(path + "/p", data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT), + Op.create(path + "/q", data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL) + )); + } catch (KeeperException.SessionExpiredException e) { + // the scenario that inspired this unit test + Assert.fail("received session expired for a session promotion in a multi-op"); + } + + Assert.assertNotNull(multi); + Assert.assertEquals(4, multi.size()); + Assert.assertEquals(data, new String(zk.getData(path + "/e", false, null))); + Assert.assertEquals(data, new String(zk.getData(path + "/p", false, null))); + Assert.assertEquals(data, new String(zk.getData(path + "/q", false, null))); + Assert.assertTrue("session not promoted", sessionTracker.isGlobalSession(zk.getSessionId())); + } + + @Test + public void directCheckUpgradeSessionTest() throws IOException, InterruptedException, KeeperException { + final ZooKeeper zk = createClient(); + + String path = "/directcheckupgradesession"; + zk.create(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + + QuorumZooKeeperServer server = getConnectedServer(zk.getSessionId()); + Assert.assertNotNull("unable to find server interlocutor", server); + + Request readRequest = makeGetDataRequest(path, zk.getSessionId()); + Request createRequest = makeCreateRequest(path + "/e", zk.getSessionId()); + Assert.assertNull("tried to upgrade on a read", server.checkUpgradeSession(readRequest)); + Assert.assertNotNull("failed to upgrade on a create", server.checkUpgradeSession(createRequest)); + Assert.assertNull("tried to upgrade after successful promotion", + server.checkUpgradeSession(createRequest)); + } + + private Request makeGetDataRequest(String path, long sessionId) throws IOException { + ByteArrayOutputStream boas = new ByteArrayOutputStream(); + BinaryOutputArchive boa = BinaryOutputArchive.getArchive(boas); + GetDataRequest getDataRequest = new GetDataRequest(path, false); + getDataRequest.serialize(boa, "request"); + ByteBuffer bb = ByteBuffer.wrap(boas.toByteArray()); + return new Request(null, sessionId, 1, ZooDefs.OpCode.getData, bb, new ArrayList()); + } + + private Request makeCreateRequest(String path, long sessionId) throws IOException { + ByteArrayOutputStream boas = new ByteArrayOutputStream(); + BinaryOutputArchive boa = BinaryOutputArchive.getArchive(boas); + CreateRequest createRequest = new CreateRequest(path, + "data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL.toFlag()); + createRequest.serialize(boa, "request"); + ByteBuffer bb = ByteBuffer.wrap(boas.toByteArray()); + return new Request(null, sessionId, 1, ZooDefs.OpCode.create2, bb, new ArrayList()); + } + + private QuorumZooKeeperServer getConnectedServer(long sessionId) { + for (QuorumPeer peer : getPeerList()) { + if (peer.getActiveServer().getSessionTracker().isTrackingSession(sessionId)) { + return (QuorumZooKeeperServer)peer.getActiveServer(); + } + } + return null; + } +} diff --git a/src/java/test/org/apache/zookeeper/server/NIOServerCnxnTest.java b/src/java/test/org/apache/zookeeper/server/NIOServerCnxnTest.java index 8bc916fa8ad..538c06a2a25 100644 --- a/src/java/test/org/apache/zookeeper/server/NIOServerCnxnTest.java +++ b/src/java/test/org/apache/zookeeper/server/NIOServerCnxnTest.java @@ -19,13 +19,13 @@ import java.io.IOException; -import junit.framework.Assert; 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; diff --git a/src/java/test/org/apache/zookeeper/server/NettyServerCnxnTest.java b/src/java/test/org/apache/zookeeper/server/NettyServerCnxnTest.java index 1eeca4329b8..7d1b854720d 100644 --- a/src/java/test/org/apache/zookeeper/server/NettyServerCnxnTest.java +++ b/src/java/test/org/apache/zookeeper/server/NettyServerCnxnTest.java @@ -18,18 +18,12 @@ 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.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,21 +57,6 @@ public void testSendCloseSession() throws Exception { "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 { @@ -93,9 +72,14 @@ public void channelDisconnected(ChannelHandlerContext ctx, 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()); + int timeout = 0; + while (serverFactory.getNumAliveConnections() != 0) { + Thread.sleep(1000); + timeout += 1000; + if (timeout > CONNECTION_TIMEOUT) { + Assert.fail("The number of live connections should be 0"); + } + } } 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 index 8caf419bcfc..8223583b1fb 100644 --- a/src/java/test/org/apache/zookeeper/server/PrepRequestProcessorTest.java +++ b/src/java/test/org/apache/zookeeper/server/PrepRequestProcessorTest.java @@ -18,63 +18,192 @@ package org.apache.zookeeper.server; -import static org.junit.Assert.*; +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.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; - -import org.apache.zookeeper.PortAssignment; -import org.apache.zookeeper.KeeperException.Code; -import org.apache.zookeeper.KeeperException.SessionExpiredException; -import org.apache.zookeeper.KeeperException.SessionMovedException; -import org.apache.zookeeper.ZooDefs.OpCode; -import org.apache.zookeeper.server.PrepRequestProcessor; -import org.apache.zookeeper.server.Request; -import org.apache.zookeeper.server.RequestProcessor; -import org.apache.zookeeper.server.ServerCnxnFactory; -import org.apache.zookeeper.server.SyncRequestProcessor; -import org.apache.zookeeper.server.ZooKeeperServer; -import org.apache.zookeeper.test.ClientBase; -import org.apache.zookeeper.txn.ErrorTxn; -import org.junit.Assert; -import org.junit.Test; +import java.util.concurrent.TimeUnit; public class PrepRequestProcessorTest extends ClientBase { - private static String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); + private static final Logger LOG = LoggerFactory.getLogger(PrepRequestProcessorTest.class); private static final int CONNECTION_TIMEOUT = 3000; - private final CountDownLatch testEnd = new CountDownLatch(1); + private static String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); + private CountDownLatch pLatch; - @Test - public void testPRequest() throws Exception { + private ZooKeeperServer zks; + private ServerCnxnFactory servcnxnf; + private PrepRequestProcessor processor; + private Request outcome; + + @Before + public void setup() throws Exception { File tmpDir = ClientBase.createTmpDir(); ClientBase.setupTestEnv(); - ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); + 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); + + servcnxnf = ServerCnxnFactory.createFactory(PORT, -1); + servcnxnf.startup(zks); Assert.assertTrue("waiting for server being up ", - ClientBase.waitForServerUp(HOSTPORT,CONNECTION_TIMEOUT)); - zks.sessionTracker = new MySessionTracker(); - PrepRequestProcessor processor = new PrepRequestProcessor(zks, new MyRequestProcessor()); + 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); - testEnd.await(5, java.util.concurrent.TimeUnit.SECONDS); - f.shutdown(); - zks.shutdown(); + + Assert.assertEquals("Request should have marshalling error", new ErrorTxn(KeeperException.Code.MARSHALLINGERROR.intValue()), + outcome.getTxn()); + 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.getHdr().getType(), OpCode.error); + Assert.assertEquals(outcome.getException().code(), KeeperException.Code.BADARGUMENTS); } - private class MyRequestProcessor implements RequestProcessor { @Override public void processRequest(Request request) { - Assert.assertEquals("Request should have marshalling error", new ErrorTxn(Code.MARSHALLINGERROR.intValue()), request.getTxn()); - testEnd.countDown(); + // getting called by PrepRequestProcessor + outcome = request; + pLatch.countDown(); } @Override public void shutdown() { diff --git a/src/java/test/org/apache/zookeeper/server/PurgeTxnTest.java b/src/java/test/org/apache/zookeeper/server/PurgeTxnTest.java index 4a685ac93b0..7898e6eaafe 100644 --- a/src/java/test/org/apache/zookeeper/server/PurgeTxnTest.java +++ b/src/java/test/org/apache/zookeeper/server/PurgeTxnTest.java @@ -18,6 +18,8 @@ package org.apache.zookeeper.server; +import static org.junit.Assert.assertEquals; + import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -27,11 +29,10 @@ 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; @@ -40,6 +41,7 @@ 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; @@ -47,7 +49,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class PurgeTxnTest extends ZKTestCase implements Watcher { +public class PurgeTxnTest extends ZKTestCase { 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; @@ -76,7 +78,7 @@ public void testPurge() 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); + ZooKeeper zk = ClientBase.createZKClient(HOSTPORT); try { for (int i = 0; i< 2000; i++) { zk.create("/invalidsnap-" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, @@ -123,7 +125,7 @@ public void testPurgeWhenLogRollingInProgress() throws Exception { 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 ZooKeeper zk = ClientBase.createZKClient(HOSTPORT); final CountDownLatch doPurge = new CountDownLatch(1); final CountDownLatch purgeFinished = new CountDownLatch(1); final AtomicBoolean opFailed = new AtomicBoolean(false); @@ -172,10 +174,17 @@ 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++) { @@ -194,14 +203,25 @@ public void testFindNRecentSnapshots() throws Exception { } } - FileTxnSnapLog txnLog = new FileTxnSnapLog(tmpDir, tmpDir); + // 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); - txnLog.close(); 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(); } /** @@ -225,14 +245,21 @@ public void testSnapFilesGreaterThanToRetain() throws Exception { List logs = new ArrayList(); List snapsAboveRecentFiles = new ArrayList(); List logsAboveRecentFiles = new ArrayList(); - createDataDirFiles(offset, fileToPurgeCount, version2, snapsToPurge, + createDataDirFiles(offset, fileToPurgeCount, false, version2, snapsToPurge, logsToPurge); - createDataDirFiles(offset, nRecentCount, version2, snaps, logs); - createDataDirFiles(offset, fileAboveRecentCount, version2, + 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.retainNRecentSnapshots(txnLog, snaps); + PurgeTxnLog.purgeOlderSnapshots(txnLog, snaps.get(snaps.size() - 1)); txnLog.close(); verifyFilesAfterPurge(snapsToPurge, false); verifyFilesAfterPurge(logsToPurge, false); @@ -243,11 +270,24 @@ public void testSnapFilesGreaterThanToRetain() throws Exception { } /** - * Tests purge where the data directory contains snap files equals to the + * 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(); @@ -256,10 +296,10 @@ public void testSnapFilesEqualsToRetain() throws Exception { version2.mkdir()); List snaps = new ArrayList(); List logs = new ArrayList(); - createDataDirFiles(offset, nRecentCount, version2, snaps, logs); + createDataDirFiles(offset, nRecentCount, testWithPrecedingLogFile, version2, snaps, logs); FileTxnSnapLog txnLog = new FileTxnSnapLog(tmpDir, tmpDir); - PurgeTxnLog.retainNRecentSnapshots(txnLog, snaps); + PurgeTxnLog.purgeOlderSnapshots(txnLog, snaps.get(snaps.size() - 1)); txnLog.close(); verifyFilesAfterPurge(snaps, true); verifyFilesAfterPurge(logs, true); @@ -282,12 +322,19 @@ public void testSnapFilesLessThanToRetain() throws Exception { List logsToPurge = new ArrayList(); List snaps = new ArrayList(); List logs = new ArrayList(); - createDataDirFiles(offset, fileToPurgeCount, version2, snapsToPurge, + createDataDirFiles(offset, fileToPurgeCount, false, version2, snapsToPurge, logsToPurge); - createDataDirFiles(offset, nRecentCount, version2, snaps, logs); + 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.retainNRecentSnapshots(txnLog, snaps); + PurgeTxnLog.purgeOlderSnapshots(txnLog, snaps.get(snaps.size() - 1)); txnLog.close(); verifyFilesAfterPurge(snapsToPurge, false); verifyFilesAfterPurge(logsToPurge, false); @@ -295,17 +342,198 @@ public void testSnapFilesLessThanToRetain() throws Exception { verifyFilesAfterPurge(logs, true); } - private void createDataDirFiles(AtomicInteger offset, int limit, + /** + * 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 - File logFile = new File(version_2 + "/log." + Long.toHexString(--counter)); - Assert.assertTrue("Failed to create log File:" + logFile.toString(), - logFile.createNewFile()); - logs.add(logFile); + logs.add(createDataDirLogFile(version_2, --counter)); // simulate snapshot file File snapFile = new File(version_2 + "/snapshot." + Long.toHexString(--counter)); @@ -313,6 +541,9 @@ private void createDataDirFiles(AtomicInteger offset, int limit, snapFile.createNewFile()); snaps.add(snapFile); } + if (createPrecedingLogFile) { + logs.add(createDataDirLogFile(version_2, --counter)); + } } private void verifyFilesAfterPurge(List logs, boolean exists) { @@ -339,7 +570,7 @@ public void run() { zk.create(mynode, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } catch (Exception e) { - LOG.error("Unexpected exception occured!", e); + LOG.error("Unexpected exception occurred!", e); } if (i == 200) { doPurge.countDown(); @@ -358,14 +589,9 @@ public void run() { Assert.assertTrue("ZkClient ops is not finished!", finished.await(OP_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS)); } catch (InterruptedException ie) { - LOG.error("Unexpected exception occured!", ie); - Assert.fail("Unexpected exception occured!"); + 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..795472f1429 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/ReferenceCountedACLCacheTest.java @@ -0,0 +1,287 @@ +/** + * 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.jute.InputArchive; +import org.apache.jute.OutputArchive; +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); + } + + @Test + public void testNPEInDeserialize() throws IOException { + ReferenceCountedACLCache serializeCache = new ReferenceCountedACLCache(){ + @Override + public synchronized void serialize(OutputArchive oa) throws IOException { + oa.writeInt(1, "map"); + oa.writeLong(1, "long"); + oa.startVector(null, "acls"); + oa.endVector(null, "acls"); + } + }; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive archive = BinaryOutputArchive.getArchive(baos); + serializeCache.serialize(archive); + BinaryInputArchive inArchive = BinaryInputArchive.getArchive(new ByteArrayInputStream(baos.toByteArray())); + ReferenceCountedACLCache deserializedCache = new ReferenceCountedACLCache(); + try { + deserializedCache.deserialize(inArchive); + } catch (NullPointerException e){ + fail("should not throw NPE while do deserialized"); + } catch (RuntimeException e) { + // do nothing. + } + } + + + 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/ServerIdTest.java b/src/java/test/org/apache/zookeeper/server/ServerIdTest.java new file mode 100644 index 00000000000..a04c37b3533 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/ServerIdTest.java @@ -0,0 +1,119 @@ +/** + * 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.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.ZKParameterized; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.test.ClientBase; +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 java.util.ArrayList; +import java.util.List; + +@RunWith(Parameterized.class) +@Parameterized.UseParametersRunnerFactory(ZKParameterized.RunnerFactory.class) +public class ServerIdTest extends ClientBase { + private final TestType testType; + + private static class TestType { + final boolean ttlsEnabled; + final int serverId; + + TestType(boolean ttlsEnabled, int serverId) { + this.ttlsEnabled = ttlsEnabled; + this.serverId = serverId; + } + } + + @Parameterized.Parameters + public static List data() { + List testTypes = new ArrayList<>(); + for ( boolean ttlsEnabled : new boolean[]{true, false} ) { + for ( int serverId = 0; serverId <= 255; ++serverId ) { + testTypes.add(new TestType(ttlsEnabled, serverId)); + } + } + return testTypes; + } + + @After + @Override + public void tearDown() throws Exception { + super.tearDown(); + System.clearProperty("zookeeper.extendedTypesEnabled"); + } + + public ServerIdTest(TestType testType) { + this.testType = testType; + } + + @Before + @Override + public void setUp() throws Exception { + System.setProperty("zookeeper.extendedTypesEnabled", Boolean.toString(testType.ttlsEnabled)); + LOG.info("ttlsEnabled: {} - ServerId: {}", testType.ttlsEnabled, testType.serverId); + try { + super.setUpWithServerId(testType.serverId); + } catch (RuntimeException e) { + if ( testType.ttlsEnabled && (testType.serverId >= EphemeralType.MAX_EXTENDED_SERVER_ID) ) { + return; // expected + } + throw e; + } + } + + @Test + public void doTest() throws Exception { + if ( testType.ttlsEnabled && (testType.serverId >= EphemeralType.MAX_EXTENDED_SERVER_ID) ) { + return; + } + + TestableZooKeeper zk = null; + try { + zk = createClient(); + + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.delete("/foo", -1); + + if ( testType.ttlsEnabled ) { + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, new Stat(), 1000); // should work + } else { + try { + zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_WITH_TTL, new Stat(), 1000); + Assert.fail("Should have thrown KeeperException.UnimplementedException"); + } catch (KeeperException.UnimplementedException e) { + // expected + } + } + } finally { + if ( zk != null ) { + zk.close(); + } + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/SessionTrackerTest.java b/src/java/test/org/apache/zookeeper/server/SessionTrackerTest.java index 61072e677d9..00e34faad54 100644 --- a/src/java/test/org/apache/zookeeper/server/SessionTrackerTest.java +++ b/src/java/test/org/apache/zookeeper/server/SessionTrackerTest.java @@ -23,13 +23,13 @@ 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.Assert; import org.junit.Test; /** @@ -143,8 +143,8 @@ public FirstProcessor(ZooKeeperServer zks, public void processRequest(Request request) { // check session close request if (request.type == OpCode.closeSession) { - latch.countDown(); countOfCloseSessionReq++; + latch.countDown(); } } diff --git a/src/java/test/org/apache/zookeeper/server/WatchesPathReportTest.java b/src/java/test/org/apache/zookeeper/server/WatchesPathReportTest.java index 3953c8c6677..c0b107debba 100644 --- a/src/java/test/org/apache/zookeeper/server/WatchesPathReportTest.java +++ b/src/java/test/org/apache/zookeeper/server/WatchesPathReportTest.java @@ -20,11 +20,12 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import org.apache.zookeeper.ZKTestCase; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; -public class WatchesPathReportTest { +public class WatchesPathReportTest extends ZKTestCase { private Map> m; private WatchesPathReport r; @Before public void setUp() { diff --git a/src/java/test/org/apache/zookeeper/server/WatchesReportTest.java b/src/java/test/org/apache/zookeeper/server/WatchesReportTest.java index c6221548d18..7f0343b329a 100644 --- a/src/java/test/org/apache/zookeeper/server/WatchesReportTest.java +++ b/src/java/test/org/apache/zookeeper/server/WatchesReportTest.java @@ -20,11 +20,12 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import org.apache.zookeeper.ZKTestCase; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; -public class WatchesReportTest { +public class WatchesReportTest extends ZKTestCase { private Map> m; private WatchesReport r; @Before public void setUp() { diff --git a/src/java/test/org/apache/zookeeper/server/WatchesSummaryTest.java b/src/java/test/org/apache/zookeeper/server/WatchesSummaryTest.java index d04a22e920a..d679065c5bc 100644 --- a/src/java/test/org/apache/zookeeper/server/WatchesSummaryTest.java +++ b/src/java/test/org/apache/zookeeper/server/WatchesSummaryTest.java @@ -17,11 +17,12 @@ package org.apache.zookeeper.server; import java.util.Map; +import org.apache.zookeeper.ZKTestCase; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; -public class WatchesSummaryTest { +public class WatchesSummaryTest extends ZKTestCase { private WatchesSummary s; @Before public void setUp() { s = new WatchesSummary(1, 2, 3); diff --git a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerBeanTest.java b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerBeanTest.java new file mode 100644 index 00000000000..08adfdc9fa0 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerBeanTest.java @@ -0,0 +1,144 @@ +/** + * 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 static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.jute.Record; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.txn.SetDataTxn; +import org.apache.zookeeper.txn.TxnHeader; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ZooKeeperServerBeanTest { + @Before + public void setup() { + System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, + "org.apache.zookeeper.server.NettyServerCnxnFactory"); + } + + @After + public void teardown() throws Exception { + System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + } + + @Test + public void testTxnLogElapsedSyncTime() throws IOException { + + File tmpDir = ClientBase.createTmpDir(); + FileTxnSnapLog fileTxnSnapLog = new FileTxnSnapLog(new File(tmpDir, "data"), + new File(tmpDir, "data_txnlog")); + + ZooKeeperServer zks = new ZooKeeperServer(); + zks.setTxnLogFactory(fileTxnSnapLog); + + ZooKeeperServerBean serverBean = new ZooKeeperServerBean(zks); + long elapsedTime = serverBean.getTxnLogElapsedSyncTime(); + assertEquals(-1, elapsedTime); + + TxnHeader hdr = new TxnHeader(1, 1, 1, 1, ZooDefs.OpCode.setData); + Record txn = new SetDataTxn("/foo", new byte[0], 1); + Request req = new Request(0, 0, 0, hdr, txn, 0); + + try { + + zks.getTxnLogFactory().append(req); + zks.getTxnLogFactory().commit(); + elapsedTime = serverBean.getTxnLogElapsedSyncTime(); + + assertNotEquals(-1, elapsedTime); + + assertEquals(elapsedTime, serverBean.getTxnLogElapsedSyncTime()); + + } finally { + fileTxnSnapLog.close(); + } + } + + @Test + public void testGetSecureClientPort() throws IOException { + ZooKeeperServer zks = new ZooKeeperServer(); + /** + * case 1: When secure client is not configured GetSecureClientPort + * should return empty string + */ + ZooKeeperServerBean serverBean = new ZooKeeperServerBean(zks); + String result = serverBean.getSecureClientPort(); + assertEquals("", result); + + /** + * case 2: When secure client is configured GetSecureClientPort should + * return configured port + */ + + ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory(); + int secureClientPort = 8443; + InetSocketAddress address = new InetSocketAddress(secureClientPort); + cnxnFactory.configure(address, 5, true); + zks.setSecureServerCnxnFactory(cnxnFactory); + + result = serverBean.getSecureClientPort(); + assertEquals(Integer.toString(secureClientPort), result); + + // cleanup + cnxnFactory.shutdown(); + + } + + @Test + public void testGetSecureClientAddress() throws IOException { + ZooKeeperServer zks = new ZooKeeperServer(); + /** + * case 1: When secure client is not configured getSecureClientAddress + * should return empty string + */ + ZooKeeperServerBean serverBean = new ZooKeeperServerBean(zks); + String result = serverBean.getSecureClientPort(); + assertEquals("", result); + + /** + * case 2: When secure client is configured getSecureClientAddress + * should return configured SecureClientAddress + */ + + ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory(); + int secureClientPort = 8443; + InetSocketAddress address = new InetSocketAddress(secureClientPort); + cnxnFactory.configure(address, 5, true); + zks.setSecureServerCnxnFactory(cnxnFactory); + + result = serverBean.getSecureClientAddress(); + String ipv4 = "0.0.0.0:" + secureClientPort; + String ipv6 = "0:0:0:0:0:0:0:0:" + secureClientPort; + assertTrue(result.equals(ipv4) || result.equals(ipv6)); + + // cleanup + cnxnFactory.shutdown(); + } + +} diff --git a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerConfTest.java b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerConfTest.java index 7e4e575c078..b53321a950e 100644 --- a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerConfTest.java +++ b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerConfTest.java @@ -17,11 +17,12 @@ package org.apache.zookeeper.server; import java.util.Map; +import org.apache.zookeeper.ZKTestCase; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; -public class ZooKeeperServerConfTest { +public class ZooKeeperServerConfTest extends ZKTestCase { private ZooKeeperServerConf c; @Before public void setUp() { c = new ZooKeeperServerConf(1, "a", "b", 2, 3, 4, 5, 6L); diff --git a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerMainTest.java b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerMainTest.java index 3c15bc3f5e3..01b65606f5e 100644 --- a/src/java/test/org/apache/zookeeper/server/ZooKeeperServerMainTest.java +++ b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerMainTest.java @@ -19,6 +19,7 @@ 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; @@ -58,11 +59,18 @@ 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, String configs) throws IOException { + this(clientPort, preCreateDirs, ClientBase.createTmpDir(), configs); + } + + public MainThread(int clientPort, boolean preCreateDirs, File tmpDir, String configs) + throws IOException { super("Standalone server with clientPort:" + clientPort); - tmpDir = ClientBase.createTmpDir(); + this.tmpDir = tmpDir; confFile = new File(tmpDir, "zoo.cfg"); FileWriter fwriter = new FileWriter(confFile); @@ -73,20 +81,21 @@ public MainThread(int clientPort, boolean preCreateDirs, String configs) fwriter.write(configs); } - File dataDir = new File(tmpDir, "data"); - String dir = dataDir.toString(); - String dirLog = dataDir.toString() + "_txnlog"; + dataDir = new File(this.tmpDir, "data"); + logDir = new File(dataDir.toString() + "_txnlog"); if (preCreateDirs) { if (!dataDir.mkdir()) { throw new IOException("unable to mkdir " + dataDir); } - dirLog = dataDir.toString(); + if (!logDir.mkdir()) { + throw new IOException("unable to mkdir " + logDir); + } } - dir = PathUtils.normalizeFileSystemPath(dir); - dirLog = PathUtils.normalizeFileSystemPath(dirLog); - fwriter.write("dataDir=" + dir + "\n"); - fwriter.write("dataLogDir=" + dirLog + "\n"); + String normalizedDataDir = PathUtils.normalizeFileSystemPath(dataDir.toString()); + String normalizedLogDir = PathUtils.normalizeFileSystemPath(logDir.toString()); + fwriter.write("dataDir=" + normalizedDataDir + "\n"); + fwriter.write("dataLogDir=" + normalizedLogDir + "\n"); fwriter.write("clientPort=" + clientPort + "\n"); fwriter.flush(); fwriter.close(); @@ -124,6 +133,10 @@ void delete(File f) throws IOException { throw new IOException("Failed to delete file: " + f); } } + + ServerCnxnFactory getCnxnFactory() { + return main.getCnxnFactory(); + } } public static class TestZKSMain extends ZooKeeperServerMain { @@ -132,6 +145,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), ZooKeeper is still available. + */ + @Test(timeout = 30000) + public void testNonRecoverableError() throws Exception { + ClientBase.setupTestEnv(); + + final int CLIENT_PORT = PortAssignment.unique(); + + MainThread main = new MainThread(CLIENT_PORT, true, null); + 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, null); + 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, null); + 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, null); + 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, null); + 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. */ @@ -148,9 +298,11 @@ public void testStandalone() throws Exception { ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT, CONNECTION_TIMEOUT)); - + clientConnected = new CountDownLatch(1); ZooKeeper zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT, ClientBase.CONNECTION_TIMEOUT, this); + Assert.assertTrue("Failed to establish zkclient connection!", + clientConnected.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)); zk.create("/foo", "foobar".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); @@ -210,9 +362,11 @@ public void testWithAutoCreateDataLogDir() throws Exception { Assert.assertTrue("waiting for server being up", ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT, CONNECTION_TIMEOUT)); - + clientConnected = new CountDownLatch(1); ZooKeeper zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT, ClientBase.CONNECTION_TIMEOUT, this); + Assert.assertTrue("Failed to establish zkclient connection!", + clientConnected.await(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)); zk.create("/foo", "foobar".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); @@ -334,6 +488,77 @@ private void verifySessionTimeOut(int sessionTimeout, zk.close(); } + @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); + } + } + } + + 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) { if (event.getState() == KeeperState.SyncConnected) { clientConnected.countDown(); 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..c78a9a0cdea --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/ZooKeeperServerStartupTest.java @@ -0,0 +1,270 @@ +/** + * 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 static org.apache.zookeeper.server.command.AbstractFourLetterCommand.ZK_NOT_SERVING; + +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.common.X509Exception.SSLContextException; +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 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(); + } + 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); + return; + } + 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 testFourLetterWords() throws Exception { + startSimpleZKServer(startupDelayLatch); + verify("conf", ZK_NOT_SERVING); + verify("crst", ZK_NOT_SERVING); + verify("cons", ZK_NOT_SERVING); + verify("dirs", ZK_NOT_SERVING); + verify("dump", ZK_NOT_SERVING); + verify("mntr", ZK_NOT_SERVING); + verify("stat", ZK_NOT_SERVING); + verify("srst", ZK_NOT_SERVING); + verify("wchp", ZK_NOT_SERVING); + verify("wchc", ZK_NOT_SERVING); + verify("wchs", ZK_NOT_SERVING); + verify("isro", "null"); + } + + private void verify(String cmd, String expected) + throws IOException, SSLContextException { + String resp = sendRequest(cmd); + LOG.info("cmd " + cmd + " expected " + expected + " got " + resp); + Assert.assertTrue("Unexpected response", resp.contains(expected)); + } + + private String sendRequest(String cmd) + throws IOException, SSLContextException { + 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); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/ZooKeeperThreadTest.java b/src/java/test/org/apache/zookeeper/server/ZooKeeperThreadTest.java index 78f8b30acc9..2c737fc55ee 100644 --- a/src/java/test/org/apache/zookeeper/server/ZooKeeperThreadTest.java +++ b/src/java/test/org/apache/zookeeper/server/ZooKeeperThreadTest.java @@ -20,11 +20,12 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import junit.framework.Assert; +import org.apache.zookeeper.ZKTestCase; +import org.junit.Assert; import org.junit.Test; -public class ZooKeeperThreadTest { +public class ZooKeeperThreadTest extends ZKTestCase { private CountDownLatch runningLatch = new CountDownLatch(1); public class MyThread extends ZooKeeperThread { @@ -46,7 +47,13 @@ protected void handleException(String thName, Throwable e) { public class MyCriticalThread extends ZooKeeperCriticalThread { public MyCriticalThread(String threadName) { - super(threadName); + super(threadName, new ZooKeeperServerListener() { + + @Override + public void notifyStopping(String threadName, int erroCode) { + + } + }); } public void run() { diff --git a/src/java/test/org/apache/zookeeper/server/ZxidRolloverTest.java b/src/java/test/org/apache/zookeeper/server/ZxidRolloverTest.java index 281f9eb04e2..838b0a80954 100644 --- a/src/java/test/org/apache/zookeeper/server/ZxidRolloverTest.java +++ b/src/java/test/org/apache/zookeeper/server/ZxidRolloverTest.java @@ -18,13 +18,11 @@ 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.ZKTestCase; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.test.ClientBase; @@ -32,14 +30,18 @@ import org.apache.zookeeper.test.ClientTest; import org.apache.zookeeper.test.QuorumUtil; import org.apache.zookeeper.test.QuorumUtil.PeerStruct; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Verify ZOOKEEPER-1277 - ensure that we handle epoch rollover correctly. */ -public class ZxidRolloverTest extends TestCase { - private static final Logger LOG = Logger.getLogger(ZxidRolloverTest.class); +public class ZxidRolloverTest extends ZKTestCase { + private static final Logger LOG = LoggerFactory.getLogger(ZxidRolloverTest.class); private QuorumUtil qu; private ZooKeeperServer zksLeader; @@ -52,9 +54,9 @@ private ZooKeeper getClient(int idx) { return zkClients[idx-1]; } - @Override - protected void setUp() throws Exception { - LOG.info("STARTING " + getName()); + @Before + public void setUp() throws Exception { + System.setProperty("zookeeper.admin.enableServer", "false"); // set the snap count to something low so that we force log rollover // and verify that is working as part of the epoch rollover. @@ -100,7 +102,7 @@ private void checkClientConnected(int idx) throws Exception { return; } try { - assertNull(zk.exists("/foofoofoo-connected", false)); + Assert.assertNull(zk.exists("/foofoofoo-connected", false)); } catch (ConnectionLossException e) { // second chance... // in some cases, leader change in particular, the timing is @@ -114,7 +116,7 @@ private void checkClientConnected(int idx) throws Exception { Assert.assertTrue("Waiting for server down", ClientBase.waitForServerUp( "127.0.0.1:" + peer.clientPort, ClientBase.CONNECTION_TIMEOUT)); - assertNull(zk.exists("/foofoofoo-connected", false)); + Assert.assertNull(zk.exists("/foofoofoo-connected", false)); } } @@ -138,8 +140,8 @@ private void checkClientDisconnected(int idx) throws Exception { return; } try { - assertNull(zk.exists("/foofoofoo-disconnected", false)); - fail("expected client to be disconnected"); + Assert.assertNull(zk.exists("/foofoofoo-disconnected", false)); + Assert.fail("expected client to be disconnected"); } catch (KeeperException e) { // success } @@ -194,7 +196,7 @@ private void shutdown(int idx) throws Exception { checkClientDisconnected(idx); try { checkClientsDisconnected(); - } catch (AssertionFailedError e) { + } catch (AssertionError e) { // the clients may or may not have already reconnected // to the recovered cluster, force a check, but ignore } @@ -208,8 +210,8 @@ private void adjustEpochNearEnd() { zksLeader.setZxid((zksLeader.getZxid() & 0xffffffff00000000L) | 0xfffffffcL); } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { LOG.info("tearDown starting"); for (int i = 0; i < zkClients.length; i++) { zkClients[i].close(); @@ -222,7 +224,7 @@ protected void tearDown() throws Exception { * 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)); + LOG.info("Creating nodes {} thru {}", start, (start + count)); int j = 0; try { for (int i = start; i < start + count; i++) { @@ -241,12 +243,12 @@ private int createNodes(ZooKeeper zk, int start, int count) throws Exception { * 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)); + LOG.info("Validating nodes {} thru {}", start, (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())); + Assert.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)); + Assert.assertNull(zk.exists("/foo" + (start + count), false)); } /** @@ -304,8 +306,8 @@ public void testRolloverThenRestart() throws Exception { countCreated += createNodes(zk, countCreated, 10); // sanity check - assertTrue(countCreated > 0); - assertTrue(countCreated < 60); + Assert.assertTrue(countCreated > 0); + Assert.assertTrue(countCreated < 60); } /** @@ -346,8 +348,8 @@ public void testRolloverThenFollowerRestart() throws Exception { countCreated += createNodes(zk, countCreated, 10); // sanity check - assertTrue(countCreated > 0); - assertTrue(countCreated < 60); + Assert.assertTrue(countCreated > 0); + Assert.assertTrue(countCreated < 60); } /** @@ -391,8 +393,8 @@ public void testRolloverThenLeaderRestart() throws Exception { countCreated += createNodes(zk, countCreated, 10); // sanity check - assertTrue(countCreated > 0); - assertTrue(countCreated < 50); + Assert.assertTrue(countCreated > 0); + Assert.assertTrue(countCreated < 50); } /** @@ -438,7 +440,7 @@ public void testMultipleRollover() throws Exception { countCreated += createNodes(zk, countCreated, 10); // sanity check - assertTrue(countCreated > 0); - assertTrue(countCreated < 70); + Assert.assertTrue(countCreated > 0); + Assert.assertTrue(countCreated < 70); } } diff --git a/src/java/test/org/apache/zookeeper/server/admin/CommandResponseTest.java b/src/java/test/org/apache/zookeeper/server/admin/CommandResponseTest.java index 2e1c708785a..ab8c25860a1 100644 --- a/src/java/test/org/apache/zookeeper/server/admin/CommandResponseTest.java +++ b/src/java/test/org/apache/zookeeper/server/admin/CommandResponseTest.java @@ -19,11 +19,12 @@ import java.util.HashMap; import java.util.Map; +import org.apache.zookeeper.ZKTestCase; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; -public class CommandResponseTest { +public class CommandResponseTest extends ZKTestCase { private CommandResponse r; @Before public void setUp() throws Exception { diff --git a/src/java/test/org/apache/zookeeper/server/persistence/FileTxnLogTest.java b/src/java/test/org/apache/zookeeper/server/persistence/FileTxnLogTest.java new file mode 100644 index 00000000000..32001aed50d --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/persistence/FileTxnLogTest.java @@ -0,0 +1,111 @@ +/** + * 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.persistence; + +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.txn.CreateTxn; +import org.apache.zookeeper.txn.TxnHeader; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; + +public class FileTxnLogTest extends ZKTestCase { + protected static final Logger LOG = LoggerFactory.getLogger(FileTxnLogTest.class); + + private static final int KB = 1024; + + @Test + public void testInvalidPreallocSize() { + Assert.assertEquals("file should not be padded", + 10 * KB, FilePadding.calculateFileSizeWithPadding(7 * KB, 10 * KB, 0)); + Assert.assertEquals("file should not be padded", + 10 * KB, FilePadding.calculateFileSizeWithPadding(7 * KB, 10 * KB, -1)); + } + + @Test + public void testCalculateFileSizeWithPaddingWhenNotToCurrentSize() { + Assert.assertEquals("file should not be padded", + 10 * KB, FilePadding.calculateFileSizeWithPadding(5 * KB, 10 * KB, 10 * KB)); + } + + @Test + public void testCalculateFileSizeWithPaddingWhenCloseToCurrentSize() { + Assert.assertEquals("file should be padded an additional 10 KB", + 20 * KB, FilePadding.calculateFileSizeWithPadding(7 * KB, 10 * KB, 10 * KB)); + } + + @Test + public void testFileSizeGreaterThanPosition() { + Assert.assertEquals("file should be padded to 40 KB", + 40 * KB, FilePadding.calculateFileSizeWithPadding(31 * KB, 10 * KB, 10 * KB)); + } + + @Test + public void testPreAllocSizeSmallerThanTxnData() throws IOException { + File logDir = ClientBase.createTmpDir(); + FileTxnLog fileTxnLog = new FileTxnLog(logDir); + + // Set a small preAllocSize (.5 MB) + final int preAllocSize = 500 * KB; + FilePadding.setPreallocSize(preAllocSize); + + // Create dummy txn larger than preAllocSize + // Since the file padding inserts a 0, we will fill the data with 0xff to ensure we corrupt the data if we put the 0 in the data + byte[] data = new byte[2 * preAllocSize]; + Arrays.fill(data, (byte) 0xff); + + // Append and commit 2 transactions to the log + // Prior to ZOOKEEPER-2249, attempting to pad in association with the second transaction will corrupt the first + fileTxnLog.append(new TxnHeader(1, 1, 1, 1, ZooDefs.OpCode.create), + new CreateTxn("/testPreAllocSizeSmallerThanTxnData1", data, ZooDefs.Ids.OPEN_ACL_UNSAFE, false, 0)); + fileTxnLog.commit(); + fileTxnLog.append(new TxnHeader(1, 1, 2, 2, ZooDefs.OpCode.create), + new CreateTxn("/testPreAllocSizeSmallerThanTxnData2", new byte[]{}, ZooDefs.Ids.OPEN_ACL_UNSAFE, false, 0)); + fileTxnLog.commit(); + fileTxnLog.close(); + + // Read the log back from disk, this will throw a java.io.IOException: CRC check failed prior to ZOOKEEPER-2249 + FileTxnLog.FileTxnIterator fileTxnIterator = new FileTxnLog.FileTxnIterator(logDir, 0); + + // Verify the data in the first transaction + CreateTxn createTxn = (CreateTxn) fileTxnIterator.getTxn(); + Assert.assertTrue(Arrays.equals(createTxn.getData(), data)); + + // Verify the data in the second transaction + fileTxnIterator.next(); + createTxn = (CreateTxn) fileTxnIterator.getTxn(); + Assert.assertTrue(Arrays.equals(createTxn.getData(), new byte[]{})); + } + + @Test + public void testSetPreallocSize() { + long customPreallocSize = 10101; + FileTxnLog.setPreallocSize(customPreallocSize); + Assert.assertThat(FilePadding.getPreAllocSize(), is(equalTo(customPreallocSize))); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/persistence/FileTxnSnapLogTest.java b/src/java/test/org/apache/zookeeper/server/persistence/FileTxnSnapLogTest.java new file mode 100644 index 00000000000..9334d93e501 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/persistence/FileTxnSnapLogTest.java @@ -0,0 +1,224 @@ +/** + * 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.persistence; + +import org.apache.jute.Record; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.TestUtils; +import org.apache.zookeeper.txn.SetDataTxn; +import org.apache.zookeeper.txn.TxnHeader; +import org.junit.After; +import org.junit.Before; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +public class FileTxnSnapLogTest { + + private File tmpDir; + + private File logDir; + + private File snapDir; + + private File logVersionDir; + + private File snapVersionDir; + + @Before + public void setUp() throws Exception { + tmpDir = ClientBase.createEmptyTestDir(); + logDir = new File(tmpDir, "logdir"); + snapDir = new File(tmpDir, "snapdir"); + } + + @After + public void tearDown() throws Exception { + if(tmpDir != null){ + TestUtils.deleteFileRecursively(tmpDir); + } + this.tmpDir = null; + this.logDir = null; + this.snapDir = null; + this.logVersionDir = null; + this.snapVersionDir = null; + } + + private File createVersionDir(File parentDir) { + File versionDir = new File(parentDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION); + versionDir.mkdirs(); + return versionDir; + } + + private void createLogFile(File dir, long zxid) throws IOException { + File file = new File(dir.getPath() + File.separator + Util.makeLogName(zxid)); + file.createNewFile(); + } + + private void createSnapshotFile(File dir, long zxid) throws IOException { + File file = new File(dir.getPath() + File.separator + Util.makeSnapshotName(zxid)); + file.createNewFile(); + } + + private void twoDirSetupWithCorrectFiles() throws IOException { + logVersionDir = createVersionDir(logDir); + snapVersionDir = createVersionDir(snapDir); + + // transaction log files in log dir + createLogFile(logVersionDir,1); + createLogFile(logVersionDir,2); + + // snapshot files in snap dir + createSnapshotFile(snapVersionDir,1); + createSnapshotFile(snapVersionDir,2); + } + + private void singleDirSetupWithCorrectFiles() throws IOException { + logVersionDir = createVersionDir(logDir); + + // transaction log and snapshot files in the same dir + createLogFile(logVersionDir,1); + createLogFile(logVersionDir,2); + createSnapshotFile(logVersionDir,1); + createSnapshotFile(logVersionDir,2); + } + + private FileTxnSnapLog createFileTxnSnapLogWithNoAutoCreateDataDir(File logDir, File snapDir) throws IOException { + return createFileTxnSnapLogWithAutoCreateDataDir(logDir, snapDir, "false"); + } + + private FileTxnSnapLog createFileTxnSnapLogWithAutoCreateDataDir(File logDir, File snapDir, String autoCreateValue) throws IOException { + String priorAutocreateDirValue = System.getProperty(FileTxnSnapLog.ZOOKEEPER_DATADIR_AUTOCREATE); + System.setProperty(FileTxnSnapLog.ZOOKEEPER_DATADIR_AUTOCREATE, autoCreateValue); + FileTxnSnapLog fileTxnSnapLog; + try { + fileTxnSnapLog = new FileTxnSnapLog(logDir, snapDir); + } finally { + if (priorAutocreateDirValue == null) { + System.clearProperty(FileTxnSnapLog.ZOOKEEPER_DATADIR_AUTOCREATE); + } else { + System.setProperty(FileTxnSnapLog.ZOOKEEPER_DATADIR_AUTOCREATE, priorAutocreateDirValue); + } + } + return fileTxnSnapLog; + } + + /** + * Test verifies the auto creation of log dir and snap dir. + * Sets "zookeeper.datadir.autocreate" to true. + */ + @Test + public void testWithAutoCreateDataDir() throws IOException { + Assert.assertFalse("log directory already exists", logDir.exists()); + Assert.assertFalse("snapshot directory already exists", snapDir.exists()); + + FileTxnSnapLog fileTxnSnapLog = createFileTxnSnapLogWithAutoCreateDataDir(logDir, snapDir, "true"); + + Assert.assertTrue(logDir.exists()); + Assert.assertTrue(snapDir.exists()); + Assert.assertTrue(fileTxnSnapLog.getDataDir().exists()); + Assert.assertTrue(fileTxnSnapLog.getSnapDir().exists()); + } + + /** + * Test verifies server should fail when log dir or snap dir doesn't exist. + * Sets "zookeeper.datadir.autocreate" to false. + */ + @Test(expected = FileTxnSnapLog.DatadirException.class) + public void testWithoutAutoCreateDataDir() throws Exception { + Assert.assertFalse("log directory already exists", logDir.exists()); + Assert.assertFalse("snapshot directory already exists", snapDir.exists()); + + try { + createFileTxnSnapLogWithAutoCreateDataDir(logDir, snapDir, "false"); + } catch (FileTxnSnapLog.DatadirException e) { + Assert.assertFalse(logDir.exists()); + Assert.assertFalse(snapDir.exists()); + // rethrow exception + throw e; + } + Assert.fail("Expected exception from FileTxnSnapLog"); + } + + @Test + public void testGetTxnLogSyncElapsedTime() throws IOException { + FileTxnSnapLog fileTxnSnapLog = createFileTxnSnapLogWithAutoCreateDataDir(logDir, snapDir, "true"); + + TxnHeader hdr = new TxnHeader(1, 1, 1, 1, ZooDefs.OpCode.setData); + Record txn = new SetDataTxn("/foo", new byte[0], 1); + Request req = new Request(0, 0, 0, hdr, txn, 0); + + try { + fileTxnSnapLog.append(req); + fileTxnSnapLog.commit(); + long syncElapsedTime = fileTxnSnapLog.getTxnLogElapsedSyncTime(); + Assert.assertNotEquals("Did not update syncElapsedTime!", -1L, syncElapsedTime); + } finally { + fileTxnSnapLog.close(); + } + } + + @Test + public void testDirCheckWithCorrectFiles() throws IOException { + twoDirSetupWithCorrectFiles(); + + try { + createFileTxnSnapLogWithNoAutoCreateDataDir(logDir, snapDir); + } catch (FileTxnSnapLog.LogDirContentCheckException | FileTxnSnapLog.SnapDirContentCheckException e) { + Assert.fail("Should not throw ContentCheckException."); + } + } + + @Test + public void testDirCheckWithSingleDirSetup() throws IOException { + singleDirSetupWithCorrectFiles(); + + try { + createFileTxnSnapLogWithNoAutoCreateDataDir(logDir, logDir); + } catch (FileTxnSnapLog.LogDirContentCheckException | FileTxnSnapLog.SnapDirContentCheckException e) { + Assert.fail("Should not throw ContentCheckException."); + } + } + + @Test(expected = FileTxnSnapLog.LogDirContentCheckException.class) + public void testDirCheckWithSnapFilesInLogDir() throws IOException { + twoDirSetupWithCorrectFiles(); + + // add snapshot files to the log version dir + createSnapshotFile(logVersionDir,3); + createSnapshotFile(logVersionDir,4); + + createFileTxnSnapLogWithNoAutoCreateDataDir(logDir, snapDir); + } + + @Test(expected = FileTxnSnapLog.SnapDirContentCheckException.class) + public void testDirCheckWithLogFilesInSnapDir() throws IOException { + twoDirSetupWithCorrectFiles(); + + // add transaction log files to the snap version dir + createLogFile(snapVersionDir,3); + createLogFile(snapVersionDir,4); + + createFileTxnSnapLogWithNoAutoCreateDataDir(logDir, snapDir); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/persistence/TxnLogToolkitTest.java b/src/java/test/org/apache/zookeeper/server/persistence/TxnLogToolkitTest.java new file mode 100644 index 00000000000..79a69c4e921 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/persistence/TxnLogToolkitTest.java @@ -0,0 +1,155 @@ +/** + * 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.persistence; + +import org.apache.commons.io.FileUtils; +import org.apache.zookeeper.test.ClientBase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + + +public class TxnLogToolkitTest { + private static final File testData = new File( + System.getProperty("test.data.dir", "build/test/data")); + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private File mySnapDir; + + @Before + public void setUp() throws IOException { + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + File snapDir = new File(testData, "invalidsnap"); + mySnapDir = ClientBase.createTmpDir(); + FileUtils.copyDirectory(snapDir, mySnapDir); + } + + @After + public void tearDown() throws IOException { + System.setOut(System.out); + System.setErr(System.err); + mySnapDir.setWritable(true); + FileUtils.deleteDirectory(mySnapDir); + } + + @Test + public void testDumpMode() throws Exception { + // Arrange + File logfile = new File(new File(mySnapDir, "version-2"), "log.274"); + TxnLogToolkit lt = new TxnLogToolkit(false, false, logfile.toString(), true); + + // Act + lt.dump(null); + + // Assert + // no exception thrown + } + + @Test(expected = TxnLogToolkit.TxnLogToolkitException.class) + public void testInitMissingFile() throws FileNotFoundException, TxnLogToolkit.TxnLogToolkitException { + // Arrange & Act + File logfile = new File("this_file_should_not_exists"); + TxnLogToolkit lt = new TxnLogToolkit(false, false, logfile.toString(), true); + } + + @Test(expected = TxnLogToolkit.TxnLogToolkitException.class) + public void testInitWithRecoveryFileExists() throws IOException, TxnLogToolkit.TxnLogToolkitException { + // Arrange & Act + File logfile = new File(new File(mySnapDir, "version-2"), "log.274"); + File recoveryFile = new File(new File(mySnapDir, "version-2"), "log.274.fixed"); + recoveryFile.createNewFile(); + TxnLogToolkit lt = new TxnLogToolkit(true, false, logfile.toString(), true); + } + + @Test + public void testDumpWithCrcError() throws Exception { + // Arrange + File logfile = new File(new File(mySnapDir, "version-2"), "log.42"); + TxnLogToolkit lt = new TxnLogToolkit(false, false, logfile.toString(), true); + + // Act + lt.dump(null); + + // Assert + String output = outContent.toString(); + Pattern p = Pattern.compile("^CRC ERROR.*session 0x8061fac5ddeb0000 cxid 0x0 zxid 0x8800000002 createSession 30000$", Pattern.MULTILINE); + Matcher m = p.matcher(output); + assertTrue("Output doesn't indicate CRC error for the broken session id: " + output, m.find()); + } + + @Test + public void testRecoveryFixBrokenFile() throws Exception { + // Arrange + File logfile = new File(new File(mySnapDir, "version-2"), "log.42"); + TxnLogToolkit lt = new TxnLogToolkit(true, false, logfile.toString(), true); + + // Act + lt.dump(null); + + // Assert + String output = outContent.toString(); + assertThat(output, containsString("CRC FIXED")); + + // Should be able to dump the recovered logfile with no CRC error + outContent.reset(); + logfile = new File(new File(mySnapDir, "version-2"), "log.42.fixed"); + lt = new TxnLogToolkit(false, false, logfile.toString(), true); + lt.dump(null); + output = outContent.toString(); + assertThat(output, not(containsString("CRC ERROR"))); + } + + @Test + public void testRecoveryInteractiveMode() throws Exception { + // Arrange + File logfile = new File(new File(mySnapDir, "version-2"), "log.42"); + TxnLogToolkit lt = new TxnLogToolkit(true, false, logfile.toString(), false); + + // Act + lt.dump(new Scanner("y\n")); + + // Assert + String output = outContent.toString(); + assertThat(output, containsString("CRC ERROR")); + + // Should be able to dump the recovered logfile with no CRC error + outContent.reset(); + logfile = new File(new File(mySnapDir, "version-2"), "log.42.fixed"); + lt = new TxnLogToolkit(false, false, logfile.toString(), true); + lt.dump(null); + output = outContent.toString(); + assertThat(output, not(containsString("CRC ERROR"))); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/CommitProcessorConcurrencyTest.java b/src/java/test/org/apache/zookeeper/server/quorum/CommitProcessorConcurrencyTest.java index 8b2b5319af1..8cbed9159f4 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/CommitProcessorConcurrencyTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/CommitProcessorConcurrencyTest.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import org.apache.jute.BinaryOutputArchive; +import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooDefs.OpCode; import org.apache.zookeeper.data.Id; @@ -33,6 +34,7 @@ import org.apache.zookeeper.server.RequestProcessor; import org.apache.zookeeper.server.WorkerService; import org.apache.zookeeper.server.RequestProcessor.RequestProcessorException; +import org.apache.zookeeper.server.ZooKeeperServerListener; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -40,7 +42,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CommitProcessorConcurrencyTest { +public class CommitProcessorConcurrencyTest extends ZKTestCase { protected static final Logger LOG = LoggerFactory.getLogger(CommitProcessorConcurrencyTest.class); @@ -69,7 +71,12 @@ public void processRequest(Request request) public void shutdown(){} }, "0", - false); + false, new ZooKeeperServerListener(){ + + @Override + public void notifyStopping(String errMsg, int exitCode) { + + }}); } public void testStart() { diff --git a/src/java/test/org/apache/zookeeper/server/quorum/CommitProcessorTest.java b/src/java/test/org/apache/zookeeper/server/quorum/CommitProcessorTest.java index 93c00a9178a..1418158ecb1 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/CommitProcessorTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/CommitProcessorTest.java @@ -30,6 +30,7 @@ import java.util.concurrent.LinkedBlockingQueue; import org.apache.jute.BinaryOutputArchive; +import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooDefs.OpCode; @@ -69,7 +70,7 @@ * 3. The pipeline needs to be drained before a write request can enter. * 4. No in-flight write requests while processing a read request. */ -public class CommitProcessorTest { +public class CommitProcessorTest extends ZKTestCase { protected static final Logger LOG = LoggerFactory.getLogger(CommitProcessorTest.class); @@ -87,6 +88,7 @@ public void setUp(int numCommitThreads, int numClientThreads) System.setProperty( CommitProcessor.ZOOKEEPER_COMMIT_PROC_NUM_WORKER_THREADS, Integer.toString(numCommitThreads)); + System.setProperty("zookeeper.admin.enableServer", "false"); tmpDir = ClientBase.createTmpDir(); ClientBase.setupTestEnv(); zks = new TestZooKeeperServer(tmpDir, tmpDir, 4000); @@ -232,7 +234,8 @@ protected void setupRequestProcessors() { // processor, so it can do pre/post validating of requests ValidateProcessor validateProcessor = new ValidateProcessor(finalProcessor); - commitProcessor = new CommitProcessor(validateProcessor, "1", true); + commitProcessor = new CommitProcessor(validateProcessor, "1", true, + getZooKeeperServerListener()); validateProcessor.setCommitProcessor(commitProcessor); commitProcessor.start(); MockProposalRequestProcessor proposalProcessor = 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..9edb4be69f5 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/EphemeralNodeDeletionTest.java @@ -0,0 +1,228 @@ +/** + * 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.assertEquals; +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() + ":participant;127.0.0.1:" + + clientPorts[i]; + sb.append(server + "\n"); + } + String currentQuorumCfgSection = sb.toString(); + // start all the servers + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, + false) { + @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 + */ + + Stat firstEphemeralNode = new Stat(); + + // 1: create ephemeral node + String nodePath = "/e1"; + zk.create(nodePath, "1".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.EPHEMERAL, firstEphemeralNode); + assertEquals("Current session and ephemeral owner should be same", + zk.getSessionId(), firstEphemeralNode.getEphemeralOwner()); + + // 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 + Stat currentEphemeralNode = new Stat(); + zk.create(nodePath, "2".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.EPHEMERAL, currentEphemeralNode); + + // 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, 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(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/FLEBackwardElectionRoundTest.java b/src/java/test/org/apache/zookeeper/server/quorum/FLEBackwardElectionRoundTest.java index 302fc09493a..6c88f0f2ddc 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/FLEBackwardElectionRoundTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/FLEBackwardElectionRoundTest.java @@ -99,7 +99,8 @@ public void testBackwardElectionRound() throws Exception { port[i] = clientport; } - ByteBuffer initialMsg = FLETestUtils.createMsg(ServerState.FOLLOWING.ordinal(), 0, 0, 1); + ByteBuffer initialMsg0 = getMsg(); + ByteBuffer initialMsg1 = getMsg(); /* * Start server 0 @@ -113,19 +114,19 @@ public void testBackwardElectionRound() throws Exception { * Start mock server 1 */ QuorumPeer mockPeer = new QuorumPeer(peers, tmpdir[1], tmpdir[1], port[1], 3, 1, 1000, 2, 2); - cnxManagers[0] = new QuorumCnxManager(mockPeer); + cnxManagers[0] = mockPeer.createCnxnManager(); cnxManagers[0].listener.start(); - cnxManagers[0].toSend(0l, initialMsg); + cnxManagers[0].toSend(0l, initialMsg0); /* * Start mock server 2 */ mockPeer = new QuorumPeer(peers, tmpdir[2], tmpdir[2], port[2], 3, 2, 1000, 2, 2); - cnxManagers[1] = new QuorumCnxManager(mockPeer); + cnxManagers[1] = mockPeer.createCnxnManager(); cnxManagers[1].listener.start(); - cnxManagers[1].toSend(0l, initialMsg); + cnxManagers[1].toSend(0l, initialMsg1); /* * Run another instance of leader election. @@ -137,8 +138,8 @@ public void testBackwardElectionRound() throws Exception { /* * Send the same messages, this time should not make 0 the leader. */ - cnxManagers[0].toSend(0l, initialMsg); - cnxManagers[1].toSend(0l, initialMsg); + cnxManagers[0].toSend(0l, initialMsg0); + cnxManagers[1].toSend(0l, initialMsg1); thread.join(5000); @@ -147,4 +148,8 @@ public void testBackwardElectionRound() throws Exception { } } + + private ByteBuffer getMsg() { + return FLETestUtils.createMsg(ServerState.FOLLOWING.ordinal(), 0, 0, 1); + } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/FLELostMessageTest.java b/src/java/test/org/apache/zookeeper/server/quorum/FLELostMessageTest.java index cc44243fdb0..6583f9071da 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/FLELostMessageTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/FLELostMessageTest.java @@ -95,7 +95,7 @@ public void testLostMessage() throws Exception { void mockServer() throws InterruptedException, IOException { QuorumPeer peer = new QuorumPeer(peers, tmpdir[0], tmpdir[0], port[0], 3, 0, 1000, 2, 2); - cnxManager = new QuorumCnxManager(peer); + cnxManager = peer.createCnxnManager(); cnxManager.listener.start(); cnxManager.toSend(1l, FLETestUtils.createMsg(ServerState.LOOKING.ordinal(), 0, 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 index 43fe17b4560..a907abdb9b4 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/FLETestUtils.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/FLETestUtils.java @@ -19,6 +19,7 @@ import java.nio.ByteBuffer; +import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.server.quorum.FastLeaderElection; import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.quorum.Vote; @@ -29,7 +30,7 @@ import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; -public class FLETestUtils { +public class FLETestUtils extends ZKTestCase { protected static final Logger LOG = LoggerFactory.getLogger(FLETestUtils.class); /* diff --git a/src/java/test/org/apache/zookeeper/server/quorum/LeaderBeanTest.java b/src/java/test/org/apache/zookeeper/server/quorum/LeaderBeanTest.java new file mode 100644 index 00000000000..f8108cdbcb3 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/LeaderBeanTest.java @@ -0,0 +1,151 @@ +/** + * 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 org.apache.jute.OutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; +import org.apache.zookeeper.server.util.SerializeUtils; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.txn.TxnHeader; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +public class LeaderBeanTest { + private Leader leader; + private LeaderBean leaderBean; + private FileTxnSnapLog fileTxnSnapLog; + private LeaderZooKeeperServer zks; + private QuorumPeer qp; + + @Before + public void setUp() throws IOException { + qp = new QuorumPeer(); + QuorumVerifier quorumVerifierMock = mock(QuorumVerifier.class); + qp.setQuorumVerifier(quorumVerifierMock, false); + File tmpDir = ClientBase.createTmpDir(); + fileTxnSnapLog = new FileTxnSnapLog(new File(tmpDir, "data"), + new File(tmpDir, "data_txnlog")); + ZKDatabase zkDb = new ZKDatabase(fileTxnSnapLog); + + zks = new LeaderZooKeeperServer(fileTxnSnapLog, qp, zkDb); + leader = new Leader(qp, zks); + leaderBean = new LeaderBean(leader, zks); + } + + @After + public void tearDown() throws IOException { + fileTxnSnapLog.close(); + } + + @Test + public void testGetName() { + assertEquals("Leader", leaderBean.getName()); + } + + @Test + public void testGetCurrentZxid() { + // Arrange + zks.setZxid(1); + + // Assert + assertEquals("0x1", leaderBean.getCurrentZxid()); + } + + @Test + public void testGetElectionTimeTaken() { + // Arrange + qp.setElectionTimeTaken(1); + + // Assert + assertEquals(1, leaderBean.getElectionTimeTaken()); + } + + @Test + public void testGetProposalSize() throws IOException, Leader.XidRolloverException { + // Arrange + Request req = createMockRequest(); + + // Act + leader.propose(req); + + // Assert + byte[] data = SerializeUtils.serializeRequest(req); + assertEquals(data.length, leaderBean.getLastProposalSize()); + assertEquals(data.length, leaderBean.getMinProposalSize()); + assertEquals(data.length, leaderBean.getMaxProposalSize()); + } + + @Test + public void testResetProposalStats() throws IOException, Leader.XidRolloverException { + // Arrange + int initialProposalSize = leaderBean.getLastProposalSize(); + Request req = createMockRequest(); + + // Act + leader.propose(req); + + // Assert + assertNotEquals(initialProposalSize, leaderBean.getLastProposalSize()); + leaderBean.resetProposalStatistics(); + assertEquals(initialProposalSize, leaderBean.getLastProposalSize()); + assertEquals(initialProposalSize, leaderBean.getMinProposalSize()); + assertEquals(initialProposalSize, leaderBean.getMaxProposalSize()); + } + + private Request createMockRequest() throws IOException { + TxnHeader header = mock(TxnHeader.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + OutputArchive oa = (OutputArchive) args[0]; + oa.writeString("header", "test"); + return null; + } + }).when(header).serialize(any(OutputArchive.class), anyString()); + Record txn = mock(Record.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + OutputArchive oa = (OutputArchive) args[0]; + oa.writeString("record", "test"); + return null; + } + }).when(txn).serialize(any(OutputArchive.class), anyString()); + return new Request(1, 2, 3, header, txn, 4); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/LeaderWithObserverTest.java b/src/java/test/org/apache/zookeeper/server/quorum/LeaderWithObserverTest.java new file mode 100644 index 00000000000..2548acad1cd --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/LeaderWithObserverTest.java @@ -0,0 +1,185 @@ +/** + * 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 org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.test.ClientBase; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Set; + +import static org.apache.zookeeper.server.quorum.ZabUtils.createLeader; +import static org.apache.zookeeper.server.quorum.ZabUtils.createQuorumPeer; + +public class LeaderWithObserverTest { + + QuorumPeer peer; + Leader leader; + File tmpDir; + long participantId; + long observerId; + + @Before + public void setUp() throws Exception { + tmpDir = ClientBase.createTmpDir(); + peer = createQuorumPeer(tmpDir); + participantId = 1; + Map peers = peer.getQuorumVerifier().getAllMembers(); + observerId = peers.size(); + leader = createLeader(tmpDir, peer); + peer.leader = leader; + peers.put(observerId, new QuorumPeer.QuorumServer( + observerId, new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + QuorumPeer.LearnerType.OBSERVER)); + + // these tests are serial, we can speed up InterruptedException + peer.tickTime = 1; + } + + @After + public void tearDown(){ + leader.shutdown("end of test"); + tmpDir.delete(); + } + + @Test + public void testGetEpochToPropose() throws Exception { + long lastAcceptedEpoch = 5; + peer.setAcceptedEpoch(5); + + Assert.assertEquals("Unexpected vote in connectingFollowers", 0, leader.connectingFollowers.size()); + Assert.assertTrue(leader.waitingForNewEpoch); + try { + // Leader asks for epoch (mocking Leader.lead behavior) + // First add to connectingFollowers + leader.getEpochToPropose(peer.getId(), lastAcceptedEpoch); + } catch (InterruptedException e) { + // ignore timeout + } + + Assert.assertEquals("Unexpected vote in connectingFollowers", 1, leader.connectingFollowers.size()); + Assert.assertEquals("Leader shouldn't set new epoch until quorum of participants is in connectingFollowers", + lastAcceptedEpoch, peer.getAcceptedEpoch()); + Assert.assertTrue(leader.waitingForNewEpoch); + try { + // Observer asks for epoch (mocking LearnerHandler behavior) + leader.getEpochToPropose(observerId, lastAcceptedEpoch); + } catch (InterruptedException e) { + // ignore timeout + } + + Assert.assertEquals("Unexpected vote in connectingFollowers", 1, leader.connectingFollowers.size()); + Assert.assertEquals("Leader shouldn't set new epoch after observer asks for epoch", + lastAcceptedEpoch, peer.getAcceptedEpoch()); + Assert.assertTrue(leader.waitingForNewEpoch); + try { + // Now participant asks for epoch (mocking LearnerHandler behavior). Second add to connectingFollowers. + // Triggers verifier.containsQuorum = true + leader.getEpochToPropose(participantId, lastAcceptedEpoch); + } catch (Exception e) { + Assert.fail("Timed out in getEpochToPropose"); + } + + Assert.assertEquals("Unexpected vote in connectingFollowers", 2, leader.connectingFollowers.size()); + Assert.assertEquals("Leader should record next epoch", lastAcceptedEpoch + 1, peer.getAcceptedEpoch()); + Assert.assertFalse(leader.waitingForNewEpoch); + } + + @Test + public void testWaitForEpochAck() throws Exception { + // things needed for waitForEpochAck to run (usually in leader.lead(), but we're not running leader here) + leader.leaderStateSummary = new StateSummary(leader.self.getCurrentEpoch(), leader.zk.getLastProcessedZxid()); + + Assert.assertEquals("Unexpected vote in electingFollowers", 0, leader.electingFollowers.size()); + Assert.assertFalse(leader.electionFinished); + try { + // leader calls waitForEpochAck, first add to electingFollowers + leader.waitForEpochAck(peer.getId(), new StateSummary(0, 0)); + } catch (InterruptedException e) { + // ignore timeout + } + + Assert.assertEquals("Unexpected vote in electingFollowers", 1, leader.electingFollowers.size()); + Assert.assertFalse(leader.electionFinished); + try { + // observer calls waitForEpochAck, should fail verifier.containsQuorum + leader.waitForEpochAck(observerId, new StateSummary(0, 0)); + } catch (InterruptedException e) { + // ignore timeout + } + + Assert.assertEquals("Unexpected vote in electingFollowers", 1, leader.electingFollowers.size()); + Assert.assertFalse(leader.electionFinished); + try { + // second add to electingFollowers, verifier.containsQuorum=true, waitForEpochAck returns without exceptions + leader.waitForEpochAck(participantId, new StateSummary(0, 0)); + Assert.assertEquals("Unexpected vote in electingFollowers", 2, leader.electingFollowers.size()); + Assert.assertTrue(leader.electionFinished); + } catch (Exception e) { + Assert.fail("Timed out in waitForEpochAck"); + } + } + + @Test + public void testWaitForNewLeaderAck() throws Exception { + long zxid = leader.zk.getZxid(); + + // things needed for waitForNewLeaderAck to run (usually in leader.lead(), but we're not running leader here) + leader.newLeaderProposal.packet = new QuorumPacket(0, zxid, null, null); + leader.newLeaderProposal.addQuorumVerifier(peer.getQuorumVerifier()); + + Set ackSet = leader.newLeaderProposal.qvAcksetPairs.get(0).getAckset(); + Assert.assertEquals("Unexpected vote in ackSet", 0, ackSet.size()); + Assert.assertFalse(leader.quorumFormed); + try { + // leader calls waitForNewLeaderAck, first add to ackSet + leader.waitForNewLeaderAck(peer.getId(), zxid); + } catch (InterruptedException e) { + // ignore timeout + } + + Assert.assertEquals("Unexpected vote in ackSet", 1, ackSet.size()); + Assert.assertFalse(leader.quorumFormed); + try { + // observer calls waitForNewLeaderAck, should fail verifier.containsQuorum + leader.waitForNewLeaderAck(observerId, zxid); + } catch (InterruptedException e) { + // ignore timeout + } + + Assert.assertEquals("Unexpected vote in ackSet", 1, ackSet.size()); + Assert.assertFalse(leader.quorumFormed); + try { + // second add to ackSet, verifier.containsQuorum=true, waitForNewLeaderAck returns without exceptions + leader.waitForNewLeaderAck(participantId, zxid); + Assert.assertEquals("Unexpected vote in ackSet", 2, ackSet.size()); + Assert.assertTrue(leader.quorumFormed); + } catch (Exception e) { + Assert.fail("Timed out in waitForEpochAck"); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/LearnerHandlerTest.java b/src/java/test/org/apache/zookeeper/server/quorum/LearnerHandlerTest.java index 95e5e537397..843c8aa7c95 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/LearnerHandlerTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/LearnerHandlerTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.BufferedInputStream; import java.io.IOException; import java.net.Socket; import java.util.Iterator; @@ -54,7 +55,7 @@ class MockLearnerHandler extends LearnerHandler { boolean threadStarted = false; MockLearnerHandler(Socket sock, Leader leader) throws IOException { - super(sock, leader); + super(sock, new BufferedInputStream(sock.getInputStream()), leader); } protected void startSendingPackets() { diff --git a/src/java/test/org/apache/zookeeper/server/quorum/LearnerSnapshotThrottlerTest.java b/src/java/test/org/apache/zookeeper/server/quorum/LearnerSnapshotThrottlerTest.java index 78c1f852c8f..c2d65e371bc 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/LearnerSnapshotThrottlerTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/LearnerSnapshotThrottlerTest.java @@ -29,8 +29,13 @@ import org.apache.zookeeper.ZKTestCase; import org.junit.Assert; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class LearnerSnapshotThrottlerTest extends ZKTestCase { + private static final Logger LOG = + LoggerFactory.getLogger(LearnerSnapshotThrottlerTest.class); + @Test(expected = SnapshotThrottleException.class) public void testTooManySnapshotsNonessential() throws Exception { LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(5); @@ -177,10 +182,9 @@ public Boolean call() { public void testHighContentionWithTimeout() throws Exception { int numThreads = 20; - final LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(2, 200); + final LearnerSnapshotThrottler throttler = new LearnerSnapshotThrottler(2, 5000); ExecutorService threadPool = Executors.newFixedThreadPool(numThreads); final CountDownLatch threadStartLatch = new CountDownLatch(numThreads); - final CountDownLatch snapshotProgressLatch = new CountDownLatch(numThreads); List> results = new ArrayList>(numThreads); for (int i = 0; i < numThreads; i++) { @@ -201,6 +205,7 @@ public Boolean call() { return snapshotNumber <= 2; } catch (Exception e) { + LOG.error("Exception trying to begin snapshot", e); return false; } } 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 4a3260f453e..17935505ca7 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/LearnerTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/LearnerTest.java @@ -24,6 +24,7 @@ import java.io.EOFException; import java.io.File; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.Socket; import java.util.ArrayList; @@ -34,6 +35,7 @@ import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.test.TestUtils; import org.apache.zookeeper.txn.CreateTxn; import org.apache.zookeeper.txn.TxnHeader; import org.junit.Assert; @@ -47,7 +49,8 @@ static class SimpleLearnerZooKeeperServer extends LearnerZooKeeperServer { Learner learner; - public SimpleLearnerZooKeeperServer(FileTxnSnapLog ftsl, QuorumPeer self) throws IOException { + public SimpleLearnerZooKeeperServer(FileTxnSnapLog ftsl, QuorumPeer self) + throws IOException { super(ftsl, 2000, 2000, 2000, new ZKDatabase(ftsl), self); } @@ -65,15 +68,74 @@ static class SimpleLearner extends Learner { } } - static private void recursiveDelete(File dir) { - if (dir == null || !dir.exists()) { - return; + static class TimeoutLearner extends Learner { + int passSocketConnectOnAttempt = 10; + int socketConnectAttempt = 0; + long timeMultiplier = 0; + + public void setTimeMultiplier(long multiplier) { + timeMultiplier = multiplier; + } + + public void setPassConnectAttempt(int num) { + passSocketConnectOnAttempt = num; + } + + protected long nanoTime() { + return socketConnectAttempt * timeMultiplier; + } + + protected int getSockConnectAttempt() { + return socketConnectAttempt; } - if (!dir.isDirectory()) { - dir.delete(); + + @Override + protected void sockConnect(Socket sock, InetSocketAddress addr, int timeout) + throws IOException { + if (++socketConnectAttempt < passSocketConnectOnAttempt) { + throw new IOException("Test injected Socket.connect() error."); + } } - for (File child : dir.listFiles()) { - recursiveDelete(child); + } + + @Test(expected=IOException.class) + public void connectionRetryTimeoutTest() throws Exception { + Learner learner = new TimeoutLearner(); + learner.self = new QuorumPeer(); + learner.self.setTickTime(2000); + learner.self.setInitLimit(5); + learner.self.setSyncLimit(2); + + // this addr won't even be used since we fake the Socket.connect + InetSocketAddress addr = new InetSocketAddress(1111); + + // we expect this to throw an IOException since we're faking socket connect errors every time + learner.connectToLeader(addr, ""); + } + @Test + public void connectionInitLimitTimeoutTest() throws Exception { + TimeoutLearner learner = new TimeoutLearner(); + learner.self = new QuorumPeer(); + learner.self.setTickTime(2000); + learner.self.setInitLimit(5); + learner.self.setSyncLimit(2); + + // this addr won't even be used since we fake the Socket.connect + InetSocketAddress addr = new InetSocketAddress(1111); + + // pretend each connect attempt takes 4000 milliseconds + learner.setTimeMultiplier((long)4000 * 1000000); + + learner.setPassConnectAttempt(5); + + // we expect this to throw an IOException since we're faking socket connect errors every time + try { + learner.connectToLeader(addr, ""); + Assert.fail("should have thrown IOException!"); + } catch (IOException e) { + //good, wanted to see that, let's make sure we ran out of time + Assert.assertTrue(learner.nanoTime() > 2000*5*1000000); + Assert.assertEquals(3, learner.getSockConnectAttempt()); } } @@ -121,7 +183,7 @@ public void syncTest() throws Exception { sl = new SimpleLearner(ftsl); Assert.assertEquals(startZxid, sl.zk.getLastProcessedZxid()); } finally { - recursiveDelete(tmpFile); + TestUtils.deleteFileRecursively(tmpFile); } } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/LocalPeerBeanTest.java b/src/java/test/org/apache/zookeeper/server/quorum/LocalPeerBeanTest.java new file mode 100644 index 00000000000..96688ffddc7 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/LocalPeerBeanTest.java @@ -0,0 +1,82 @@ +/** + * 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.net.InetAddress; +import java.net.InetSocketAddress; + +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.junit.Test; + +public class LocalPeerBeanTest { + + /** + * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2299 + */ + @Test + public void testClientAddress() throws Exception { + QuorumPeer quorumPeer = new QuorumPeer(); + LocalPeerBean remotePeerBean = new LocalPeerBean(quorumPeer); + + /** + * Case 1: When cnxnFactory is null + */ + String result = remotePeerBean.getClientAddress(); + assertNotNull(result); + assertEquals(0, result.length()); + + /** + * Case 2: When only client port is configured + */ + ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory(); + int clientPort = PortAssignment.unique(); + InetSocketAddress address = new InetSocketAddress(clientPort); + cnxnFactory.configure(address, 5, false); + quorumPeer.setCnxnFactory(cnxnFactory); + + result = remotePeerBean.getClientAddress(); + String ipv4 = "0.0.0.0:" + clientPort; + String ipv6 = "0:0:0:0:0:0:0:0:" + clientPort; + assertTrue(result.equals(ipv4) || result.equals(ipv6)); + // cleanup + cnxnFactory.shutdown(); + + /** + * Case 3: When both client port and client address is configured + */ + clientPort = PortAssignment.unique(); + InetAddress clientIP = InetAddress.getLoopbackAddress(); + address = new InetSocketAddress(clientIP, clientPort); + cnxnFactory = ServerCnxnFactory.createFactory(); + cnxnFactory.configure(address, 5, false); + quorumPeer.setCnxnFactory(cnxnFactory); + + result = remotePeerBean.getClientAddress(); + String expectedResult = clientIP.getHostAddress() + ":" + clientPort; + assertEquals(expectedResult, result); + // cleanup + cnxnFactory.shutdown(); + } + +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ProposalStatsTest.java b/src/java/test/org/apache/zookeeper/server/quorum/ProposalStatsTest.java new file mode 100644 index 00000000000..1f7197922a8 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/ProposalStatsTest.java @@ -0,0 +1,58 @@ +/** + * 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 org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ProposalStatsTest { + @Test + public void testSetProposalSizeSetMinMax() { + ProposalStats stats = new ProposalStats(); + assertEquals(-1, stats.getLastProposalSize()); + assertEquals(-1, stats.getMinProposalSize()); + assertEquals(-1, stats.getMaxProposalSize()); + stats.setLastProposalSize(10); + assertEquals(10, stats.getLastProposalSize()); + assertEquals(10, stats.getMinProposalSize()); + assertEquals(10, stats.getMaxProposalSize()); + stats.setLastProposalSize(20); + assertEquals(20, stats.getLastProposalSize()); + assertEquals(10, stats.getMinProposalSize()); + assertEquals(20, stats.getMaxProposalSize()); + stats.setLastProposalSize(5); + assertEquals(5, stats.getLastProposalSize()); + assertEquals(5, stats.getMinProposalSize()); + assertEquals(20, stats.getMaxProposalSize()); + } + + @Test + public void testReset() { + ProposalStats stats = new ProposalStats(); + stats.setLastProposalSize(10); + assertEquals(10, stats.getLastProposalSize()); + assertEquals(10, stats.getMinProposalSize()); + assertEquals(10, stats.getMaxProposalSize()); + stats.reset(); + assertEquals(-1, stats.getLastProposalSize()); + assertEquals(-1, stats.getMinProposalSize()); + assertEquals(-1, stats.getMaxProposalSize()); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerConfigTest.java b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerConfigTest.java new file mode 100644 index 00000000000..b9cdce8461d --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerConfigTest.java @@ -0,0 +1,112 @@ +/** + * 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; +import org.junit.Test; + +public class QuorumPeerConfigTest { + + /** + * test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2264 + */ + @Test + public void testErrorMessageWhensecureClientPortNotSetButsecureClientPortAddressSet() + throws IOException, ConfigException { + QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig(); + try { + Properties zkProp = getDefaultZKProperties(); + zkProp.setProperty("secureClientPortAddress", "localhost"); + quorumPeerConfig.parseProperties(zkProp); + fail("IllegalArgumentException is expected"); + } catch (IllegalArgumentException e) { + String expectedMessage = "secureClientPortAddress is set but secureClientPort is not set"; + assertEquals(expectedMessage, e.getMessage()); + } + } + + /** + * + * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2264 + */ + @Test + public void testErrorMessageWhenclientPortNotSetButclientPortAddressSet() + throws IOException, ConfigException { + QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig(); + try { + Properties zkProp = getDefaultZKProperties(); + zkProp.setProperty("clientPortAddress", "localhost"); + quorumPeerConfig.parseProperties(zkProp); + fail("IllegalArgumentException is expected"); + } catch (IllegalArgumentException e) { + String expectedMessage = "clientPortAddress is set but clientPort is not set"; + assertEquals(expectedMessage, e.getMessage()); + } + } + + /** + * https://issues.apache.org/jira/browse/ZOOKEEPER-2297 + */ + @Test + public void testConfigureSSLAuthGetsConfiguredIfSecurePortConfigured() + throws IOException, ConfigException { + String sslAuthProp = "zookeeper.authProvider.x509"; + QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig(); + Properties zkProp = getDefaultZKProperties(); + zkProp.setProperty("secureClientPort", "12345"); + quorumPeerConfig.parseProperties(zkProp); + String expected = "org.apache.zookeeper.server.auth.X509AuthenticationProvider"; + String result = System.getProperty(sslAuthProp); + assertEquals(expected, result); + } + + /** + * https://issues.apache.org/jira/browse/ZOOKEEPER-2297 + */ + @Test + public void testCustomSSLAuth() + throws IOException{ + System.setProperty(ZKConfig.SSL_AUTHPROVIDER, "y509"); + QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig(); + try { + Properties zkProp = getDefaultZKProperties(); + zkProp.setProperty("secureClientPort", "12345"); + quorumPeerConfig.parseProperties(zkProp); + fail("ConfigException is expected"); + } catch (ConfigException e) { + assertNotNull(e.getMessage()); + } + } + + private Properties getDefaultZKProperties() { + Properties zkProp = new Properties(); + zkProp.setProperty("dataDir", new File("myDataDir").getAbsolutePath()); + return zkProp; + } + +} 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 9abe47910f5..12e9bad0d57 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java @@ -19,30 +19,44 @@ package org.apache.zookeeper.server.quorum; import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; +import static org.apache.zookeeper.test.ClientBase.createEmptyTestDir; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.LineNumberReader; import java.io.StringReader; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; +import java.nio.file.Paths; +import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeoutException; import java.util.regex.Pattern; -import org.apache.log4j.Layout; +import org.apache.commons.io.FileUtils; import org.apache.log4j.Level; import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; import org.apache.log4j.WriterAppender; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.Watcher; 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.server.persistence.FileTxnSnapLog; import org.apache.zookeeper.server.quorum.Leader.Proposal; import org.apache.zookeeper.test.ClientBase; +import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -51,6 +65,23 @@ * */ public class QuorumPeerMainTest extends QuorumPeerTestBase { + + 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. */ @@ -225,17 +256,10 @@ 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) { - leader = i; - } - } + int leader = servers.findLeader(); // make sure there is a leader Assert.assertTrue("There should be a leader", leader >= 0); @@ -247,47 +271,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", @@ -295,37 +319,128 @@ 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); } + /** + * This test validates that if a quorum member determines that it is leader without the support of the rest of the + * quorum (the other members do not believe it to be the leader) it will stop attempting to lead and become a follower. + * + * @throws IOException + * @throws InterruptedException + */ + @Test + public void testElectionFraud() throws IOException, InterruptedException { + // capture QuorumPeer logging + ByteArrayOutputStream os = new ByteArrayOutputStream(); + WriterAppender appender = getConsoleAppender(os, Level.INFO); + Logger qlogger = Logger.getLogger(QuorumPeer.class); + qlogger.addAppender(appender); + + numServers = 3; + + // used for assertions later + boolean foundLeading = false; + boolean foundLooking = false; + boolean foundFollowing = false; + + try { + // spin up a quorum, we use a small ticktime to make the test run faster + servers = LaunchServers(numServers, 500); + + // find the leader + int trueLeader = servers.findLeader(); + Assert.assertTrue("There should be a leader", trueLeader >= 0); + + // find a follower + int falseLeader = (trueLeader + 1) % numServers; + Assert.assertTrue("All servers should join the quorum", servers.mt[falseLeader].main.quorumPeer.follower != null); + + // to keep the quorum peer running and force it to go into the looking state, we kill leader election + // and close the connection to the leader + servers.mt[falseLeader].main.quorumPeer.electionAlg.shutdown(); + servers.mt[falseLeader].main.quorumPeer.follower.getSocket().close(); + + // wait for the falseLeader to disconnect + waitForOne(servers.zk[falseLeader], States.CONNECTING); + + // convince falseLeader that it is the leader + servers.mt[falseLeader].main.quorumPeer.setPeerState(QuorumPeer.ServerState.LEADING); + + // provide time for the falseleader to realize no followers have connected + // (this is twice the timeout used in Leader#getEpochToPropose) + Thread.sleep(2 * servers.mt[falseLeader].main.quorumPeer.initLimit * servers.mt[falseLeader].main.quorumPeer.tickTime); + + // Restart leader election + servers.mt[falseLeader].main.quorumPeer.startLeaderElection(); + + // The previous client connection to falseLeader likely closed, create a new one + servers.zk[falseLeader] = new ZooKeeper("127.0.0.1:" + servers.mt[falseLeader].getClientPort(), ClientBase.CONNECTION_TIMEOUT, this); + + // Wait for falseLeader to rejoin the quorum + waitForOne(servers.zk[falseLeader], States.CONNECTED); + + // and ensure trueLeader is still the leader + Assert.assertTrue(servers.mt[trueLeader].main.quorumPeer.leader != null); + + // Look through the logs for output that indicates the falseLeader is LEADING, then LOOKING, then FOLLOWING + LineNumberReader r = new LineNumberReader(new StringReader(os.toString())); + Pattern leading = Pattern.compile(".*myid=" + falseLeader + ".*LEADING.*"); + Pattern looking = Pattern.compile(".*myid=" + falseLeader + ".*LOOKING.*"); + Pattern following = Pattern.compile(".*myid=" + falseLeader + ".*FOLLOWING.*"); + + String line; + while ((line = r.readLine()) != null) { + if (!foundLeading) { + foundLeading = leading.matcher(line).matches(); + } else if(!foundLooking) { + foundLooking = looking.matcher(line).matches(); + } else if (following.matcher(line).matches()){ + foundFollowing = true; + break; + } + } + } finally { + qlogger.removeAppender(appender); + } + + Assert.assertTrue("falseLeader never attempts to become leader", foundLeading); + Assert.assertTrue("falseLeader never gives up on leadership", foundLooking); + Assert.assertTrue("falseLeader never rejoins the quorum", foundFollowing); + } + private void waitForOne(ZooKeeper zk, States state) throws InterruptedException { int iterations = ClientBase.CONNECTION_TIMEOUT / 500; while (zk.getState() != state) { if (iterations-- == 0) { - throw new RuntimeException("Waiting too long"); + throw new RuntimeException("Waiting too long " + zk.getState() + " != " + state); } Thread.sleep(500); } } + private void waitForAll(Servers servers, States state) throws InterruptedException { + waitForAll(servers.zk, state); + } + private void waitForAll(ZooKeeper[] zks, States state) throws InterruptedException { int iterations = ClientBase.CONNECTION_TIMEOUT / 1000; boolean someoneNotConnected = true; @@ -350,39 +465,78 @@ private void waitForAll(ZooKeeper[] zks, States state) throws InterruptedExcepti private static class Servers { MainThread mt[]; ZooKeeper zk[]; + int[] clientPorts; + + public void shutDownAllServers() throws InterruptedException { + for (MainThread t: mt) { + t.shutdown(); + } + } + + public void restartAllServersAndClients(Watcher watcher) throws IOException { + for (MainThread t : mt) { + if (!t.isAlive()) { + t.start(); + } + } + for (int i = 0; i < zk.length; i++) { + restartClient(i, watcher); + } + } + + public void restartClient(int clientIndex, Watcher watcher) throws IOException { + zk[clientIndex] = new ZooKeeper("127.0.0.1:" + clientPorts[clientIndex], ClientBase.CONNECTION_TIMEOUT, watcher); + } + + public int findLeader() { + for (int i = 0; i < mt.length; i++) { + if (mt[i].main.quorumPeer.leader != null) { + return i; + } + } + return -1; + } + } + + + private Servers LaunchServers(int numServers) throws IOException, InterruptedException { + return LaunchServers(numServers, null); } /** * This is a helper function for launching a set of servers * - * @param numServers + * @param numServers the number of servers + * @param tickTime A ticktime to pass to MainThread * @return * @throws IOException * @throws InterruptedException */ - private Servers LaunchServers(int numServers) throws IOException, InterruptedException { + private Servers LaunchServers(int numServers, Integer tickTime) throws IOException, InterruptedException { int SERVER_COUNT = numServers; Servers svrs = new Servers(); - final int clientPorts[] = new int[SERVER_COUNT]; + svrs.clientPorts = new int[SERVER_COUNT]; StringBuilder sb = new StringBuilder(); for (int i = 0; i < SERVER_COUNT; i++) { - clientPorts[i] = PortAssignment.unique(); - sb.append("server."+i+"=127.0.0.1:"+PortAssignment.unique()+":"+PortAssignment.unique()+";"+clientPorts[i]+"\n"); + svrs.clientPorts[i] = PortAssignment.unique(); + sb.append("server."+i+"=127.0.0.1:"+PortAssignment.unique()+":"+PortAssignment.unique()+";"+svrs.clientPorts[i]+"\n"); } String quorumCfgSection = sb.toString(); - MainThread mt[] = new MainThread[SERVER_COUNT]; - ZooKeeper zk[] = new ZooKeeper[SERVER_COUNT]; - for (int i = 0; i < SERVER_COUNT; i++) { - mt[i] = new MainThread(i, clientPorts[i], quorumCfgSection); - mt[i].start(); - zk[i] = new ZooKeeper("127.0.0.1:" + clientPorts[i], ClientBase.CONNECTION_TIMEOUT, this); + svrs.mt = new MainThread[SERVER_COUNT]; + svrs.zk = new ZooKeeper[SERVER_COUNT]; + for(int i = 0; i < SERVER_COUNT; i++) { + if (tickTime != null) { + svrs.mt[i] = new MainThread(i, svrs.clientPorts[i], quorumCfgSection, new HashMap(), tickTime); + } else { + svrs.mt[i] = new MainThread(i, svrs.clientPorts[i], quorumCfgSection); + } + svrs.mt[i].start(); + svrs.restartClient(i, this); } - waitForAll(zk, States.CONNECTED); + waitForAll(svrs, States.CONNECTED); - svrs.mt = mt; - svrs.zk = zk; return svrs; } @@ -394,11 +548,8 @@ public void testBadPeerAddressInQuorum() throws Exception { 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.setThreshold(Level.WARN); + WriterAppender appender = getConsoleAppender(os, Level.WARN); Logger qlogger = Logger.getLogger("org.apache.zookeeper.server.quorum"); qlogger.addAppender(appender); @@ -453,11 +604,8 @@ public void testInconsistentPeerType() throws Exception { 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.setThreshold(Level.INFO); + WriterAppender appender = getConsoleAppender(os, Level.INFO); Logger qlogger = Logger.getLogger("org.apache.zookeeper.server.quorum"); qlogger.addAppender(appender); @@ -550,7 +698,7 @@ public void testBadPackets() throws Exception { + ":" + electionPort1 + ";" + CLIENT_PORT_QP1 + "\nserver.2=127.0.0.1:" + PortAssignment.unique() + ":" + electionPort2 + ";" + CLIENT_PORT_QP2; - + MainThread q1 = new MainThread(1, CLIENT_PORT_QP1, quorumCfgSection); MainThread q2 = new MainThread(2, CLIENT_PORT_QP2, quorumCfgSection); q1.start(); @@ -596,12 +744,9 @@ public void testQuorumDefaults() throws Exception { 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); + WriterAppender appender = getConsoleAppender(os, Level.INFO); appender.setImmediateFlush(true); - appender.setThreshold(Level.INFO); Logger zlogger = Logger.getLogger("org.apache.zookeeper"); zlogger.addAppender(appender); @@ -671,9 +816,9 @@ 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); @@ -765,4 +910,263 @@ public void testWithOnlyMinSessionTimeout() throws Exception { maxSessionTimeOut, quorumPeer.getMaxSessionTimeout()); } + @Test + public void testFailedTxnAsPartOfQuorumLoss() throws Exception { + final int LEADER_TIMEOUT_MS = 10_000; + // 1. start up server and wait for leader election to finish + ClientBase.setupTestEnv(); + final int SERVER_COUNT = 3; + servers = LaunchServers(SERVER_COUNT); + + waitForAll(servers, States.CONNECTED); + + // we need to shutdown and start back up to make sure that the create session isn't the first transaction since + // that is rather innocuous. + servers.shutDownAllServers(); + waitForAll(servers, States.CONNECTING); + servers.restartAllServersAndClients(this); + waitForAll(servers, States.CONNECTED); + + // 2. kill all followers + int leader = servers.findLeader(); + Map outstanding = servers.mt[leader].main.quorumPeer.leader.outstandingProposals; + // increase the tick time to delay the leader going to looking + servers.mt[leader].main.quorumPeer.tickTime = LEADER_TIMEOUT_MS; + LOG.warn("LEADER {}", leader); + + for (int i = 0; i < SERVER_COUNT; i++) { + if (i != leader) { + servers.mt[i].shutdown(); + } + } + + // 3. start up the followers to form a new quorum + for (int i = 0; i < SERVER_COUNT; i++) { + if (i != leader) { + servers.mt[i].start(); + } + } + + // 4. wait one of the follower to be the new leader + for (int i = 0; i < SERVER_COUNT; i++) { + if (i != leader) { + // Recreate a client session since the previous session was not persisted. + servers.restartClient(i, this); + waitForOne(servers.zk[i], States.CONNECTED); + } + } + + // 5. send a create request to old leader and make sure it's synced to disk, + // which means it acked from itself + try { + servers.zk[leader].create("/zk" + leader, "zk".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + Assert.fail("create /zk" + leader + " should have failed"); + } catch (KeeperException e) { + } + + // just make sure that we actually did get it in process at the + // leader + Assert.assertEquals(1, outstanding.size()); + Proposal p = outstanding.values().iterator().next(); + Assert.assertEquals(OpCode.create, p.request.getHdr().getType()); + + // make sure it has a chance to write it to disk + int sleepTime = 0; + Long longLeader = new Long(leader); + while (!p.qvAcksetPairs.get(0).getAckset().contains(longLeader)) { + if (sleepTime > 2000) { + Assert.fail("Transaction not synced to disk within 1 second " + p.qvAcksetPairs.get(0).getAckset() + + " expected " + leader); + } + Thread.sleep(100); + sleepTime += 100; + } + + // 6. wait for the leader to quit due to not enough followers and come back up as a part of the new quorum + LOG.info("Waiting for leader {} to timeout followers", leader); + sleepTime = 0; + Follower f = servers.mt[leader].main.quorumPeer.follower; + while (f == null || !f.isRunning()) { + if (sleepTime > LEADER_TIMEOUT_MS * 2) { + Assert.fail("Took too long for old leader to time out " + servers.mt[leader].main.quorumPeer.getPeerState()); + } + Thread.sleep(100); + sleepTime += 100; + f = servers.mt[leader].main.quorumPeer.follower; + } + + int newLeader = servers.findLeader(); + // make sure a different leader was elected + Assert.assertNotEquals(leader, newLeader); + + // 7. restart the previous leader to force it to replay the edits and possibly come up in a bad state + servers.mt[leader].shutdown(); + servers.mt[leader].start(); + waitForAll(servers, States.CONNECTED); + + // 8. check the node exist in previous leader but not others + // make sure everything is consistent + for (int i = 0; i < SERVER_COUNT; i++) { + Assert.assertNull("server " + i + " should not have /zk" + leader, servers.zk[i].exists("/zk" + leader, false)); + } + } + + /** + * Verify that a node without the leader in its view will not attempt to connect to the leader. + */ + @Test + public void testLeaderOutOfView() throws Exception { + ClientBase.setupTestEnv(); + + int numServers = 3; + + // used for assertions later + boolean foundLeading = false; + boolean foundFollowing = false; + + // capture QuorumPeer logging + ByteArrayOutputStream os = new ByteArrayOutputStream(); + WriterAppender appender = getConsoleAppender(os, Level.DEBUG); + Logger qlogger = Logger.getLogger("org.apache.zookeeper.server.quorum"); + qlogger.addAppender(appender); + + try { + Servers svrs = new Servers(); + svrs.clientPorts = new int[numServers]; + for (int i = 0; i < numServers; i++) { + svrs.clientPorts[i] = PortAssignment.unique(); + } + + String quorumCfgIncomplete = getUniquePortCfgForId(1) + "\n" + getUniquePortCfgForId(2); + String quorumCfgComplete = quorumCfgIncomplete + "\n" + getUniquePortCfgForId(3); + svrs.mt = new MainThread[3]; + + // Node 1 is started without the leader (3) in its config view + svrs.mt[0] = new MainThread(1, svrs.clientPorts[0], quorumCfgIncomplete); + for (int i = 1; i < numServers; i++) { + svrs.mt[i] = new MainThread(i + 1, svrs.clientPorts[i], quorumCfgComplete); + } + + // Node 1 must be started first, before quorum is formed, to trigger the attempted invalid connection to 3 + svrs.mt[0].start(); + QuorumPeer quorumPeer1 = waitForQuorumPeer(svrs.mt[0], CONNECTION_TIMEOUT); + Assert.assertTrue(quorumPeer1.getPeerState() == QuorumPeer.ServerState.LOOKING); + + // Node 3 started second to avoid 1 and 2 forming a quorum before 3 starts up + int highestServerIndex = numServers - 1; + svrs.mt[highestServerIndex].start(); + QuorumPeer quorumPeer3 = waitForQuorumPeer(svrs.mt[highestServerIndex], CONNECTION_TIMEOUT); + Assert.assertTrue(quorumPeer3.getPeerState() == QuorumPeer.ServerState.LOOKING); + + // Node 2 started last, kicks off leader election + for (int i = 1; i < highestServerIndex; i++) { + svrs.mt[i].start(); + } + + // Nodes 2 and 3 now form quorum and fully start. 1 attempts to vote for 3, fails, returns to LOOKING state + for (int i = 1; i < numServers; i++) { + Assert.assertTrue("waiting for server to start", + ClientBase.waitForServerUp("127.0.0.1:" + svrs.clientPorts[i], CONNECTION_TIMEOUT)); + } + + Assert.assertTrue(svrs.mt[0].getQuorumPeer().getPeerState() == QuorumPeer.ServerState.LOOKING); + Assert.assertTrue(svrs.mt[highestServerIndex].getQuorumPeer().getPeerState() == QuorumPeer.ServerState.LEADING); + for (int i = 1; i < highestServerIndex; i++) { + Assert.assertTrue(svrs.mt[i].getQuorumPeer().getPeerState() == QuorumPeer.ServerState.FOLLOWING); + } + + // Look through the logs for output that indicates Node 1 is LEADING or FOLLOWING + LineNumberReader r = new LineNumberReader(new StringReader(os.toString())); + Pattern leading = Pattern.compile(".*myid=1.*QuorumPeer.*LEADING.*"); + Pattern following = Pattern.compile(".*myid=1.*QuorumPeer.*FOLLOWING.*"); + + String line; + while ((line = r.readLine()) != null && !foundLeading && !foundFollowing) { + foundLeading = leading.matcher(line).matches(); + foundFollowing = following.matcher(line).matches(); + } + + } finally { + qlogger.removeAppender(appender); + } + + Assert.assertFalse("Corrupt peer should never become leader", foundLeading); + Assert.assertFalse("Corrupt peer should not attempt connection to out of view leader", foundFollowing); + } + + @Test + public void testDataDirAndDataLogDir() throws Exception { + File dataDir = createEmptyTestDir(); + File dataLogDir = createEmptyTestDir(); + + // Arrange + try { + QuorumPeerConfig configMock = mock(QuorumPeerConfig.class); + when(configMock.getDataDir()).thenReturn(dataDir); + when(configMock.getDataLogDir()).thenReturn(dataLogDir); + + QuorumPeer qpMock = mock(QuorumPeer.class); + + doCallRealMethod().when(qpMock).setTxnFactory(any(FileTxnSnapLog.class)); + when(qpMock.getTxnFactory()).thenCallRealMethod(); + InjectableQuorumPeerMain qpMain = new InjectableQuorumPeerMain(qpMock); + + // Act + qpMain.runFromConfig(configMock); + + // Assert + FileTxnSnapLog txnFactory = qpMain.getQuorumPeer().getTxnFactory(); + Assert.assertEquals(Paths.get(dataLogDir.getAbsolutePath(), "version-2").toString(), txnFactory.getDataDir().getAbsolutePath()); + Assert.assertEquals(Paths.get(dataDir.getAbsolutePath(), "version-2").toString(), txnFactory.getSnapDir().getAbsolutePath()); + } finally { + FileUtils.deleteDirectory(dataDir); + FileUtils.deleteDirectory(dataLogDir); + } + } + + private class InjectableQuorumPeerMain extends QuorumPeerMain { + QuorumPeer qp; + + InjectableQuorumPeerMain(QuorumPeer qp) { + this.qp = qp; + } + + @Override + protected QuorumPeer getQuorumPeer() { + return qp; + } + } + + private WriterAppender getConsoleAppender(ByteArrayOutputStream os, Level level) { + String loggingPattern = ((PatternLayout) Logger.getRootLogger().getAppender("CONSOLE").getLayout()).getConversionPattern(); + WriterAppender appender = new WriterAppender(new PatternLayout(loggingPattern), os); + appender.setThreshold(level); + return appender; + } + + private String getUniquePortCfgForId(int id) { + return String.format("server.%d=127.0.0.1:%d:%d", id, PortAssignment.unique(), PortAssignment.unique()); + } + + private QuorumPeer waitForQuorumPeer(MainThread mainThread, int timeout) throws TimeoutException { + long start = Time.currentElapsedTime(); + while (true) { + QuorumPeer quorumPeer = mainThread.isAlive() ? mainThread.getQuorumPeer() : null; + if (quorumPeer != null) { + return quorumPeer; + } + + if (Time.currentElapsedTime() > start + timeout) { + LOG.error("Timed out while waiting for QuorumPeer"); + throw new TimeoutException(); + } + + try { + Thread.sleep(250); + } catch (InterruptedException e) { + // ignore + } + } + } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerTest.java b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerTest.java new file mode 100644 index 00000000000..fae7e5bb121 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerTest.java @@ -0,0 +1,87 @@ +/** + * 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.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; + +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.server.quorum.QuorumPeer.LearnerType; +import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.apache.zookeeper.test.ClientBase; +import org.junit.Test; + +public class QuorumPeerTest { + + private int electionAlg = 3; + private int tickTime = 2000; + private int initLimit = 3; + private int syncLimit = 3; + + /** + * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2301 + */ + @Test + public void testQuorumPeerListendOnSpecifiedClientIP() throws IOException { + long myId = 1; + File dataDir = ClientBase.createTmpDir(); + int clientPort = PortAssignment.unique(); + Map peersView = new HashMap(); + InetAddress clientIP = InetAddress.getLoopbackAddress(); + + peersView.put(Long.valueOf(myId), + new QuorumServer(myId, new InetSocketAddress(clientIP, PortAssignment.unique()), + new InetSocketAddress(clientIP, PortAssignment.unique()), + new InetSocketAddress(clientIP, clientPort), LearnerType.PARTICIPANT)); + + /** + * QuorumPeer constructor without QuorumVerifier + */ + QuorumPeer peer1 = new QuorumPeer(peersView, dataDir, dataDir, clientPort, electionAlg, myId, tickTime, + initLimit, syncLimit); + String hostString1 = peer1.cnxnFactory.getLocalAddress().getHostString(); + assertEquals(clientIP.getHostAddress(), hostString1); + + // cleanup + peer1.shutdown(); + + /** + * QuorumPeer constructor with QuorumVerifier + */ + peersView.clear(); + clientPort = PortAssignment.unique(); + peersView.put(Long.valueOf(myId), + new QuorumServer(myId, new InetSocketAddress(clientIP, PortAssignment.unique()), + new InetSocketAddress(clientIP, PortAssignment.unique()), + new InetSocketAddress(clientIP, clientPort), LearnerType.PARTICIPANT)); + QuorumPeer peer2 = new QuorumPeer(peersView, dataDir, dataDir, clientPort, electionAlg, myId, tickTime, + initLimit, syncLimit); + String hostString2 = peer2.cnxnFactory.getLocalAddress().getHostString(); + assertEquals(clientIP.getHostAddress(), hostString2); + // cleanup + peer2.shutdown(); + } + +} 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 89416ca12b6..ffc00f39a20 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerTestBase.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerTestBase.java @@ -22,8 +22,15 @@ package org.apache.zookeeper.server.quorum; import java.io.File; +import java.io.FileReader; import java.io.FileWriter; +import java.io.FilenameFilter; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +38,7 @@ import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.common.PathUtils; +import org.apache.zookeeper.server.admin.AdminServer.AdminServerException; import org.apache.zookeeper.server.admin.JettyAdminServer; import org.apache.zookeeper.test.ClientBase; import org.apache.zookeeper.test.QuorumBase; @@ -43,6 +51,8 @@ public class QuorumPeerTestBase extends ZKTestCase implements Watcher { protected static final Logger LOG = LoggerFactory .getLogger(QuorumPeerTestBase.class); + public static final int TIMEOUT = 5000; + public void process(WatchedEvent event) { // ignore for this test } @@ -58,14 +68,91 @@ public void shutdown() { public static class MainThread implements Runnable { final File confFile; - final File dynamicConfigFile; final File tmpDir; - + + public static final int UNSET_STATIC_CLIENTPORT = -1; + // standalone mode doens't need myid + public static final int UNSET_MYID = -1; + volatile TestQPMain main; - public MainThread(int myid, int clientPort, String quorumCfgSection) + File baseDir; + private int myid; + private int clientPort; + private String quorumCfgSection; + private Map otherConfigs; + + /** + * Create a MainThread + * + * @param myid + * @param clientPort + * @param quorumCfgSection + * @param otherConfigs + * @param tickTime initLimit will be 10 and syncLimit will be 5 + * @throws IOException + */ + public MainThread(int myid, int clientPort, String quorumCfgSection, + Map otherConfigs, int tickTime) 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=" + tickTime + "\n"); + fwriter.write("initLimit=10\n"); + fwriter.write("syncLimit=5\n"); + + tmpDir = new File(baseDir, "data"); + if (!tmpDir.mkdir()) { + throw new IOException("Unable to mkdir " + tmpDir); + } + + // Convert windows path to UNIX to avoid problems with "\" + String dir = tmpDir.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"); + + // 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(); + + File myidFile = new File(tmpDir, "myid"); + fwriter = new FileWriter(myidFile); + fwriter.write(Integer.toString(myid)); + fwriter.flush(); + fwriter.close(); + } + + public MainThread(int myid, String quorumCfgSection) throws IOException { + this(myid, quorumCfgSection, true); + } + + public MainThread(int myid, String quorumCfgSection, Integer secureClientPort, boolean writeDynamicConfigFile) + throws IOException { + this(myid, UNSET_STATIC_CLIENTPORT, JettyAdminServer.DEFAULT_PORT, secureClientPort, + quorumCfgSection, null, writeDynamicConfigFile, null); + } + + public MainThread(int myid, String quorumCfgSection, boolean writeDynamicConfigFile) throws IOException { - this(myid, clientPort, JettyAdminServer.DEFAULT_PORT, quorumCfgSection, null, true); + this(myid, UNSET_STATIC_CLIENTPORT, quorumCfgSection, writeDynamicConfigFile); } public MainThread(int myid, int clientPort, String quorumCfgSection, boolean writeDynamicConfigFile) @@ -73,6 +160,12 @@ public MainThread(int myid, int clientPort, String quorumCfgSection, boolean wri this(myid, clientPort, JettyAdminServer.DEFAULT_PORT, quorumCfgSection, null, writeDynamicConfigFile); } + public MainThread(int myid, int clientPort, String quorumCfgSection, boolean writeDynamicConfigFile, + String version) throws IOException { + this(myid, clientPort, JettyAdminServer.DEFAULT_PORT, quorumCfgSection, null, + writeDynamicConfigFile, version); + } + public MainThread(int myid, int clientPort, String quorumCfgSection, String configs) throws IOException { this(myid, clientPort, JettyAdminServer.DEFAULT_PORT, quorumCfgSection, configs, true); @@ -86,6 +179,17 @@ public MainThread(int myid, int clientPort, int adminServerPort, String quorumCf public MainThread(int myid, int clientPort, int adminServerPort, String quorumCfgSection, String configs, boolean writeDynamicConfigFile) throws IOException { + this(myid, clientPort, adminServerPort, quorumCfgSection, configs, writeDynamicConfigFile, null); + } + + public MainThread(int myid, int clientPort, int adminServerPort, String quorumCfgSection, + String configs, boolean writeDynamicConfigFile, String version) throws IOException { + this(myid, clientPort, adminServerPort, null, quorumCfgSection, configs, writeDynamicConfigFile, version); + } + + public MainThread(int myid, int clientPort, int adminServerPort, Integer secureClientPort, + String quorumCfgSection, String configs, boolean writeDynamicConfigFile, String version) + throws IOException { tmpDir = ClientBase.createTmpDir(); LOG.info("id = " + myid + " tmpDir = " + tmpDir + " clientPort = " + clientPort + " adminServerPort = " + adminServerPort); @@ -96,7 +200,6 @@ public MainThread(int myid, int clientPort, int adminServerPort, String quorumCf } confFile = new File(tmpDir, "zoo.cfg"); - dynamicConfigFile = new File(tmpDir, "zoo.cfg.dynamic"); FileWriter fwriter = new FileWriter(confFile); fwriter.write("tickTime=4000\n"); @@ -110,20 +213,24 @@ public MainThread(int myid, int clientPort, int adminServerPort, String quorumCf String dir = PathUtils.normalizeFileSystemPath(dataDir.toString()); fwriter.write("dataDir=" + dir + "\n"); + fwriter.write("admin.serverPort=" + adminServerPort + "\n"); - fwriter.write("clientPort=" + clientPort + "\n"); + // For backward compatibility test, some tests create dynamic configuration + // without setting client port. + // This could happen both in static file or dynamic file. + if (clientPort != UNSET_STATIC_CLIENTPORT) { + fwriter.write("clientPort=" + clientPort + "\n"); + } - fwriter.write("admin.serverPort=" + adminServerPort + "\n"); + if (secureClientPort != null) { + fwriter.write("secureClientPort=" + secureClientPort + "\n"); + } if (writeDynamicConfigFile) { - String dynamicConfigFilename = PathUtils.normalizeFileSystemPath(dynamicConfigFile.toString()); + String dynamicConfigFilename = createDynamicFile(quorumCfgSection, version); fwriter.write("dynamicConfigFile=" + dynamicConfigFilename + "\n"); - FileWriter fDynamicConfigWriter = new FileWriter(dynamicConfigFile); - fDynamicConfigWriter.write(quorumCfgSection + "\n"); - fDynamicConfigWriter.flush(); - fDynamicConfigWriter.close(); } else { - fwriter.write(quorumCfgSection + "\n"); + fwriter.write(quorumCfgSection); } fwriter.flush(); fwriter.close(); @@ -135,23 +242,76 @@ public MainThread(int myid, int clientPort, int adminServerPort, String quorumCf fwriter.close(); } - public void writeTempDynamicConfigFile(String nextQuorumCfgSection) + private String createDynamicFile(String quorumCfgSection, String version) throws IOException { - File nextDynamicConfigFile = new File(tmpDir, "zoo.cfg.dynamic.next"); + String filename = "zoo.cfg.dynamic"; + if( version != null ){ + filename = filename + "." + version; + } + + File dynamicConfigFile = new File(tmpDir, filename); + String dynamicConfigFilename = PathUtils.normalizeFileSystemPath(dynamicConfigFile.toString()); + + FileWriter fDynamicConfigWriter = new FileWriter(dynamicConfigFile); + fDynamicConfigWriter.write(quorumCfgSection); + fDynamicConfigWriter.flush(); + fDynamicConfigWriter.close(); + + return dynamicConfigFilename; + } + + public File[] getDynamicFiles() { + return getFilesWithPrefix("zoo.cfg.dynamic"); + } + + public File[] getFilesWithPrefix(final String prefix) { + return tmpDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.startsWith(prefix); + }}); + } + + public File getFileByName(String filename) { + File f = new File(tmpDir.getPath(), filename); + return f.isFile() ? f : null; + } + + public void writeTempDynamicConfigFile(String nextQuorumCfgSection, String version) + throws IOException { + File nextDynamicConfigFile = new File(tmpDir, + "zoo.cfg" + QuorumPeerConfig.nextDynamicConfigFileSuffix); FileWriter fwriter = new FileWriter(nextDynamicConfigFile); - fwriter.write(nextQuorumCfgSection + "\n"); + fwriter.write(nextQuorumCfgSection + + "\n" + + "version=" + version); fwriter.flush(); fwriter.close(); } + public MainThread(int myid, int clientPort, String quorumCfgSection) + throws IOException { + this(myid, clientPort, quorumCfgSection, + new HashMap()); + } + + public MainThread(int myid, int clientPort, String quorumCfgSection, + Map otherConfigs) throws IOException { + this(myid, clientPort, quorumCfgSection, otherConfigs, 4000); + } + Thread currentThread; synchronized public void start() { - main = new TestQPMain(); + main = getTestQPMain(); currentThread = new Thread(this); currentThread.start(); } + public TestQPMain getTestQPMain() { + return new TestQPMain(); + } + public void run() { String args[] = new String[1]; args[0] = confFile.toString(); @@ -193,5 +353,41 @@ public void clean() { public boolean isQuorumPeerRunning() { return main.quorumPeer != null; } + + public String getPropFromStaticFile(String key) throws IOException { + Properties props = new Properties(); + props.load(new FileReader(confFile)); + return props.getProperty(key, ""); + } + + 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; + } + } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/QuorumRequestPipelineTest.java b/src/java/test/org/apache/zookeeper/server/quorum/QuorumRequestPipelineTest.java index 64d10dd788b..e1651fbc331 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/QuorumRequestPipelineTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/QuorumRequestPipelineTest.java @@ -38,8 +38,10 @@ import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; import org.apache.zookeeper.test.QuorumBase; +import org.apache.zookeeper.ZKParameterized; @RunWith(Parameterized.class) +@Parameterized.UseParametersRunnerFactory(ZKParameterized.RunnerFactory.class) public class QuorumRequestPipelineTest extends QuorumBase { protected ServerState serverState; protected final CountDownLatch callComplete = new CountDownLatch(1); diff --git a/src/java/test/org/apache/zookeeper/server/quorum/QuorumServerTest.java b/src/java/test/org/apache/zookeeper/server/quorum/QuorumServerTest.java index c75835456dc..78c16afd87e 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/QuorumServerTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/QuorumServerTest.java @@ -16,6 +16,8 @@ */ package org.apache.zookeeper.server.quorum; +import static org.junit.Assert.assertEquals; + import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; @@ -57,4 +59,28 @@ public void testToString() throws ConfigException { qs = new QuorumServer(0, config); Assert.assertEquals("Use hostname", expected, qs.toString()); } + + @Test + public void constructionUnderstandsIpv6LiteralsInServerConfig() throws ConfigException { + String config = "[::1]:1234:1236:participant"; + QuorumServer qs = new QuorumServer(0, config); + assertEquals("[0:0:0:0:0:0:0:1]:1234:1236:participant", qs.toString()); + } + + @Test + public void constructionUnderstandsIpv6LiteralsInClientConfig() throws ConfigException { + String config = "127.0.0.1:1234:1236:participant;[::1]:1237"; + QuorumServer qs = new QuorumServer(0, config); + assertEquals("127.0.0.1:1234:1236:participant;[0:0:0:0:0:0:0:1]:1237", qs.toString()); + } + + @Test(expected = ConfigException.class) + public void unbalancedIpv6LiteralsInServerConfigFailToBeParsed() throws ConfigException { + new QuorumServer(0, "[::1:1234:1236:participant"); + } + + @Test(expected = ConfigException.class) + public void unbalancedIpv6LiteralsInClientConfigFailToBeParsed() throws ConfigException { + new QuorumServer(0, "127.0.0.1:1234:1236:participant;[::1:1237"); + } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/RaceConditionTest.java b/src/java/test/org/apache/zookeeper/server/quorum/RaceConditionTest.java new file mode 100644 index 00000000000..e96d2736581 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/RaceConditionTest.java @@ -0,0 +1,251 @@ +/** + * 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.assertTrue; + +import java.io.IOException; +import java.net.SocketException; +import java.nio.ByteBuffer; + +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.server.FinalRequestProcessor; +import org.apache.zookeeper.server.PrepRequestProcessor; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.server.RequestProcessor; +import org.apache.zookeeper.server.SyncRequestProcessor; +import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +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.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.sasl.SaslException; + +/** + * This test class contains test cases related to race condition in complete + * ZooKeeper + */ +public class RaceConditionTest extends QuorumPeerTestBase { + protected static final Logger LOG = LoggerFactory.getLogger(RaceConditionTest.class); + private static int SERVER_COUNT = 3; + private MainThread[] mt; + + /** + * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2380. + * Deadlock while shutting down the ZooKeeper + */ + + @Test(timeout = 30000) + public void testRaceConditionBetweenLeaderAndAckRequestProcessor() throws Exception { + mt = startQuorum(); + // get leader + QuorumPeer leader = getLeader(mt); + long oldLeaderCurrentEpoch = leader.getCurrentEpoch(); + assertNotNull("Leader should not be null", leader); + // shutdown 2 followers so that leader does not have majority and goes + // into looking state or following/leading state. + shutdownFollowers(mt); + /** + *

    +         * Verify that there is no deadlock in following ways:
    +         * 1) If leader is in LOOKING or FOLLOWING, we are sure there is no deadlock.
    +         * 2) If leader in in LEADING state then we have to check that this LEADING state is
    +         * after the leader election, not the old LEADING state.
    +         * 
    + */ + boolean leaderStateChanged = ClientBase.waitForServerState(leader, 15000, + QuorumStats.Provider.LOOKING_STATE, QuorumStats.Provider.FOLLOWING_STATE); + // Wait for the old leader to start completely + Assert.assertTrue("Failed to bring up the old leader server", ClientBase + .waitForServerUp("127.0.0.1:" + leader.getClientPort(), CONNECTION_TIMEOUT)); + assertTrue( + "Leader failed to transition to new state. Current state is " + + leader.getServerState(), + leaderStateChanged || (leader.getCurrentEpoch() > oldLeaderCurrentEpoch)); + } + + @After + public void tearDown() { + // stop all severs + if (null != mt) { + for (int i = 0; i < SERVER_COUNT; i++) { + try { + // With the defect, leader hangs here also, but with fix + // it does not + mt[i].shutdown(); + } catch (InterruptedException e) { + LOG.warn("Quorum Peer interrupted while shutting it down", e); + } + } + } + } + + private MainThread[] startQuorum() throws IOException { + 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() + + ":participant;127.0.0.1:" + clientPorts[i]; + sb.append(server + "\n"); + } + String currentQuorumCfgSection = sb.toString(); + MainThread mt[] = new MainThread[SERVER_COUNT]; + + // start all the servers + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, false) { + @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)); + } + return mt; + } + + private QuorumPeer getLeader(MainThread[] mt) { + for (int i = mt.length - 1; i >= 0; i--) { + QuorumPeer quorumPeer = mt[i].getQuorumPeer(); + if (quorumPeer != null && ServerState.LEADING == quorumPeer.getPeerState()) { + return quorumPeer; + } + } + return null; + } + + private void shutdownFollowers(MainThread[] mt) { + for (int i = 0; i < mt.length; i++) { + CustomQuorumPeer quorumPeer = (CustomQuorumPeer) mt[i].getQuorumPeer(); + if (quorumPeer != null && ServerState.FOLLOWING == quorumPeer.getPeerState()) { + quorumPeer.setStopPing(true); + } + } + } + + private static class CustomQuorumPeer extends QuorumPeer { + private boolean stopPing; + + public CustomQuorumPeer() throws SaslException { + } + + public void setStopPing(boolean stopPing) { + this.stopPing = stopPing; + } + + @Override + protected Follower makeFollower(FileTxnSnapLog logFactory) throws IOException { + + return new Follower(this, new FollowerZooKeeperServer(logFactory, this, this.getZkDb())) { + @Override + protected void processPacket(QuorumPacket qp) throws Exception { + if (stopPing && qp.getType() == Leader.PING) { + LOG.info("Follower skipped ping"); + throw new SocketException("Socket time out while sending the ping response"); + } else { + super.processPacket(qp); + } + } + }; + } + + @Override + protected Leader makeLeader(FileTxnSnapLog logFactory) throws IOException { + LeaderZooKeeperServer zk = new LeaderZooKeeperServer(logFactory, this, this.getZkDb()) { + @Override + protected void setupRequestProcessors() { + /** + * This method is overridden to make a place to inject + * MockSyncRequestProcessor + */ + RequestProcessor finalProcessor = new FinalRequestProcessor(this); + RequestProcessor toBeAppliedProcessor = new Leader.ToBeAppliedRequestProcessor(finalProcessor, + getLeader()); + commitProcessor = new CommitProcessor(toBeAppliedProcessor, Long.toString(getServerId()), false, + getZooKeeperServerListener()); + commitProcessor.start(); + ProposalRequestProcessor proposalProcessor = new MockProposalRequestProcessor(this, + commitProcessor); + proposalProcessor.initialize(); + prepRequestProcessor = new PrepRequestProcessor(this, proposalProcessor); + prepRequestProcessor.start(); + firstProcessor = new LeaderRequestProcessor(this, prepRequestProcessor); + } + + }; + return new Leader(this, zk); + } + } + + private static class MockSyncRequestProcessor extends SyncRequestProcessor { + + public MockSyncRequestProcessor(ZooKeeperServer zks, RequestProcessor nextProcessor) { + super(zks, nextProcessor); + } + + @Override + public void shutdown() { + /** + * Add a request so that something is there for SyncRequestProcessor + * to process, while we are in shutdown flow + */ + Request request = new Request(null, 0, 0, ZooDefs.OpCode.delete, + ByteBuffer.wrap("/deadLockIssue".getBytes()), null); + processRequest(request); + super.shutdown(); + } + } + + private static class MockProposalRequestProcessor extends ProposalRequestProcessor { + public MockProposalRequestProcessor(LeaderZooKeeperServer zks, RequestProcessor nextProcessor) { + super(zks, nextProcessor); + + /** + * The only purpose here is to inject the mocked + * SyncRequestProcessor + */ + AckRequestProcessor ackProcessor = new AckRequestProcessor(zks.getLeader()); + syncProcessor = new MockSyncRequestProcessor(zks, ackProcessor); + } + } + + private static class MockTestQPMain extends TestQPMain { + + @Override + protected QuorumPeer getQuorumPeer() throws SaslException { + return new CustomQuorumPeer(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigBackupTest.java b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigBackupTest.java new file mode 100644 index 00000000000..8bc04bd6ec7 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigBackupTest.java @@ -0,0 +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.server.quorum; + +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.admin.ZooKeeperAdmin; +import org.apache.zookeeper.common.StringUtils; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ReconfigTest; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import java.util.Scanner; + +import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; + +public class ReconfigBackupTest extends QuorumPeerTestBase { + + public static String getVersionFromConfigStr(String config) throws IOException { + Properties props = new Properties(); + props.load(new StringReader(config)); + return props.getProperty("version", ""); + } + + // upgrade this once we have Google-Guava or Java 7+ + public static String getFileContent(File file) throws FileNotFoundException { + Scanner sc = new Scanner(file); + StringBuilder sb = new StringBuilder(); + while (sc.hasNextLine()) { + sb.append(sc.nextLine() + "\n"); + } + return sb.toString(); + } + + @Before + public void setup() { + ClientBase.setupTestEnv(); + System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", + "super:D/InIHSb7yEEbrWz8b9l71RjZJU="/* password is 'test'*/); + } + + /** + * This test checks that it will backup static file on bootup. + */ + @Test + public void testBackupStatic() throws Exception { + final 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 + "=localhost:" + PortAssignment.unique() + + ":" + PortAssignment.unique() + ":participant;localhost:" + + clientPorts[i]; + sb.append(server + "\n"); + } + + String currentQuorumCfgSection = sb.toString(); + + MainThread mt[] = new MainThread[SERVER_COUNT]; + String[] staticFileContent = new String[SERVER_COUNT]; + String[] staticBackupContent = new String[SERVER_COUNT]; + + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, false); + // check that a dynamic configuration file doesn't exist + Assert.assertNull("static file backup shouldn't exist before bootup", + mt[i].getFileByName("zoo.cfg.bak")); + staticFileContent[i] = getFileContent(mt[i].confFile); + mt[i].start(); + } + + 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)); + File backupFile = mt[i].getFileByName("zoo.cfg.bak"); + Assert.assertNotNull("static file backup should exist", backupFile); + staticBackupContent[i] = getFileContent(backupFile); + Assert.assertEquals(staticFileContent[i], staticBackupContent[i]); + } + + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i].shutdown(); + } + } + + /** + * This test checks that on reconfig, a new dynamic file will be created with + * current version appended to file name. Meanwhile, the dynamic file pointer + * in static config file should also be changed. + */ + @Test + public void testReconfigCreateNewVersionFile() throws Exception { + final int SERVER_COUNT = 3; + final int NEW_SERVER_COUNT = 5; + + final int clientPorts[] = new int[NEW_SERVER_COUNT]; + final int quorumPorts[] = new int[NEW_SERVER_COUNT]; + final int electionPorts[] = new int[NEW_SERVER_COUNT]; + final String servers[] = new String[NEW_SERVER_COUNT]; + + StringBuilder sb = new StringBuilder(); + ArrayList oldServers = new ArrayList(); + ArrayList newServers = new ArrayList(); + + for (int i = 0; i < NEW_SERVER_COUNT; i++) { + clientPorts[i] = PortAssignment.unique(); + quorumPorts[i] = PortAssignment.unique(); + electionPorts[i] = PortAssignment.unique(); + servers[i] = "server." + i + "=localhost:" + quorumPorts[i] + + ":" + electionPorts[i] + ":participant;localhost:" + + clientPorts[i]; + + newServers.add(servers[i]); + + if (i >= SERVER_COUNT) { + continue; + } + oldServers.add(servers[i]); + sb.append(servers[i] + "\n"); + } + + String quorumCfgSection = sb.toString(); + + MainThread mt[] = new MainThread[NEW_SERVER_COUNT]; + ZooKeeper zk[] = new ZooKeeper[NEW_SERVER_COUNT]; + ZooKeeperAdmin zkAdmin[] = new ZooKeeperAdmin[NEW_SERVER_COUNT]; + + // start old cluster + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, clientPorts[i], quorumCfgSection, "reconfigEnabled=true\n"); + mt[i].start(); + } + + String firstVersion = null, secondVersion = null; + + // test old cluster + 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)); + zk[i] = ClientBase.createZKClient("127.0.0.1:" + clientPorts[i]); + zkAdmin[i] = new ZooKeeperAdmin("127.0.0.1:" + clientPorts[i], + ClientBase.CONNECTION_TIMEOUT, this); + zkAdmin[i].addAuthInfo("digest", "super:test".getBytes()); + + Properties cfg = ReconfigLegacyTest.readPropertiesFromFile(mt[i].confFile); + String filename = cfg.getProperty("dynamicConfigFile", ""); + + String version = QuorumPeerConfig.getVersionFromFilename(filename); + Assert.assertNotNull(version); + + String configStr = ReconfigTest.testServerHasConfig( + zk[i], oldServers, null); + + String configVersion = getVersionFromConfigStr(configStr); + // the version appended to filename should be the same as + // the one of quorum verifier. + Assert.assertEquals(version, configVersion); + + if (i == 0) { + firstVersion = version; + } else { + Assert.assertEquals(firstVersion, version); + } + } + + ReconfigTest.reconfig(zkAdmin[1], null, null, newServers, -1); + + // start additional new servers + for (int i = SERVER_COUNT; i < NEW_SERVER_COUNT; i++) { + mt[i] = new MainThread(i, clientPorts[i], quorumCfgSection + servers[i]); + mt[i].start(); + } + + // wait for new servers to be up running + for (int i = SERVER_COUNT; i < NEW_SERVER_COUNT; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], + CONNECTION_TIMEOUT)); + zk[i] = ClientBase.createZKClient("127.0.0.1:" + clientPorts[i]); + } + + // test that all servers have: + // a different, larger version dynamic file + for (int i = 0; i < NEW_SERVER_COUNT; i++) { + Properties cfg = ReconfigLegacyTest.readPropertiesFromFile(mt[i].confFile); + String filename = cfg.getProperty("dynamicConfigFile", ""); + + String version = QuorumPeerConfig.getVersionFromFilename(filename); + Assert.assertNotNull(version); + + String configStr = ReconfigTest.testServerHasConfig(zk[i], + newServers, null); + + String quorumVersion = getVersionFromConfigStr(configStr); + Assert.assertEquals(version, quorumVersion); + + if (i == 0) { + secondVersion = version; + Assert.assertTrue( + Long.parseLong(secondVersion, 16) + > Long.parseLong(firstVersion, 16)); + } else { + Assert.assertEquals(secondVersion, version); + } + } + + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i].shutdown(); + zk[i].close(); + zkAdmin[i].close(); + } + } + + /** + * This test checks that if a version is appended to dynamic file, + * then peer should use that version as quorum config version. + *

    + * The scenario: one server has an older version of 3 servers, and + * four others have newer version of 5 servers. Finally, the lag-off one + * should have server config of 5 servers. + */ + @Test + public void testVersionOfDynamicFilename() throws Exception { + final int SERVER_COUNT = 5; + final int oldServerCount = 3; + final int lagOffServerId = 0; + final int clientPorts[] = new int[SERVER_COUNT]; + StringBuilder sb = new StringBuilder(); + String server; + StringBuilder oldSb = new StringBuilder(); + ArrayList allServers = new ArrayList(); + + + for (int i = 0; i < SERVER_COUNT; i++) { + clientPorts[i] = PortAssignment.unique(); + server = "server." + i + "=localhost:" + PortAssignment.unique() + + ":" + PortAssignment.unique() + ":participant;localhost:" + + clientPorts[i]; + sb.append(server + "\n"); + allServers.add(server); + + if (i < oldServerCount) { + // only take in the first 3 servers as old quorum config. + oldSb.append(server + "\n"); + } + } + + String currentQuorumCfgSection = sb.toString(); + + String oldQuorumCfg = oldSb.toString(); + + MainThread mt[] = new MainThread[SERVER_COUNT]; + + + for (int i = 0; i < SERVER_COUNT; i++) { + if (i == lagOffServerId) { + mt[i] = new MainThread(i, clientPorts[i], oldQuorumCfg, true, "100000000"); + } else { + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, + true, "200000000"); + } + + // before connecting to quorum, servers should have set up dynamic file + // version and pointer. And the lag-off server is using the older + // version dynamic file. + if (i == lagOffServerId) { + Assert.assertNotNull( + mt[i].getFileByName("zoo.cfg.dynamic.100000000")); + Assert.assertNull( + mt[i].getFileByName("zoo.cfg.dynamic.200000000")); + Assert.assertTrue( + mt[i].getPropFromStaticFile("dynamicConfigFile") + .endsWith(".100000000")); + } else { + Assert.assertNotNull( + mt[i].getFileByName("zoo.cfg.dynamic.200000000")); + Assert.assertTrue( + mt[i].getPropFromStaticFile("dynamicConfigFile") + .endsWith(".200000000")); + } + + mt[i].start(); + } + + String dynamicFileContent = null; + + 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)); + ZooKeeper zk = ClientBase.createZKClient("127.0.0.1:" + clientPorts[i]); + + // we should see that now all servers have the same config of 5 servers + // including the lag-off server. + String configStr = ReconfigTest.testServerHasConfig(zk, allServers, null); + Assert.assertEquals("200000000", getVersionFromConfigStr(configStr)); + + List configLines = Arrays.asList(configStr.split("\n")); + Collections.sort(configLines); + String sortedConfigStr = StringUtils.joinStrings(configLines, "\n"); + + File dynamicConfigFile = mt[i].getFileByName("zoo.cfg.dynamic.200000000"); + Assert.assertNotNull(dynamicConfigFile); + + // All dynamic files created with the same version should have + // same configs, and they should be equal to the config we get from QuorumPeer. + if (i == 0) { + dynamicFileContent = getFileContent(dynamicConfigFile); + Assert.assertEquals(sortedConfigStr, dynamicFileContent + + "version=200000000"); + } else { + String otherDynamicFileContent = getFileContent(dynamicConfigFile); + Assert.assertEquals(dynamicFileContent, otherDynamicFileContent); + } + + zk.close(); + } + + // finally, we should also check that the lag-off server has updated + // the dynamic file pointer. + Assert.assertTrue( + mt[lagOffServerId].getPropFromStaticFile("dynamicConfigFile") + .endsWith(".200000000")); + + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i].shutdown(); + } + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigDuringLeaderSyncTest.java b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigDuringLeaderSyncTest.java new file mode 100644 index 00000000000..f350abf05ad --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigDuringLeaderSyncTest.java @@ -0,0 +1,269 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.zookeeper.server.quorum; + +import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.admin.ZooKeeperAdmin; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.admin.AdminServer.AdminServerException; +import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.server.quorum.flexible.QuorumMaj; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +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 ReconfigDuringLeaderSyncTest extends QuorumPeerTestBase { + protected static final Logger LOG = LoggerFactory.getLogger(ReconfigDuringLeaderSyncTest.class); + private static int SERVER_COUNT = 3; + private MainThread[] mt; + + @Before + public void setup() { + System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", + "super:D/InIHSb7yEEbrWz8b9l71RjZJU="/* password is 'test'*/); + QuorumPeerConfig.setReconfigEnabled(true); + } + + /** + *

    +     * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2172.
    +     * Cluster crashes when reconfig a new node as a participant.
    +     * 
    + * + * This issue occurs when reconfig's PROPOSAL and COMMITANDACTIVATE come in + * between the snapshot and the UPTODATE. In this case processReconfig was + * not invoked on the newly added node, and zoo.cfg.dynamic.next wasn't + * deleted. + */ + + @Test + public void testDuringLeaderSync() throws Exception { + final int clientPorts[] = new int[SERVER_COUNT + 1]; + StringBuilder sb = new StringBuilder(); + String[] serverConfig = new String[SERVER_COUNT + 1]; + + for (int i = 0; i < SERVER_COUNT; i++) { + clientPorts[i] = PortAssignment.unique(); + serverConfig[i] = "server." + i + "=127.0.0.1:" + PortAssignment.unique() + ":" + PortAssignment.unique() + + ":participant;127.0.0.1:" + clientPorts[i]; + sb.append(serverConfig[i] + "\n"); + } + String currentQuorumCfgSection = sb.toString(); + mt = new MainThread[SERVER_COUNT + 1]; + + // start 3 servers + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, false); + 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(); + ZooKeeperAdmin preReconfigClient = new ZooKeeperAdmin("127.0.0.1:" + clientPorts[0], + ClientBase.CONNECTION_TIMEOUT, watch); + preReconfigClient.addAuthInfo("digest", "super:test".getBytes()); + watch.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + + // new server joining + int joinerId = SERVER_COUNT; + clientPorts[joinerId] = PortAssignment.unique(); + serverConfig[joinerId] = "server." + joinerId + "=127.0.0.1:" + PortAssignment.unique() + ":" + + PortAssignment.unique() + ":participant;127.0.0.1:" + clientPorts[joinerId]; + + // Find leader id. + int leaderId = -1; + for (int i = 0; i < SERVER_COUNT; i++) { + if (mt[i].main.quorumPeer.leader != null) { + leaderId = i; + break; + } + } + assertFalse(leaderId == -1); + + // Joiner initial config consists of itself and the leader. + sb = new StringBuilder(); + sb.append(serverConfig[leaderId] + "\n").append(serverConfig[joinerId] + "\n"); + + /** + * This server will delay the response to a NEWLEADER message, and run + * reconfig command so that message at this processed in bellow order + * + *
    +         * NEWLEADER
    +         * reconfig's PROPOSAL
    +         * reconfig's COMMITANDACTIVATE
    +         * UPTODATE
    +         * 
    + */ + mt[joinerId] = new MainThread(joinerId, clientPorts[joinerId], sb.toString(), false) { + @Override + public TestQPMain getTestQPMain() { + return new MockTestQPMain(); + } + }; + mt[joinerId].start(); + CustomQuorumPeer qp = getCustomQuorumPeer(mt[joinerId]); + + // delete any already existing .next file + String nextDynamicConfigFilename = qp.getNextDynamicConfigFilename(); + File nextDynaFile = new File(nextDynamicConfigFilename); + nextDynaFile.delete(); + + // call reconfig API when the new server has received + // Leader.NEWLEADER + while (true) { + if (qp.isNewLeaderMessage()) { + preReconfigClient.reconfigure(serverConfig[joinerId], null, null, -1, null, null); + break; + } else { + // sleep for 10 millisecond and then again check + Thread.sleep(10); + } + } + watch = new CountdownWatcher(); + ZooKeeper postReconfigClient = new ZooKeeper("127.0.0.1:" + clientPorts[joinerId], + ClientBase.CONNECTION_TIMEOUT, watch); + watch.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + // do one successful operation on the newly added node + postReconfigClient.create("/reconfigIssue", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + assertFalse("zoo.cfg.dynamic.next is not deleted.", nextDynaFile.exists()); + + // verify that joiner has up-to-date config, including all four servers. + for (long j = 0; j <= SERVER_COUNT; j++) { + assertNotNull("server " + j + " is not present in the new quorum", + qp.getQuorumVerifier().getVotingMembers().get(j)); + } + + // close clients + preReconfigClient.close(); + postReconfigClient.close(); + } + + private static CustomQuorumPeer getCustomQuorumPeer(MainThread mt) { + while (true) { + QuorumPeer quorumPeer = mt.getQuorumPeer(); + if (null != quorumPeer) { + return (CustomQuorumPeer) quorumPeer; + } else { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + @After + public void tearDown() { + // stop all severs + if (null != mt) { + 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 static class CustomQuorumPeer extends QuorumPeer { + private boolean newLeaderMessage = false; + + public CustomQuorumPeer(Map quorumPeers, File snapDir, File logDir, int clientPort, + int electionAlg, long myid, int tickTime, int initLimit, int syncLimit) + throws IOException { + super(quorumPeers, snapDir, logDir, electionAlg, myid, tickTime, initLimit, syncLimit, false, + ServerCnxnFactory.createFactory(new InetSocketAddress(clientPort), -1), new QuorumMaj(quorumPeers)); + } + + /** + * If true, after 100 millisecond NEWLEADER response is send to leader + * + * @return + */ + public boolean isNewLeaderMessage() { + return newLeaderMessage; + } + + @Override + protected Follower makeFollower(FileTxnSnapLog logFactory) throws IOException { + + return new Follower(this, new FollowerZooKeeperServer(logFactory, this, this.getZkDb())) { + + @Override + void writePacket(QuorumPacket pp, boolean flush) throws IOException { + if (pp != null && pp.getType() == Leader.ACK) { + newLeaderMessage = true; + try { + /** + * Delaying the ACK message, a follower sends as + * response to a NEWLEADER message, so that the + * leader has a chance to send the reconfig and only + * then the UPTODATE message. + */ + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + super.writePacket(pp, flush); + } + }; + } + } + + private static class MockTestQPMain extends TestQPMain { + @Override + public void runFromConfig(QuorumPeerConfig config) + throws IOException, AdminServerException { + quorumPeer = new CustomQuorumPeer(config.getQuorumVerifier().getAllMembers(), config.getDataDir(), + config.getDataLogDir(), config.getClientPortAddress().getPort(), config.getElectionAlg(), + config.getServerId(), config.getTickTime(), config.getInitLimit(), config.getSyncLimit()); + quorumPeer.setConfigFileName(config.getConfigFilename()); + quorumPeer.start(); + try { + quorumPeer.join(); + } catch (InterruptedException e) { + LOG.warn("Quorum Peer interrupted", e); + } + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigFailureCasesTest.java b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigFailureCasesTest.java new file mode 100644 index 00000000000..8120d0fa28a --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigFailureCasesTest.java @@ -0,0 +1,286 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.quorum; + +import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.KeeperException.NewConfigNoQuorum; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.admin.ZooKeeperAdmin; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.QuorumUtil; +import org.apache.zookeeper.test.ReconfigTest; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class ReconfigFailureCasesTest extends QuorumPeerTestBase { + + private QuorumUtil qu; + + @Before + public void setup() { + QuorumPeerConfig.setReconfigEnabled(true); + System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", + "super:D/InIHSb7yEEbrWz8b9l71RjZJU="/* password is 'test'*/); + } + + @After + public void tearDown() throws Exception { + if (qu != null) { + qu.tearDown(); + } + } + + /* + * Tests that an incremental reconfig fails if the current config is hiearchical. + */ + @Test + public void testIncrementalReconfigInvokedOnHiearchicalQS() throws Exception { + qu = new QuorumUtil(2); // create 5 servers + qu.disableJMXTest = true; + qu.startAll(); + ZooKeeper[] zkArr = ReconfigTest.createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = ReconfigTest.createAdminHandles(qu); + + ArrayList members = new ArrayList(); + members.add("group.1=3:4:5"); + members.add("group.2=1:2"); + members.add("weight.1=0"); + members.add("weight.2=0"); + members.add("weight.3=1"); + members.add("weight.4=1"); + members.add("weight.5=1"); + + for (int i = 1; i <= 5; i++) { + members.add("server." + i + "=127.0.0.1:" + + qu.getPeer(i).peer.getQuorumAddress().getPort() + ":" + + qu.getPeer(i).peer.getElectionAddress().getPort() + ";" + + "127.0.0.1:" + qu.getPeer(i).peer.getClientPort()); + } + + // Change the quorum system from majority to hierarchical. + ReconfigTest.reconfig(zkAdminArr[1], null, null, members, -1); + ReconfigTest.testNormalOperation(zkArr[1], zkArr[2]); + + // Attempt an incremental reconfig. + List leavingServers = new ArrayList(); + leavingServers.add("3"); + try { + zkAdminArr[1].reconfigure(null, leavingServers, null, -1, null); + Assert.fail("Reconfig should have failed since the current config isn't Majority QS"); + } catch (KeeperException.BadArgumentsException e) { + // We expect this to happen. + } catch (Exception e) { + Assert.fail("Should have been BadArgumentsException!"); + } + + ReconfigTest.closeAllHandles(zkArr, zkAdminArr); + } + + /* + * Test that a reconfiguration fails if the proposed change would leave the + * cluster with less than 2 participants (StandaloneEnabled = true). + * StandaloneDisabledTest.java (startSingleServerTest) checks that if + * StandaloneEnabled = false its legal to remove all but one remaining + * server. + */ + @Test + public void testTooFewRemainingPariticipants() throws Exception { + qu = new QuorumUtil(1); // create 3 servers + qu.disableJMXTest = true; + qu.startAll(); + ZooKeeper[] zkArr = ReconfigTest.createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = ReconfigTest.createAdminHandles(qu); + + List leavingServers = new ArrayList(); + leavingServers.add("2"); + leavingServers.add("3"); + try { + zkAdminArr[1].reconfigure(null, leavingServers, null, -1, null); + Assert.fail("Reconfig should have failed since the current config version is not 8"); + } catch (KeeperException.BadArgumentsException e) { + // We expect this to happen. + } catch (Exception e) { + Assert.fail("Should have been BadArgumentsException!"); + } + + ReconfigTest.closeAllHandles(zkArr, zkAdminArr); + } + + /* + * Tests that a conditional reconfig fails if the specified version doesn't correspond + * to the version of the current config. + */ + @Test + public void testReconfigVersionConditionFails() throws Exception { + qu = new QuorumUtil(1); // create 3 servers + qu.disableJMXTest = true; + qu.startAll(); + ZooKeeper[] zkArr = ReconfigTest.createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = ReconfigTest.createAdminHandles(qu); + + List leavingServers = new ArrayList(); + leavingServers.add("3"); + try { + zkAdminArr[1].reconfigure(null, leavingServers, null, 8, null); + Assert.fail("Reconfig should have failed since the current config version is not 8"); + } catch (KeeperException.BadVersionException e) { + // We expect this to happen. + } catch (Exception e) { + Assert.fail("Should have been BadVersionException!"); + } + + ReconfigTest.closeAllHandles(zkArr, zkAdminArr); + } + + /* + * Tests that if a quorum of a new config is synced with the leader and a reconfig + * is allowed to start but then the new quorum is lost, the leader will time out and + * we go to leader election. + */ + @Test + public void testLeaderTimesoutOnNewQuorum() throws Exception { + qu = new QuorumUtil(1); // create 3 servers + qu.disableJMXTest = true; + qu.startAll(); + ZooKeeper[] zkArr = ReconfigTest.createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = ReconfigTest.createAdminHandles(qu); + + List leavingServers = new ArrayList(); + leavingServers.add("3"); + qu.shutdown(2); + try { + // Since we just shut down server 2, its still considered "synced" + // by the leader, which allows us to start the reconfig + // (PrepRequestProcessor checks that a quorum of the new + // config is synced before starting a reconfig). + // We try to remove server 3, which requires a quorum of {1,2,3} + // (we have that) and of {1,2}, but 2 is down so we won't get a + // quorum of new config ACKs. + zkAdminArr[1].reconfigure(null, leavingServers, null, -1, null); + Assert.fail("Reconfig should have failed since we don't have quorum of new config"); + } catch (KeeperException.ConnectionLossException e) { + // We expect leader to lose quorum of proposed config and time out + } catch (Exception e) { + Assert.fail("Should have been ConnectionLossException!"); + } + + // The leader should time out and remaining servers should go into + // LOOKING state. A new leader won't be established since that + // would require completing the reconfig, which is not possible while + // 2 is down. + Assert.assertEquals(QuorumStats.Provider.LOOKING_STATE, + qu.getPeer(1).peer.getServerState()); + Assert.assertEquals(QuorumStats.Provider.LOOKING_STATE, + qu.getPeer(3).peer.getServerState()); + ReconfigTest.closeAllHandles(zkArr, zkAdminArr); + } + + /* + * Converting an observer into a participant may sometimes fail with a + * NewConfigNoQuorum exception. This test-case demonstrates the scenario. + * Current configuration is (A, B, C, D), where A, B and C are participant + * and D is an observer. Suppose that B has crashed (or never booted). If a + * reconfiguration is submitted where D is said to become a participant, it + * will fail with NewConfigNoQuorum since in this configuration, a majority + * of voters in the new configuration (any 3 voters), must be connected and + * up-to-date with the leader. An observer cannot acknowledge the history + * prefix sent during reconfiguration, and therefore it does not count towards + * these 3 required servers and the reconfiguration will be aborted. In case + * this happens, a client can achieve the same task by two reconfig commands: + * first invoke a reconfig to remove D from the configuration and then invoke a + * second command to add it back as a participant (follower). During the + * intermediate state D is a non-voting follower and can ACK the state + * transfer performed during the second reconfig command. + */ + @Test + public void testObserverToParticipantConversionFails() throws Exception { + ClientBase.setupTestEnv(); + + final int SERVER_COUNT = 4; + int[][] ports = ReconfigRecoveryTest.generatePorts(SERVER_COUNT); + + // generate old config string + HashSet observers = new HashSet(); + observers.add(3); + StringBuilder sb = ReconfigRecoveryTest.generateConfig(SERVER_COUNT, ports, observers); + String currentQuorumCfgSection = sb.toString(); + String nextQuorumCfgSection = currentQuorumCfgSection.replace("observer", "participant"); + + MainThread mt[] = new MainThread[SERVER_COUNT]; + ZooKeeper zk[] = new ZooKeeper[SERVER_COUNT]; + ZooKeeperAdmin zkAdmin[] = new ZooKeeperAdmin[SERVER_COUNT]; + + // Server 0 stays down + for (int i = 1; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, ports[i][2], currentQuorumCfgSection, + true, "100000000"); + mt[i].start(); + zk[i] = new ZooKeeper("127.0.0.1:" + ports[i][2], + ClientBase.CONNECTION_TIMEOUT, this); + zkAdmin[i] = new ZooKeeperAdmin("127.0.0.1:" + ports[i][2], + ClientBase.CONNECTION_TIMEOUT, this); + zkAdmin[i].addAuthInfo("digest", "super:test".getBytes()); + } + + for (int i = 1; i < SERVER_COUNT; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + ports[i][2], + CONNECTION_TIMEOUT * 2)); + } + + try { + zkAdmin[1].reconfigure("", "", nextQuorumCfgSection, -1, new Stat()); + Assert.fail("Reconfig should have failed with NewConfigNoQuorum"); + } catch (NewConfigNoQuorum e) { + // This is expected case since server 0 is down and 3 can't vote + // (observer in current role) and we need 3 votes from 0, 1, 2, 3, + } catch (Exception e) { + Assert.fail("Reconfig should have failed with NewConfigNoQuorum"); + } + // In this scenario to change 3's role to participant we need to remove it first + ArrayList leavingServers = new ArrayList(); + leavingServers.add("3"); + ReconfigTest.reconfig(zkAdmin[1], null, leavingServers, null, -1); + ReconfigTest.testNormalOperation(zk[2], zk[3]); + ReconfigTest.testServerHasConfig(zk[3], null, leavingServers); + + // Now we're adding it back as a participant and everything should work. + List newMembers = Arrays.asList(nextQuorumCfgSection.split("\n")); + ReconfigTest.reconfig(zkAdmin[1], null, null, newMembers, -1); + ReconfigTest.testNormalOperation(zk[2], zk[3]); + for (int i = 1; i < SERVER_COUNT; i++) { + ReconfigTest.testServerHasConfig(zk[i], newMembers, null); + } + for (int i = 1; i < SERVER_COUNT; i++) { + zk[i].close(); + zkAdmin[i].close(); + mt[i].shutdown(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigLegacyTest.java b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigLegacyTest.java index c05aa1b9ef1..80a8bfe13cb 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigLegacyTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigLegacyTest.java @@ -19,16 +19,19 @@ package org.apache.zookeeper.server.quorum; import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; +import static org.junit.Assert.assertEquals; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.List; import java.util.Properties; +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.admin.ZooKeeperAdmin; import org.apache.zookeeper.test.ClientBase; import org.apache.zookeeper.test.ReconfigTest; import org.junit.Assert; @@ -42,6 +45,9 @@ public class ReconfigLegacyTest extends QuorumPeerTestBase { @Before public void setup() { ClientBase.setupTestEnv(); + QuorumPeerConfig.setReconfigEnabled(true); + System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", + "super:D/InIHSb7yEEbrWz8b9l71RjZJU="/* password is 'test'*/); } /** @@ -75,7 +81,7 @@ public void testConfigFileBackwardCompatibility() throws Exception { for (int i = 0; i < SERVER_COUNT; i++) { mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, false); // check that a dynamic configuration file doesn't exist - Assert.assertFalse(mt[i].dynamicConfigFile.exists()); + Assert.assertEquals( mt[i].getDynamicFiles().length, 0 ); mt[i].start(); } // Check that the servers are up, have the right config and can process operations. @@ -84,9 +90,10 @@ public void testConfigFileBackwardCompatibility() throws Exception { Assert.assertTrue("waiting for server " + i + " being up", ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], CONNECTION_TIMEOUT)); - zk[i] = new ZooKeeper("127.0.0.1:" + clientPorts[i], - ClientBase.CONNECTION_TIMEOUT, this); - Assert.assertTrue(mt[i].dynamicConfigFile.exists()); + zk[i] = ClientBase.createZKClient("127.0.0.1:" + clientPorts[i]); + File[] dynamicFiles = mt[i].getDynamicFiles(); + + Assert.assertTrue( dynamicFiles.length== 1 ); ReconfigTest.testServerHasConfig(zk[i], allServers, null); // check that static config file doesn't include membership info // and has a pointer to the dynamic configuration file @@ -98,7 +105,7 @@ public void testConfigFileBackwardCompatibility() throws Exception { Assert.assertFalse(cfg.containsKey("clientPort")); // check that the dynamic configuration file contains the membership info - cfg = readPropertiesFromFile(mt[i].dynamicConfigFile); + cfg = readPropertiesFromFile(dynamicFiles[0]); for (int j = 0; j < SERVER_COUNT; j++) { String serverLine = cfg.getProperty("server." + j, ""); Assert.assertEquals(allServers.get(j), "server." + j + "=" @@ -120,8 +127,7 @@ public void testConfigFileBackwardCompatibility() throws Exception { Assert.assertTrue("waiting for server " + i + " being up", ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], CONNECTION_TIMEOUT)); - zk[i] = new ZooKeeper("127.0.0.1:" + clientPorts[i], - ClientBase.CONNECTION_TIMEOUT, this); + zk[i] = ClientBase.createZKClient("127.0.0.1:" + clientPorts[i]); ReconfigTest.testServerHasConfig(zk[i], allServers, null); } ReconfigTest.testNormalOperation(zk[0], zk[1]); @@ -172,12 +178,11 @@ public void testReconfigRemoveClientFromStatic() throws Exception { MainThread mt[] = new MainThread[SERVER_COUNT]; ZooKeeper zk[] = new ZooKeeper[SERVER_COUNT]; + ZooKeeperAdmin zkAdmin[] = new ZooKeeperAdmin[SERVER_COUNT]; // Start the servers with a static config file, without a dynamic config file. for (int i = 0; i < SERVER_COUNT; i++) { mt[i] = new MainThread(i, clientPorts[i], quorumCfgSection, false); - // check that a dynamic configuration file doesn't exist - Assert.assertFalse(mt[i].dynamicConfigFile.exists()); mt[i].start(); } @@ -187,8 +192,10 @@ public void testReconfigRemoveClientFromStatic() throws Exception { Assert.assertTrue("waiting for server " + i + " being up", ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], CONNECTION_TIMEOUT)); - zk[i] = new ZooKeeper("127.0.0.1:" + clientPorts[i], + zk[i] = ClientBase.createZKClient("127.0.0.1:" + clientPorts[i]); + zkAdmin[i] = new ZooKeeperAdmin("127.0.0.1:" + clientPorts[i], ClientBase.CONNECTION_TIMEOUT, this); + zkAdmin[i].addAuthInfo("digest", "super:test".getBytes()); ReconfigTest.testServerHasConfig(zk[i], allServers, null); Properties cfg = readPropertiesFromFile(mt[i].confFile); @@ -198,7 +205,7 @@ public void testReconfigRemoveClientFromStatic() throws Exception { } ReconfigTest.testNormalOperation(zk[0], zk[1]); - ReconfigTest.reconfig(zk[1], null, null, newServers, -1); + ReconfigTest.reconfig(zkAdmin[1], null, null, newServers, -1); ReconfigTest.testNormalOperation(zk[0], zk[1]); // Sleep since writing the config files may take time. @@ -221,11 +228,11 @@ public void testReconfigRemoveClientFromStatic() throws Exception { for (int i = 0; i < SERVER_COUNT; i++) { mt[i].shutdown(); zk[i].close(); + zkAdmin[i].close(); } } - - private Properties readPropertiesFromFile(File file) throws IOException { + public static Properties readPropertiesFromFile(File file) throws IOException { Properties cfg = new Properties(); FileInputStream in = new FileInputStream(file); try { @@ -235,4 +242,70 @@ private Properties readPropertiesFromFile(File file) throws IOException { } return cfg; } + + /** + * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2244 + * + * @throws Exception + */ + @Test(timeout = 120000) + public void testRestartZooKeeperServer() 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() + ":participant;127.0.0.1:" + + clientPorts[i]; + 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, + false); + 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)); + } + + ZooKeeper zk = ClientBase.createZKClient("127.0.0.1:" + clientPorts[0]); + + String zNodePath="/serverRestartTest"; + String data = "originalData"; + zk.create(zNodePath, data.getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + zk.close(); + + /** + * stop two servers out of three and again start them + */ + mt[0].shutdown(); + mt[1].shutdown(); + mt[0].start(); + mt[1].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)); + } + zk = ClientBase.createZKClient("127.0.0.1:" + clientPorts[0]); + + byte[] dataBytes = zk.getData(zNodePath, null, null); + String receivedData = new String(dataBytes); + assertEquals(data, receivedData); + + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i].shutdown(); + } + } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRecoveryTest.java b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRecoveryTest.java index 1a090dc9503..4de9b976894 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRecoveryTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRecoveryTest.java @@ -28,9 +28,15 @@ import org.apache.zookeeper.test.ClientBase; import org.apache.zookeeper.test.ReconfigTest; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; public class ReconfigRecoveryTest extends QuorumPeerTestBase { + @Before + public void setup() { + QuorumPeerConfig.setReconfigEnabled(true); + } + /** * Reconfiguration recovery - test that a reconfiguration is completed if * leader has .next file during startup and new config is not running yet @@ -56,9 +62,8 @@ public void testNextConfigCompletion() throws Exception { allServers.add(server); sb.append(server + "\n"); if (i == 1) - currentQuorumCfgSection = sb.toString() + "version=100000000\n"; + currentQuorumCfgSection = sb.toString(); } - sb.append("version=200000000\n"); // version of current config is 100000000 nextQuorumCfgSection = sb.toString(); // Both servers 0 and 1 will have the .next config file, which means @@ -67,12 +72,13 @@ public void testNextConfigCompletion() throws Exception { MainThread mt[] = new MainThread[SERVER_COUNT]; ZooKeeper zk[] = new ZooKeeper[SERVER_COUNT]; for (int i = 0; i < SERVER_COUNT - 1; i++) { - mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection); + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, + true, "100000000"); // note that we should run the server, shut it down and only then // simulate a reconfig in progress by writing the temp file, but here no // other server is competing with them in FLE, so we can skip this step // (server 2 is booted after FLE ends) - mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection); + mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection, "200000000"); mt[i].start(); zk[i] = new ZooKeeper("127.0.0.1:" + clientPorts[i], ClientBase.CONNECTION_TIMEOUT, this); @@ -132,9 +138,8 @@ public void testCurrentServersAreObserversInNextConfig() throws Exception { StringBuilder sb = new StringBuilder(); String server; - String currentQuorumCfg = null, currentQuorumCfgSection = null, nextQuorumCfgSection = null; + String currentQuorumCfg, nextQuorumCfgSection; - ArrayList allServersCurrent = new ArrayList(); ArrayList allServersNext = new ArrayList(); for (int i = 0; i < 2; i++) { @@ -142,13 +147,10 @@ public void testCurrentServersAreObserversInNextConfig() throws Exception { server = "server." + i + "=localhost:" + PortAssignment.unique() + ":" + PortAssignment.unique() + ":participant;localhost:" + oldClientPorts[i]; - allServersCurrent.add(server); sb.append(server + "\n"); } currentQuorumCfg = sb.toString(); - sb.append("version=100000000\n"); - currentQuorumCfgSection = sb.toString(); sb = new StringBuilder(); String role; @@ -165,7 +167,6 @@ public void testCurrentServersAreObserversInNextConfig() throws Exception { allServersNext.add(server); sb.append(server + "\n"); } - sb.append("version=200000000\n"); // version of current config is 100000000 nextQuorumCfgSection = sb.toString(); MainThread mt[] = new MainThread[SERVER_COUNT]; @@ -173,8 +174,8 @@ public void testCurrentServersAreObserversInNextConfig() throws Exception { // run servers 0 and 1 normally for (int i = 0; i < 2; i++) { - mt[i] = new MainThread(i, oldClientPorts[i], - currentQuorumCfgSection); + mt[i] = new MainThread(i, oldClientPorts[i], currentQuorumCfg, + true, "100000000"); mt[i].start(); zk[i] = new ZooKeeper("127.0.0.1:" + oldClientPorts[i], ClientBase.CONNECTION_TIMEOUT, this); @@ -203,7 +204,7 @@ public void testCurrentServersAreObserversInNextConfig() throws Exception { } for (int i = 0; i < 2; i++) { - mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection); + mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection, "200000000"); mt[i].start(); zk[i] = new ZooKeeper("127.0.0.1:" + clientPorts[i], ClientBase.CONNECTION_TIMEOUT, this); @@ -252,18 +253,15 @@ public void testNextConfigUnreachable() throws Exception { String currentQuorumCfgSection = null, nextQuorumCfgSection; - ArrayList allServers = new ArrayList(); for (int i = 0; i < SERVER_COUNT; i++) { clientPorts[i] = PortAssignment.unique(); server = "server." + i + "=localhost:" + PortAssignment.unique() + ":" + PortAssignment.unique() + ":participant;localhost:" + clientPorts[i]; - allServers.add(server); sb.append(server + "\n"); if (i == 1) - currentQuorumCfgSection = sb.toString() + "version=100000000\n"; + currentQuorumCfgSection = sb.toString(); } - sb.append("version=200000000\n"); // version of current config is 100000000 nextQuorumCfgSection = sb.toString(); MainThread mt[] = new MainThread[SERVER_COUNT]; @@ -272,11 +270,12 @@ public void testNextConfigUnreachable() throws Exception { // Both servers 0 and 1 will have the .next config file, which means // for them that a reconfiguration was in progress when they failed for (int i = 0; i < 2; i++) { - mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection); + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, + true, "100000000"); // note that we should run the server, shut it down and only then // simulate a reconfig in progress by writing the temp file, but here no // other server is competing with them in FLE, so we can skip this step - mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection); + mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection, "200000000"); mt[i].start(); zk[i] = new ZooKeeper("127.0.0.1:" + clientPorts[i], ClientBase.CONNECTION_TIMEOUT, this); @@ -322,16 +321,16 @@ public void testNextConfigAlreadyActive() throws Exception { + clientPorts[i]; allServers.add(server); sb.append(server + "\n"); - if (i == 1) currentQuorumCfgSection = sb.toString() + "version=100000000\n"; + if (i == 1) currentQuorumCfgSection = sb.toString(); } - sb.append("version=200000000\n"); // version of current config is 100000000 nextQuorumCfgSection = sb.toString(); // lets start servers 2, 3, 4 with the new config MainThread mt[] = new MainThread[SERVER_COUNT]; ZooKeeper zk[] = new ZooKeeper[SERVER_COUNT]; for (int i = 2; i < SERVER_COUNT; i++) { - mt[i] = new MainThread(i, clientPorts[i], nextQuorumCfgSection); + mt[i] = new MainThread(i, clientPorts[i], nextQuorumCfgSection, + true, "200000000"); mt[i].start(); zk[i] = new ZooKeeper("127.0.0.1:" + clientPorts[i], ClientBase.CONNECTION_TIMEOUT, this); @@ -350,8 +349,9 @@ public void testNextConfigAlreadyActive() throws Exception { // for them that a reconfiguration was in progress when they failed // and the leader will complete it. for (int i = 0; i < 2; i++) { - mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection); - mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection); + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, + true, "100000000"); + mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection, "200000000"); mt[i].start(); zk[i] = new ZooKeeper("127.0.0.1:" + clientPorts[i], ClientBase.CONNECTION_TIMEOUT, this); @@ -402,7 +402,6 @@ public void testObserverConvertedToParticipantDuringFLE() throws Exception { HashSet observers = new HashSet(); observers.add(2); StringBuilder sb = generateConfig(3, ports, observers); - sb.append("version=100000000"); currentQuorumCfgSection = sb.toString(); // generate new config string @@ -414,20 +413,21 @@ public void testObserverConvertedToParticipantDuringFLE() throws Exception { allServersNext.add(server); sb.append(server + "\n"); } - sb.append("version=200000000"); // version of current config is 100000000 nextQuorumCfgSection = sb.toString(); MainThread mt[] = new MainThread[SERVER_COUNT]; ZooKeeper zk[] = new ZooKeeper[SERVER_COUNT]; // start server 2 with old config, where it is an observer - mt[2] = new MainThread(2, ports[2][2], currentQuorumCfgSection); + mt[2] = new MainThread(2, ports[2][2], currentQuorumCfgSection, + true, "100000000"); mt[2].start(); zk[2] = new ZooKeeper("127.0.0.1:" + ports[2][2], ClientBase.CONNECTION_TIMEOUT, this); // start server 3 with new config - mt[3] = new MainThread(3, ports[3][2], nextQuorumCfgSection); + mt[3] = new MainThread(3, ports[3][2], nextQuorumCfgSection, + true, "200000000"); mt[3].start(); zk[3] = new ZooKeeper("127.0.0.1:" + ports[3][2], ClientBase.CONNECTION_TIMEOUT, this); @@ -439,9 +439,9 @@ public void testObserverConvertedToParticipantDuringFLE() throws Exception { ReconfigTest.testServerHasConfig(zk[i], allServersNext, null); } - Assert.assertEquals(nextQuorumCfgSection, + Assert.assertEquals(nextQuorumCfgSection + "version=200000000", ReconfigTest.testServerHasConfig(zk[2], null, null)); - Assert.assertEquals(nextQuorumCfgSection, + Assert.assertEquals(nextQuorumCfgSection + "version=200000000", ReconfigTest.testServerHasConfig(zk[3], null, null)); ReconfigTest.testNormalOperation(zk[2], zk[2]); ReconfigTest.testNormalOperation(zk[3], zk[2]); @@ -470,7 +470,7 @@ public void testCurrentObserverIsParticipantInNewConfig() throws Exception { final int SERVER_COUNT = 4; int[][] ports = generatePorts(SERVER_COUNT); - String currentQuorumCfg, currentQuorumCfgSection, nextQuorumCfgSection; + String currentQuorumCfg, nextQuorumCfgSection; // generate old config string HashSet observers = new HashSet(); @@ -478,14 +478,13 @@ public void testCurrentObserverIsParticipantInNewConfig() throws Exception { StringBuilder sb = generateConfig(3, ports, observers); currentQuorumCfg = sb.toString(); - sb.append("version=100000000"); - currentQuorumCfgSection = sb.toString(); // Run servers 0..2 for a while MainThread mt[] = new MainThread[SERVER_COUNT]; ZooKeeper zk[] = new ZooKeeper[SERVER_COUNT]; for (int i = 0; i <= 2; i++) { - mt[i] = new MainThread(i, ports[i][2], currentQuorumCfgSection); + mt[i] = new MainThread(i, ports[i][2], currentQuorumCfg + , true, "100000000"); mt[i].start(); zk[i] = new ZooKeeper("127.0.0.1:" + ports[i][2], ClientBase.CONNECTION_TIMEOUT, this); @@ -514,13 +513,12 @@ public void testCurrentObserverIsParticipantInNewConfig() throws Exception { allServersNext.add(server); sb.append(server + "\n"); } - sb.append("version=200000000"); // version of current config is 100000000 nextQuorumCfgSection = sb.toString(); // simulate reconfig in progress - servers 0..2 have a temp reconfig // file when they boot for (int i = 0; i <= 2; i++) { - mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection); + mt[i].writeTempDynamicConfigFile(nextQuorumCfgSection, "200000000"); mt[i].start(); zk[i] = new ZooKeeper("127.0.0.1:" + ports[i][2], ClientBase.CONNECTION_TIMEOUT, this); @@ -536,15 +534,15 @@ public void testCurrentObserverIsParticipantInNewConfig() throws Exception { for (int i = 2; i < SERVER_COUNT; i++) { Assert.assertTrue("waiting for server " + i + " being up", ClientBase.waitForServerUp("127.0.0.1:" + ports[i][2], - CONNECTION_TIMEOUT * 2)); + CONNECTION_TIMEOUT * 3)); ReconfigTest.testServerHasConfig(zk[i], allServersNext, null); } ReconfigTest.testNormalOperation(zk[0], zk[2]); ReconfigTest.testNormalOperation(zk[3], zk[1]); - Assert.assertEquals(nextQuorumCfgSection, + Assert.assertEquals(nextQuorumCfgSection + "version=200000000", ReconfigTest.testServerHasConfig(zk[2], null, null)); - Assert.assertEquals(nextQuorumCfgSection, + Assert.assertEquals(nextQuorumCfgSection + "version=200000000", ReconfigTest.testServerHasConfig(zk[3], null, null)); for (int i = 0; i < SERVER_COUNT; i++) { @@ -556,7 +554,7 @@ public void testCurrentObserverIsParticipantInNewConfig() throws Exception { /* * Generates 3 ports per server */ - private int[][] generatePorts(int numServers) { + public static int[][] generatePorts(int numServers) { int[][] ports = new int[numServers][]; for (int i = 0; i < numServers; i++) { ports[i] = new int[3]; @@ -571,7 +569,7 @@ private int[][] generatePorts(int numServers) { * Creates a configuration string for servers 0..numServers-1 Ids in * observerIds correspond to observers, other ids are for participants. */ - private StringBuilder generateConfig(int numServers, int[][] ports, + public static StringBuilder generateConfig(int numServers, int[][] ports, HashSet observerIds) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < numServers; i++) { diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRollingRestartCompatibilityTest.java b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRollingRestartCompatibilityTest.java new file mode 100644 index 00000000000..65ec469eea8 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/ReconfigRollingRestartCompatibilityTest.java @@ -0,0 +1,261 @@ +/** + * 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.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.List; +import java.util.HashMap; +import java.util.HashSet; +import java.util.ArrayList; + +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ReconfigTest; +import org.junit.Assert; +import org.junit.Test; + +import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; + +/** + * ReconfigRollingRestartCompatibilityTest - we want to make sure that users + * can continue using the rolling restart approach when reconfig feature is disabled. + * It is important to stay compatible with rolling restart because dynamic reconfig + * has its limitation: it requires a quorum of server to work. When no quorum can be formed, + * rolling restart is the only approach to reconfigure the ensemble (e.g. removing bad nodes + * such that a new quorum with smaller number of nodes can be formed.). + * + * See ZOOKEEPER-2819 for more details. + */ +public class ReconfigRollingRestartCompatibilityTest extends QuorumPeerTestBase { + private static final String ZOO_CFG_BAK_FILE = "zoo.cfg.bak"; + + Map clientPorts = new HashMap<>(5); + Map serverAddress = new HashMap<>(5); + + private String generateNewQuorumConfig(int serverCount) { + StringBuilder sb = new StringBuilder(); + String server; + for (int i = 0; i < serverCount; i++) { + clientPorts.put(i, PortAssignment.unique()); + server = "server." + i + "=localhost:" + PortAssignment.unique() + + ":" + PortAssignment.unique() + ":participant;localhost:" + + clientPorts.get(i); + serverAddress.put(i, server); + sb.append(server + "\n"); + } + return sb.toString(); + } + + private String updateExistingQuorumConfig(List sidsToAdd, List sidsToRemove) { + StringBuilder sb = new StringBuilder(); + for (Integer sid : sidsToAdd) { + clientPorts.put(sid, PortAssignment.unique()); + serverAddress.put(sid, "server." + sid + "=localhost:" + PortAssignment.unique() + + ":" + PortAssignment.unique() + ":participant;localhost:" + + clientPorts.get(sid)); + } + + for (Integer sid : sidsToRemove) { + clientPorts.remove(sid); + serverAddress.remove(sid); + } + + for (String server : serverAddress.values()) { + sb.append(server + "\n"); + } + + return sb.toString(); + } + + @Test(timeout = 60000) + // Verify no zoo.cfg.dynamic and zoo.cfg.bak files existing locally + // when reconfig feature flag is off by default. + public void testNoLocalDynamicConfigAndBackupFiles() + throws InterruptedException, IOException { + int serverCount = 3; + String config = generateNewQuorumConfig(serverCount); + QuorumPeerTestBase.MainThread mt[] = new QuorumPeerTestBase.MainThread[serverCount]; + String[] staticFileContent = new String[serverCount]; + + for (int i = 0; i < serverCount; i++) { + mt[i] = new QuorumPeerTestBase.MainThread(i, clientPorts.get(i), + config, false); + mt[i].start(); + } + + for (int i = 0; i < serverCount; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts.get(i), + CONNECTION_TIMEOUT)); + Assert.assertNull("static file backup (zoo.cfg.bak) shouldn't exist!", + mt[i].getFileByName(ZOO_CFG_BAK_FILE)); + Assert.assertNull("dynamic configuration file (zoo.cfg.dynamic.*) shouldn't exist!", + mt[i].getFileByName(mt[i].getQuorumPeer().getNextDynamicConfigFilename())); + staticFileContent[i] = Files.readAllLines(mt[i].confFile.toPath(), StandardCharsets.UTF_8).toString(); + Assert.assertTrue("static config file should contain server entry " + serverAddress.get(i), + staticFileContent[i].contains(serverAddress.get(i))); + } + + for (int i = 0; i < serverCount; i++) { + mt[i].shutdown(); + } + } + + @Test(timeout = 60000) + // This test simulate the usual rolling restart with no membership change: + // 1. A node is shutdown first (e.g. to upgrade software, or hardware, or cleanup local data.). + // 2. After upgrade, start the node. + // 3. Do this for every node, one at a time. + public void testRollingRestartWithoutMembershipChange() throws Exception { + int serverCount = 3; + String config = generateNewQuorumConfig(serverCount); + List joiningServers = new ArrayList<>(); + QuorumPeerTestBase.MainThread mt[] = new QuorumPeerTestBase.MainThread[serverCount]; + for (int i = 0; i < serverCount; ++i) { + mt[i] = new QuorumPeerTestBase.MainThread(i, clientPorts.get(i), + config, false); + mt[i].start(); + joiningServers.add(serverAddress.get(i)); + } + + for (int i = 0; i < serverCount; ++i) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts.get(i), + CONNECTION_TIMEOUT)); + } + + for (int i = 0; i < serverCount; ++i) { + mt[i].shutdown(); + mt[i].start(); + verifyQuorumConfig(i, joiningServers, null); + verifyQuorumMembers(mt[i]); + } + + for (int i = 0; i < serverCount; i++) { + mt[i].shutdown(); + } + } + + @Test(timeout = 90000) + // This test simulate the use case of change of membership through rolling + // restart. For a 3 node ensemble we expand it to a 5 node ensemble, verify + // during the process each node has the expected configuration setting pushed + // via updating local zoo.cfg file. + public void testRollingRestartWithMembershipChange() throws Exception { + int serverCount = 3; + String config = generateNewQuorumConfig(serverCount); + QuorumPeerTestBase.MainThread mt[] = new QuorumPeerTestBase.MainThread[serverCount]; + List joiningServers = new ArrayList<>(); + for (int i = 0; i < serverCount; ++i) { + mt[i] = new QuorumPeerTestBase.MainThread(i, clientPorts.get(i), + config, false); + mt[i].start(); + joiningServers.add(serverAddress.get(i)); + } + + for (int i = 0; i < serverCount; ++i) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts.get(i), + CONNECTION_TIMEOUT)); + } + + for (int i = 0; i < serverCount; ++i) { + verifyQuorumConfig(i, joiningServers, null); + verifyQuorumMembers(mt[i]); + } + + Map oldServerAddress = new HashMap<>(serverAddress); + List newServers = new ArrayList<>(joiningServers); + config = updateExistingQuorumConfig(Arrays.asList(3, 4), new ArrayList()); + newServers.add(serverAddress.get(3)); newServers.add(serverAddress.get(4)); + serverCount = serverAddress.size(); + Assert.assertEquals("Server count should be 5 after config update.", serverCount, 5); + + // We are adding two new servers to the ensemble. These two servers should have the config which includes + // all five servers (the old three servers, plus the two servers added). The old three servers should only + // have the old three server config, because disabling reconfig will prevent synchronizing configs between + // peers. + mt = Arrays.copyOf(mt, mt.length + 2); + for (int i = 3; i < 5; ++i) { + mt[i] = new QuorumPeerTestBase.MainThread(i, clientPorts.get(i), + config, false); + mt[i].start(); + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts.get(i), + CONNECTION_TIMEOUT)); + verifyQuorumConfig(i, newServers, null); + verifyQuorumMembers(mt[i]); + } + + Set expectedConfigs = new HashSet<>(); + for (String conf : oldServerAddress.values()) { + // Remove "server.x=" prefix which quorum peer does not include. + expectedConfigs.add(conf.substring(conf.indexOf('=') + 1)); + } + + for (int i = 0; i < 3; ++i) { + verifyQuorumConfig(i, joiningServers, null); + verifyQuorumMembers(mt[i], expectedConfigs); + } + + for (int i = 0; i < serverCount; ++i) { + mt[i].shutdown(); + } + } + + // Verify each quorum peer has expected config in its config zNode. + private void verifyQuorumConfig(int sid, List joiningServers, List leavingServers) throws Exception { + ZooKeeper zk = ClientBase.createZKClient("127.0.0.1:" + clientPorts.get(sid)); + ReconfigTest.testNormalOperation(zk, zk); + ReconfigTest.testServerHasConfig(zk, joiningServers, leavingServers); + zk.close(); + } + + // Verify each quorum peer has expected quorum member view. + private void verifyQuorumMembers(QuorumPeerTestBase.MainThread mt) { + Set expectedConfigs = new HashSet<>(); + for (String config : serverAddress.values()) { + expectedConfigs.add(config.substring(config.indexOf('=') + 1)); + } + verifyQuorumMembers(mt, expectedConfigs); + } + + private void verifyQuorumMembers(QuorumPeerTestBase.MainThread mt, Set expectedConfigs) { + Map members = + mt.getQuorumPeer().getQuorumVerifier().getAllMembers(); + + Assert.assertTrue("Quorum member should not change.", + members.size() == expectedConfigs.size()); + + for (QuorumPeer.QuorumServer qs : members.values()) { + String actualConfig = qs.toString(); + Assert.assertTrue("Unexpected config " + actualConfig + " found!", + expectedConfigs.contains(actualConfig)); + } + } +} + + + diff --git a/src/java/test/org/apache/zookeeper/server/quorum/RemotePeerBeanTest.java b/src/java/test/org/apache/zookeeper/server/quorum/RemotePeerBeanTest.java new file mode 100644 index 00000000000..b75a7fbd54b --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/RemotePeerBeanTest.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.quorum; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.net.InetSocketAddress; + +import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; +import org.junit.Test; + +public class RemotePeerBeanTest { + + /** + * Test case for https://issues.apache.org/jira/browse/ZOOKEEPER-2269 + */ + @Test + public void testGetClientAddressShouldReturnEmptyStringWhenClientAddressIsNull() { + InetSocketAddress peerCommunicationAddress = null; + // Here peerCommunicationAddress is null, also clientAddr is null + QuorumServer peer = new QuorumServer(1, peerCommunicationAddress); + RemotePeerBean remotePeerBean = new RemotePeerBean(peer); + String clientAddress = remotePeerBean.getClientAddress(); + assertNotNull(clientAddress); + assertEquals(0, clientAddress.length()); + } + +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/StandaloneDisabledTest.java b/src/java/test/org/apache/zookeeper/server/quorum/StandaloneDisabledTest.java index 06aa2299d24..a50e98ae34f 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/StandaloneDisabledTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/StandaloneDisabledTest.java @@ -27,12 +27,9 @@ import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.admin.ZooKeeperAdmin; import org.apache.zookeeper.test.ClientBase; import org.apache.zookeeper.test.ReconfigTest; -import org.apache.zookeeper.AsyncCallback.StatCallback; -import org.apache.zookeeper.server.quorum.QuorumPeerConfig; import org.junit.Assert; import org.junit.Test; @@ -41,6 +38,7 @@ public class StandaloneDisabledTest extends QuorumPeerTestBase { private final int NUM_SERVERS = 5; private MainThread peers[]; private ZooKeeper zkHandles[]; + private ZooKeeperAdmin zkAdminHandles[]; private int clientPorts[]; private final int leaderId = 0; private final int follower1 = 1; @@ -70,7 +68,7 @@ public void startSingleServerTest() throws Exception { LOG.info("Configuration after adding 2 followers:\n" + new String(zkHandles[leaderId].getConfig(this, new Stat()))); - //shutdown leader- quorum should still exist + //shutdown leader- quorum should still exist shutDownServer(leaderId); ReconfigTest.testNormalOperation(zkHandles[follower1], zkHandles[follower2]); @@ -79,7 +77,7 @@ public void startSingleServerTest() throws Exception { reconfigServers.clear(); reconfigServers.add(Integer.toString(follower2)); try { - ReconfigTest.reconfig(zkHandles[follower1], null, reconfigServers, null, -1); + ReconfigTest.reconfig(zkAdminHandles[follower1], null, reconfigServers, null, -1); Assert.fail("reconfig completed successfully even though there is no quorum up in new config!"); } catch (KeeperException.NewConfigNoQuorum e) { } @@ -92,6 +90,21 @@ public void startSingleServerTest() throws Exception { LOG.info("Configuration after removing leader and follower 1:\n" + new String(zkHandles[follower2].getConfig(this, new Stat()))); + // Kill server 1 to avoid it interferences with FLE of the quorum {2, 3, 4}. + shutDownServer(follower1); + + // Try to remove follower2, which is the only remaining server. This should fail. + reconfigServers.clear(); + reconfigServers.add(Integer.toString(follower2)); + try { + zkAdminHandles[follower2].reconfigure(null, reconfigServers, null, -1, new Stat()); + Assert.fail("reconfig completed successfully even though there is no quorum up in new config!"); + } catch (KeeperException.BadArgumentsException e) { + // This is expected. + } catch (Exception e) { + Assert.fail("Should have been BadArgumentsException!"); + } + //Add two participants and change them to observers to check //that we can reconfigure down to one participant with observers. ArrayList observerStrings = new ArrayList(); @@ -110,11 +123,15 @@ public void startSingleServerTest() throws Exception { private void setUpData() throws Exception { ClientBase.setupTestEnv(); QuorumPeerConfig.setStandaloneEnabled(false); + QuorumPeerConfig.setReconfigEnabled(true); peers = new MainThread[NUM_SERVERS]; zkHandles = new ZooKeeper[NUM_SERVERS]; + zkAdminHandles = new ZooKeeperAdmin[NUM_SERVERS]; clientPorts = new int[NUM_SERVERS]; serverStrings = buildServerStrings(); reconfigServers = new ArrayList(); + System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", + "super:D/InIHSb7yEEbrWz8b9l71RjZJU="/* password is 'test'*/); } /** @@ -123,6 +140,7 @@ private void setUpData() throws Exception { private void shutDownData() throws Exception { for (int i = 0; i < NUM_SERVERS; i++) { zkHandles[i].close(); + zkAdminHandles[i].close(); } for (int i = 1; i < NUM_SERVERS; i++) { peers[i].shutdown(); @@ -153,13 +171,14 @@ private ArrayList buildServerStrings() { */ private void startServer(int id, String config) throws Exception { peers[id] = new MainThread(id, clientPorts[id], config); - zkHandles[id] = new ZooKeeper("127.0.0.1:" + clientPorts[id], - CONNECTION_TIMEOUT, this); peers[id].start(); Assert.assertTrue("Server " + id + " is not up", ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[id], CONNECTION_TIMEOUT)); Assert.assertTrue("Error- Server started in Standalone Mode!", - peers[id].isQuorumPeerRunning()); + peers[id].isQuorumPeerRunning()); + zkHandles[id] = ClientBase.createZKClient("127.0.0.1:" + clientPorts[id]); + zkAdminHandles[id] = new ZooKeeperAdmin("127.0.0.1:" + clientPorts[id], CONNECTION_TIMEOUT, this); + zkAdminHandles[id].addAuthInfo("digest", "super:test".getBytes()); } /** @@ -214,16 +233,17 @@ private void startObservers(ArrayList observerStrings) throws Exception private void testReconfig(int id, boolean adding, ArrayList servers) throws Exception { if (adding) { - ReconfigTest.reconfig(zkHandles[id], servers, null, null, -1); + ReconfigTest.reconfig(zkAdminHandles[id], servers, null, null, -1); for (String server : servers) { int id2 = Integer.parseInt(server.substring(7, 8)); //server.# ReconfigTest.testNormalOperation(zkHandles[id], zkHandles[id2]); } + ReconfigTest.testServerHasConfig(zkHandles[id], servers, null); } else { - ReconfigTest.reconfig(zkHandles[id], null, servers, null, -1); + ReconfigTest.reconfig(zkAdminHandles[id], null, servers, null, -1); + ReconfigTest.testServerHasConfig(zkHandles[id], null, servers); } - ReconfigTest.testServerHasConfig(zkHandles[id], servers, null); } /** @@ -240,4 +260,4 @@ public void startObserver() throws Exception { Assert.assertFalse("Observer was able to start by itself!", ClientBase.waitForServerUp("127.0.0.1:" + clientPort, CONNECTION_TIMEOUT)); } -} \ No newline at end of file +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/StatCommandTest.java b/src/java/test/org/apache/zookeeper/server/quorum/StatCommandTest.java new file mode 100644 index 00000000000..0328b7a7e53 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/StatCommandTest.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.quorum; + +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.ServerStats; +import org.apache.zookeeper.server.ZKDatabase; +import org.apache.zookeeper.server.command.FourLetterCommands; +import org.apache.zookeeper.server.command.StatCommand; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class StatCommandTest { + private StringWriter outputWriter; + private StatCommand statCommand; + private ServerStats.Provider providerMock; + + @Before + public void setUp() throws IOException { + outputWriter = new StringWriter(); + ServerCnxn serverCnxnMock = mock(ServerCnxn.class); + + LeaderZooKeeperServer zks = mock(LeaderZooKeeperServer.class); + when(zks.isRunning()).thenReturn(true); + providerMock = mock(ServerStats.Provider.class); + when(zks.serverStats()).thenReturn(new ServerStats(providerMock)); + ZKDatabase zkDatabaseMock = mock(ZKDatabase.class); + when(zks.getZKDatabase()).thenReturn(zkDatabaseMock); + Leader leaderMock = mock(Leader.class); + when(leaderMock.getProposalStats()).thenReturn(new ProposalStats()); + when(zks.getLeader()).thenReturn(leaderMock); + + ServerCnxnFactory serverCnxnFactory = mock(ServerCnxnFactory.class); + ServerCnxn serverCnxn = mock(ServerCnxn.class); + List connections = new ArrayList<>(); + connections.add(serverCnxn); + when(serverCnxnFactory.getConnections()).thenReturn(connections); + + statCommand = new StatCommand(new PrintWriter(outputWriter), serverCnxnMock, FourLetterCommands.statCmd); + statCommand.setZkServer(zks); + statCommand.setFactory(serverCnxnFactory); + } + + @Test + public void testLeaderStatCommand() { + // Arrange + when(providerMock.getState()).thenReturn("leader"); + + // Act + statCommand.commandRun(); + + // Assert + String output = outputWriter.toString(); + assertCommonStrings(output); + assertThat(output, containsString("Mode: leader")); + assertThat(output, containsString("Proposal sizes last/min/max:")); + } + + @Test + public void testFollowerStatCommand() { + // Arrange + when(providerMock.getState()).thenReturn("follower"); + + // Act + statCommand.commandRun(); + + // Assert + String output = outputWriter.toString(); + assertCommonStrings(output); + assertThat(output, containsString("Mode: follower")); + } + + private void assertCommonStrings(String output) { + assertThat(output, containsString("Clients:")); + assertThat(output, containsString("Zookeeper version:")); + assertThat(output, containsString("Node count:")); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/StatResetCommandTest.java b/src/java/test/org/apache/zookeeper/server/quorum/StatResetCommandTest.java new file mode 100644 index 00000000000..ddaf8316d8e --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/StatResetCommandTest.java @@ -0,0 +1,111 @@ +/** + * 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 org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.ServerStats; +import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.command.StatResetCommand; +import org.junit.Before; +import org.junit.Test; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import static org.apache.zookeeper.server.command.AbstractFourLetterCommand.ZK_NOT_SERVING; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class StatResetCommandTest { + private StatResetCommand statResetCommand; + private StringWriter outputWriter; + private ZooKeeperServer zks; + private ServerStats serverStats; + + @Before + public void setUp() { + outputWriter = new StringWriter(); + ServerCnxn serverCnxnMock = mock(ServerCnxn.class); + + zks = mock(ZooKeeperServer.class); + when(zks.isRunning()).thenReturn(true); + + serverStats = mock(ServerStats.class); + when(zks.serverStats()).thenReturn(serverStats); + + statResetCommand = new StatResetCommand(new PrintWriter(outputWriter), serverCnxnMock); + statResetCommand.setZkServer(zks); + } + + @Test + public void testStatResetWithZKNotRunning() { + // Arrange + when(zks.isRunning()).thenReturn(false); + + // Act + statResetCommand.commandRun(); + + // Assert + String output = outputWriter.toString(); + assertEquals(ZK_NOT_SERVING + "\n", output); + } + + @Test + public void testStatResetWithFollower() { + // Arrange + when(zks.isRunning()).thenReturn(true); + when(serverStats.getServerState()).thenReturn("follower"); + + // Act + statResetCommand.commandRun(); + + // Assert + String output = outputWriter.toString(); + assertEquals("Server stats reset.\n", output); + verify(serverStats, times(1)).reset(); + } + + @Test + public void testStatResetWithLeader() { + // Arrange + LeaderZooKeeperServer leaderZks = mock(LeaderZooKeeperServer.class); + when(leaderZks.isRunning()).thenReturn(true); + when(leaderZks.serverStats()).thenReturn(serverStats); + Leader leader = mock(Leader.class); + when(leaderZks.getLeader()).thenReturn(leader); + statResetCommand.setZkServer(leaderZks); + + when(serverStats.getServerState()).thenReturn("leader"); + + ProposalStats proposalStats = mock(ProposalStats.class); + when(leader.getProposalStats()).thenReturn(proposalStats); + + // Act + statResetCommand.commandRun(); + + // Assert + String output = outputWriter.toString(); + assertEquals("Server stats reset.\n", output); + verify(serverStats, times(1)).reset(); + verify(proposalStats, times(1)).reset(); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/quorum/WatchLeakTest.java b/src/java/test/org/apache/zookeeper/server/quorum/WatchLeakTest.java index 76c614d7f24..d8667dc14dd 100644 --- a/src/java/test/org/apache/zookeeper/server/quorum/WatchLeakTest.java +++ b/src/java/test/org/apache/zookeeper/server/quorum/WatchLeakTest.java @@ -40,9 +40,7 @@ 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.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -61,8 +59,9 @@ import org.apache.zookeeper.server.NIOServerCnxnFactory; import org.apache.zookeeper.server.MockSelectorThread; import org.apache.zookeeper.server.ZKDatabase; -import org.apache.zookeeper.server.ZooTrace; import org.apache.zookeeper.server.persistence.FileTxnSnapLog; +import org.apache.zookeeper.ZKParameterized; +import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -73,6 +72,7 @@ * Demonstrate ZOOKEEPER-1382 : Watches leak on expired session */ @RunWith(Parameterized.class) +@Parameterized.UseParametersRunnerFactory(ZKParameterized.RunnerFactory.class) public class WatchLeakTest { protected static final Logger LOG = LoggerFactory @@ -82,6 +82,11 @@ public class WatchLeakTest { private final boolean sessionTimedout; + @Before + public void setUp() { + System.setProperty("zookeeper.admin.enableServer", "false"); + } + public WatchLeakTest(boolean sessionTimedout) { this.sessionTimedout = sessionTimedout; } 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 6ce058e48d1..046092cb911 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,30 +18,34 @@ package org.apache.zookeeper.server.quorum; +import static org.apache.zookeeper.server.quorum.ZabUtils.createQuorumPeer; +import static org.apache.zookeeper.server.quorum.ZabUtils.createMockLeader; +import static org.apache.zookeeper.server.quorum.ZabUtils.MockLeader; +import static org.apache.zookeeper.server.quorum.ZabUtils.createLeader; + 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.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.jute.BinaryInputArchive; import org.apache.jute.BinaryOutputArchive; import org.apache.jute.InputArchive; import org.apache.jute.OutputArchive; -import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.EventType; @@ -49,34 +53,38 @@ 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.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.flexible.QuorumMaj; + import org.apache.zookeeper.server.util.ZxidUtils; +import org.apache.zookeeper.test.TestUtils; 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.apache.zookeeper.ZKTestCase; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; -import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Zab1_0Test { - private static final int SYNC_LIMIT = 2; +public class Zab1_0Test extends ZKTestCase { 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")); + @Before + public void setUp() { + System.setProperty("zookeeper.admin.enableServer", "false"); + } + private static final class LeadThread extends Thread { private final Leader leader; @@ -96,26 +104,6 @@ public void run() { } } } - - - 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; @@ -181,7 +169,7 @@ public void testLeaderInConnectingFollowers() throws Exception { if (leader != null) { leader.shutdown("end of test"); } - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } @@ -233,7 +221,7 @@ public void testLastAcceptedEpoch() throws Exception { leadThread.interrupt(); leadThread.join(); } - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } @@ -270,56 +258,10 @@ public void testLeaderInElectingFollowers() throws Exception { if (leader != null) { leader.shutdown("end of test"); } - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } - 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; - } - @Override - public void reconfigure(InetSocketAddress addr) { - } - @Override - public void resetAllConnectionStats() { - } - @Override - public Iterable> getAllConnectionInfo(boolean brief) { - return null; - } - } static Socket[] getSocketPair() throws IOException { ServerSocket ss = new ServerSocket(0, 50, InetAddress.getByName("127.0.0.1")); @@ -372,7 +314,9 @@ public void testLeaderConversation(LeaderConversation conversation) throws Excep Thread.sleep(20); } - LearnerHandler lh = new LearnerHandler(leaderSocket, leader); + LearnerHandler lh = new LearnerHandler(leaderSocket, + new BufferedInputStream(leaderSocket.getInputStream()), + leader); lh.start(); leaderSocket.setSoTimeout(4000); @@ -390,7 +334,7 @@ public void testLeaderConversation(LeaderConversation conversation) throws Excep leadThread.interrupt(); leadThread.join(); } - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } @@ -440,8 +384,10 @@ public void testPopulatedLeaderConversation(PopulatedLeaderConversation conversa while(leader.cnxAcceptor == null || !leader.cnxAcceptor.isAlive()) { Thread.sleep(20); } - - LearnerHandler lh = new LearnerHandler(leaderSocket, leader); + + LearnerHandler lh = new LearnerHandler(leaderSocket, + new BufferedInputStream(leaderSocket.getInputStream()), + leader); lh.start(); leaderSocket.setSoTimeout(4000); @@ -459,11 +405,10 @@ public void testPopulatedLeaderConversation(PopulatedLeaderConversation conversa leadThread.interrupt(); leadThread.join(); } - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } - public void testFollowerConversation(FollowerConversation conversation) throws Exception { File tmpDir = File.createTempFile("test", "dir", testData); tmpDir.delete(); @@ -477,8 +422,10 @@ public void testFollowerConversation(FollowerConversation conversation) throws E peer.follower = follower; ServerSocket ss = - new ServerSocket(0, 50, InetAddress.getByName("127.0.0.1")); - follower.setLeaderSocketAddress((InetSocketAddress)ss.getLocalSocketAddress()); + new ServerSocket(0, 50, InetAddress.getByName("127.0.0.1")); + QuorumServer leaderQS = new QuorumServer(1, + (InetSocketAddress) ss.getLocalSocketAddress()); + follower.setLeaderQuorumServer(leaderQS); final Follower followerForThread = follower; followerThread = new Thread() { @@ -512,7 +459,7 @@ public void run() { if (peer != null) { peer.shutdown(); } - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } @@ -531,7 +478,8 @@ public void testObserverConversation(ObserverConversation conversation) throws E ServerSocket ss = new ServerSocket(0, 50, InetAddress.getByName("127.0.0.1")); - observer.setLeaderSocketAddress((InetSocketAddress)ss.getLocalSocketAddress()); + QuorumServer leaderQS = new QuorumServer(1, (InetSocketAddress) ss.getLocalSocketAddress()); + observer.setLeaderQuorumServer(leaderQS); final Observer observerForThread = observer; observerThread = new Thread() { @@ -563,7 +511,7 @@ public void run() { if (peer != null) { peer.shutdown(); } - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } @@ -641,6 +589,8 @@ public void converseWithFollower(InputArchive ia, OutputArchive oa, 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()); @@ -683,6 +633,10 @@ public void converseWithFollower(InputArchive ia, OutputArchive oa, 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); @@ -693,7 +647,8 @@ public void converseWithFollower(InputArchive ia, OutputArchive oa, 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 @@ -739,7 +694,7 @@ public void converseWithFollower(InputArchive ia, OutputArchive oa, Assert.assertEquals("data2", new String(zkDb2.getData("/foo", stat, null))); Assert.assertEquals(proposalZxid, lastZxid); } finally { - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } @@ -769,6 +724,8 @@ public void converseWithFollower(InputArchive ia, OutputArchive oa, 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()); @@ -836,15 +793,30 @@ public void converseWithFollower(InputArchive ia, OutputArchive oa, 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); + TestUtils.deleteFileRecursively(tmpDir); } } @@ -961,7 +933,7 @@ public void converseWithLeader(InputArchive ia, OutputArchive oa, Leader l) LOG.info("Proposal sent."); - for (int i = 0; i < (2 * SYNC_LIMIT) + 2; i++) { + for (int i = 0; i < (2 * ZabUtils.SYNC_LIMIT) + 2; i++) { try { ia.readRecord(qp, null); LOG.info("Ping received: " + i); @@ -1118,7 +1090,7 @@ public void converseWithObserver(InputArchive ia, OutputArchive oa, Assert.assertEquals("data2", new String(zkDb2.getData("/foo2", stat, null))); Assert.assertEquals(informZxid, lastZxid); } finally { - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } @@ -1207,56 +1179,20 @@ public void converseWithLeader(InputArchive ia, OutputArchive oa, Leader l) }); } - private void recursiveDelete(File file) { - if (file.isFile()) { - file.delete(); - } else { - // 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{ - 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); - ZKDatabase zkDb = new ZKDatabase(logFactory); - LeaderZooKeeperServer zk = new LeaderZooKeeperServer(logFactory, peer, zkDb); - return zk; - } - static class ConversableFollower extends Follower { ConversableFollower(QuorumPeer self, FollowerZooKeeperServer zk) { super(self, zk); } - InetSocketAddress leaderAddr; - public void setLeaderSocketAddress(InetSocketAddress addr) { - leaderAddr = addr; + QuorumServer leaderQuorumServer; + public void setLeaderQuorumServer(QuorumServer quorumServer) { + leaderQuorumServer = quorumServer; } @Override - protected InetSocketAddress findLeader() { - return leaderAddr; + protected QuorumServer findLeader() { + return leaderQuorumServer; } } private ConversableFollower createFollower(File tmpDir, QuorumPeer peer) @@ -1275,14 +1211,14 @@ static class ConversableObserver extends Observer { super(self, zk); } - InetSocketAddress leaderAddr; - public void setLeaderSocketAddress(InetSocketAddress addr) { - leaderAddr = addr; + QuorumServer leaderQuorumServer; + public void setLeaderQuorumServer(QuorumServer quorumServer) { + leaderQuorumServer = quorumServer; } @Override - protected InetSocketAddress findLeader() { - return leaderAddr; + protected QuorumServer findLeader() { + return leaderQuorumServer; } } @@ -1296,40 +1232,6 @@ private ConversableObserver createObserver(File tmpDir, QuorumPeer peer) return new ConversableObserver(peer, zk); } - private QuorumPeer createQuorumPeer(File tmpDir) throws IOException, FileNotFoundException { - HashMap peers = new HashMap(); - QuorumPeer peer = new QuorumPeer(); - peer.syncLimit = SYNC_LIMIT; - peer.initLimit = 2; - peer.tickTime = 2000; - - peers.put(0L, new QuorumServer( - 0, new InetSocketAddress("127.0.0.1", PortAssignment.unique()), - new InetSocketAddress("127.0.0.1", PortAssignment.unique()), - new InetSocketAddress("127.0.0.1", PortAssignment.unique()))); - peers.put(1L, new QuorumServer( - 1, new InetSocketAddress("127.0.0.1", PortAssignment.unique()), - new InetSocketAddress("127.0.0.1", PortAssignment.unique()), - new InetSocketAddress("127.0.0.1", PortAssignment.unique()))); - peers.put(2L, new QuorumServer( - 2, new InetSocketAddress("127.0.0.1", PortAssignment.unique()), - new InetSocketAddress("127.0.0.1", PortAssignment.unique()), - new InetSocketAddress("127.0.0.1", PortAssignment.unique()))); - - peer.setQuorumVerifier(new QuorumMaj(peers), false); - peer.setCnxnFactory(new NullServerCnxnFactory()); - 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 String readContentsOfFile(File f) throws IOException { return new BufferedReader(new FileReader(f)).readLine(); } @@ -1343,13 +1245,14 @@ public void testInitialAcceptedCurrent() throws Exception { FileTxnSnapLog logFactory = new FileTxnSnapLog(tmpDir, tmpDir); File version2 = new File(tmpDir, "version-2"); version2.mkdir(); + logFactory.save(new DataTree(), new ConcurrentHashMap()); long zxid = ZxidUtils.makeZxid(3, 3); logFactory.append(new Request(1, 1, ZooDefs.OpCode.error, new TxnHeader(1, 1, zxid, 1, ZooDefs.OpCode.error), new ErrorTxn(1), zxid)); logFactory.commit(); ZKDatabase zkDb = new ZKDatabase(logFactory); - QuorumPeer peer = new QuorumPeer(); + QuorumPeer peer = QuorumPeer.testingQuorumPeer(); peer.setZKDatabase(zkDb); peer.setTxnFactory(logFactory); peer.getLastLoggedZxid(); @@ -1362,7 +1265,7 @@ public void testInitialAcceptedCurrent() throws Exception { .parseInt(readContentsOfFile(new File(version2, QuorumPeer.ACCEPTED_EPOCH_FILENAME)))); } finally { - recursiveDelete(tmpDir); + TestUtils.deleteFileRecursively(tmpDir); } } } diff --git a/src/java/test/org/apache/zookeeper/server/quorum/ZabUtils.java b/src/java/test/org/apache/zookeeper/server/quorum/ZabUtils.java new file mode 100644 index 00000000000..4aae29209ec --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/ZabUtils.java @@ -0,0 +1,164 @@ +/** + * 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 org.apache.zookeeper.PortAssignment; +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.flexible.QuorumMaj; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; + +public class ZabUtils { + + private ZabUtils() {} + + public static final int SYNC_LIMIT = 2; + + public static QuorumPeer createQuorumPeer(File tmpDir) throws IOException, FileNotFoundException { + HashMap peers = new HashMap(); + QuorumPeer peer = QuorumPeer.testingQuorumPeer(); + peer.syncLimit = SYNC_LIMIT; + peer.initLimit = 2; + peer.tickTime = 2000; + + peers.put(0L, new QuorumPeer.QuorumServer( + 0, new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()))); + peers.put(1L, new QuorumPeer.QuorumServer( + 1, new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()))); + peers.put(2L, new QuorumPeer.QuorumServer( + 2, new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()))); + + peer.setQuorumVerifier(new QuorumMaj(peers), false); + peer.setCnxnFactory(new NullServerCnxnFactory()); + File version2 = new File(tmpDir, "version-2"); + version2.mkdir(); + FileOutputStream 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; + } + + public static Leader createLeader(File tmpDir, QuorumPeer peer) + throws IOException, NoSuchFieldException, IllegalAccessException { + LeaderZooKeeperServer zk = prepareLeader(tmpDir, peer); + return new Leader(peer, zk); + } + + public static Leader createMockLeader(File tmpDir, QuorumPeer peer) + throws IOException, NoSuchFieldException, IllegalAccessException { + LeaderZooKeeperServer zk = prepareLeader(tmpDir, peer); + return new MockLeader(peer, zk); + } + + private static LeaderZooKeeperServer prepareLeader(File tmpDir, QuorumPeer peer) + throws IOException, NoSuchFieldException, IllegalAccessException { + FileTxnSnapLog logFactory = new FileTxnSnapLog(tmpDir, tmpDir); + peer.setTxnFactory(logFactory); + ZKDatabase zkDb = new ZKDatabase(logFactory); + LeaderZooKeeperServer zk = new LeaderZooKeeperServer(logFactory, peer, zkDb); + return zk; + } + + private static final class NullServerCnxnFactory extends ServerCnxnFactory { + public void startup(ZooKeeperServer zkServer, boolean startServer) + 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 maxcc, boolean secure) + throws IOException { + } + + public boolean closeSession(long sessionId) { + return false; + } + public void closeAll() { + } + @Override + public int getNumAliveConnections() { + return 0; + } + @Override + public void reconfigure(InetSocketAddress addr) { + } + @Override + public void resetAllConnectionStats() { + } + @Override + public Iterable> getAllConnectionInfo(boolean brief) { + return null; + } + } + + public 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; + } + } +} 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..4afef41eadc --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/MiniKdc.java @@ -0,0 +1,418 @@ +/** + * 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.kerby.kerberos.kerb.KrbException; +import org.apache.kerby.kerberos.kerb.server.KdcConfigKey; +import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer; +import org.apache.kerby.util.IOUtil; +import org.apache.kerby.util.NetworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +/** + * 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 one System property when started and un-set when stopped: + *

      + *
    • 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: 916140604ffef59466ba30832478311d3e6249bd + */ +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"; + + 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, "false"); + } + + /** + * 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 SimpleKdcServer simpleKdc; + private int port; + private String realm; + private File workDir; + private File krb5conf; + private String transport; + private boolean krb5Debug; + + public void setTransport(String transport) { + this.transport = transport; + } + /** + * 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)); + 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() { + krb5conf = new File(System.getProperty(JAVA_SECURITY_KRB5_CONF)); + return krb5conf; + } + + /** + * Starts the MiniKdc. + * + * @throws Exception thrown if the MiniKdc could not be started. + */ + public synchronized void start() throws Exception { + if (simpleKdc != null) { + throw new RuntimeException("Already started"); + } + simpleKdc = new SimpleKdcServer(); + prepareKdcServer(); + simpleKdc.init(); + resetDefaultRealm(); + simpleKdc.start(); + LOG.info("MiniKdc stated."); + } + + private void resetDefaultRealm() throws IOException { + InputStream templateResource = new FileInputStream( + getKrb5conf().getAbsolutePath()); + String content = IOUtil.readInput(templateResource); + content = content.replaceAll("default_realm = .*\n", + "default_realm = " + getRealm() + "\n"); + IOUtil.writeFile(content, getKrb5conf()); + } + + private void prepareKdcServer() throws Exception { + // transport + simpleKdc.setWorkDir(workDir); + simpleKdc.setKdcHost(getHost()); + simpleKdc.setKdcRealm(realm); + if (transport == null) { + transport = conf.getProperty(TRANSPORT); + } + if (port == 0) { + port = NetworkUtil.getServerPort(); + } + if (transport != null) { + if (transport.trim().equals("TCP")) { + simpleKdc.setKdcTcpPort(port); + simpleKdc.setAllowUdp(false); + } else if (transport.trim().equals("UDP")) { + simpleKdc.setKdcUdpPort(port); + simpleKdc.setAllowTcp(false); + } else { + throw new IllegalArgumentException("Invalid transport: " + transport); + } + } else { + throw new IllegalArgumentException("Need to set transport!"); + } + simpleKdc.getKdcConfig().setString(KdcConfigKey.KDC_SERVICE_NAME, + conf.getProperty(INSTANCE)); + if (conf.getProperty(DEBUG) != null) { + krb5Debug = getAndSet(SUN_SECURITY_KRB5_DEBUG, conf.getProperty(DEBUG)); + } + } + + /** + * Stops the MiniKdc + */ + public synchronized void stop() { + if (simpleKdc != null) { + try { + simpleKdc.stop(); + } catch (KrbException e) { + e.printStackTrace(); + } finally { + if(conf.getProperty(DEBUG) != null) { + System.setProperty(SUN_SECURITY_KRB5_DEBUG, + Boolean.toString(krb5Debug)); + } + } + } + delete(workDir); + try { + // Will be fixed in next Kerby version. + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + LOG.info("MiniKdc stopped."); + } + + 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 { + simpleKdc.createPrincipal(principal, password); + } + + /** + * 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 { + simpleKdc.createPrincipals(principals); + if (keytabFile.exists() && !keytabFile.delete()) { + LOG.error("Failed to delete keytab file: " + keytabFile); + } + for (String principal : principals) { + simpleKdc.getKadmin().exportKeytab(keytabFile, principal); + } + } + + /** + * Set the System property; return the old value for caching. + * + * @param sysprop property + * @param debug true or false + * @return the previous value + */ + private boolean getAndSet(String sysprop, String debug) { + boolean old = Boolean.getBoolean(sysprop); + System.setProperty(sysprop, debug); + return old; + } +} \ No newline at end of file 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..69dbcd1e57f --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/MiniKdcTest.java @@ -0,0 +1,185 @@ +/** + * 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.kerby.kerberos.kerb.keytab.Keytab; +import org.apache.kerby.kerberos.kerb.type.base.PrincipalName; +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.List; +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"); + List principalNameList = + Keytab.loadKeytab(new File(workDir, "keytab")).getPrincipals(); + + Set principals = new HashSet(); + for (PrincipalName principalName : principalNameList) { + principals.add(principalName.getName()); + } + + 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.getSubject() != null + && !loginContext.getSubject().getPrincipals().isEmpty()) { + 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..8978d170f26 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumAuthTestBase.java @@ -0,0 +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.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) throws IOException { + StringBuilder connectStr = new StringBuilder(); + final int[] clientPorts = startQuorum(serverCount, connectStr, + authConfigs, authServerCount); + 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(); + } + + protected int[] startQuorum(final int serverCount, StringBuilder connectStr, + Map authConfigs, int authServerCount) throws IOException { + final int clientPorts[] = new int[serverCount]; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < serverCount; i++) { + clientPorts[i] = PortAssignment.unique(); + String 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++) { + startServer(authConfigs, clientPorts, quorumCfg, i); + } + // servers without any authentication configured + for (int j = 0; j < serverCount - authServerCount; j++, i++) { + MainThread mthread = new MainThread(i, clientPorts[i], quorumCfg); + mt.add(mthread); + mthread.start(); + } + return clientPorts; + } + + private void startServer(Map authConfigs, + final int[] clientPorts, String quorumCfg, int i) + throws IOException { + MainThread mthread = new MainThread(i, clientPorts[i], quorumCfg, + authConfigs); + mt.add(mthread); + mthread.start(); + } + + 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..359324549e2 --- /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); + 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); + 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); + 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); + 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); + 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..5eebdb335c0 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumDigestAuthTest.java @@ -0,0 +1,222 @@ +/** + * 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 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.admin.AdminServer; +import org.apache.zookeeper.server.quorum.QuorumPeerMain; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase; +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 { + + 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(); + } + } + + @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); + 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 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); + 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 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, new StringBuilder(), + authConfigs, serverCount); + 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, AdminServer.AdminServerException { + 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, AdminServer.AdminServerException { + 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, AdminServer.AdminServerException { + super.initializeAndRun(args); + } + }.initializeAndRun(args); + Assert.fail("Must throw exception as quorum sasl is not enabled!"); + } catch (ConfigException e) { + // expected + } + } +} 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..2cc56a76794 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumKerberosAuthTest.java @@ -0,0 +1,110 @@ +/** + * 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=false\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=false\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); + 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..fcb76919f1b --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/quorum/auth/QuorumKerberosHostBasedAuthTest.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 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=false\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=false\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=false\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); + 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); + 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/SerializeUtilsTest.java b/src/java/test/org/apache/zookeeper/server/util/SerializeUtilsTest.java new file mode 100644 index 00000000000..61893f70220 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/util/SerializeUtilsTest.java @@ -0,0 +1,128 @@ +/** + * 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 org.apache.jute.BinaryOutputArchive; +import org.apache.jute.OutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.txn.TxnHeader; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class SerializeUtilsTest { + + @Test + public void testSerializeRequestRequestIsNull() { + byte[] data = SerializeUtils.serializeRequest(null); + assertNull(data); + } + + @Test + public void testSerializeRequestRequestHeaderIsNull() { + Request request = new Request(0, 0, 0, null, null, 0); + byte[] data = SerializeUtils.serializeRequest(request); + assertNull(data); + } + + @Test + public void testSerializeRequestWithoutTxn() throws IOException { + // Arrange + TxnHeader header = mock(TxnHeader.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + OutputArchive oa = (OutputArchive) args[0]; + oa.writeString("header", "test"); + return null; + } + }).when(header).serialize(any(OutputArchive.class), anyString()); + Request request = new Request(1, 2, 3, header, null, 4); + + // Act + byte[] data = SerializeUtils.serializeRequest(request); + + // Assert + assertNotNull(data); + verify(header).serialize(any(OutputArchive.class), eq("hdr")); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos); + boa.writeString("header", "test"); + baos.close(); + assertArrayEquals(baos.toByteArray(), data); + } + + @Test + public void testSerializeRequestWithTxn() throws IOException { + // Arrange + TxnHeader header = mock(TxnHeader.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + OutputArchive oa = (OutputArchive) args[0]; + oa.writeString("header", "test"); + return null; + } + }).when(header).serialize(any(OutputArchive.class), anyString()); + Record txn = mock(Record.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + OutputArchive oa = (OutputArchive) args[0]; + oa.writeString("record", "test"); + return null; + } + }).when(txn).serialize(any(OutputArchive.class), anyString()); + Request request = new Request(1, 2, 3, header, txn, 4); + + // Act + byte[] data = SerializeUtils.serializeRequest(request); + + // Assert + assertNotNull(data); + InOrder inOrder = inOrder(header, txn); + inOrder.verify(header).serialize(any(OutputArchive.class), eq("hdr")); + inOrder.verify(txn).serialize(any(OutputArchive.class), eq("txn")); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos); + boa.writeString("header", "test"); + boa.writeString("record", "test"); + baos.close(); + assertArrayEquals(baos.toByteArray(), data); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/util/VerifyingFileFactoryTest.java b/src/java/test/org/apache/zookeeper/server/util/VerifyingFileFactoryTest.java index f08cc12ab48..0bf5b61dbd5 100644 --- a/src/java/test/org/apache/zookeeper/server/util/VerifyingFileFactoryTest.java +++ b/src/java/test/org/apache/zookeeper/server/util/VerifyingFileFactoryTest.java @@ -22,12 +22,13 @@ import java.io.File; +import org.apache.zookeeper.ZKTestCase; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class VerifyingFileFactoryTest { +public class VerifyingFileFactoryTest extends ZKTestCase { private Logger log; diff --git a/src/java/test/org/apache/zookeeper/test/ACLCountTest.java b/src/java/test/org/apache/zookeeper/test/ACLCountTest.java index 88b8869adf3..6c876cbb497 100644 --- a/src/java/test/org/apache/zookeeper/test/ACLCountTest.java +++ b/src/java/test/org/apache/zookeeper/test/ACLCountTest.java @@ -22,18 +22,14 @@ import java.io.File; import java.util.ArrayList; -import java.util.concurrent.CountDownLatch; import java.util.List; 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.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.ACL; @@ -44,11 +40,10 @@ import org.junit.Assert; import org.junit.Test; -public class ACLCountTest extends ZKTestCase implements Watcher { - private static final Logger LOG = LoggerFactory.getLogger(ACLTest.class); +public class ACLCountTest extends ZKTestCase{ + private static final Logger LOG = LoggerFactory.getLogger(ACLCountTest.class); private static final String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); - private volatile CountDownLatch startSignal; /** * @@ -86,7 +81,7 @@ public void testAclCount() throws Exception { 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); + zk = ClientBase.createZKClient(HOSTPORT); zk.addAuthInfo("digest", "pat:test".getBytes()); zk.setACL("/", Ids.CREATOR_ALL_ACL, -1); @@ -113,24 +108,4 @@ public void testAclCount() throws Exception { f.shutdown(); zks.shutdown(); } - - - /* - * (non-Javadoc) - * - * @see org.apache.zookeeper.Watcher#process(org.apache.zookeeper.WatcherEvent) - */ - public void process(WatchedEvent event) { - LOG.info("Event:" + event.getState() + " " + event.getType() + " " - + event.getPath()); - if (event.getState() == KeeperState.SyncConnected) { - if (startSignal != null && startSignal.getCount() > 0) { - LOG.info("startsignal.countDown()"); - startSignal.countDown(); - } else { - LOG.warn("startsignal " + startSignal); - } - } - } - } diff --git a/src/java/test/org/apache/zookeeper/test/ACLTest.java b/src/java/test/org/apache/zookeeper/test/ACLTest.java index bb02458385f..e88f7f4d44e 100644 --- a/src/java/test/org/apache/zookeeper/test/ACLTest.java +++ b/src/java/test/org/apache/zookeeper/test/ACLTest.java @@ -23,7 +23,6 @@ import java.io.File; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,9 +30,9 @@ import org.apache.zookeeper.PortAssignment; 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.ZooKeeper; -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; @@ -73,7 +72,7 @@ public void testDisconnectedAddAuth() throws Exception { LOG.info("starting up the zookeeper server .. waiting"); Assert.assertTrue("waiting for server being up", ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); - ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); + ZooKeeper zk = ClientBase.createZKClient(HOSTPORT); try { zk.addAuthInfo("digest", "pat:test".getBytes()); zk.setACL("/", Ids.CREATOR_ALL_ACL, -1); @@ -109,14 +108,15 @@ public void testAcls() throws Exception { 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); + zk = ClientBase.createZKClient(HOSTPORT); LOG.info("starting creating acls"); for (int i = 0; i < 100; i++) { path = "/" + i; zk.create(path, path.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } - Assert.assertTrue("size of the acl map ", (1 == zks.getZKDatabase().getAclSize())); + int size = zks.getZKDatabase().getAclSize(); + Assert.assertTrue("size of the acl map ", (2 == zks.getZKDatabase().getAclSize())); for (int j = 100; j < 200; j++) { path = "/" + j; ACL acl = new ACL(); @@ -129,7 +129,7 @@ public void testAcls() throws Exception { list.add(acl); zk.create(path, path.getBytes(), list, CreateMode.PERSISTENT); } - Assert.assertTrue("size of the acl map ", (101 == zks.getZKDatabase().getAclSize())); + Assert.assertTrue("size of the acl map ", (102 == zks.getZKDatabase().getAclSize())); } finally { // now shutdown the server and restart it f.shutdown(); @@ -137,7 +137,6 @@ public void testAcls() throws Exception { Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); } - startSignal = new CountDownLatch(1); zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); f = ServerCnxnFactory.createFactory(PORT, -1); @@ -146,12 +145,8 @@ public void testAcls() throws Exception { try { Assert.assertTrue("waiting for server up", ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); - - startSignal.await(CONNECTION_TIMEOUT, - TimeUnit.MILLISECONDS); - Assert.assertTrue("count == 0", startSignal.getCount() == 0); - - Assert.assertTrue("acl map ", (101 == zks.getZKDatabase().getAclSize())); + zk = ClientBase.createZKClient(HOSTPORT); + Assert.assertTrue("acl map ", (102 == zks.getZKDatabase().getAclSize())); for (int j = 200; j < 205; j++) { path = "/" + j; ACL acl = new ACL(); @@ -164,7 +159,7 @@ public void testAcls() throws Exception { list.add(acl); zk.create(path, path.getBytes(), list, CreateMode.PERSISTENT); } - Assert.assertTrue("acl map ", (106 == zks.getZKDatabase().getAclSize())); + Assert.assertTrue("acl map ", (107 == zks.getZKDatabase().getAclSize())); zk.close(); } finally { diff --git a/src/java/test/org/apache/zookeeper/test/AsyncHammerTest.java b/src/java/test/org/apache/zookeeper/test/AsyncHammerTest.java index ebfc96396d1..1ccdc0bc02d 100644 --- a/src/java/test/org/apache/zookeeper/test/AsyncHammerTest.java +++ b/src/java/test/org/apache/zookeeper/test/AsyncHammerTest.java @@ -103,8 +103,7 @@ public void run() { } finally { if (zk != null) { try { - zk.close(); - if (!zk.testableWaitForShutdown(CONNECTION_TIMEOUT)) { + if (!zk.close(CONNECTION_TIMEOUT)) { failed = true; LOG.error("Client did not shutdown"); } diff --git a/src/java/test/org/apache/zookeeper/test/AsyncTest.java b/src/java/test/org/apache/zookeeper/test/AsyncTest.java index 98433e154e6..801d08ce0b1 100644 --- a/src/java/test/org/apache/zookeeper/test/AsyncTest.java +++ b/src/java/test/org/apache/zookeeper/test/AsyncTest.java @@ -18,32 +18,25 @@ package org.apache.zookeeper.test; -import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; -import java.io.IOException; import java.util.LinkedList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.zookeeper.CreateMode; -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZKTestCase; -import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.AsyncCallback.DataCallback; import org.apache.zookeeper.AsyncCallback.StringCallback; import org.apache.zookeeper.AsyncCallback.VoidCallback; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.Code; -import org.apache.zookeeper.Watcher.Event.KeeperState; +import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; 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 AsyncTest extends ZKTestCase implements StringCallback, VoidCallback, DataCallback @@ -63,37 +56,20 @@ public void tearDown() throws Exception { qb.tearDown(); } - private static class CountdownWatcher implements Watcher { - volatile CountDownLatch clientConnected = new CountDownLatch(1); - - public void process(WatchedEvent event) { - if (event.getState() == KeeperState.SyncConnected) { - clientConnected.countDown(); - } - } - } - - private ZooKeeper createClient() throws IOException,InterruptedException { + private ZooKeeper createClient() throws Exception,InterruptedException { return createClient(qb.hostPort); } private ZooKeeper createClient(String hp) - throws IOException, InterruptedException + throws Exception, InterruptedException { - CountdownWatcher watcher = new CountdownWatcher(); - ZooKeeper zk = new ZooKeeper(hp, CONNECTION_TIMEOUT, watcher); - if(!watcher.clientConnected.await(CONNECTION_TIMEOUT, - TimeUnit.MILLISECONDS)) - { - Assert.fail("Unable to connect to server"); - } + ZooKeeper zk = ClientBase.createZKClient(hp); return zk; } LinkedList results = new LinkedList(); @Test - public void testAsync() - throws IOException, InterruptedException, KeeperException + public void testAsync() throws Exception { ZooKeeper zk = null; zk = createClient(); diff --git a/src/java/test/org/apache/zookeeper/test/AtomicFileOutputStreamTest.java b/src/java/test/org/apache/zookeeper/test/AtomicFileOutputStreamTest.java index fe86f54d0b1..cbd2b7700dc 100644 --- a/src/java/test/org/apache/zookeeper/test/AtomicFileOutputStreamTest.java +++ b/src/java/test/org/apache/zookeeper/test/AtomicFileOutputStreamTest.java @@ -43,7 +43,7 @@ public class AtomicFileOutputStreamTest extends ZKTestCase { @Before public void setupTestDir() throws IOException { - testDir = ClientBase.createTmpDir(); + testDir = ClientBase.createEmptyTestDir(); dstFile = new File(testDir, "test.txt"); } @After diff --git a/src/java/test/org/apache/zookeeper/test/ClientBase.java b/src/java/test/org/apache/zookeeper/test/ClientBase.java index a6229b50b4a..811109951d1 100644 --- a/src/java/test/org/apache/zookeeper/test/ClientBase.java +++ b/src/java/test/org/apache/zookeeper/test/ClientBase.java @@ -40,8 +40,9 @@ import javax.management.MBeanServerConnection; import javax.management.ObjectName; -import junit.framework.TestCase; +import org.apache.zookeeper.common.Time; +import org.apache.zookeeper.common.X509Exception.SSLContextException; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.TestableZooKeeper; @@ -55,6 +56,7 @@ import org.apache.zookeeper.server.ServerCnxnFactoryAccessor; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.persistence.FilePadding; import org.apache.zookeeper.server.persistence.FileTxnLog; import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.util.OSMXBean; @@ -95,7 +97,12 @@ public void process(WatchedEvent event) { /* nada */ } 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(); @@ -103,16 +110,28 @@ 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 public boolean isConnected() { @@ -121,25 +140,51 @@ synchronized public boolean isConnected() { synchronized public void waitForConnected(long timeout) throws InterruptedException, TimeoutException { - long expire = System.currentTimeMillis() + timeout; + 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"); + throw new TimeoutException("Failed to connect to ZooKeeper server."); } } + synchronized public void waitForSyncConnected(long timeout) + throws InterruptedException, TimeoutException + { + long expire = Time.currentElapsedTime() + timeout; + long left = timeout; + while(!syncConnected && left > 0) { + wait(left); + left = expire - Time.currentElapsedTime(); + } + 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 = System.currentTimeMillis() + timeout; + 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 disconnect"); @@ -228,22 +273,28 @@ public static List parseHostPortList(String hplist) { } public static boolean waitForServerUp(String hp, long timeout) { - long start = System.currentTimeMillis(); + return waitForServerUp(hp, timeout, false); + } + + public static boolean waitForServerUp(String hp, long timeout, boolean secure) { + long start = Time.currentElapsedTime(); while (true) { try { // if there are multiple hostports, just take the first one HostPort hpobj = parseHostPortList(hp).get(0); - String result = send4LetterWord(hpobj.host, hpobj.port, "stat"); + String result = send4LetterWord(hpobj.host, hpobj.port, "stat", secure); if (result.startsWith("Zookeeper version:") && !result.contains("READ-ONLY")) { return true; } } catch (IOException e) { // ignore as this is expected - LOG.info("server " + hp + " not up " + e); + LOG.info("server {} not up", hp, e); + } catch (SSLContextException e) { + LOG.error("server {} not up", hp, e); } - if (System.currentTimeMillis() > start + timeout) { + if (Time.currentElapsedTime() > start + timeout) { break; } try { @@ -254,17 +305,24 @@ public static boolean waitForServerUp(String hp, long timeout) { } return false; } + public static boolean waitForServerDown(String hp, long timeout) { - long start = System.currentTimeMillis(); + return waitForServerDown(hp, timeout, false); + } + + public static boolean waitForServerDown(String hp, long timeout, boolean secure) { + long start = Time.currentElapsedTime(); while (true) { try { HostPort hpobj = parseHostPortList(hp).get(0); - send4LetterWord(hpobj.host, hpobj.port, "stat"); + send4LetterWord(hpobj.host, hpobj.port, "stat", secure); } catch (IOException e) { return true; + } catch (SSLContextException e) { + return true; } - if (System.currentTimeMillis() > start + timeout) { + if (Time.currentElapsedTime() > start + timeout) { break; } try { @@ -276,18 +334,24 @@ public static boolean waitForServerDown(String hp, long timeout) { return false; } + /** + * Return true if any of the states is achieved + */ public static boolean waitForServerState(QuorumPeer qp, int timeout, - String serverState) { - long start = System.currentTimeMillis(); + String... serverStates) { + long start = Time.currentElapsedTime(); while (true) { try { Thread.sleep(250); } catch (InterruptedException e) { // ignore } - if (qp.getServerState().equals(serverState)) - return true; - if (System.currentTimeMillis() > start + timeout) { + for (String state : serverStates) { + if (qp.getServerState().equals(state)) { + return true; + } + } + if (Time.currentElapsedTime() > start + timeout) { return false; } } @@ -305,11 +369,15 @@ static void verifyThreadTerminated(Thread thread, long millis) } } + public static File createEmptyTestDir() throws IOException { + return createTmpDir(BASETEST, false); + } public static File createTmpDir() throws IOException { - return createTmpDir(BASETEST); + return createTmpDir(BASETEST, true); } - static File createTmpDir(File parentDir) throws IOException { + + static File createTmpDir(File parentDir, boolean createInitFile) 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 @@ -317,8 +385,21 @@ static File createTmpDir(File parentDir) throws IOException { Assert.assertFalse(tmpDir.exists()); // never true if tmpfile does it's job Assert.assertTrue(tmpDir.mkdirs()); + // todo not every tmp directory needs this file + if (createInitFile) { + createInitializeFile(tmpDir); + } + return tmpDir; } + + public static void createInitializeFile(File dir) throws IOException { + File initFile = new File(dir, "initialize"); + if (!initFile.exists()) { + Assert.assertTrue(initFile.createNewFile()); + } + } + private static int getPort(String hostPort) { String[] split = hostPort.split(":"); String portstr = split[split.length-1]; @@ -333,20 +414,21 @@ private static int getPort(String hostPort) { * Starting the given server instance */ public static void startServerInstance(File dataDir, - ServerCnxnFactory factory, String hostPort) throws IOException, + ServerCnxnFactory factory, String hostPort, int serverId) 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); + zks.setCreateSessionTrackerServerId(serverId); factory.startup(zks); Assert.assertTrue("waiting for server up", ClientBase.waitForServerUp( - "127.0.0.1:" + port, CONNECTION_TIMEOUT)); + "127.0.0.1:" + port, CONNECTION_TIMEOUT, factory.isSecure())); } /** * This method instantiates a new server. Starting of the server * instance has been moved to a separate method - * {@link ClientBase#startServerInstance(File, ServerCnxnFactory, String)}. + * {@link ClientBase#startServerInstance(File, ServerCnxnFactory, String, int)}. * 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. @@ -391,7 +473,8 @@ static void shutdownServerInstance(ServerCnxnFactory factory, Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown("127.0.0.1:" + PORT, - CONNECTION_TIMEOUT)); + CONNECTION_TIMEOUT, + factory.isSecure())); } } @@ -404,7 +487,7 @@ public static void setupTestEnv() { // resulting in test Assert.failure (client timeout on first session). // set env and directly in order to handle static init/gc issues System.setProperty("zookeeper.preAllocSize", "100"); - FileTxnLog.setPreallocSize(100 * 1024); + FilePadding.setPreallocSize(100 * 1024); } protected void setUpAll() throws Exception { @@ -414,6 +497,10 @@ protected void setUpAll() throws Exception { @Before public void setUp() throws Exception { + setUpWithServerId(1); + } + + protected void setUpWithServerId(int serverId) throws Exception { /* some useful information - log the number of fds used before * and after a test is run. Helps to verify we are freeing resources * correctly. Unfortunately this only works on unix systems (the @@ -432,18 +519,22 @@ public void setUp() throws Exception { setUpAll(); - tmpDir = createTmpDir(BASETEST); + tmpDir = createTmpDir(BASETEST, true); - startServer(); + startServer(serverId); LOG.info("Client test setup finished"); } protected void startServer() throws Exception { + startServer(1); + } + + private void startServer(int serverId) throws Exception { LOG.info("STARTING server"); serverFactory = createNewServerInstance(serverFactory, hostPort, maxCnxns); - startServerInstance(tmpDir, serverFactory, hostPort); + startServerInstance(tmpDir, serverFactory, hostPort, serverId); // ensure that server and data bean are registered Set children = JMXEnv.ensureParent("InMemoryDataTree", "StandaloneServer_port"); @@ -472,7 +563,7 @@ private void verifyUnexpectedBeans(Set children) { for (ObjectName bean : children) { LOG.info("unexpected:" + bean.toString()); } - TestCase.assertEquals("Unexpected bean exists!", 0, children.size()); + Assert.assertEquals("Unexpected bean exists!", 0, children.size()); } /** @@ -556,13 +647,7 @@ public static MBeanServerConnection jmxConn() throws IOException { } public static boolean recursiveDelete(File d) { - if (d.isDirectory()) { - File children[] = d.listFiles(); - for (File f : children) { - Assert.assertTrue("delete " + f.toString(), recursiveDelete(f)); - } - } - return d.delete(); + return TestUtils.deleteFileRecursively(d, true); } public static void logAllStackTraces() { @@ -662,4 +747,29 @@ public static String join(String separator, Object[] parts) { } 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 | 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 b807dbb0f43..01cdf2780c5 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 * (long)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 bf83f14ac3f..100d2a0b51a 100644 --- a/src/java/test/org/apache/zookeeper/test/ClientPortBindTest.java +++ b/src/java/test/org/apache/zookeeper/test/ClientPortBindTest.java @@ -24,29 +24,23 @@ 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.Watcher.Event.KeeperState; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ZooKeeperServer; import org.junit.Assert; import org.junit.Test; -public class ClientPortBindTest extends ZKTestCase implements Watcher { +public class ClientPortBindTest extends ZKTestCase{ protected static final Logger LOG = LoggerFactory.getLogger(ClientPortBindTest.class); - private volatile CountDownLatch startSignal; - /** * Verify that the server binds to the specified address */ @@ -58,11 +52,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(); + 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) { @@ -89,13 +91,8 @@ public void testBindByAddress() throws Exception { Assert.assertTrue("waiting for server up", ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); - - startSignal = new CountDownLatch(1); - ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); + ZooKeeper zk = ClientBase.createZKClient(HOSTPORT); try { - startSignal.await(CONNECTION_TIMEOUT, - TimeUnit.MILLISECONDS); - Assert.assertTrue("count == 0", startSignal.getCount() == 0); zk.close(); } finally { f.shutdown(); @@ -106,13 +103,4 @@ public void testBindByAddress() throws Exception { CONNECTION_TIMEOUT)); } } - - public void process(WatchedEvent event) { - LOG.info("Event:" + event.getState() + " " + event.getType() + " " + event.getPath()); - if (event.getState() == KeeperState.SyncConnected - && startSignal != null && startSignal.getCount() > 0) - { - startSignal.countDown(); - } - } } diff --git a/src/java/test/org/apache/zookeeper/test/ClientRetry.java b/src/java/test/org/apache/zookeeper/test/ClientRetryTest.java similarity index 97% rename from src/java/test/org/apache/zookeeper/test/ClientRetry.java rename to src/java/test/org/apache/zookeeper/test/ClientRetryTest.java index d0b6b43f4df..e53d911ef59 100644 --- a/src/java/test/org/apache/zookeeper/test/ClientRetry.java +++ b/src/java/test/org/apache/zookeeper/test/ClientRetryTest.java @@ -25,7 +25,7 @@ import org.junit.Assert; import org.junit.Test; -public class ClientRetry extends ClientBase { +public class ClientRetryTest extends ClientBase { @Override public void setUp() throws Exception { @@ -58,6 +58,7 @@ public void testClientRetry() throws IOException, InterruptedException, TimeoutE Assert.assertSame(s1,States.CONNECTED); Assert.assertSame(s2,States.CONNECTING); cdw1.reset(); + zk.close(); cdw1.waitForDisconnected(CONNECTION_TIMEOUT); cdw2.waitForConnected(CONNECTION_TIMEOUT); Assert.assertSame(zk2.getState(),States.CONNECTED); diff --git a/src/java/test/org/apache/zookeeper/test/ClientTest.java b/src/java/test/org/apache/zookeeper/test/ClientTest.java index dbe595c83c3..6373bb36e3e 100644 --- a/src/java/test/org/apache/zookeeper/test/ClientTest.java +++ b/src/java/test/org/apache/zookeeper/test/ClientTest.java @@ -18,12 +18,11 @@ package org.apache.zookeeper.test; -import static org.junit.Assert.fail; - 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.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -33,6 +32,7 @@ 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; @@ -47,6 +47,7 @@ import org.apache.zookeeper.proto.RequestHeader; import org.apache.zookeeper.server.PrepRequestProcessor; import org.apache.zookeeper.server.util.OSMXBean; +import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; @@ -113,8 +114,7 @@ public void testTestability() throws Exception { LOG.info("{}",zk.testableRemoteSocketAddress()); LOG.info("{}",zk.toString()); } finally { - zk.close(); - zk.testableWaitForShutdown(CONNECTION_TIMEOUT); + zk.close(CONNECTION_TIMEOUT); LOG.info("{}",zk.testableLocalSocketAddress()); LOG.info("{}",zk.testableRemoteSocketAddress()); LOG.info("{}",zk.toString()); @@ -759,11 +759,10 @@ public void run() { try { for (; current < count; current++) { TestableZooKeeper zk = createClient(); - zk.close(); // we've asked to close, wait for it to finish closing // all the sub-threads otw the selector may not be // closed when we check (false positive on test Assert.failure - zk.testableWaitForShutdown(CONNECTION_TIMEOUT); + zk.close(CONNECTION_TIMEOUT); } } catch (Throwable t) { LOG.error("test Assert.failed", t); @@ -829,7 +828,16 @@ public void testClientCleanup() throws Throwable { */ @Test public void testNonExistingOpCode() throws Exception { - TestableZooKeeper zk = createClient(); + 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"; @@ -839,14 +847,24 @@ public void testNonExistingOpCode() throws Exception { 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()); - try { - zk.exists("/m1", false); - fail("The connection should have been closed"); - } catch (KeeperException.ConnectionLossException expected) { + // Sending a nonexisting opcode should cause the server to disconnect + Assert.assertTrue("failed to disconnect", + clientDisconnected.await(5000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testTryWithResources() throws Exception { + ZooKeeper zooKeeper; + try (ZooKeeper zk = createClient()) { + zooKeeper = zk; + Assert.assertTrue(zooKeeper.getState().isAlive()); } + + Assert.assertFalse(zooKeeper.getState().isAlive()); } } diff --git a/src/java/test/org/apache/zookeeper/test/CnxManagerTest.java b/src/java/test/org/apache/zookeeper/test/CnxManagerTest.java index 563c77c41c8..a072bc065ce 100644 --- a/src/java/test/org/apache/zookeeper/test/CnxManagerTest.java +++ b/src/java/test/org/apache/zookeeper/test/CnxManagerTest.java @@ -18,6 +18,9 @@ package org.apache.zookeeper.test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.net.InetSocketAddress; @@ -30,12 +33,14 @@ import java.util.concurrent.TimeUnit; import java.net.Socket; +import org.apache.zookeeper.common.Time; 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.QuorumCnxManager.Message; +import org.apache.zookeeper.server.quorum.QuorumCnxManager.InitialMessage; import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.quorum.QuorumPeer.LearnerType; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; @@ -104,7 +109,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(); @@ -148,7 +153,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(); @@ -195,7 +200,7 @@ public void testCnxManagerTimeout() throws Exception { 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(); @@ -203,9 +208,9 @@ public void testCnxManagerTimeout() throws Exception { LOG.error("Null listener when initializing cnx manager"); } - long begin = System.currentTimeMillis(); + long begin = Time.currentElapsedTime(); cnxManager.toSend(2L, createMsg(ServerState.LOOKING.ordinal(), 1, -1, 1)); - long end = System.currentTimeMillis(); + long end = Time.currentElapsedTime(); if((end - begin) > 6000) Assert.fail("Waited more than necessary"); cnxManager.halt(); @@ -223,7 +228,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(); @@ -241,9 +246,9 @@ public void testCnxManagerSpinLock() throws Exception { InetSocketAddress otherAddr = peers.get(new Long(2)).electionAddr; DataOutputStream dout = new DataOutputStream(sc.socket().getOutputStream()); - dout.writeLong(0xffff0000); + dout.writeLong(QuorumCnxManager.PROTOCOL_VERSION); dout.writeLong(new Long(2)); - String addr = otherAddr.getHostName()+ ":" + otherAddr.getPort(); + String addr = otherAddr.getHostString()+ ":" + otherAddr.getPort(); byte[] addr_bytes = addr.getBytes(); dout.writeInt(addr_bytes.length); dout.write(addr_bytes); @@ -288,7 +293,7 @@ public void testCnxManagerNPE() throws Exception { peers.get(2L).type = LearnerType.OBSERVER; 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(); @@ -335,7 +340,7 @@ public void testCnxManagerNPE() throws Exception { @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(); @@ -348,10 +353,10 @@ public void testSocketTimeout() throws Exception { Socket sock = new Socket(); sock.connect(peers.get(1L).electionAddr, 5000); - long begin = System.currentTimeMillis(); + long begin = Time.currentElapsedTime(); // Read without sending data. Verify timeout. cnxManager.receiveConnection(sock); - long end = System.currentTimeMillis(); + long end = Time.currentElapsedTime(); if((end - begin) > ((peer.getSyncLimit() * peer.getTickTime()) + 500)) Assert.fail("Waited more than necessary"); cnxManager.halt(); Assert.assertFalse(cnxManager.listener.isAlive()); @@ -363,41 +368,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); + Assert.assertNull(failure, 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(); + } } } @@ -432,4 +439,85 @@ public String _verifyThreadCount(ArrayList peerList, long ecnt) { } return null; } + + @Test + public void testInitialMessage() throws Exception { + InitialMessage msg; + ByteArrayOutputStream bos; + DataInputStream din; + DataOutputStream dout; + String hostport; + + // message with bad protocol version + try { + + // the initial message (without the protocol version) + hostport = "10.0.0.2:3888"; + bos = new ByteArrayOutputStream(); + dout = new DataOutputStream(bos); + dout.writeLong(5L); // sid + dout.writeInt(hostport.getBytes().length); + dout.writeBytes(hostport); + + // now parse it + din = new DataInputStream(new ByteArrayInputStream(bos.toByteArray())); + msg = InitialMessage.parse(-65530L, din); + Assert.fail("bad protocol version accepted"); + } catch (InitialMessage.InitialMessageException ex) {} + + // message too long + try { + + hostport = createLongString(1048576); + bos = new ByteArrayOutputStream(); + dout = new DataOutputStream(bos); + dout.writeLong(5L); // sid + dout.writeInt(hostport.getBytes().length); + dout.writeBytes(hostport); + + din = new DataInputStream(new ByteArrayInputStream(bos.toByteArray())); + msg = InitialMessage.parse(QuorumCnxManager.PROTOCOL_VERSION, din); + Assert.fail("long message accepted"); + } catch (InitialMessage.InitialMessageException ex) {} + + // bad hostport string + try { + + hostport = "what's going on here?"; + bos = new ByteArrayOutputStream(); + dout = new DataOutputStream(bos); + dout.writeLong(5L); // sid + dout.writeInt(hostport.getBytes().length); + dout.writeBytes(hostport); + + din = new DataInputStream(new ByteArrayInputStream(bos.toByteArray())); + msg = InitialMessage.parse(QuorumCnxManager.PROTOCOL_VERSION, din); + Assert.fail("bad hostport accepted"); + } catch (InitialMessage.InitialMessageException ex) {} + + // good message + try { + + hostport = "10.0.0.2:3888"; + bos = new ByteArrayOutputStream(); + dout = new DataOutputStream(bos); + dout.writeLong(5L); // sid + dout.writeInt(hostport.getBytes().length); + dout.writeBytes(hostport); + + // now parse it + din = new DataInputStream(new ByteArrayInputStream(bos.toByteArray())); + msg = InitialMessage.parse(QuorumCnxManager.PROTOCOL_VERSION, din); + } catch (InitialMessage.InitialMessageException ex) { + Assert.fail(ex.toString()); + } + } + + private String createLongString(int size) { + StringBuilder sb = new StringBuilder(size); + for (int i=0; i < size; i++) { + sb.append('x'); + } + return sb.toString(); + } } diff --git a/src/java/test/org/apache/zookeeper/test/ConnectStringParserTest.java b/src/java/test/org/apache/zookeeper/test/ConnectStringParserTest.java index a169b7ba7d6..393cc0363bc 100644 --- a/src/java/test/org/apache/zookeeper/test/ConnectStringParserTest.java +++ b/src/java/test/org/apache/zookeeper/test/ConnectStringParserTest.java @@ -46,8 +46,8 @@ public void testParseServersWithoutPort(){ String servers = "10.10.10.1,10.10.10.2"; ConnectStringParser parser = new ConnectStringParser(servers); - Assert.assertEquals("10.10.10.1", parser.getServerAddresses().get(0).getHostName()); - Assert.assertEquals("10.10.10.2", parser.getServerAddresses().get(1).getHostName()); + Assert.assertEquals("10.10.10.1", parser.getServerAddresses().get(0).getHostString()); + Assert.assertEquals("10.10.10.2", parser.getServerAddresses().get(1).getHostString()); } @Test @@ -55,8 +55,8 @@ public void testParseServersWithPort(){ String servers = "10.10.10.1:112,10.10.10.2:110"; ConnectStringParser parser = new ConnectStringParser(servers); - Assert.assertEquals("10.10.10.1", parser.getServerAddresses().get(0).getHostName()); - Assert.assertEquals("10.10.10.2", parser.getServerAddresses().get(1).getHostName()); + Assert.assertEquals("10.10.10.1", parser.getServerAddresses().get(0).getHostString()); + Assert.assertEquals("10.10.10.2", parser.getServerAddresses().get(1).getHostString()); Assert.assertEquals(112, parser.getServerAddresses().get(0).getPort()); Assert.assertEquals(110, parser.getServerAddresses().get(1).getPort()); diff --git a/src/java/test/org/apache/zookeeper/test/CreateModeTest.java b/src/java/test/org/apache/zookeeper/test/CreateModeTest.java index 9db01bba19a..fc61adff1e3 100644 --- a/src/java/test/org/apache/zookeeper/test/CreateModeTest.java +++ b/src/java/test/org/apache/zookeeper/test/CreateModeTest.java @@ -35,21 +35,31 @@ public void testBasicCreateMode() { Assert.assertEquals(cm.toFlag(), 0); Assert.assertFalse(cm.isEphemeral()); Assert.assertFalse(cm.isSequential()); - + Assert.assertFalse(cm.isContainer()); + cm = CreateMode.EPHEMERAL; Assert.assertEquals(cm.toFlag(), 1); Assert.assertTrue(cm.isEphemeral()); Assert.assertFalse(cm.isSequential()); - + Assert.assertFalse(cm.isContainer()); + cm = CreateMode.PERSISTENT_SEQUENTIAL; Assert.assertEquals(cm.toFlag(), 2); Assert.assertFalse(cm.isEphemeral()); Assert.assertTrue(cm.isSequential()); - + Assert.assertFalse(cm.isContainer()); + cm = CreateMode.EPHEMERAL_SEQUENTIAL; Assert.assertEquals(cm.toFlag(), 3); Assert.assertTrue(cm.isEphemeral()); Assert.assertTrue(cm.isSequential()); + Assert.assertFalse(cm.isContainer()); + + cm = CreateMode.CONTAINER; + Assert.assertEquals(cm.toFlag(), 4); + Assert.assertFalse(cm.isEphemeral()); + Assert.assertFalse(cm.isSequential()); + Assert.assertTrue(cm.isContainer()); } @Test diff --git a/src/java/test/org/apache/zookeeper/test/CreateTest.java b/src/java/test/org/apache/zookeeper/test/CreateTest.java index 8693515800f..1b427a18a18 100644 --- a/src/java/test/org/apache/zookeeper/test/CreateTest.java +++ b/src/java/test/org/apache/zookeeper/test/CreateTest.java @@ -18,14 +18,9 @@ package org.apache.zookeeper.test; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooKeeper; -import org.apache.zookeeper.AsyncCallback.Create2Callback; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.Stat; import org.junit.Assert; diff --git a/src/java/test/org/apache/zookeeper/test/DisconnectedWatcherTest.java b/src/java/test/org/apache/zookeeper/test/DisconnectedWatcherTest.java index 4a76f124bfe..cad38f03a33 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,80 @@ 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/EmptiedSnapshotRecoveryTest.java b/src/java/test/org/apache/zookeeper/test/EmptiedSnapshotRecoveryTest.java new file mode 100644 index 00000000000..5b2f8a48153 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/EmptiedSnapshotRecoveryTest.java @@ -0,0 +1,133 @@ +/** + * 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.io.File; +import java.io.PrintWriter; +import java.util.List; +import java.util.LinkedList; + +import org.apache.log4j.Logger; +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.quorum.Leader.Proposal; +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; + +/** If snapshots are corrupted to the empty file or deleted, Zookeeper should + * not proceed to read its transactiong log files + * Test that zxid == -1 in the presence of emptied/deleted snapshots + */ +public class EmptiedSnapshotRecoveryTest extends ZKTestCase implements Watcher { + private static final Logger LOG = Logger.getLogger(RestoreCommittedLogTest.class); + private static String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); + private static final int CONNECTION_TIMEOUT = 3000; + private static final int N_TRANSACTIONS = 150; + private static final int SNAP_COUNT = 100; + + public void runTest(boolean leaveEmptyFile) throws Exception { + File tmpSnapDir = ClientBase.createTmpDir(); + File tmpLogDir = ClientBase.createTmpDir(); + ClientBase.setupTestEnv(); + ZooKeeperServer zks = new ZooKeeperServer(tmpSnapDir, tmpLogDir, 3000); + SyncRequestProcessor.setSnapCount(SNAP_COUNT); + 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< N_TRANSACTIONS; i++) { + zk.create("/node-" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } + } finally { + zk.close(); + } + f.shutdown(); + zks.shutdown(); + Assert.assertTrue("waiting for server to shutdown", + ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); + + // start server again with intact database + zks = new ZooKeeperServer(tmpSnapDir, tmpLogDir, 3000); + zks.startdata(); + long zxid = zks.getZKDatabase().getDataTreeLastProcessedZxid(); + LOG.info("After clean restart, zxid = " + zxid); + Assert.assertTrue("zxid > 0", zxid > 0); + zks.shutdown(); + + // Make all snapshots empty + FileTxnSnapLog txnLogFactory = zks.getTxnLogFactory(); + List snapshots = txnLogFactory.findNRecentSnapshots(10); + Assert.assertTrue("We have a snapshot to corrupt", snapshots.size() > 0); + for (File file: snapshots) { + if (leaveEmptyFile) { + new PrintWriter(file).close (); + } else { + file.delete(); + } + } + + // start server again with corrupted database + zks = new ZooKeeperServer(tmpSnapDir, tmpLogDir, 3000); + try { + zks.startdata(); + zxid = zks.getZKDatabase().loadDataBase(); + Assert.fail("Should have gotten exception for corrupted database"); + } catch (IOException e) { + // expected behavior + } + zks.shutdown(); + } + + /** + * Test resilience to empty Snapshots + * @throws Exception an exception might be thrown here + */ + @Test + public void testRestoreWithEmptySnapFiles() throws Exception { + runTest(true); + } + + /** + * Test resilience to deletion of Snapshots + * @throws Exception an exception might be thrown here + */ + @Test + public void testRestoreWithNoSnapFiles() throws Exception { + runTest(false); + } + + public void process(WatchedEvent event) { + // do nothing + } + +} diff --git a/src/java/test/org/apache/zookeeper/test/FLEPredicateTest.java b/src/java/test/org/apache/zookeeper/test/FLEPredicateTest.java index a4244d89846..bc437754e76 100644 --- a/src/java/test/org/apache/zookeeper/test/FLEPredicateTest.java +++ b/src/java/test/org/apache/zookeeper/test/FLEPredicateTest.java @@ -41,7 +41,7 @@ public class FLEPredicateTest extends ZKTestCase { class MockFLE extends FastLeaderElection { MockFLE(QuorumPeer peer){ - super(peer, new QuorumCnxManager(peer)); + super(peer, peer.createCnxnManager()); } boolean predicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch){ diff --git a/src/java/test/org/apache/zookeeper/test/FLETest.java b/src/java/test/org/apache/zookeeper/test/FLETest.java index ef694a58c67..51bcecb9bda 100644 --- a/src/java/test/org/apache/zookeeper/test/FLETest.java +++ b/src/java/test/org/apache/zookeeper/test/FLETest.java @@ -528,4 +528,9 @@ public void testJoinInconsistentEnsemble() throws Exception { } } } + + @Test + public void testElectionTimeUnit() throws Exception { + Assert.assertEquals("MS", QuorumPeer.FLE_TIME_UNIT); + } } diff --git a/src/java/test/org/apache/zookeeper/test/FollowerResyncConcurrencyTest.java b/src/java/test/org/apache/zookeeper/test/FollowerResyncConcurrencyTest.java index e6dd653ce91..50867113653 100644 --- a/src/java/test/org/apache/zookeeper/test/FollowerResyncConcurrencyTest.java +++ b/src/java/test/org/apache/zookeeper/test/FollowerResyncConcurrencyTest.java @@ -108,7 +108,7 @@ public void testLaggingFollowerResyncsUnderNewEpoch() throws Exception { ZooKeeper zk1 = createClient(qu.getPeer(1).peer.getClientPort(), watcher1); - LOG.info("zk1 has session id 0x" + Long.toHexString(zk1.getSessionId())); + 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); @@ -133,19 +133,19 @@ public void testLaggingFollowerResyncsUnderNewEpoch() throws Exception { + 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())); + 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())); + 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())); + LOG.info("zk3 has session id 0x{}", Long.toHexString(zk3.getSessionId())); assertNotNull("zk3 has data", zk3.exists(resyncPath, false)); @@ -224,13 +224,13 @@ public void followerResyncCrashTest(boolean useTxnLogResync) /* Reusing the index variable to select a follower to connect to */ index = (index == 1) ? 2 : 1; - LOG.info("Connecting to follower:" + index); + LOG.info("Connecting to follower: {}", index); qu.shutdown(index); final ZooKeeper zk3 = createClient(qu.getPeer(3).peer.getClientPort(), watcher3); - LOG.info("zk3 has session id 0x" + Long.toHexString(zk3.getSessionId())); + LOG.info("zk3 has session id 0x{}", Long.toHexString(zk3.getSessionId())); zk3.create("/mybar", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); @@ -238,11 +238,11 @@ public void followerResyncCrashTest(boolean useTxnLogResync) final ZooKeeper zk1 = createClient(qu.getPeer(index).peer.getClientPort(), watcher1); - LOG.info("zk1 has session id 0x" + Long.toHexString(zk1.getSessionId())); + LOG.info("zk1 has session id 0x{}", Long.toHexString(zk1.getSessionId())); final ZooKeeper zk2 = createClient(qu.getPeer(index).peer.getClientPort(), watcher2); - LOG.info("zk2 has session id 0x" + Long.toHexString(zk2.getSessionId())); + LOG.info("zk2 has session id 0x{}", Long.toHexString(zk2.getSessionId())); zk1.create("/first", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); @@ -309,15 +309,15 @@ public void processResult(int rc, String path, Object ctx, String name) { // should use txnlog to catchup. For subsequent restart, the // follower should use a diff to catchup. mytestfooThread.start(); - LOG.info("Restarting follower " + index); + LOG.info("Restarting follower: {}", index); qu.restart(index); Thread.sleep(300); - LOG.info("Shutdown follower " + index); + LOG.info("Shutdown follower: {}", index); qu.shutdown(index); Thread.sleep(300); - LOG.info("Restarting follower " + index); + LOG.info("Restarting follower: {}", index); qu.restart(index); - LOG.info("Setting up server: " + index); + LOG.info("Setting up server: {}", index); } if((i % 1000) == 0){ Thread.sleep(1000); @@ -407,19 +407,19 @@ public void testResyncByDiffAfterFollowerCrashes() /* Reusing the index variable to select a follower to connect to */ index = (index == 1) ? 2 : 1; - LOG.info("Connecting to follower:" + index); + LOG.info("Connecting to follower: {}", index); final ZooKeeper zk1 = createClient(qu.getPeer(index).peer.getClientPort(), watcher1); - LOG.info("zk1 has session id 0x" + Long.toHexString(zk1.getSessionId())); + LOG.info("zk1 has session id 0x{}", Long.toHexString(zk1.getSessionId())); final ZooKeeper zk2 = createClient(qu.getPeer(index).peer.getClientPort(), watcher2); - LOG.info("zk2 has session id 0x" + Long.toHexString(zk2.getSessionId())); + 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())); + 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); @@ -490,7 +490,7 @@ public void processResult(int rc, String path, Object ctx, String name) { qu.startThenShutdown(index); runNow.set(true); qu.restart(index); - LOG.info("Setting up server: " + index); + LOG.info("Setting up server: {}", index); } if(i>=1000 && i%2== 0) { @@ -553,14 +553,14 @@ private static DisconnectableZooKeeper createClient(int port, * verifying the state */ private boolean waitForPendingRequests(int timeout) throws InterruptedException { - LOG.info("Wait for pending requests: " + pending.get()); + LOG.info("Wait for pending requests: {}", pending.get()); for (int i = 0; i < timeout; ++i) { Thread.sleep(1000); if (pending.get() == 0) { return true; } } - LOG.info("Timeout waiting for pending requests: " + pending.get()); + LOG.info("Timeout waiting for pending requests: {}", pending.get()); return false; } @@ -585,9 +585,10 @@ private boolean waitForSync(QuorumUtil qu, int index, int timeout) throws Interr } Thread.sleep(1000); } - LOG.info("Timeout waiting for zxid to sync: leader 0x" + Long.toHexString(leadZxid)+ - "clean 0x" + Long.toHexString(cleanZxid) + - "restarted 0x" + Long.toHexString(restartedZxid)); + LOG.info("Timeout waiting for zxid to sync: leader 0x{}" + + "clean 0x{}" + + "restarted 0x{}", Long.toHexString(leadZxid), Long.toHexString(cleanZxid), + Long.toHexString(restartedZxid)); return false; } @@ -628,7 +629,7 @@ private void verifyState(QuorumUtil qu, int index, Leader leader) { ZKDatabase clean = qu.getPeer(3).peer.getActiveServer().getZKDatabase(); ZKDatabase lead = qu.getPeer(leaderIndex).peer.getActiveServer().getZKDatabase(); for(Long l : sessionsRestarted) { - LOG.info("Validating ephemeral for session id 0x" + Long.toHexString(l)); + LOG.info("Validating ephemeral for session id 0x{}", Long.toHexString(l)); assertTrue("Should have same set of sessions in both servers, did not expect: " + l, sessionsNotRestarted.contains(l)); Set ephemerals = restarted.getEphemerals(l); Set cleanEphemerals = clean.getEphemerals(l); @@ -674,7 +675,7 @@ public void testFollowerSendsLastZxid() throws Exception { while(qu.getPeer(index).peer.follower == null) { index++; } - LOG.info("Connecting to follower:" + index); + LOG.info("Connecting to follower: {}", index); TestableZooKeeper zk = createTestableClient("localhost:" + qu.getPeer(index).peer.getClientPort()); @@ -716,7 +717,7 @@ public void testFollowerWatcherResync() throws Exception { while(qu.getPeer(index).peer.follower == null) { index++; } - LOG.info("Connecting to follower:" + index); + LOG.info("Connecting to follower: {}", index); TestableZooKeeper zk1 = createTestableClient( "localhost:" + qu.getPeer(index).peer.getClientPort()); diff --git a/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java b/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java index 49d90f76e07..28fbde5f607 100644 --- a/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java +++ b/src/java/test/org/apache/zookeeper/test/FourLetterWordsQuorumTest.java @@ -23,7 +23,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.common.X509Exception.SSLContextException; + import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord; + import org.junit.Assert; import org.junit.Test; @@ -61,6 +64,7 @@ public void testFourLetterWords() throws Exception { verify(hp, "srvr", "Outstanding"); verify(hp, "cons", sid); verify(hp, "dump", sid); + verify(hp, "dirs", "size"); zk.getData("/", true, null); @@ -71,6 +75,7 @@ public void testFourLetterWords() throws Exception { verify(hp, "wchs", "watching 1"); verify(hp, "wchp", sid); verify(hp, "wchc", sid); + verify(hp, "dirs", "size"); zk.close(); @@ -84,6 +89,7 @@ public void testFourLetterWords() throws Exception { verify(hp, "wchs", "watch"); verify(hp, "wchp", ""); verify(hp, "wchc", ""); + verify(hp, "dirs", "size"); verify(hp, "srst", "reset"); verify(hp, "crst", "reset"); @@ -97,7 +103,7 @@ public void testFourLetterWords() throws Exception { } private void verify(String hp, String cmd, String expected) - throws IOException + throws IOException, SSLContextException { for(HostPort hpobj: parseHostPortList(hp)) { String resp = send4LetterWord(hpobj.host, hpobj.port, cmd); diff --git a/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java b/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java index 281b1786954..ad71eabb3bd 100644 --- a/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java +++ b/src/java/test/org/apache/zookeeper/test/FourLetterWordsTest.java @@ -19,15 +19,23 @@ 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 org.apache.zookeeper.common.X509Exception.SSLContextException; + 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; @@ -35,6 +43,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 { @@ -55,6 +66,8 @@ public void testFourLetterWords() throws Exception { verify("stat", "Outstanding"); verify("srvr", "Outstanding"); verify("cons", "queued"); + verify("gtmk", "306"); + verify("isro", "rw"); TestableZooKeeper zk = createClient(); String sid = getHexSessionId(zk.getSessionId()); @@ -63,6 +76,7 @@ public void testFourLetterWords() throws Exception { verify("srvr", "Outstanding"); verify("cons", sid); verify("dump", sid); + verify("dirs", "size"); zk.getData("/", true, null); @@ -74,6 +88,7 @@ public void testFourLetterWords() throws Exception { verify("wchs", "watching 1"); verify("wchp", sid); verify("wchc", sid); + verify("dirs", "size"); zk.close(); verify("ruok", "imok"); @@ -97,21 +112,26 @@ public void testFourLetterWords() throws Exception { verify("mntr", "num_alive_connections"); verify("stat", "Connections"); verify("srvr", "Connections"); + verify("dirs", "size"); } - private String sendRequest(String cmd) throws IOException { + private String sendRequest(String cmd) throws IOException, SSLContextException { HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0); return send4LetterWord(hpobj.host, hpobj.port, cmd); } + private String sendRequest(String cmd, int timeout) throws IOException, SSLContextException { + HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0); + return send4LetterWord(hpobj.host, hpobj.port, cmd, false, timeout); + } - private void verify(String cmd, String expected) throws IOException { + private void verify(String cmd, String expected) throws IOException, SSLContextException { String resp = sendRequest(cmd); LOG.info("cmd " + cmd + " expected " + expected + " got " + resp); Assert.assertTrue(resp.contains(expected)); } @Test - public void validateStatOutput() throws Exception { + public void testValidateStatOutput() throws Exception { ZooKeeper zk1 = createClient(); ZooKeeper zk2 = createClient(); @@ -154,7 +174,7 @@ public void validateStatOutput() throws Exception { } @Test - public void validateConsOutput() throws Exception { + public void testValidateConsOutput() throws Exception { ZooKeeper zk1 = createClient(); ZooKeeper zk2 = createClient(); @@ -173,4 +193,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..f5d69673e0e --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/FourLetterWordsWhiteListTest.java @@ -0,0 +1,251 @@ +/** + * 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.common.X509Exception.SSLContextException; + +import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord; + +import org.apache.zookeeper.server.command.FourLetterCommands; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FourLetterWordsWhiteListTest extends ClientBase { + protected static final Logger LOG = + LoggerFactory.getLogger(FourLetterWordsWhiteListTest.class); + + /* + * 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(); + FourLetterCommands.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(); + FourLetterCommands.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("dirs", generateExpectedMessage("dirs")); + 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(); + FourLetterCommands.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(); + FourLetterCommands.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(); + FourLetterCommands.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "*"); + startServer(); + verifyAllCommandsSuccess(); + } + + @Test(timeout=30000) + public void testFourLetterWordsEnableAllCommandsThroughExplicitList() throws Exception { + stopServer(); + FourLetterCommands.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", + "ruok, envi, conf, stat, srvr, cons, dump," + + "wchs, wchp, wchc, srst, crst, " + + "dirs, 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); + verifyFuzzyMatch("dirs", "size"); + + 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); + verifyFuzzyMatch("dirs", "size"); + 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"); + verifyFuzzyMatch("dirs", "size"); + } + + private void verifyAllCommandsFail() throws Exception { + verifyExactMatch("ruok", generateExpectedMessage("ruok")); + verifyExactMatch("conf", generateExpectedMessage("conf")); + verifyExactMatch("cons", generateExpectedMessage("cons")); + verifyExactMatch("crst", generateExpectedMessage("crst")); + verifyExactMatch("dirs", generateExpectedMessage("dirs")); + 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 String sendRequest(String cmd) throws IOException, SSLContextException { + HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0); + return send4LetterWord(hpobj.host, hpobj.port, cmd); + } + + private void verifyFuzzyMatch(String cmd, String expected) throws IOException, SSLContextException { + 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, SSLContextException { + String resp = sendRequest(cmd); + LOG.info("cmd " + cmd + " expected an exact match of " + expected + "; got " + resp); + Assert.assertTrue(resp.trim().equals(expected)); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/GetProposalFromTxnTest.java b/src/java/test/org/apache/zookeeper/test/GetProposalFromTxnTest.java index aaf195401ba..660980afe31 100644 --- a/src/java/test/org/apache/zookeeper/test/GetProposalFromTxnTest.java +++ b/src/java/test/org/apache/zookeeper/test/GetProposalFromTxnTest.java @@ -24,11 +24,8 @@ import java.util.Iterator; import org.apache.jute.Record; -import org.apache.log4j.Logger; 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.ZooDefs.OpCode; @@ -48,9 +45,7 @@ * Test loading committed proposal from txnlog. Learner uses these proposals to * catch-up with leader */ -public class GetProposalFromTxnTest extends ZKTestCase implements Watcher { - private static final Logger LOG = Logger - .getLogger(GetProposalFromTxnTest.class); +public class GetProposalFromTxnTest extends ZKTestCase{ private static String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); private static final int CONNECTION_TIMEOUT = 3000; @@ -73,7 +68,7 @@ public void testGetProposalFromTxn() 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); + ZooKeeper zk = ClientBase.createZKClient(HOSTPORT); // Generate transaction so we will have some txnlog Long[] zxids = new Long[MSG_COUNT]; @@ -139,9 +134,4 @@ public void testGetProposalFromTxn() throws Exception { f.shutdown(); zks.shutdown(); } - - public void process(WatchedEvent event) { - // do nothing - } - } diff --git a/src/java/test/org/apache/zookeeper/test/HierarchicalQuorumTest.java b/src/java/test/org/apache/zookeeper/test/HierarchicalQuorumTest.java index 6caa3947c3b..3050093941e 100644 --- a/src/java/test/org/apache/zookeeper/test/HierarchicalQuorumTest.java +++ b/src/java/test/org/apache/zookeeper/test/HierarchicalQuorumTest.java @@ -108,11 +108,11 @@ public void setUp() throws Exception { "weight.3=1\n" + "weight.4=0\n" + "weight.5=0\n" + - "server.1=127.0.0.1:" + (port1 + 1000) + ":" + (leport1 + 1000) + ";" + clientport1 + "\n" + - "server.2=127.0.0.1:" + (port2 + 1000) + ":" + (leport2 + 1000) + ";" + clientport2 + "\n" + - "server.3=127.0.0.1:" + (port3 + 1000) + ":" + (leport3 + 1000) + ";" + clientport3 + "\n" + - "server.4=127.0.0.1:" + (port4 + 1000) + ":" + (leport4 + 1000) + ";" + clientport4 + "\n" + - "server.5=127.0.0.1:" + (port5 + 1000) + ":" + (leport5 + 1000) + ";" + clientport5 + "\n"; + "server.1=127.0.0.1:" + port1 + ":" + leport1 + ";" + clientport1 + "\n" + + "server.2=127.0.0.1:" + port2 + ":" + leport2 + ";" + clientport2 + "\n" + + "server.3=127.0.0.1:" + port3 + ":" + leport3 + ";" + clientport3 + "\n" + + "server.4=127.0.0.1:" + port4 + ":" + leport4 + ";" + clientport4 + "\n" + + "server.5=127.0.0.1:" + port5 + ":" + leport5 + ";" + clientport5 + "\n"; ByteArrayInputStream is = new ByteArrayInputStream(config.getBytes()); this.qp = new Properties(); @@ -147,26 +147,26 @@ void startServers(boolean withObservers) throws Exception { 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), + new InetSocketAddress("127.0.0.1", port1), + new InetSocketAddress("127.0.0.1", leport1), new InetSocketAddress("127.0.0.1", clientport1))); peers.put(Long.valueOf(2), new QuorumServer(2, - new InetSocketAddress("127.0.0.1", port2 + 1000), - new InetSocketAddress("127.0.0.1", leport2 + 1000), + new InetSocketAddress("127.0.0.1", port2), + new InetSocketAddress("127.0.0.1", leport2), new InetSocketAddress("127.0.0.1", clientport2))); peers.put(Long.valueOf(3), new QuorumServer(3, - new InetSocketAddress("127.0.0.1", port3 + 1000), - new InetSocketAddress("127.0.0.1", leport3 + 1000), + new InetSocketAddress("127.0.0.1", port3), + new InetSocketAddress("127.0.0.1", leport3), new InetSocketAddress("127.0.0.1", clientport3))); peers.put(Long.valueOf(4), new QuorumServer(4, - new InetSocketAddress("127.0.0.1", port4 + 1000), + new InetSocketAddress("127.0.0.1", port4), new InetSocketAddress("127.0.0.1", leport4), new InetSocketAddress("127.0.0.1", clientport4), 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), + new InetSocketAddress("127.0.0.1", port5), + new InetSocketAddress("127.0.0.1", leport5), new InetSocketAddress("127.0.0.1", clientport5), withObservers ? QuorumPeer.LearnerType.OBSERVER : QuorumPeer.LearnerType.PARTICIPANT)); @@ -174,8 +174,8 @@ void startServers(boolean withObservers) throws Exception { LOG.info("creating QuorumPeer 1 port " + clientport1); if (withObservers) { - qp.setProperty("server.4", "127.0.0.1:" + (port4 + 1000) + ":" + (leport4 + 1000) + ":observer" + ";" + clientport4); - qp.setProperty("server.5", "127.0.0.1:" + (port5 + 1000) + ":" + (leport5 + 1000) + ":observer" + ";" + clientport5); + qp.setProperty("server.4", "127.0.0.1:" + port4 + ":" + leport4 + ":observer" + ";" + clientport4); + qp.setProperty("server.5", "127.0.0.1:" + port5 + ":" + leport5 + ":observer" + ";" + clientport5); } QuorumHierarchical hq1 = new QuorumHierarchical(qp); s1 = new QuorumPeer(peers, s1dir, s1dir, clientport1, 3, 1, tickTime, initLimit, syncLimit, hq1); diff --git a/src/java/test/org/apache/zookeeper/test/IntegrityCheck.java b/src/java/test/org/apache/zookeeper/test/IntegrityCheck.java index 9a01a6587f7..4914f2d5431 100644 --- a/src/java/test/org/apache/zookeeper/test/IntegrityCheck.java +++ b/src/java/test/org/apache/zookeeper/test/IntegrityCheck.java @@ -31,7 +31,6 @@ * a value that we have previously read or set. (Each time we set a value, the * value will be one more than the previous set.) */ -import java.io.IOException; import java.util.Date; import java.util.HashMap; @@ -39,16 +38,13 @@ import org.slf4j.LoggerFactory; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.WatchedEvent; -import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.AsyncCallback.DataCallback; import org.apache.zookeeper.AsyncCallback.StatCallback; -import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.data.Stat; -public class IntegrityCheck implements Watcher, StatCallback, DataCallback { +public class IntegrityCheck implements StatCallback, DataCallback { private static final Logger LOG = LoggerFactory.getLogger(IntegrityCheck.class); ZooKeeper zk; @@ -81,8 +77,8 @@ synchronized void waitOutstanding() throws InterruptedException { } IntegrityCheck(String hostPort, String path, int count) throws - IOException { - zk = new ZooKeeper(hostPort, 30000, this); + Exception { + zk = ClientBase.createZKClient(hostPort); this.path = path; this.count = count; } @@ -147,15 +143,6 @@ void doPopulate() { } } - // watcher callback - public void process(WatchedEvent event) { - if(event.getState()==KeeperState.SyncConnected){ - synchronized(this){ - notifyAll(); - } - } - } - synchronized void ensureConnected(){ while(zk.getState()!=ZooKeeper.States.CONNECTED){ try { diff --git a/src/java/test/org/apache/zookeeper/test/InvalidSnapshotTest.java b/src/java/test/org/apache/zookeeper/test/InvalidSnapshotTest.java index 6f56b062def..df32ba8e726 100644 --- a/src/java/test/org/apache/zookeeper/test/InvalidSnapshotTest.java +++ b/src/java/test/org/apache/zookeeper/test/InvalidSnapshotTest.java @@ -21,12 +21,8 @@ import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; import java.io.File; -import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.PortAssignment; -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.ZooKeeper; import org.apache.zookeeper.server.LogFormatter; @@ -39,14 +35,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class InvalidSnapshotTest extends ZKTestCase implements Watcher { +public class InvalidSnapshotTest extends ZKTestCase{ private final static Logger LOG = LoggerFactory.getLogger(InvalidSnapshotTest.class); private static final String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); private static final File testData = new File( System.getProperty("test.data.dir", "build/test/data")); - private CountDownLatch startSignal; /** * Verify the LogFormatter by running it on a known file. @@ -96,7 +91,7 @@ public void testSnapshot() throws Exception { LOG.info("starting up the zookeeper server .. waiting"); Assert.assertTrue("waiting for server being up", ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); - ZooKeeper zk = new ZooKeeper(HOSTPORT, 20000, this); + ZooKeeper zk = ClientBase.createZKClient(HOSTPORT); try { // we know this from the data files // this node is the last node in the snapshot @@ -112,13 +107,4 @@ public void testSnapshot() throws Exception { ClientBase.CONNECTION_TIMEOUT)); } - - public void process(WatchedEvent event) { - LOG.info("Event:" + event.getState() + " " + event.getType() + " " + event.getPath()); - if (event.getState() == KeeperState.SyncConnected - && startSignal != null && startSignal.getCount() > 0) - { - startSignal.countDown(); - } - } } diff --git a/src/java/test/org/apache/zookeeper/test/JMXEnv.java b/src/java/test/org/apache/zookeeper/test/JMXEnv.java index 6d6a2faf4e8..4edcc0eb123 100644 --- a/src/java/test/org/apache/zookeeper/test/JMXEnv.java +++ b/src/java/test/org/apache/zookeeper/test/JMXEnv.java @@ -33,10 +33,10 @@ import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; -import junit.framework.TestCase; 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 +60,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); @@ -121,7 +125,7 @@ public static Set ensureAll(String... expectedNames) beans.removeAll(found); } } while ((expectedNames.length != found.size()) && (nTry < 600)); - TestCase.assertEquals("expected " + Arrays.toString(expectedNames), + Assert.assertEquals("expected " + Arrays.toString(expectedNames), expectedNames.length, found.size()); return beans; } @@ -144,7 +148,7 @@ public static Set ensureOnly(String... expectedNames) for (ObjectName bean : beans) { LOG.info("unexpected:" + bean.toString()); } - TestCase.assertEquals(0, beans.size()); + Assert.assertEquals(0, beans.size()); return beans; } @@ -186,7 +190,7 @@ public static void ensureNone(String... expectedNames) for (ObjectName bean : beans) { LOG.info("bean:" + bean.toString()); } - TestCase.fail(unexpectedName); + Assert.fail(unexpectedName); } } @@ -250,7 +254,7 @@ public static Set ensureParent(String... expectedNames) beans.removeAll(found); } } while (expectedNames.length != found.size() && nTry < 120); - TestCase.assertEquals("expected " + Arrays.toString(expectedNames), + Assert.assertEquals("expected " + Arrays.toString(expectedNames), expectedNames.length, found.size()); return beans; } @@ -296,7 +300,7 @@ public static Object ensureBeanAttribute(String expectedName, } } } while (nTry < 120); - TestCase.fail("Failed to find bean:" + expectedName + ", attribute:" + 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 e9471beadf0..2bbf7b581aa 100644 --- a/src/java/test/org/apache/zookeeper/test/LENonTerminateTest.java +++ b/src/java/test/org/apache/zookeeper/test/LENonTerminateTest.java @@ -219,7 +219,7 @@ public MockQuorumPeer(Map quorumPeers, File snapDir, super(quorumPeers, snapDir, logDir, electionAlg, myid,tickTime, initLimit,syncLimit, false, ServerCnxnFactory.createFactory(clientPort, -1), - new QuorumMaj(quorumPeers), null); + new QuorumMaj(quorumPeers)); } protected Election createElectionAlgorithm(int electionAlgorithm){ diff --git a/src/java/test/org/apache/zookeeper/test/LeaderSessionTrackerTest.java b/src/java/test/org/apache/zookeeper/test/LeaderSessionTrackerTest.java index 6f62b7d5ba6..a45b9cbfff6 100644 --- a/src/java/test/org/apache/zookeeper/test/LeaderSessionTrackerTest.java +++ b/src/java/test/org/apache/zookeeper/test/LeaderSessionTrackerTest.java @@ -18,7 +18,6 @@ package org.apache.zookeeper.test; -import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; @@ -26,8 +25,6 @@ import org.apache.jute.BinaryOutputArchive; import org.apache.zookeeper.CreateMode; -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.ZooDefs.OpCode; @@ -49,7 +46,7 @@ * expired session. We need to make sure that we never allow ephmeral node * to be created in those case, but we do allow normal node to be created. */ -public class LeaderSessionTrackerTest extends ZKTestCase implements Watcher { +public class LeaderSessionTrackerTest extends ZKTestCase { protected static final Logger LOG = LoggerFactory .getLogger(LeaderSessionTrackerTest.class); @@ -85,16 +82,13 @@ public void testExpiredSessionWithoutLocalSession() throws Exception { * is not in closing state */ public void testCreateEphemeral(boolean localSessionEnabled) throws Exception { - QuorumUtil qu = new QuorumUtil(1); if (localSessionEnabled) { qu.enableLocalSession(true); } qu.startAll(); - QuorumPeer leader = qu.getLeaderQuorumPeer(); - ZooKeeper zk = new ZooKeeper(qu.getConnectString(leader), - CONNECTION_TIMEOUT, this); + ZooKeeper zk = ClientBase.createZKClient(qu.getConnectString(leader)); CreateRequest createRequest = new CreateRequest("/impossible", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL.toFlag()); @@ -131,14 +125,12 @@ public void testCreateEphemeral(boolean localSessionEnabled) throws Exception { */ @Test public void testCreatePersistent() throws Exception { - QuorumUtil qu = new QuorumUtil(1); qu.enableLocalSession(true); qu.startAll(); QuorumPeer leader = qu.getLeaderQuorumPeer(); - ZooKeeper zk = new ZooKeeper(qu.getConnectString(leader), - CONNECTION_TIMEOUT, this); + ZooKeeper zk = ClientBase.createZKClient(qu.getConnectString(leader)); CreateRequest createRequest = new CreateRequest("/success", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT.toFlag()); @@ -168,9 +160,4 @@ public void testCreatePersistent() throws Exception { Assert.assertTrue("Request from local sesson failed", stat != null); } - - @Override - public void process(WatchedEvent event) { - } - } diff --git a/src/java/test/org/apache/zookeeper/test/LoadFromLogNoServerTest.java b/src/java/test/org/apache/zookeeper/test/LoadFromLogNoServerTest.java new file mode 100644 index 00000000000..4d56f60db2f --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/LoadFromLogNoServerTest.java @@ -0,0 +1,176 @@ +/** + * 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.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.common.Time; +import org.apache.zookeeper.server.DataNode; +import org.apache.zookeeper.server.DataTree; +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.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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class LoadFromLogNoServerTest extends ZKTestCase { + protected static final Logger LOG = LoggerFactory.getLogger(LoadFromLogNoServerTest.class); + + /** + * For ZOOKEEPER-1046. Verify if cversion and pzxid if incremented + * after create/delete failure during restore. + */ + @Test + public void testTxnFailure() throws Exception { + long count = 1; + File tmpDir = ClientBase.createTmpDir(); + FileTxnSnapLog logFile = new FileTxnSnapLog(tmpDir, tmpDir); + DataTree dt = new DataTree(); + 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, + Time.currentElapsedTime()); + } + DataNode zk = dt.getNode("/test"); + + // Make create to fail, then verify cversion. + LOG.info("Attempting to create " + "/test/" + (count - 1)); + doOp(logFile, ZooDefs.OpCode.create, "/test/" + (count - 1), dt, zk, -1); + + LOG.info("Attempting to create " + "/test/" + (count - 1)); + doOp(logFile, ZooDefs.OpCode.create, "/test/" + (count - 1), dt, zk, + zk.stat.getCversion() + 1); + + LOG.info("Attempting to create " + "/test/" + (count - 1)); + doOp(logFile, ZooDefs.OpCode.multi, "/test/" + (count - 1), dt, zk, + zk.stat.getCversion() + 1); + + LOG.info("Attempting to create " + "/test/" + (count - 1)); + doOp(logFile, ZooDefs.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 + // LOG.info("Attempting to delete " + "/test/" + (count + 1)); + // doOp(logFile, OpCode.delete, "/test/" + (count + 1), dt, zk); + } + + /* + * Does create/delete depending on the type and verifies + * if cversion before the operation is 1 less than cversion afer. + */ + private void doOp(FileTxnSnapLog logFile, int type, String path, + DataTree dt, DataNode parent, int cversion) throws Exception { + int lastSlash = path.lastIndexOf('/'); + String parentName = path.substring(0, lastSlash); + + int prevCversion = parent.stat.getCversion(); + long prevPzxid = parent.stat.getPzxid(); + List child = dt.getChildren(parentName, null, null); + StringBuilder childStr = new StringBuilder(); + for (String s : child) { + childStr.append(s).append(" "); + } + LOG.info("Children: " + childStr + " for " + parentName); + LOG.info("(cverions, pzxid): " + prevCversion + ", " + prevPzxid); + + Record txn = null; + TxnHeader txnHeader = null; + if (type == ZooDefs.OpCode.delete) { + txn = new DeleteTxn(path); + txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, + Time.currentElapsedTime(), ZooDefs.OpCode.delete); + } else if (type == ZooDefs.OpCode.create) { + txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, + Time.currentElapsedTime(), ZooDefs.OpCode.create); + txn = new CreateTxn(path, new byte[0], null, false, cversion); + } + else if (type == ZooDefs.OpCode.multi) { + txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, + Time.currentElapsedTime(), ZooDefs.OpCode.create); + txn = new CreateTxn(path, new byte[0], null, false, cversion); + List 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(ZooDefs.OpCode.create, bb.array()); + txnList.add(txact); + txn = new MultiTxn(txnList); + txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, + Time.currentElapsedTime(), ZooDefs.OpCode.multi); + } + logFile.processTransaction(txnHeader, dt, null, txn); + + int newCversion = parent.stat.getCversion(); + long newPzxid = parent.stat.getPzxid(); + child = dt.getChildren(parentName, null, null); + childStr = new StringBuilder(); + for (String s : child) { + childStr.append(s).append(" "); + } + LOG.info("Children: " + childStr + " for " + parentName); + LOG.info("(cverions, pzxid): " +newCversion + ", " + newPzxid); + Assert.assertTrue(type + " verification failed. Expected: <" + + (prevCversion + 1) + ", " + (prevPzxid + 1) + ">, found: <" + + newCversion + ", " + newPzxid + ">", + (newCversion == prevCversion + 1 && newPzxid == prevPzxid + 1)); + } + + /** + * Simulates ZOOKEEPER-1069 and verifies that flush() before padLogFile + * fixes it. + */ + @Test + public void testPad() throws Exception { + File tmpDir = ClientBase.createTmpDir(); + FileTxnLog txnLog = new FileTxnLog(tmpDir); + TxnHeader txnHeader = new TxnHeader(0xabcd, 0x123, 0x123, + Time.currentElapsedTime(), ZooDefs.OpCode.create); + Record txn = new CreateTxn("/Test", new byte[0], null, false, 1); + txnLog.append(txnHeader, txn); + FileInputStream in = new FileInputStream(tmpDir.getPath() + "/log." + + Long.toHexString(txnHeader.getZxid())); + BinaryInputArchive ia = BinaryInputArchive.getArchive(in); + FileHeader header = new FileHeader(); + header.deserialize(ia, "fileheader"); + LOG.info("Received magic : " + header.getMagic() + + " Expected : " + FileTxnLog.TXNLOG_MAGIC); + Assert.assertTrue("Missing magic number ", + header.getMagic() == FileTxnLog.TXNLOG_MAGIC); + } + +} diff --git a/src/java/test/org/apache/zookeeper/test/LoadFromLogTest.java b/src/java/test/org/apache/zookeeper/test/LoadFromLogTest.java index ab84146f58e..90de7557163 100644 --- a/src/java/test/org/apache/zookeeper/test/LoadFromLogTest.java +++ b/src/java/test/org/apache/zookeeper/test/LoadFromLogTest.java @@ -18,58 +18,41 @@ 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.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.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.FileHeader; import org.apache.zookeeper.server.persistence.FileTxnLog; -import org.apache.zookeeper.server.persistence.FileTxnSnapLog; -import org.apache.zookeeper.server.persistence.Util; import org.apache.zookeeper.server.persistence.FileTxnLog.FileTxnIterator; +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.server.persistence.Util; import org.apache.zookeeper.txn.TxnHeader; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; 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(); - private static final int CONNECTION_TIMEOUT = 3000; +import java.io.File; +import java.io.IOException; + +public class LoadFromLogTest extends ClientBase { private static final int NUM_MESSAGES = 300; protected static final Logger LOG = LoggerFactory.getLogger(LoadFromLogTest.class); // setting up the quorum has a transaction overhead for creating and closing the session private static final int TRANSACTION_OVERHEAD = 2; private static final int TOTAL_TRANSACTIONS = NUM_MESSAGES + TRANSACTION_OVERHEAD; - private volatile boolean connected; + + @Before + public void setUp() throws Exception { + SyncRequestProcessor.setSnapCount(50); + super.setUp(); + } /** * test that all transactions from the Log are loaded, and only once @@ -77,19 +60,8 @@ public class LoadFromLogTest extends ZKTestCase implements Watcher { */ @Test public void testLoad() throws Exception { - // setup a single server cluster - 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); - // generate some transactions that will get logged + ZooKeeper zk = createZKClient(hostPort); try { for (int i = 0; i< NUM_MESSAGES; i++) { zk.create("/invalidsnap-" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, @@ -98,14 +70,11 @@ public void testLoad() throws Exception { } finally { zk.close(); } - f.shutdown(); - Assert.assertTrue("waiting for server to shutdown", - ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); + stopServer(); // now verify that the FileTxnLog reads every transaction only once File logDir = new File(tmpDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION); FileTxnLog txnLog = new FileTxnLog(logDir); - TxnIterator itr = txnLog.read(0); // Check that storage space return some value @@ -127,7 +96,6 @@ public void testLoad() throws Exception { }while(itr.next()); Assert.assertTrue("processed all transactions. " + expectedZxid + " == " + TOTAL_TRANSACTIONS, (expectedZxid == TOTAL_TRANSACTIONS)); - zks.shutdown(); } /** @@ -137,20 +105,8 @@ public void testLoad() throws Exception { */ @Test public void testLoadFailure() throws Exception { - // setup a single server cluster - File tmpDir = ClientBase.createTmpDir(); - ClientBase.setupTestEnv(); - ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); - // So we have at least 4 logs - SyncRequestProcessor.setSnapCount(50); - 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); - // generate some transactions that will get logged + ZooKeeper zk = createZKClient(hostPort); try { for (int i = 0; i< NUM_MESSAGES; i++) { zk.create("/data-", new byte[0], Ids.OPEN_ACL_UNSAFE, @@ -159,14 +115,12 @@ public void testLoadFailure() throws Exception { } finally { zk.close(); } - f.shutdown(); - Assert.assertTrue("waiting for server to shutdown", - ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); + stopServer(); File logDir = new File(tmpDir, FileTxnSnapLog.version + FileTxnSnapLog.VERSION); File[] logFiles = FileTxnLog.getLogFiles(logDir.listFiles(), 0); - // Verify that we have at least 4 txnlog - Assert.assertTrue(logFiles.length > 4); + // Verify that we have at least NUM_MESSAGES / SNAPCOUNT txnlog + Assert.assertTrue(logFiles.length > NUM_MESSAGES / 100); // Delete the first log file, so we will fail to read it back from disk Assert.assertTrue("delete the first log file", logFiles[0].delete()); @@ -201,148 +155,6 @@ public void testLoadFailure() throws Exception { nextZxid = itr.getHeader().getZxid(); itr = txnLog.read(nextZxid, false); Assert.assertEquals(secondStartZxid, itr.getHeader().getZxid()); - - } - - public void process(WatchedEvent event) { - switch (event.getType()) { - case None: - switch (event.getState()) { - case SyncConnected: - connected = true; - break; - case Disconnected: - connected = false; - break; - default: - break; - } - break; - default: - break; - } - } - - /** - * For ZOOKEEPER-1046. Verify if cversion and pzxid if incremented - * after create/delete failure during restore. - */ - @Test - public void testTxnFailure() throws Exception { - long count = 1; - File tmpDir = ClientBase.createTmpDir(); - FileTxnSnapLog logFile = new FileTxnSnapLog(tmpDir, tmpDir); - DataTree dt = new DataTree(); - 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()); - } - DataNode zk = dt.getNode("/test"); - - // Make create to fail, then verify cversion. - LOG.info("Attempting to create " + "/test/" + (count - 1)); - doOp(logFile, OpCode.create, "/test/" + (count - 1), dt, zk, -1); - - 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 - // LOG.info("Attempting to delete " + "/test/" + (count + 1)); - // doOp(logFile, OpCode.delete, "/test/" + (count + 1), dt, zk); - } - /* - * Does create/delete depending on the type and verifies - * if cversion before the operation is 1 less than cversion afer. - */ - private void doOp(FileTxnSnapLog logFile, int type, String path, - DataTree dt, DataNode parent, int cversion) throws Exception { - int lastSlash = path.lastIndexOf('/'); - String parentName = path.substring(0, lastSlash); - - int prevCversion = parent.stat.getCversion(); - long prevPzxid = parent.stat.getPzxid(); - List child = dt.getChildren(parentName, null, null); - StringBuilder childStr = new StringBuilder(); - for (String s : child) { - childStr.append(s).append(" "); - } - LOG.info("Children: " + childStr + " for " + parentName); - LOG.info("(cverions, pzxid): " + prevCversion + ", " + prevPzxid); - - Record txn = null; - TxnHeader txnHeader = null; - if (type == OpCode.delete) { - txn = new DeleteTxn(path); - txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, - System.currentTimeMillis(), OpCode.delete); - } else if (type == OpCode.create) { - txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, - System.currentTimeMillis(), OpCode.create); - txn = new CreateTxn(path, new byte[0], null, false, cversion); - } - else if (type == OpCode.multi) { - txnHeader = new TxnHeader(0xabcd, 0x123, prevPzxid + 1, - System.currentTimeMillis(), 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, - System.currentTimeMillis(), OpCode.multi); - } - logFile.processTransaction(txnHeader, dt, null, txn); - - int newCversion = parent.stat.getCversion(); - long newPzxid = parent.stat.getPzxid(); - child = dt.getChildren(parentName, null, null); - childStr = new StringBuilder(); - for (String s : child) { - childStr.append(s).append(" "); - } - LOG.info("Children: " + childStr + " for " + parentName); - LOG.info("(cverions, pzxid): " +newCversion + ", " + newPzxid); - Assert.assertTrue(type + " verification failed. Expected: <" + - (prevCversion + 1) + ", " + (prevPzxid + 1) + ">, found: <" + - newCversion + ", " + newPzxid + ">", - (newCversion == prevCversion + 1 && newPzxid == prevPzxid + 1)); - } - /** - * Simulates ZOOKEEPER-1069 and verifies that flush() before padLogFile - * fixes it. - */ - @Test - 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); - Record txn = new CreateTxn("/Test", new byte[0], null, false, 1); - txnLog.append(txnHeader, txn); - FileInputStream in = new FileInputStream(tmpDir.getPath() + "/log." + - Long.toHexString(txnHeader.getZxid())); - BinaryInputArchive ia = BinaryInputArchive.getArchive(in); - FileHeader header = new FileHeader(); - header.deserialize(ia, "fileheader"); - LOG.info("Received magic : " + header.getMagic() + - " Expected : " + FileTxnLog.TXNLOG_MAGIC); - Assert.assertTrue("Missing magic number ", - header.getMagic() == FileTxnLog.TXNLOG_MAGIC); } /** @@ -351,80 +163,63 @@ public void testPad() throws Exception { */ @Test public void testRestore() 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 - String lastPath = null; - try { - zk.create("/invalidsnap", new byte[0], Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT); - for (int i = 0; i < NUM_MESSAGES; i++) { - lastPath = zk.create("/invalidsnap/test-", new byte[0], - Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); - } - } finally { - zk.close(); - } - String[] tokens = lastPath.split("-"); - String expectedPath = "/invalidsnap/test-" - + String.format("%010d", - (new Integer(tokens[1])).intValue() + 1); - long eZxid = zks.getZKDatabase().getDataTreeLastProcessedZxid(); - // 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(); + // generate some transactions + ZooKeeper zk = createZKClient(hostPort); + String lastPath = null; + try { + zk.create("/invalidsnap", new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + for (int i = 0; i < NUM_MESSAGES; i++) { + lastPath = zk.create("/invalidsnap/test-", new byte[0], + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); + } + } finally { + zk.close(); + } + String[] tokens = lastPath.split("-"); + String expectedPath = "/invalidsnap/test-" + + String.format("%010d", + (new Integer(tokens[1])).intValue() + 1); + ZooKeeperServer zks = getServer(serverFactory); + long eZxid = zks.getZKDatabase().getDataTreeLastProcessedZxid(); + // 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(); + stopServer(); - 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)); - connected = false; - long fZxid = zks.getZKDatabase().getDataTreeLastProcessedZxid(); + startServer(); + zks = getServer(serverFactory); + long fZxid = zks.getZKDatabase().getDataTreeLastProcessedZxid(); - // Verify lastProcessedZxid is set correctly - Assert.assertTrue("Restore failed expected zxid=" + eZxid + " found=" - + fZxid, fZxid == eZxid); - zk = getConnectedZkClient(); + // Verify lastProcessedZxid is set correctly + Assert.assertTrue("Restore failed expected zxid=" + eZxid + " found=" + + fZxid, fZxid == eZxid); + zk = createZKClient(hostPort); - // Verify correctness of data and whether sequential znode creation - // proceeds correctly after this point - String[] children; - String path; - try { - children = zk.getChildren("/invalidsnap", false).toArray( - new String[0]); - path = zk.create("/invalidsnap/test-", new byte[0], - Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); - } finally { - zk.close(); - } - LOG.info("Expected " + expectedPath + " found " + path); - Assert.assertTrue("Error in sequential znode creation expected " - + expectedPath + " found " + path, path.equals(expectedPath)); - Assert.assertTrue("Unexpected number of children " + children.length - + " expected " + NUM_MESSAGES, - (children.length == NUM_MESSAGES)); - f.shutdown(); - zks.shutdown(); - } + // Verify correctness of data and whether sequential znode creation + // proceeds correctly after this point + String[] children; + String path; + try { + children = zk.getChildren("/invalidsnap", false).toArray( + new String[0]); + path = zk.create("/invalidsnap/test-", new byte[0], + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); + } finally { + zk.close(); + } + LOG.info("Expected " + expectedPath + " found " + path); + Assert.assertTrue("Error in sequential znode creation expected " + + expectedPath + " found " + path, path.equals(expectedPath)); + Assert.assertTrue("Unexpected number of children " + children.length + + " expected " + NUM_MESSAGES, + (children.length == NUM_MESSAGES)); + } /** * Test we can restore a snapshot that has errors and data ahead of the zxid @@ -432,19 +227,8 @@ public void testRestore() throws Exception { */ @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 + ZooKeeper zk = createZKClient(hostPort); try { for (int i = 0; i < NUM_MESSAGES; i++) { try { @@ -459,6 +243,7 @@ public void testRestoreWithTransactionErrors() throws Exception { } // force the zxid to be behind the content + ZooKeeperServer zks = getServer(serverFactory); zks.getZKDatabase().setlastProcessedZxid( zks.getZKDatabase().getDataTreeLastProcessedZxid() - 10); LOG.info("Set lastProcessedZxid to " @@ -467,17 +252,10 @@ public void testRestoreWithTransactionErrors() throws Exception { // Force snapshot and restore zks.takeSnapshot(); zks.shutdown(); - f.shutdown(); + stopServer(); 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(); + startServer(); } /** @@ -485,32 +263,13 @@ public void testRestoreWithTransactionErrors() throws Exception { */ @Test public void testDatadirAutocreate() throws Exception { - ClientBase.setupTestEnv(); - - // first verify the default (autocreate on) works - File tmpDir = ClientBase.createTmpDir(); - 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)); - zks.shutdown(); - f.shutdown(); - Assert.assertTrue("waiting for server being down ", ClientBase - .waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); + stopServer(); try { // now verify autocreate off works System.setProperty(FileTxnSnapLog.ZOOKEEPER_DATADIR_AUTOCREATE, "false"); - - tmpDir = ClientBase.createTmpDir(); - zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); - f = ServerCnxnFactory.createFactory(PORT, -1); - f.startup(zks); - Assert.assertTrue("waiting for server being up ", ClientBase - .waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); - + tmpDir = createTmpDir(); + startServer(); Assert.fail("Server should not have started without datadir"); } catch (IOException e) { LOG.info("Server failed to start - correct behavior " + e); @@ -526,19 +285,8 @@ public void testDatadirAutocreate() throws Exception { */ @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 + ZooKeeper zk = createZKClient(hostPort); zk.create("/a", "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); Stat stat = zk.exists("/a", false); @@ -548,39 +296,15 @@ public void testReloadSnapshotWithMissingParent() throws Exception { zk.delete("/a/b", -1); zk.delete("/a", -1); // force the zxid to be behind the content + ZooKeeperServer zks = getServer(serverFactory); 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); + stopServer(); - 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("Interrupted"); - } - } - return zk; + startServer(); } } diff --git a/src/java/test/org/apache/zookeeper/test/MultiTransactionTest.java b/src/java/test/org/apache/zookeeper/test/MultiTransactionTest.java index a573180b13a..fb864f59f10 100644 --- a/src/java/test/org/apache/zookeeper/test/MultiTransactionTest.java +++ b/src/java/test/org/apache/zookeeper/test/MultiTransactionTest.java @@ -1,10 +1,11 @@ -/* - * 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 +/** + * 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 * @@ -26,7 +27,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.apache.log4j.Logger; import org.apache.zookeeper.AsyncCallback; import org.apache.zookeeper.AsyncCallback.MultiCallback; import org.apache.zookeeper.CreateMode; @@ -45,16 +45,20 @@ import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.SyncRequestProcessor; +import org.apache.zookeeper.ZKParameterized; 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; @RunWith(Parameterized.class) +@Parameterized.UseParametersRunnerFactory(ZKParameterized.RunnerFactory.class) public class MultiTransactionTest extends ClientBase { - private static final Logger LOG = Logger.getLogger(MultiTransactionTest.class); + private static final Logger LOG = LoggerFactory.getLogger(MultiTransactionTest.class); private ZooKeeper zk; private ZooKeeper zk_chroot; @@ -241,6 +245,58 @@ public void testInvalidPath() throws Exception { 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 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 { + zk.multi(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) { + } + + zk.multi(opList); + + try { + zk.getData("/foo", false, null); + Assert.fail("persistent node should have been deleted after multi"); + } catch (KeeperException.NoNodeException e) { + } + } + /** * Test verifies the multi calls with blank znode path */ @@ -488,7 +544,7 @@ public void testUpdateConflict() throws Exception { Assert.fail("Should have thrown a KeeperException for invalid version"); } catch (KeeperException e) { //PASS - LOG.error("STACKTRACE: " + e); + LOG.error("STACKTRACE: ", e); } Assert.assertNull(zk.exists("/multi", null)); @@ -568,10 +624,10 @@ public void processResult(int rc, String path, Object ctx, Assert.assertNotNull(results); for (OpResult r : results) { - LOG.info("RESULT==> " + r); + LOG.info("RESULT==> {}", r); if (r instanceof ErrorResult) { ErrorResult er = (ErrorResult) r; - LOG.info("ERROR RESULT: " + er + " ERR=>" + KeeperException.Code.get(er.getErr())); + LOG.info("ERROR RESULT: {} ERR=>{}", er, KeeperException.Code.get(er.getErr())); } } } diff --git a/src/java/test/org/apache/zookeeper/test/NettyNettySuiteBase.java b/src/java/test/org/apache/zookeeper/test/NettyNettySuiteBase.java new file mode 100644 index 00000000000..684d67a91f2 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/NettyNettySuiteBase.java @@ -0,0 +1,49 @@ +/** + * 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.ClientCnxnSocketNetty; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.server.NettyServerCnxnFactory; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * Run tests with: Netty Client against Netty server + */ +@RunWith(Suite.class) +public class NettyNettySuiteBase { + @BeforeClass + public static void setUp() { + System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, + NettyServerCnxnFactory.class.getName()); + System.setProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET, + ClientCnxnSocketNetty.class.getName()); + System.setProperty("zookeeper.admin.enableServer", "false"); + } + + @AfterClass + public static void tearDown() { + System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + System.clearProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/NettyNettySuiteHammerTest.java b/src/java/test/org/apache/zookeeper/test/NettyNettySuiteHammerTest.java new file mode 100644 index 00000000000..586e5844de7 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/NettyNettySuiteHammerTest.java @@ -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. + */ + +package org.apache.zookeeper.test; + +import org.junit.runners.Suite; + +/** + * Run tests with: Netty Client against Netty server + */ +@Suite.SuiteClasses({ + AsyncHammerTest.class +}) +public class NettyNettySuiteHammerTest extends NettyNettySuiteBase { +} diff --git a/src/java/test/org/apache/zookeeper/test/NettyNettySuiteTest.java b/src/java/test/org/apache/zookeeper/test/NettyNettySuiteTest.java new file mode 100644 index 00000000000..35710ba3b3a --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/NettyNettySuiteTest.java @@ -0,0 +1,38 @@ +/** + * 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.runners.Suite; + +/** + * Run tests with: Netty Client against Netty server + */ +@Suite.SuiteClasses({ + ACLTest.class, + AsyncOpsTest.class, + ChrootClientTest.class, + ClientTest.class, + FourLetterWordsTest.class, + NullDataTest.class, + ReconfigTest.class, + SessionTest.class, + WatcherTest.class +}) +public class NettyNettySuiteTest extends NettyNettySuiteBase { +} diff --git a/src/java/test/org/apache/zookeeper/test/NioNettySuiteBase.java b/src/java/test/org/apache/zookeeper/test/NioNettySuiteBase.java index d61a3e2d67f..5725c170e4f 100644 --- a/src/java/test/org/apache/zookeeper/test/NioNettySuiteBase.java +++ b/src/java/test/org/apache/zookeeper/test/NioNettySuiteBase.java @@ -34,6 +34,7 @@ public class NioNettySuiteBase { public static void setUp() { System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, NettyServerCnxnFactory.class.getName()); + System.setProperty("zookeeper.admin.enableServer", "false"); } @AfterClass diff --git a/src/java/test/org/apache/zookeeper/test/NioNettySuiteTest.java b/src/java/test/org/apache/zookeeper/test/NioNettySuiteTest.java index eaf17b9458d..a1ef33db5be 100644 --- a/src/java/test/org/apache/zookeeper/test/NioNettySuiteTest.java +++ b/src/java/test/org/apache/zookeeper/test/NioNettySuiteTest.java @@ -30,6 +30,7 @@ ClientTest.class, FourLetterWordsTest.class, NullDataTest.class, + ReconfigTest.class, SessionTest.class, WatcherTest.class }) 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..31790d2ead7 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/NonRecoverableErrorTest.java @@ -0,0 +1,185 @@ +/** + * 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() + ":participant;127.0.0.1:" + + clientPorts[i]; + 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, + false); + 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.assertNotEquals("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/OSMXBeanTest.java b/src/java/test/org/apache/zookeeper/test/OSMXBeanTest.java index ce21ab82539..371c9021d39 100644 --- a/src/java/test/org/apache/zookeeper/test/OSMXBeanTest.java +++ b/src/java/test/org/apache/zookeeper/test/OSMXBeanTest.java @@ -18,6 +18,7 @@ package org.apache.zookeeper.test; +import org.apache.zookeeper.ZKTestCase; import org.junit.Assert; import org.junit.Test; import org.junit.Before; @@ -26,7 +27,7 @@ import org.apache.zookeeper.server.util.OSMXBean; -public class OSMXBeanTest { +public class OSMXBeanTest extends ZKTestCase { private OSMXBean osMbean; private Long ofdc = 0L; diff --git a/src/java/test/org/apache/zookeeper/test/QuorumBase.java b/src/java/test/org/apache/zookeeper/test/QuorumBase.java index c9234855b77..f687f453a8a 100644 --- a/src/java/test/org/apache/zookeeper/test/QuorumBase.java +++ b/src/java/test/org/apache/zookeeper/test/QuorumBase.java @@ -138,28 +138,28 @@ void startServers(boolean withObservers) throws Exception { int syncLimit = 3; HashMap peers = new HashMap(); peers.put(Long.valueOf(1), new QuorumServer(1, - new InetSocketAddress(LOCALADDR, port1 + 1000), - new InetSocketAddress(LOCALADDR, portLE1 + 1000), + new InetSocketAddress(LOCALADDR, port1), + new InetSocketAddress(LOCALADDR, portLE1), new InetSocketAddress(LOCALADDR, portClient1), LearnerType.PARTICIPANT)); peers.put(Long.valueOf(2), new QuorumServer(2, - new InetSocketAddress(LOCALADDR, port2 + 1000), - new InetSocketAddress(LOCALADDR, portLE2 + 1000), + new InetSocketAddress(LOCALADDR, port2), + new InetSocketAddress(LOCALADDR, portLE2), new InetSocketAddress(LOCALADDR, portClient2), LearnerType.PARTICIPANT)); peers.put(Long.valueOf(3), new QuorumServer(3, - new InetSocketAddress(LOCALADDR, port3 + 1000), - new InetSocketAddress(LOCALADDR, portLE3 + 1000), + new InetSocketAddress(LOCALADDR, port3), + new InetSocketAddress(LOCALADDR, portLE3), new InetSocketAddress(LOCALADDR, portClient3), LearnerType.PARTICIPANT)); peers.put(Long.valueOf(4), new QuorumServer(4, - new InetSocketAddress(LOCALADDR, port4 + 1000), - new InetSocketAddress(LOCALADDR, portLE4 + 1000), + new InetSocketAddress(LOCALADDR, port4), + new InetSocketAddress(LOCALADDR, portLE4), new InetSocketAddress(LOCALADDR, portClient4), LearnerType.PARTICIPANT)); peers.put(Long.valueOf(5), new QuorumServer(5, - new InetSocketAddress(LOCALADDR, port5 + 1000), - new InetSocketAddress(LOCALADDR, portLE5 + 1000), + new InetSocketAddress(LOCALADDR, port5), + new InetSocketAddress(LOCALADDR, portLE5), new InetSocketAddress(LOCALADDR, portClient5), LearnerType.PARTICIPANT)); @@ -303,28 +303,28 @@ public void setupServer(int i) throws IOException { peers = new HashMap(); peers.put(Long.valueOf(1), new QuorumServer(1, - new InetSocketAddress(LOCALADDR, port1 + 1000), - new InetSocketAddress(LOCALADDR, portLE1 + 1000), + new InetSocketAddress(LOCALADDR, port1), + new InetSocketAddress(LOCALADDR, portLE1), new InetSocketAddress(LOCALADDR, portClient1), LearnerType.PARTICIPANT)); peers.put(Long.valueOf(2), new QuorumServer(2, - new InetSocketAddress(LOCALADDR, port2 + 1000), - new InetSocketAddress(LOCALADDR, portLE2 + 1000), + new InetSocketAddress(LOCALADDR, port2), + new InetSocketAddress(LOCALADDR, portLE2), new InetSocketAddress(LOCALADDR, portClient2), LearnerType.PARTICIPANT)); peers.put(Long.valueOf(3), new QuorumServer(3, - new InetSocketAddress(LOCALADDR, port3 + 1000), - new InetSocketAddress(LOCALADDR, portLE3 + 1000), + new InetSocketAddress(LOCALADDR, port3), + new InetSocketAddress(LOCALADDR, portLE3), new InetSocketAddress(LOCALADDR, portClient3), LearnerType.PARTICIPANT)); peers.put(Long.valueOf(4), new QuorumServer(4, - new InetSocketAddress(LOCALADDR, port4 + 1000), - new InetSocketAddress(LOCALADDR, portLE4 + 1000), + new InetSocketAddress(LOCALADDR, port4), + new InetSocketAddress(LOCALADDR, portLE4), new InetSocketAddress(LOCALADDR, portClient4), LearnerType.PARTICIPANT)); peers.put(Long.valueOf(5), new QuorumServer(5, - new InetSocketAddress(LOCALADDR, port5 + 1000), - new InetSocketAddress(LOCALADDR, portLE5 + 1000), + new InetSocketAddress(LOCALADDR, port5), + new InetSocketAddress(LOCALADDR, portLE5), new InetSocketAddress(LOCALADDR, portClient5), LearnerType.PARTICIPANT)); } diff --git a/src/java/test/org/apache/zookeeper/test/QuorumMajorityTest.java b/src/java/test/org/apache/zookeeper/test/QuorumMajorityTest.java index 28ed9f180cb..696662655c1 100644 --- a/src/java/test/org/apache/zookeeper/test/QuorumMajorityTest.java +++ b/src/java/test/org/apache/zookeeper/test/QuorumMajorityTest.java @@ -17,14 +17,16 @@ */ package org.apache.zookeeper.test; -import java.util.HashSet; +import java.util.ArrayList; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.zookeeper.server.quorum.Leader; +import org.apache.zookeeper.jmx.CommonNames; import org.apache.zookeeper.server.quorum.Leader.Proposal; +import org.apache.zookeeper.server.quorum.QuorumPeer; +import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; import org.junit.Assert; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class QuorumMajorityTest extends QuorumBase { protected static final Logger LOG = LoggerFactory.getLogger(QuorumMajorityTest.class); @@ -36,7 +38,28 @@ public class QuorumMajorityTest extends QuorumBase { /***************************************************************/ @Test public void testMajQuorums() throws Throwable { - + LOG.info("Verify QuorumPeer#electionTimeTaken jmx bean attribute"); + + ArrayList peers = getPeerList(); + for (int i = 1; i <= peers.size(); i++) { + QuorumPeer qp = peers.get(i - 1); + 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); + } + //setup servers 1-5 to be followers setUp(false); diff --git a/src/java/test/org/apache/zookeeper/test/QuorumTest.java b/src/java/test/org/apache/zookeeper/test/QuorumTest.java index 89773136bba..cd1d1532b63 100644 --- a/src/java/test/org/apache/zookeeper/test/QuorumTest.java +++ b/src/java/test/org/apache/zookeeper/test/QuorumTest.java @@ -53,6 +53,7 @@ public class QuorumTest extends ZKTestCase { private final QuorumBase qb = new QuorumBase(); private final ClientTest ct = new ClientTest(); + private QuorumUtil qu; @Before public void setUp() throws Exception { @@ -65,6 +66,9 @@ public void setUp() throws Exception { public void tearDown() throws Exception { ct.tearDownAll(); qb.tearDown(); + if (qu != null) { + qu.tearDown(); + } } @Test @@ -296,7 +300,7 @@ ClientBase.CONNECTION_TIMEOUT, new DiscoWatcher(), * */ @Test public void testFollowersStartAfterLeader() throws Exception { - QuorumUtil qu = new QuorumUtil(1); + qu = new QuorumUtil(1); CountdownWatcher watcher = new CountdownWatcher(); qu.startQuorum(); @@ -325,94 +329,6 @@ public void testFollowersStartAfterLeader() throws Exception { 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, 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); - - } - // skip superhammer and clientcleanup as they are too expensive for quorum /** @@ -426,7 +342,7 @@ public void processResult(int rc, String path, Object ctx, */ @Test public void testMultiToFollower() throws Exception { - QuorumUtil qu = new QuorumUtil(1); + qu = new QuorumUtil(1); CountdownWatcher watcher = new CountdownWatcher(); qu.startQuorum(); diff --git a/src/java/test/org/apache/zookeeper/test/QuorumUtil.java b/src/java/test/org/apache/zookeeper/test/QuorumUtil.java index cd6ed299b83..314171d873a 100644 --- a/src/java/test/org/apache/zookeeper/test/QuorumUtil.java +++ b/src/java/test/org/apache/zookeeper/test/QuorumUtil.java @@ -105,8 +105,8 @@ public QuorumUtil(int n, int syncLimit) throws RuntimeException { peers.put(i, ps); peersView.put(Long.valueOf(i), new QuorumServer(i, - new InetSocketAddress("127.0.0.1", PortAssignment.unique() + 1000), - new InetSocketAddress("127.0.0.1", PortAssignment.unique() + 1000), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), new InetSocketAddress("127.0.0.1", ps.clientPort), LearnerType.PARTICIPANT)); hostPort += "127.0.0.1:" + ps.clientPort + ((i == ALL) ? "" : ","); diff --git a/src/java/test/org/apache/zookeeper/test/ReadOnlyModeTest.java b/src/java/test/org/apache/zookeeper/test/ReadOnlyModeTest.java index 0579858659c..68c71824056 100644 --- a/src/java/test/org/apache/zookeeper/test/ReadOnlyModeTest.java +++ b/src/java/test/org/apache/zookeeper/test/ReadOnlyModeTest.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.regex.Pattern; -import junit.framework.Assert; import org.apache.log4j.Layout; import org.apache.log4j.Level; @@ -42,12 +41,17 @@ 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.Assert; 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); @@ -165,13 +169,9 @@ public void testReadOnlyClient() throws Exception { */ @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 { @@ -184,6 +184,7 @@ 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)) @@ -191,25 +192,16 @@ public void process(WatchedEvent event) { // Re-connect the client (in case we were connected to the shut down // server and the local session was not persisted). - zk = new ZooKeeper(qu.getConnString(), CONNECTION_TIMEOUT, - new Watcher() { - public void process(WatchedEvent event) { - states.add(event.getState()); - } - }, true); - long start = System.currentTimeMillis(); + zk = new ZooKeeper(qu.getConnString(), CONNECTION_TIMEOUT, watcher, true); + long start = Time.currentElapsedTime(); while (!(zk.getState() == States.CONNECTEDREADONLY)) { Thread.sleep(200); // 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", System - .currentTimeMillis() - - start < 30000); + 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(); } @@ -229,17 +221,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(); } diff --git a/src/java/test/org/apache/zookeeper/test/ReconfigExceptionTest.java b/src/java/test/org/apache/zookeeper/test/ReconfigExceptionTest.java new file mode 100644 index 00000000000..5eda4b0f429 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/ReconfigExceptionTest.java @@ -0,0 +1,213 @@ +/** + * 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.Collections; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.admin.ZooKeeperAdmin; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; +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 ReconfigExceptionTest extends ZKTestCase { + private static final Logger LOG = LoggerFactory + .getLogger(ReconfigExceptionTest.class); + private static String authProvider = "zookeeper.DigestAuthenticationProvider.superDigest"; + // Use DigestAuthenticationProvider.base64Encode or + // run ZooKeeper jar with org.apache.zookeeper.server.auth.DigestAuthenticationProvider to generate password. + // An example: + // java -cp zookeeper-3.6.0-SNAPSHOT.jar:lib/log4j-1.2.17.jar:lib/slf4j-log4j12-1.7.5.jar: + // lib/slf4j-api-1.7.5.jar org.apache.zookeeper.server.auth.DigestAuthenticationProvider super:test + // The password here is 'test'. + private static String superDigest = "super:D/InIHSb7yEEbrWz8b9l71RjZJU="; + private QuorumUtil qu; + private ZooKeeperAdmin zkAdmin; + + @Before + public void setup() throws InterruptedException { + System.setProperty(authProvider, superDigest); + QuorumPeerConfig.setReconfigEnabled(true); + + // Get a three server quorum. + qu = new QuorumUtil(1); + qu.disableJMXTest = true; + + try { + qu.startAll(); + } catch (IOException e) { + Assert.fail("Fail to start quorum servers."); + } + + resetZKAdmin(); + } + + @After + public void tearDown() throws Exception { + System.clearProperty(authProvider); + try { + if (qu != null) { + qu.tearDown(); + } + if (zkAdmin != null) { + zkAdmin.close(); + } + } catch (Exception e) { + // Ignore. + } + } + + @Test(timeout = 10000) + public void testReconfigDisabled() throws InterruptedException { + QuorumPeerConfig.setReconfigEnabled(false); + try { + reconfigPort(); + Assert.fail("Reconfig should be disabled."); + } catch (KeeperException e) { + Assert.assertTrue(e.code() == KeeperException.Code.RECONFIGDISABLED); + } + } + + @Test(timeout = 10000) + public void testReconfigFailWithoutAuth() throws InterruptedException { + try { + reconfigPort(); + Assert.fail("Reconfig should fail without auth."); + } catch (KeeperException e) { + // However a failure is still expected as user is not authenticated, so ACL check will fail. + Assert.assertTrue(e.code() == KeeperException.Code.NOAUTH); + } + } + + @Test(timeout = 10000) + public void testReconfigEnabledWithSuperUser() throws InterruptedException { + try { + zkAdmin.addAuthInfo("digest", "super:test".getBytes()); + Assert.assertTrue(reconfigPort()); + } catch (KeeperException e) { + Assert.fail("Reconfig should not fail, but failed with exception : " + e.getMessage()); + } + } + + @Test(timeout = 10000) + public void testReconfigFailWithAuthWithNoACL() throws InterruptedException { + resetZKAdmin(); + + try { + zkAdmin.addAuthInfo("digest", "user:test".getBytes()); + reconfigPort(); + Assert.fail("Reconfig should fail without a valid ACL associated with user."); + } catch (KeeperException e) { + // Again failure is expected because no ACL is associated with this user. + Assert.assertTrue(e.code() == KeeperException.Code.NOAUTH); + } + } + + @Test(timeout = 10000) + public void testReconfigEnabledWithAuthAndWrongACL() throws InterruptedException { + resetZKAdmin(); + + try { + zkAdmin.addAuthInfo("digest", "super:test".getBytes()); + // There is ACL however the permission is wrong - need WRITE permission at leaste. + ArrayList acls = new ArrayList( + Collections.singletonList( + new ACL(ZooDefs.Perms.READ, + new Id("digest", "user:tl+z3z0vO6PfPfEENfLF96E6pM0="/* password is test */)))); + zkAdmin.setACL(ZooDefs.CONFIG_NODE, acls, -1); + resetZKAdmin(); + zkAdmin.addAuthInfo("digest", "user:test".getBytes()); + reconfigPort(); + Assert.fail("Reconfig should fail with an ACL that is read only!"); + } catch (KeeperException e) { + Assert.assertTrue(e.code() == KeeperException.Code.NOAUTH); + } + } + + @Test(timeout = 10000) + public void testReconfigEnabledWithAuthAndACL() throws InterruptedException { + resetZKAdmin(); + + try { + zkAdmin.addAuthInfo("digest", "super:test".getBytes()); + ArrayList acls = new ArrayList( + Collections.singletonList( + new ACL(ZooDefs.Perms.WRITE, + new Id("digest", "user:tl+z3z0vO6PfPfEENfLF96E6pM0="/* password is test */)))); + zkAdmin.setACL(ZooDefs.CONFIG_NODE, acls, -1); + resetZKAdmin(); + zkAdmin.addAuthInfo("digest", "user:test".getBytes()); + Assert.assertTrue(reconfigPort()); + } catch (KeeperException e) { + Assert.fail("Reconfig should not fail, but failed with exception : " + e.getMessage()); + } + } + + // Utility method that recreates a new ZooKeeperAdmin handle, and wait for the handle to connect to + // quorum servers. + private void resetZKAdmin() throws InterruptedException { + String cnxString; + ClientBase.CountdownWatcher watcher = new ClientBase.CountdownWatcher(); + try { + cnxString = "127.0.0.1:" + qu.getPeer(1).peer.getClientPort(); + if (zkAdmin != null) { + zkAdmin.close(); + } + zkAdmin = new ZooKeeperAdmin(cnxString, + ClientBase.CONNECTION_TIMEOUT, watcher); + } catch (IOException e) { + Assert.fail("Fail to create ZooKeeperAdmin handle."); + return; + } + + try { + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + } catch (InterruptedException | TimeoutException e) { + Assert.fail("ZooKeeper admin client can not connect to " + cnxString); + } + } + + private boolean reconfigPort() throws KeeperException, InterruptedException { + List joiningServers = new ArrayList(); + int leaderId = 1; + while (qu.getPeer(leaderId).peer.leader == null) + leaderId++; + int followerId = leaderId == 1 ? 2 : 1; + joiningServers.add("server." + followerId + "=localhost:" + + qu.getPeer(followerId).peer.getQuorumAddress().getPort() /*quorum port*/ + + ":" + qu.getPeer(followerId).peer.getElectionAddress().getPort() /*election port*/ + + ":participant;localhost:" + PortAssignment.unique()/* new client port */); + zkAdmin.reconfigure(joiningServers, null, null, -1, new Stat()); + return true; + } +} diff --git a/src/java/test/org/apache/zookeeper/test/ReconfigMisconfigTest.java b/src/java/test/org/apache/zookeeper/test/ReconfigMisconfigTest.java new file mode 100644 index 00000000000..1694fcf2a8a --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/ReconfigMisconfigTest.java @@ -0,0 +1,129 @@ +/** + * 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.List; +import java.util.concurrent.TimeoutException; + +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.admin.ZooKeeperAdmin; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; +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 ReconfigMisconfigTest extends ZKTestCase { + private static final Logger LOG = LoggerFactory.getLogger(ReconfigMisconfigTest.class); + private QuorumUtil qu; + private ZooKeeperAdmin zkAdmin; + private static String errorMsg = "Reconfig should fail without configuring the super " + + "user's password on server side first."; + + @Before + public void setup() throws InterruptedException { + QuorumPeerConfig.setReconfigEnabled(true); + // Get a three server quorum. + qu = new QuorumUtil(1); + qu.disableJMXTest = true; + try { + qu.startAll(); + } catch (IOException e) { + Assert.fail("Fail to start quorum servers."); + } + + instantiateZKAdmin(); + } + + @After + public void tearDown() throws Exception { + try { + if (qu != null) { + qu.tearDown(); + } + if (zkAdmin != null) { + zkAdmin.close(); + } + } catch (Exception e) { + // Ignore. + } + } + + @Test(timeout = 10000) + public void testReconfigFailWithoutSuperuserPasswordConfiguredOnServer() throws InterruptedException { + // This tests the case where ZK ensemble does not have the super user's password configured. + // Reconfig should fail as the super user has to be explicitly configured via + // zookeeper.DigestAuthenticationProvider.superDigest. + try { + reconfigPort(); + Assert.fail(errorMsg); + } catch (KeeperException e) { + Assert.assertTrue(e.getCode() == KeeperException.Code.NoAuth); + } + + try { + zkAdmin.addAuthInfo("digest", "super:".getBytes()); + reconfigPort(); + Assert.fail(errorMsg); + } catch (KeeperException e) { + Assert.assertTrue(e.getCode() == KeeperException.Code.NoAuth); + } + } + + private void instantiateZKAdmin() throws InterruptedException { + String cnxString; + ClientBase.CountdownWatcher watcher = new ClientBase.CountdownWatcher(); + try { + cnxString = "127.0.0.1:" + qu.getPeer(1).peer.getClientPort(); + zkAdmin = new ZooKeeperAdmin(cnxString, + ClientBase.CONNECTION_TIMEOUT, watcher); + } catch (IOException e) { + Assert.fail("Fail to create ZooKeeperAdmin handle."); + return; + } + + try { + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + } catch (InterruptedException | TimeoutException e) { + Assert.fail("ZooKeeper admin client can not connect to " + cnxString); + } + } + + private boolean reconfigPort() throws KeeperException, InterruptedException { + List joiningServers = new ArrayList(); + int leaderId = 1; + while (qu.getPeer(leaderId).peer.leader == null) + leaderId++; + int followerId = leaderId == 1 ? 2 : 1; + joiningServers.add("server." + followerId + "=localhost:" + + qu.getPeer(followerId).peer.getQuorumAddress().getPort() /*quorum port*/ + + ":" + qu.getPeer(followerId).peer.getElectionAddress().getPort() /*election port*/ + + ":participant;localhost:" + PortAssignment.unique()/* new client port */); + zkAdmin.reconfigure(joiningServers, null, null, -1, new Stat()); + return true; + } +} + diff --git a/src/java/test/org/apache/zookeeper/test/ReconfigTest.java b/src/java/test/org/apache/zookeeper/test/ReconfigTest.java index 775f4a22100..49de3f7447a 100644 --- a/src/java/test/org/apache/zookeeper/test/ReconfigTest.java +++ b/src/java/test/org/apache/zookeeper/test/ReconfigTest.java @@ -18,34 +18,39 @@ package org.apache.zookeeper.test; +import static java.net.InetAddress.getLoopbackAddress; + import java.io.IOException; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; -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.AsyncCallback.DataCallback; -import org.apache.zookeeper.common.HostNameUtils; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.jmx.CommonNames; import org.apache.zookeeper.server.quorum.QuorumPeer; -import org.apache.zookeeper.server.quorum.QuorumStats; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; import org.apache.zookeeper.server.quorum.QuorumPeer.ServerState; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; import org.apache.zookeeper.server.quorum.flexible.QuorumHierarchical; import org.apache.zookeeper.server.quorum.flexible.QuorumMaj; import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; -import org.junit.Assert; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.admin.ZooKeeperAdmin; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.PortAssignment; import org.junit.After; +import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +61,13 @@ public class ReconfigTest extends ZKTestCase implements DataCallback{ private QuorumUtil qu; + @Before + public void setup() { + System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", + "super:D/InIHSb7yEEbrWz8b9l71RjZJU="/* password is 'test'*/); + QuorumPeerConfig.setReconfigEnabled(true); + } + @After public void tearDown() throws Exception { if (qu != null) { @@ -63,13 +75,13 @@ public void tearDown() throws Exception { } } - public static String reconfig(ZooKeeper zk, List joiningServers, - List leavingServers, List newMembers, long fromConfig) + public static String reconfig(ZooKeeperAdmin zkAdmin, List joiningServers, + List leavingServers, List newMembers, long fromConfig) throws KeeperException, InterruptedException { byte[] config = null; for (int j = 0; j < 30; j++) { try { - config = zk.reconfig(joiningServers, leavingServers, + config = zkAdmin.reconfigure(joiningServers, leavingServers, newMembers, fromConfig, new Stat()); break; } catch (KeeperException.ConnectionLossException e) { @@ -99,10 +111,17 @@ public static String reconfig(ZooKeeper zk, List joiningServers, public static String testServerHasConfig(ZooKeeper zk, List joiningServers, List leavingServers) throws KeeperException, InterruptedException { + boolean testNodeExists = false; byte[] config = null; for (int j = 0; j < 30; j++) { try { - zk.sync("/", null, null); + if (!testNodeExists) { + createZNode(zk, "/dummy", "dummy"); + testNodeExists = true; + } + // Use setData instead of sync API to force a view update. + // Check ZOOKEEPER-2137 for details. + zk.setData("/dummy", "dummy".getBytes(), -1); config = zk.getConfig(false, new Stat()); break; } catch (KeeperException.ConnectionLossException e) { @@ -114,8 +133,8 @@ public static String testServerHasConfig(ZooKeeper zk, Assert.fail("client could not connect to reestablished quorum: giving up after 30+ seconds."); } } - } + String configStr = new String(config); if (joiningServers != null) { for (String joiner : joiningServers) { @@ -129,24 +148,29 @@ public static String testServerHasConfig(ZooKeeper zk, return configStr; } - + public static void testNormalOperation(ZooKeeper writer, ZooKeeper reader) throws KeeperException, InterruptedException { - boolean testNodeExists = false; - - for (int j = 0; j < 30; j++) { + boolean testReaderNodeExists = false; + boolean testWriterNodeExists = false; + + for (int j = 0; j < 30; j++) { try { - if (!testNodeExists) { - try{ - writer.create("/test", "test".getBytes(), - ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); - } catch (KeeperException.NodeExistsException e) { - } - testNodeExists = true; - } + if (!testWriterNodeExists) { + createZNode(writer, "/test", "test"); + testWriterNodeExists = true; + } + + if (!testReaderNodeExists) { + createZNode(reader, "/dummy", "dummy"); + testReaderNodeExists = true; + } + String data = "test" + j; writer.setData("/test", data.getBytes(), -1); - reader.sync("/", null, null); + // Use setData instead of sync API to force a view update. + // Check ZOOKEEPER-2137 for details. + reader.setData("/dummy", "dummy".getBytes(), -1); byte[] res = reader.getData("/test", null, new Stat()); Assert.assertEquals(data, new String(res)); break; @@ -159,10 +183,17 @@ public static void testNormalOperation(ZooKeeper writer, ZooKeeper reader) Assert.fail("client could not connect to reestablished quorum: giving up after 30+ seconds."); } } - } + } - } + private static void createZNode(ZooKeeper zk, String path, String data) + throws KeeperException, InterruptedException { + try{ + zk.create(path, data.getBytes(), + ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } catch (KeeperException.NodeExistsException e) { + } + } private int getLeaderId(QuorumUtil qu) { int leaderId = 1; @@ -171,7 +202,7 @@ private int getLeaderId(QuorumUtil qu) { return leaderId; } - private ZooKeeper[] createHandles(QuorumUtil qu) throws IOException { + public static ZooKeeper[] createHandles(QuorumUtil qu) throws IOException { // create an extra handle, so we can index the handles from 1 to qu.ALL // using the server id. ZooKeeper[] zkArr = new ZooKeeper[qu.ALL + 1]; @@ -187,19 +218,40 @@ public void process(WatchedEvent event) { return zkArr; } - private void closeAllHandles(ZooKeeper[] zkArr) throws InterruptedException { + public static ZooKeeperAdmin[] createAdminHandles(QuorumUtil qu) throws IOException { + // create an extra handle, so we can index the handles from 1 to qu.ALL + // using the server id. + ZooKeeperAdmin[] zkAdminArr = new ZooKeeperAdmin[qu.ALL + 1]; + zkAdminArr[0] = null; // not used. + for (int i = 1; i <= qu.ALL; i++) { + // server ids are 1, 2 and 3 + zkAdminArr[i] = new ZooKeeperAdmin("127.0.0.1:" + + qu.getPeer(i).peer.getClientPort(), + ClientBase.CONNECTION_TIMEOUT, new Watcher() { + public void process(WatchedEvent event) { + }}); + zkAdminArr[i].addAuthInfo("digest", "super:test".getBytes()); + } + + return zkAdminArr; + } + + public static void closeAllHandles(ZooKeeper[] zkArr, ZooKeeperAdmin[] zkAdminArr) throws InterruptedException { for (ZooKeeper zk : zkArr) if (zk != null) zk.close(); + for (ZooKeeperAdmin zkAdmin : zkAdminArr) + if (zkAdmin != null) + zkAdmin.close(); } - @Test public void testRemoveAddOne() throws Exception { qu = new QuorumUtil(1); // create 3 servers qu.disableJMXTest = true; qu.startAll(); ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); List leavingServers = new ArrayList(); List joiningServers = new ArrayList(); @@ -221,6 +273,10 @@ public void testRemoveAddOne() throws Exception { : zkArr[(leaderIndex % qu.ALL) + 1]; ZooKeeper zk2 = (leavingIndex == leaderIndex) ? zkArr[(leaderIndex % qu.ALL) + 1] : zkArr[leaderIndex]; + ZooKeeperAdmin zkAdmin1 = (leavingIndex == leaderIndex) ? zkAdminArr[leaderIndex] + : zkAdminArr[(leaderIndex % qu.ALL) + 1]; + ZooKeeperAdmin zkAdmin2 = (leavingIndex == leaderIndex) ? zkAdminArr[(leaderIndex % qu.ALL) + 1] + : zkAdminArr[leaderIndex]; leavingServers.add(Integer.toString(leavingIndex)); @@ -235,7 +291,7 @@ public void testRemoveAddOne() throws Exception { .getPort() + ":participant;localhost:" + qu.getPeer(leavingIndex).peer.getClientPort()); - String configStr = reconfig(zk1, null, leavingServers, null, -1); + String configStr = reconfig(zkAdmin1, null, leavingServers, null, -1); testServerHasConfig(zk2, null, leavingServers); testNormalOperation(zk2, zk1); @@ -244,13 +300,13 @@ public void testRemoveAddOne() throws Exception { // checks that conditioning on version works properly try { - reconfig(zk2, joiningServers, null, null, version + 1); + reconfig(zkAdmin2, joiningServers, null, null, version + 1); Assert.fail("reconfig succeeded even though version condition was incorrect!"); } catch (KeeperException.BadVersionException e) { } - reconfig(zk2, joiningServers, null, null, version); + reconfig(zkAdmin2, joiningServers, null, null, version); testNormalOperation(zk1, zk2); testServerHasConfig(zk1, joiningServers, null); @@ -262,7 +318,7 @@ public void testRemoveAddOne() throws Exception { joiningServers.clear(); } - closeAllHandles(zkArr); + closeAllHandles(zkArr, zkAdminArr); } /** @@ -277,6 +333,7 @@ public void testRemoveAddTwo() throws Exception { qu.disableJMXTest = true; qu.startAll(); ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); List leavingServers = new ArrayList(); List joiningServers = new ArrayList(); @@ -324,7 +381,7 @@ public void testRemoveAddTwo() throws Exception { qu.shutdown(leavingIndex2); // 3 servers still up so this should work - reconfig(zkArr[stayingIndex2], null, leavingServers, null, -1); + reconfig(zkAdminArr[stayingIndex2], null, leavingServers, null, -1); qu.shutdown(stayingIndex2); @@ -345,7 +402,7 @@ public void testRemoveAddTwo() throws Exception { Thread.sleep(10000); try { - reconfig(zkArr[stayingIndex1], joiningServers, null, null, -1); + reconfig(zkAdminArr[stayingIndex1], joiningServers, null, null, -1); Assert.fail("reconfig completed successfully even though there is no quorum up in new config!"); } catch (KeeperException.NewConfigNoQuorum e) { @@ -354,7 +411,7 @@ public void testRemoveAddTwo() throws Exception { // now start the third server so that new config has quorum qu.restart(stayingIndex2); - reconfig(zkArr[stayingIndex1], joiningServers, null, null, -1); + reconfig(zkAdminArr[stayingIndex1], joiningServers, null, null, -1); testNormalOperation(zkArr[stayingIndex2], zkArr[stayingIndex3]); testServerHasConfig(zkArr[stayingIndex2], joiningServers, null); @@ -367,7 +424,7 @@ public void testRemoveAddTwo() throws Exception { testNormalOperation(zkArr[stayingIndex2], zkArr[leavingIndex2]); testServerHasConfig(zkArr[leavingIndex2], joiningServers, null); - closeAllHandles(zkArr); + closeAllHandles(zkArr, zkAdminArr); } @Test @@ -376,6 +433,7 @@ public void testBulkReconfig() throws Exception { qu.disableJMXTest = true; qu.startAll(); ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); // new config will have three of the servers as followers // two of the servers as observers, and all ports different @@ -392,7 +450,7 @@ public void testBulkReconfig() throws Exception { qu.shutdown(6); qu.shutdown(7); - reconfig(zkArr[1], null, null, newServers, -1); + reconfig(zkAdminArr[1], null, null, newServers, -1); testNormalOperation(zkArr[1], zkArr[2]); testServerHasConfig(zkArr[1], newServers, null); @@ -405,44 +463,7 @@ public void testBulkReconfig() throws Exception { testNormalOperation(zkArr[1], zkArr[2]); - closeAllHandles(zkArr); - } - - @Test - public void testLeaderTimesoutOnNewQuorum() throws Exception { - qu = new QuorumUtil(1); // create 3 servers - qu.disableJMXTest = true; - qu.startAll(); - ZooKeeper[] zkArr = createHandles(qu); - - List leavingServers = new ArrayList(); - leavingServers.add("3"); - qu.shutdown(2); - try { - // Since we just shut down server 2, its still considered "synced" - // by the leader, which allows us to start the reconfig - // (PrepRequestProcessor checks that a quorum of the new - // config is synced before starting a reconfig). - // We try to remove server 3, which requires a quorum of {1,2,3} - // (we have that) and of {1,2}, but 2 is down so we won't get a - // quorum of new config ACKs. - zkArr[1].reconfig(null, leavingServers, null, -1, null); - Assert.fail("Reconfig should have failed since we don't have quorum of new config"); - } catch (KeeperException.ConnectionLossException e) { - // We expect leader to loose quorum of proposed config and time out - } catch (Exception e) { - Assert.fail("Should have been ConnectionLossException!"); - } - - // The leader should time out and remaining servers should go into - // LOOKING state. A new leader won't be established since that - // would require completing the reconfig, which is not possible while - // 2 is down. - Assert.assertEquals(QuorumStats.Provider.LOOKING_STATE, - qu.getPeer(1).peer.getServerState()); - Assert.assertEquals(QuorumStats.Provider.LOOKING_STATE, - qu.getPeer(3).peer.getServerState()); - closeAllHandles(zkArr); + closeAllHandles(zkArr, zkAdminArr); } @Test @@ -451,6 +472,7 @@ public void testRemoveOneAsynchronous() throws Exception { qu.disableJMXTest = true; qu.startAll(); ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); List leavingServers = new ArrayList(); @@ -459,7 +481,7 @@ public void testRemoveOneAsynchronous() throws Exception { LinkedList results = new LinkedList(); - zkArr[1].reconfig(null, leavingServers, null, -1, this, results); + zkAdminArr[1].reconfigure(null, leavingServers, null, -1, this, results); synchronized (results) { while (results.size() < 1) { @@ -472,7 +494,7 @@ public void testRemoveOneAsynchronous() throws Exception { for (int i=1; i<=5; i++) testServerHasConfig(zkArr[i], null, leavingServers); - closeAllHandles(zkArr); + closeAllHandles(zkArr, zkAdminArr); } @SuppressWarnings("unchecked") @@ -491,6 +513,7 @@ public void testRoleChange() throws Exception { qu.disableJMXTest = true; qu.startAll(); ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); // changing a server's role / port is done by "adding" it with the same // id but different role / port @@ -517,6 +540,8 @@ public void testRoleChange() throws Exception { // to removed server ZooKeeper zk1 = (changingIndex == leaderIndex) ? zkArr[leaderIndex] : zkArr[(leaderIndex % qu.ALL) + 1]; + ZooKeeperAdmin zkAdmin1 = (changingIndex == leaderIndex) ? zkAdminArr[leaderIndex] + : zkAdminArr[(leaderIndex % qu.ALL) + 1]; // exactly as it is now, except for role change joiningServers.add("server." @@ -529,7 +554,7 @@ public void testRoleChange() throws Exception { .getPort() + ":" + newRole + ";localhost:" + qu.getPeer(changingIndex).peer.getClientPort()); - reconfig(zk1, joiningServers, null, null, -1); + reconfig(zkAdmin1, joiningServers, null, null, -1); testNormalOperation(zkArr[changingIndex], zk1); if (newRole.equals("observer")) { @@ -556,7 +581,7 @@ public void testRoleChange() throws Exception { changingIndex = leaderIndex; } } - closeAllHandles(zkArr); + closeAllHandles(zkArr, zkAdminArr); } @Test @@ -565,90 +590,98 @@ public void testPortChange() throws Exception { qu.disableJMXTest = true; qu.startAll(); ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); List joiningServers = new ArrayList(); int leaderIndex = getLeaderId(qu); int followerIndex = leaderIndex == 1 ? 2 : 1; - // change leader into observer, and modify all its ports at the same - // time - int observerIndex = leaderIndex; + // modify follower's client port - // new ports - int port1 = PortAssignment.unique(); - int port2 = PortAssignment.unique(); - int port3 = PortAssignment.unique(); - joiningServers.add("server." + observerIndex + "=localhost:" + port1 - + ":" + port2 + ":observer;localhost:" + port3); + int quorumPort = qu.getPeer(followerIndex).peer.getQuorumAddress().getPort(); + int electionPort = qu.getPeer(followerIndex).peer.getElectionAddress().getPort(); + int oldClientPort = qu.getPeer(followerIndex).peer.getClientPort(); + int newClientPort = PortAssignment.unique(); + joiningServers.add("server." + followerIndex + "=localhost:" + quorumPort + + ":" + electionPort + ":participant;localhost:" + newClientPort); // create a /test znode and check that read/write works before // any reconfig is invoked - testNormalOperation(zkArr[observerIndex], zkArr[followerIndex]); + testNormalOperation(zkArr[followerIndex], zkArr[leaderIndex]); - reconfig(zkArr[followerIndex], joiningServers, null, null, -1); + reconfig(zkAdminArr[followerIndex], joiningServers, null, null, -1); - // the change of port may not be immediate -- we repeatedly - // invoke an operation expecting it to eventually fail once - // the client port of observerIndex changes. If it didn't - // change -- that's an error. try { - for (int i=0; i < 30; i++) { + for (int i=0; i < 20; i++) { Thread.sleep(1000); - zkArr[observerIndex].setData("/test", "teststr".getBytes(), -1); + zkArr[followerIndex].setData("/test", "teststr".getBytes(), -1); } - Assert.fail("client port didn't change"); } catch (KeeperException.ConnectionLossException e) { - zkArr[observerIndex] = new ZooKeeper("127.0.0.1:" - + qu.getPeer(observerIndex).peer.getClientPort(), - ClientBase.CONNECTION_TIMEOUT, new Watcher() { - public void process(WatchedEvent event) {}}); + Assert.fail("Existing client disconnected when client port changed!"); } - leaderIndex = getLeaderId(qu); + zkArr[followerIndex].close(); + zkArr[followerIndex] = new ZooKeeper("127.0.0.1:" + + oldClientPort, + ClientBase.CONNECTION_TIMEOUT, new Watcher() { + public void process(WatchedEvent event) {}}); - followerIndex = 1; - while (followerIndex == leaderIndex || followerIndex == observerIndex) - followerIndex++; + zkAdminArr[followerIndex].close(); + zkAdminArr[followerIndex] = new ZooKeeperAdmin("127.0.0.1:" + + oldClientPort, + ClientBase.CONNECTION_TIMEOUT, new Watcher() { + public void process(WatchedEvent event) {}}); + zkAdminArr[followerIndex].addAuthInfo("digest", "super:test".getBytes()); - testNormalOperation(zkArr[observerIndex], zkArr[followerIndex]); + for (int i = 0; i < 10; i++) { + try { + Thread.sleep(1000); + zkArr[followerIndex].setData("/test", "teststr".getBytes(), -1); + Assert.fail("New client connected to old client port!"); + } catch (KeeperException.ConnectionLossException e) { + } + } + + zkArr[followerIndex].close(); + zkArr[followerIndex] = new ZooKeeper("127.0.0.1:" + + newClientPort, + ClientBase.CONNECTION_TIMEOUT, new Watcher() { + public void process(WatchedEvent event) {}}); - testServerHasConfig(zkArr[observerIndex], joiningServers, null); + zkAdminArr[followerIndex].close(); + zkAdminArr[followerIndex] = new ZooKeeperAdmin("127.0.0.1:" + + newClientPort, + ClientBase.CONNECTION_TIMEOUT, new Watcher() { + public void process(WatchedEvent event) {}}); + zkAdminArr[followerIndex].addAuthInfo("digest", "super:test".getBytes()); - Assert.assertTrue(qu.getPeer(observerIndex).peer.getQuorumAddress() - .getPort() == port1); - Assert.assertTrue(qu.getPeer(observerIndex).peer.getElectionAddress() - .getPort() == port2); - Assert.assertTrue(qu.getPeer(observerIndex).peer.getClientPort() == port3); - Assert.assertTrue(qu.getPeer(observerIndex).peer.getPeerState() == ServerState.OBSERVING); - Assert.assertTrue(qu.getPeer(observerIndex).peer.getName() - .endsWith(String.format(":%d", port3))); + testNormalOperation(zkArr[followerIndex], zkArr[leaderIndex]); + testServerHasConfig(zkArr[followerIndex], joiningServers, null); + Assert.assertEquals(newClientPort, qu.getPeer(followerIndex).peer.getClientPort()); joiningServers.clear(); // change leader's leading port - should renounce leadership - port1 = PortAssignment.unique(); - joiningServers.add("server." + leaderIndex + "=localhost:" + port1 + int newQuorumPort = PortAssignment.unique(); + joiningServers.add("server." + leaderIndex + "=localhost:" + + newQuorumPort + ":" + qu.getPeer(leaderIndex).peer.getElectionAddress().getPort() + ":participant;localhost:" + qu.getPeer(leaderIndex).peer.getClientPort()); - reconfig(zkArr[followerIndex], joiningServers, null, null, -1); + reconfig(zkAdminArr[leaderIndex], joiningServers, null, null, -1); - testNormalOperation(zkArr[observerIndex], zkArr[followerIndex]); + testNormalOperation(zkArr[followerIndex], zkArr[leaderIndex]); Assert.assertTrue(qu.getPeer(leaderIndex).peer.getQuorumAddress() - .getPort() == port1); - Assert.assertTrue(qu.getPeer(leaderIndex).peer.leader == null - && qu.getPeer(leaderIndex).peer.follower != null); - Assert.assertTrue(qu.getPeer(followerIndex).peer.leader != null - && qu.getPeer(followerIndex).peer.follower == null); + .getPort() == newQuorumPort); joiningServers.clear(); - // change in leader election port + // change everyone's leader election port for (int i = 1; i <= 3; i++) { joiningServers.add("server." + i + "=localhost:" @@ -657,7 +690,7 @@ ClientBase.CONNECTION_TIMEOUT, new Watcher() { + qu.getPeer(i).peer.getClientPort()); } - reconfig(zkArr[1], joiningServers, null, null, -1); + reconfig(zkAdminArr[1], joiningServers, null, null, -1); leaderIndex = getLeaderId(qu); int follower1 = leaderIndex == 1 ? 2 : 1; @@ -673,7 +706,97 @@ ClientBase.CONNECTION_TIMEOUT, new Watcher() { testServerHasConfig(zkArr[follower1], joiningServers, null); testServerHasConfig(zkArr[follower2], joiningServers, null); - closeAllHandles(zkArr); + closeAllHandles(zkArr, zkAdminArr); + } + + @Test + public void testPortChangeToBlockedPortFollower() throws Exception { + testPortChangeToBlockedPort(false); + } + @Test + public void testPortChangeToBlockedPortLeader() throws Exception { + testPortChangeToBlockedPort(true); + } + + private void testPortChangeToBlockedPort(boolean testLeader) throws Exception { + qu = new QuorumUtil(1); // create 3 servers + qu.disableJMXTest = true; + qu.startAll(); + ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); + + List joiningServers = new ArrayList(); + + int leaderIndex = getLeaderId(qu); + int followerIndex = leaderIndex == 1 ? 2 : 1; + int serverIndex = testLeader ? leaderIndex : followerIndex; + int reconfigIndex = testLeader ? followerIndex : leaderIndex; + + // modify server's client port + int quorumPort = qu.getPeer(serverIndex).peer.getQuorumAddress().getPort(); + int electionPort = qu.getPeer(serverIndex).peer.getElectionAddress().getPort(); + int oldClientPort = qu.getPeer(serverIndex).peer.getClientPort(); + int newClientPort = PortAssignment.unique(); + + try(ServerSocket ss = new ServerSocket()) { + ss.bind(new InetSocketAddress(getLoopbackAddress(), newClientPort)); + + joiningServers.add("server." + serverIndex + "=localhost:" + quorumPort + + ":" + electionPort + ":participant;localhost:" + newClientPort); + + // create a /test znode and check that read/write works before + // any reconfig is invoked + testNormalOperation(zkArr[followerIndex], zkArr[leaderIndex]); + + // Reconfigure + reconfig(zkAdminArr[reconfigIndex], joiningServers, null, null, -1); + Thread.sleep(1000); + + // The follower reconfiguration will have failed + zkArr[serverIndex].close(); + zkArr[serverIndex] = new ZooKeeper("127.0.0.1:" + + newClientPort, + ClientBase.CONNECTION_TIMEOUT, new Watcher() { + public void process(WatchedEvent event) {}}); + + zkAdminArr[serverIndex].close(); + zkAdminArr[serverIndex] = new ZooKeeperAdmin("127.0.0.1:" + + newClientPort, + ClientBase.CONNECTION_TIMEOUT, new Watcher() { + public void process(WatchedEvent event) {}}); + + try { + Thread.sleep(1000); + zkArr[serverIndex].setData("/test", "teststr".getBytes(), -1); + Assert.fail("New client connected to new client port!"); + } catch (KeeperException.ConnectionLossException e) { + // Exception is expected + } + + //The old port should be clear at this stage + + try (ServerSocket ss2 = new ServerSocket()) { + ss2.bind(new InetSocketAddress(getLoopbackAddress(), oldClientPort)); + } + + // Move back to the old port + joiningServers.clear(); + joiningServers.add("server." + serverIndex + "=localhost:" + quorumPort + + ":" + electionPort + ":participant;localhost:" + oldClientPort); + + reconfig(zkAdminArr[reconfigIndex], joiningServers, null, null, -1); + + zkArr[serverIndex].close(); + zkArr[serverIndex] = new ZooKeeper("127.0.0.1:" + + oldClientPort, + ClientBase.CONNECTION_TIMEOUT, new Watcher() { + public void process(WatchedEvent event) {}}); + + testNormalOperation(zkArr[followerIndex], zkArr[leaderIndex]); + testServerHasConfig(zkArr[serverIndex], joiningServers, null); + Assert.assertEquals(oldClientPort, qu.getPeer(serverIndex).peer.getClientPort()); + } + closeAllHandles(zkArr, zkAdminArr); } @Test @@ -684,7 +807,7 @@ public void testUnspecifiedClientAddress() throws Exception { } String server = "server.0=localhost:" + ports[0] + ":" + ports[1] + ";" + ports[2]; QuorumServer qs = new QuorumServer(0, server); - Assert.assertEquals(qs.clientAddr.getHostName(), "0.0.0.0"); + Assert.assertEquals(qs.clientAddr.getHostString(), "0.0.0.0"); Assert.assertEquals(qs.clientAddr.getPort(), ports[2]); } @@ -694,6 +817,7 @@ public void testQuorumSystemChange() throws Exception { qu.disableJMXTest = true; qu.startAll(); ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); ArrayList members = new ArrayList(); members.add("group.1=3:4:5"); @@ -711,7 +835,7 @@ public void testQuorumSystemChange() throws Exception { + "127.0.0.1:" + qu.getPeer(i).peer.getClientPort()); } - reconfig(zkArr[1], null, null, members, -1); + reconfig(zkAdminArr[1], null, null, members, -1); // this should flush the config to servers 2, 3, 4 and 5 testNormalOperation(zkArr[2], zkArr[3]); @@ -743,7 +867,7 @@ public void testQuorumSystemChange() throws Exception { + "127.0.0.1:" + qu.getPeer(i).peer.getClientPort()); } - reconfig(zkArr[1], null, null, members, -1); + reconfig(zkAdminArr[1], null, null, members, -1); // flush the config to server 2 testNormalOperation(zkArr[1], zkArr[2]); @@ -761,7 +885,7 @@ public void testQuorumSystemChange() throws Exception { + " doesn't think the quorum system is a majority quorum system!"); } - closeAllHandles(zkArr); + closeAllHandles(zkArr, zkAdminArr); } @Test @@ -789,6 +913,7 @@ public void testJMXBeanAfterRemoveAddOne() throws Exception { qu.disableJMXTest = true; qu.startAll(); ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); List leavingServers = new ArrayList(); List joiningServers = new ArrayList(); @@ -813,6 +938,7 @@ public void testJMXBeanAfterRemoveAddOne() throws Exception { assertRemotePeerMXBeanAttributes(leavingQS3, remotePeerBean3); ZooKeeper zk = zkArr[leavingIndex]; + ZooKeeperAdmin zkAdmin = zkAdminArr[leavingIndex]; leavingServers.add(Integer.toString(leavingIndex)); @@ -825,7 +951,7 @@ public void testJMXBeanAfterRemoveAddOne() throws Exception { + qu.getPeer(leavingIndex).peer.getClientPort()); // Remove ReplicatedServer_1 from the ensemble - reconfig(zk, null, leavingServers, null, -1); + reconfig(zkAdmin, null, leavingServers, null, -1); // localPeerBean.1 of ReplicatedServer_1 QuorumPeer removedPeer = qu.getPeer(leavingIndex).peer; @@ -840,7 +966,7 @@ public void testJMXBeanAfterRemoveAddOne() throws Exception { JMXEnv.ensureNone(remotePeerBean3); // Add ReplicatedServer_1 back to the ensemble - reconfig(zk, joiningServers, null, null, -1); + reconfig(zkAdmin, joiningServers, null, null, -1); // localPeerBean.1 of ReplicatedServer_1 assertLocalPeerMXBeanAttributes(removedPeer, localPeerBean, true); @@ -853,7 +979,7 @@ public void testJMXBeanAfterRemoveAddOne() throws Exception { leavingQS3 = peer3.getView().get(new Long(leavingIndex)); assertRemotePeerMXBeanAttributes(leavingQS3, remotePeerBean3); - closeAllHandles(zkArr); + closeAllHandles(zkArr, zkAdminArr); } /** @@ -866,6 +992,7 @@ public void testJMXBeanAfterRoleChange() throws Exception { qu.disableJMXTest = true; qu.startAll(); ZooKeeper[] zkArr = createHandles(qu); + ZooKeeperAdmin[] zkAdminArr = createAdminHandles(qu); // changing a server's role / port is done by "adding" it with the same // id but different role / port @@ -893,6 +1020,7 @@ public void testJMXBeanAfterRoleChange() throws Exception { String newRole = "observer"; ZooKeeper zk = zkArr[changingIndex]; + ZooKeeperAdmin zkAdmin = zkAdminArr[changingIndex]; // exactly as it is now, except for role change joiningServers.add("server." + changingIndex + "=127.0.0.1:" @@ -902,7 +1030,7 @@ public void testJMXBeanAfterRoleChange() throws Exception { + ":" + newRole + ";127.0.0.1:" + qu.getPeer(changingIndex).peer.getClientPort()); - reconfig(zk, joiningServers, null, null, -1); + reconfig(zkAdmin, joiningServers, null, null, -1); testNormalOperation(zkArr[changingIndex], zk); Assert.assertTrue(qu.getPeer(changingIndex).peer.observer != null @@ -926,7 +1054,7 @@ public void testJMXBeanAfterRoleChange() throws Exception { changingQS3 = peer3.getView().get(new Long(changingIndex)); assertRemotePeerMXBeanAttributes(changingQS3, remotePeerBean3); - closeAllHandles(zkArr); + closeAllHandles(zkArr, zkAdminArr); } private void assertLocalPeerMXBeanAttributes(QuorumPeer qp, @@ -934,12 +1062,10 @@ private void assertLocalPeerMXBeanAttributes(QuorumPeer qp, Assert.assertEquals("Mismatches LearnerType!", qp.getLearnerType() .name(), JMXEnv.ensureBeanAttribute(beanName, "LearnerType")); Assert.assertEquals("Mismatches ClientAddress!", - HostNameUtils.getHostString(qp.getClientAddress()) + ":" - + qp.getClientAddress().getPort(), + qp.getClientAddress().getHostString() + ":" + qp.getClientAddress().getPort(), JMXEnv.ensureBeanAttribute(beanName, "ClientAddress")); Assert.assertEquals("Mismatches LearnerType!", - HostNameUtils.getHostString(qp.getElectionAddress()) + ":" - + qp.getElectionAddress().getPort(), + qp.getElectionAddress().getHostString() + ":" + qp.getElectionAddress().getPort(), JMXEnv.ensureBeanAttribute(beanName, "ElectionAddress")); Assert.assertEquals("Mismatches PartOfEnsemble!", isPartOfEnsemble, JMXEnv.ensureBeanAttribute(beanName, "PartOfEnsemble")); @@ -974,19 +1100,13 @@ private void assertRemotePeerMXBeanAttributes(QuorumServer qs, Assert.assertEquals("Mismatches LearnerType!", qs.type.name(), JMXEnv.ensureBeanAttribute(beanName, "LearnerType")); Assert.assertEquals("Mismatches ClientAddress!", - getNumericalAddrPort( - HostNameUtils.getHostString(qs.clientAddr) + ":" - + qs.clientAddr.getPort() ), + getNumericalAddrPort(qs.clientAddr.getHostString() + ":" + qs.clientAddr.getPort()), getAddrPortFromBean(beanName, "ClientAddress") ); Assert.assertEquals("Mismatches ElectionAddress!", - getNumericalAddrPort( - HostNameUtils.getHostString(qs.electionAddr) + ":" - + qs.electionAddr.getPort() ), + getNumericalAddrPort(qs.electionAddr.getHostString() + ":" + qs.electionAddr.getPort()), getAddrPortFromBean(beanName, "ElectionAddress") ); Assert.assertEquals("Mismatches QuorumAddress!", - getNumericalAddrPort( - qs.addr.getHostName() + ":" - + qs.addr.getPort() ), + getNumericalAddrPort(qs.addr.getHostString() + ":" + qs.addr.getPort()), getAddrPortFromBean(beanName, "QuorumAddress") ); } } diff --git a/src/java/test/org/apache/zookeeper/test/RestoreCommittedLogTest.java b/src/java/test/org/apache/zookeeper/test/RestoreCommittedLogTest.java index 9c70387c0a1..1cd0337bf82 100644 --- a/src/java/test/org/apache/zookeeper/test/RestoreCommittedLogTest.java +++ b/src/java/test/org/apache/zookeeper/test/RestoreCommittedLogTest.java @@ -21,11 +21,8 @@ import java.io.File; import java.util.List; -import org.apache.log4j.Logger; 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; @@ -35,12 +32,14 @@ import org.apache.zookeeper.server.ZooKeeperServer; import org.junit.Assert; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** After a replica starts, it should load commits in its committedLog list. * This test checks if committedLog != 0 after replica restarted. */ -public class RestoreCommittedLogTest extends ZKTestCase implements Watcher { - private static final Logger LOG = Logger.getLogger(RestoreCommittedLogTest.class); +public class RestoreCommittedLogTest extends ZKTestCase{ + private static final Logger LOG = LoggerFactory.getLogger(RestoreCommittedLogTest.class); private static final String HOSTPORT = "127.0.0.1:" + PortAssignment.unique(); private static final int CONNECTION_TIMEOUT = 3000; /** @@ -58,7 +57,7 @@ public void testRestoreCommittedLog() 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); + ZooKeeper zk = ClientBase.createZKClient(HOSTPORT); try { for (int i = 0; i< 2000; i++) { zk.create("/invalidsnap-" + i, new byte[0], Ids.OPEN_ACL_UNSAFE, @@ -77,13 +76,8 @@ public void testRestoreCommittedLog() throws Exception { zks.startdata(); List committedLog = zks.getZKDatabase().getCommittedLog(); int logsize = committedLog.size(); - LOG.info("committedLog size = " + logsize); + LOG.info("committedLog size = {}", logsize); Assert.assertTrue("log size != 0", (logsize != 0)); zks.shutdown(); } - - public void process(WatchedEvent event) { - // do nothing - } - } diff --git a/src/java/test/org/apache/zookeeper/test/SSLAuthTest.java b/src/java/test/org/apache/zookeeper/test/SSLAuthTest.java new file mode 100644 index 00000000000..337c8f7df6c --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SSLAuthTest.java @@ -0,0 +1,101 @@ +/** + * 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.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SSLAuthTest extends ClientBase { + @Before + public void setUp() throws Exception { + String testDataPath = System.getProperty("test.data.dir", "build/test/data"); + System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, "org.apache.zookeeper.server.NettyServerCnxnFactory"); + System.setProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET, "org.apache.zookeeper.ClientCnxnSocketNetty"); + System.setProperty(ZKClientConfig.SECURE_CLIENT, "true"); + System.setProperty(ZKConfig.SSL_AUTHPROVIDER, "x509"); + System.setProperty(ZKConfig.SSL_KEYSTORE_LOCATION, testDataPath + "/ssl/testKeyStore.jks"); + System.setProperty(ZKConfig.SSL_KEYSTORE_PASSWD, "testpass"); + System.setProperty(ZKConfig.SSL_TRUSTSTORE_LOCATION, testDataPath + "/ssl/testTrustStore.jks"); + System.setProperty(ZKConfig.SSL_TRUSTSTORE_PASSWD, "testpass"); + System.setProperty("javax.net.debug", "ssl"); + System.setProperty("zookeeper.authProvider.x509", "org.apache.zookeeper.server.auth.X509AuthenticationProvider"); + + String host = "localhost"; + int port = PortAssignment.unique(); + hostPort = host + ":" + port; + + serverFactory = ServerCnxnFactory.createFactory(); + serverFactory.configure(new InetSocketAddress(host, port), maxCnxns, true); + + super.setUp(); + } + + @After + public void teardown() throws Exception { + System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + System.clearProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET); + System.clearProperty(ZKClientConfig.SECURE_CLIENT); + System.clearProperty(ZKConfig.SSL_AUTHPROVIDER); + System.clearProperty(ZKConfig.SSL_KEYSTORE_LOCATION); + System.clearProperty(ZKConfig.SSL_KEYSTORE_PASSWD); + System.clearProperty(ZKConfig.SSL_TRUSTSTORE_LOCATION); + System.clearProperty(ZKConfig.SSL_TRUSTSTORE_PASSWD); + System.clearProperty("javax.net.debug"); + System.clearProperty("zookeeper.authProvider.x509"); + } + + @Test + public void testRejection() throws Exception { + String testDataPath = System.getProperty("test.data.dir", "build/test/data"); + + // Replace trusted keys with a valid key that is not trusted by the server + System.setProperty(ZKConfig.SSL_KEYSTORE_LOCATION, testDataPath + "/ssl/testUntrustedKeyStore.jks"); + System.setProperty(ZKConfig.SSL_KEYSTORE_PASSWD, "testpass"); + + CountdownWatcher watcher = new CountdownWatcher(); + + // Handshake will take place, and then X509AuthenticationProvider should reject the untrusted cert + new TestableZooKeeper(hostPort, CONNECTION_TIMEOUT, watcher); + Assert.assertFalse("Untrusted certificate should not result in successful connection", + watcher.clientConnected.await(1000, TimeUnit.MILLISECONDS)); + } + + @Test + public void testMisconfiguration() throws Exception { + System.clearProperty(ZKConfig.SSL_AUTHPROVIDER); + System.clearProperty(ZKConfig.SSL_KEYSTORE_LOCATION); + System.clearProperty(ZKConfig.SSL_KEYSTORE_PASSWD); + System.clearProperty(ZKConfig.SSL_TRUSTSTORE_LOCATION); + System.clearProperty(ZKConfig.SSL_TRUSTSTORE_PASSWD); + + CountdownWatcher watcher = new CountdownWatcher(); + new TestableZooKeeper(hostPort, CONNECTION_TIMEOUT, watcher); + Assert.assertFalse("Missing SSL configuration should not result in successful connection", + watcher.clientConnected.await(1000, TimeUnit.MILLISECONDS)); + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/test/SSLTest.java b/src/java/test/org/apache/zookeeper/test/SSLTest.java new file mode 100644 index 00000000000..16911b735af --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SSLTest.java @@ -0,0 +1,130 @@ +/** + * 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.CreateMode; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.client.ZKClientConfig; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SSLTest extends QuorumPeerTestBase { + + @Before + public void setup() { + String testDataPath = System.getProperty("test.data.dir", "build/test/data"); + System.setProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY, "org.apache.zookeeper.server.NettyServerCnxnFactory"); + System.setProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET, "org.apache.zookeeper.ClientCnxnSocketNetty"); + System.setProperty(ZKClientConfig.SECURE_CLIENT, "true"); + System.setProperty(ZKConfig.SSL_KEYSTORE_LOCATION, testDataPath + "/ssl/testKeyStore.jks"); + System.setProperty(ZKConfig.SSL_KEYSTORE_PASSWD, "testpass"); + System.setProperty(ZKConfig.SSL_TRUSTSTORE_LOCATION, testDataPath + "/ssl/testTrustStore.jks"); + System.setProperty(ZKConfig.SSL_TRUSTSTORE_PASSWD, "testpass"); + } + + @After + public void teardown() throws Exception { + System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY); + System.clearProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET); + System.clearProperty(ZKClientConfig.SECURE_CLIENT); + System.clearProperty(ZKConfig.SSL_KEYSTORE_LOCATION); + System.clearProperty(ZKConfig.SSL_KEYSTORE_PASSWD); + System.clearProperty(ZKConfig.SSL_TRUSTSTORE_LOCATION); + System.clearProperty(ZKConfig.SSL_TRUSTSTORE_PASSWD); + } + + /** + * This test checks that SSL works in cluster setup of ZK servers, which includes: + * 1. setting "secureClientPort" in "zoo.cfg" file. + * 2. setting jvm flags for serverCnxn, keystore, truststore. + * Finally, a zookeeper client should be able to connect to the secure port and + * communicate with server via secure connection. + *

    + * Note that in this test a ZK server has two ports -- clientPort and secureClientPort. + */ + @Test + public void testSecureQuorumServer() throws Exception { + final int SERVER_COUNT = 3; + final int clientPorts[] = new int[SERVER_COUNT]; + final Integer secureClientPorts[] = new Integer[SERVER_COUNT]; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < SERVER_COUNT; i++) { + clientPorts[i] = PortAssignment.unique(); + secureClientPorts[i] = PortAssignment.unique(); + String server = String.format("server.%d=localhost:%d:%d:participant;localhost:%d", + i, PortAssignment.unique(), PortAssignment.unique(), clientPorts[i]); + sb.append(server + "\n"); + } + String quorumCfg = sb.toString(); + + + MainThread[] mt = new MainThread[SERVER_COUNT]; + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, quorumCfg, secureClientPorts[i], true); + mt[i].start(); + } + + // Servers have been set up. Now go test if secure connection is successful. + for (int i = 0; i < SERVER_COUNT; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], TIMEOUT)); + + ZooKeeper zk = ClientBase.createZKClient("127.0.0.1:" + secureClientPorts[i], TIMEOUT); + // Do a simple operation to make sure the connection is fine. + zk.create("/test", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.delete("/test", -1); + zk.close(); + } + + for (int i = 0; i < mt.length; i++) { + mt[i].shutdown(); + } + } + + + /** + * Developers might use standalone mode (which is the default for one server). + * This test checks SSL works in standalone mode of ZK server. + *

    + * Note that in this test the Zk server has only secureClientPort + */ + @Test + public void testSecureStandaloneServer() throws Exception { + Integer secureClientPort = PortAssignment.unique(); + MainThread mt = new MainThread(MainThread.UNSET_MYID, "", secureClientPort, false); + mt.start(); + + ZooKeeper zk = ClientBase.createZKClient("127.0.0.1:" + secureClientPort, TIMEOUT); + zk.create("/test", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.delete("/test", -1); + zk.close(); + mt.shutdown(); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedClientTest.java b/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedClientTest.java index 3c909ce1d09..c007022c01a 100644 --- a/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedClientTest.java +++ b/src/java/test/org/apache/zookeeper/test/SaslAuthDesignatedClientTest.java @@ -29,6 +29,7 @@ import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooDefs.Perms; +import org.apache.zookeeper.client.ZKClientConfig; import org.apache.zookeeper.client.ZooKeeperSaslClient; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Id; @@ -38,7 +39,8 @@ 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"); + System.setProperty(ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, + "MyZookeeperClient"); try { File tmpDir = createTmpDir(); @@ -144,7 +146,7 @@ public void testReadAccessUser() throws Exception { Thread.sleep(100); // disable Client Sasl - System.setProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY, "false"); + System.setProperty(ZKClientConfig.ENABLE_CLIENT_SASL_KEY, "false"); try { zk = createClient(); @@ -157,7 +159,8 @@ public void testReadAccessUser() throws Exception { zk.close(); } finally { // enable Client Sasl - System.setProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY, "true"); + System.setProperty(ZKClientConfig.ENABLE_CLIENT_SASL_KEY, + "true"); } } } diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthFailDesignatedClientTest.java b/src/java/test/org/apache/zookeeper/test/SaslAuthFailDesignatedClientTest.java index 5291141cf8f..df8c9033b71 100644 --- a/src/java/test/org/apache/zookeeper/test/SaslAuthFailDesignatedClientTest.java +++ b/src/java/test/org/apache/zookeeper/test/SaslAuthFailDesignatedClientTest.java @@ -22,23 +22,20 @@ 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.apache.zookeeper.client.ZKClientConfig; 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"); + System.setProperty(ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, + "MyZookeeperClient"); try { File tmpDir = createTmpDir(); @@ -70,19 +67,6 @@ to use it (we're configured by the above System.setProperty(...LOGIN_CONTEXT_NA } } - 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 { diff --git a/src/java/test/org/apache/zookeeper/test/SaslAuthMissingClientConfigTest.java b/src/java/test/org/apache/zookeeper/test/SaslAuthMissingClientConfigTest.java index 98be0be3386..ee013872f26 100644 --- a/src/java/test/org/apache/zookeeper/test/SaslAuthMissingClientConfigTest.java +++ b/src/java/test/org/apache/zookeeper/test/SaslAuthMissingClientConfigTest.java @@ -21,15 +21,12 @@ 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.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.client.ZKClientConfig; import org.junit.Assert; import org.junit.Test; @@ -38,7 +35,8 @@ public class SaslAuthMissingClientConfigTest extends ClientBase { 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"); + System.setProperty(ZKClientConfig.LOGIN_CONTEXT_NAME_KEY, + "MyZookeeperClient"); try { File tmpDir = createTmpDir(); @@ -66,20 +64,6 @@ use the (nonexistent) 'MyZookeeperClient' section. */ } } - 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(); diff --git a/src/java/test/org/apache/zookeeper/test/SaslClientTest.java b/src/java/test/org/apache/zookeeper/test/SaslClientTest.java index 8213abca455..95bf2f6af3d 100644 --- a/src/java/test/org/apache/zookeeper/test/SaslClientTest.java +++ b/src/java/test/org/apache/zookeeper/test/SaslClientTest.java @@ -18,45 +18,52 @@ package org.apache.zookeeper.test; + import org.apache.zookeeper.ZKTestCase; -import org.apache.zookeeper.client.ZooKeeperSaslClient; +import org.apache.zookeeper.client.ZKClientConfig; 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); + existingPropertyValue = System + .getProperty(ZKClientConfig.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); + System.setProperty(ZKClientConfig.ENABLE_CLIENT_SASL_KEY, + existingPropertyValue); } } @Test public void testSaslClientDisabled() { - System.clearProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY); - Assert.assertTrue("SASL client disabled", ZooKeeperSaslClient.isEnabled()); + System.clearProperty(ZKClientConfig.ENABLE_CLIENT_SASL_KEY); + Assert.assertTrue("SASL client disabled", + new ZKClientConfig().isSaslClientEnabled()); for (String value : Arrays.asList("true", "TRUE")) { - System.setProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY, value); - Assert.assertTrue("SASL client disabled", ZooKeeperSaslClient.isEnabled()); + System.setProperty(ZKClientConfig.ENABLE_CLIENT_SASL_KEY, + value); + Assert.assertTrue("SASL client disabled", + new ZKClientConfig().isSaslClientEnabled()); } for (String value : Arrays.asList("false", "FALSE")) { - System.setProperty(ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY, value); - Assert.assertFalse("SASL client disabled", ZooKeeperSaslClient.isEnabled()); + System.setProperty(ZKClientConfig.ENABLE_CLIENT_SASL_KEY, + value); + Assert.assertFalse("SASL client disabled", + new ZKClientConfig().isSaslClientEnabled()); } } } diff --git a/src/java/test/org/apache/zookeeper/test/SaslSuperUserTest.java b/src/java/test/org/apache/zookeeper/test/SaslSuperUserTest.java new file mode 100644 index 00000000000..894c0f5d108 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SaslSuperUserTest.java @@ -0,0 +1,136 @@ +/** + * 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.Arrays; +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.Perms; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; +import org.junit.Assert; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class SaslSuperUserTest extends ClientBase { + private static Id otherSaslUser = new Id ("sasl", "joe"); + private static Id otherDigestUser; + private static String oldAuthProvider; + private static String oldLoginConfig; + private static String oldSuperUser; + + @BeforeClass + public static void setupStatic() throws Exception { + oldAuthProvider = System.setProperty("zookeeper.authProvider.1","org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); + + 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_duper=\"test\";\n" + + "};\n" + + "Client {\n" + + " org.apache.zookeeper.server.auth.DigestLoginModule required\n" + + " username=\"super_duper\"\n" + + " password=\"test\";\n" + + "};" + "\n"); + fwriter.close(); + oldLoginConfig = System.setProperty("java.security.auth.login.config",saslConfFile.getAbsolutePath()); + oldSuperUser = System.setProperty("zookeeper.superUser","super_duper"); + otherDigestUser = new Id ("digest", DigestAuthenticationProvider.generateDigest("jack:jack")); + } + + @AfterClass + public static void cleanupStatic() { + if (oldAuthProvider != null) { + System.setProperty("zookeeper.authProvider.1", oldAuthProvider); + } else { + System.clearProperty("zookeeper.authProvider.1"); + } + oldAuthProvider = null; + + if (oldLoginConfig != null) { + System.setProperty("java.security.auth.login.config", oldLoginConfig); + } else { + System.clearProperty("java.security.auth.login.config"); + } + oldLoginConfig = null; + + if (oldSuperUser != null) { + System.setProperty("zookeeper.superUser", oldSuperUser); + } else { + System.clearProperty("zookeeper.superUser"); + } + oldSuperUser = null; + } + + 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) { + authFailed.incrementAndGet(); + } + else { + super.process(event); + } + } + } + + @Test + public void testSuperIsSuper() throws Exception { + ZooKeeper zk = createClient(); + try { + zk.create("/digest_read", null, Arrays.asList(new ACL(Perms.READ, otherDigestUser)), CreateMode.PERSISTENT); + zk.create("/digest_read/sub", null, Arrays.asList(new ACL(Perms.READ, otherDigestUser)), CreateMode.PERSISTENT); + zk.create("/sasl_read", null, Arrays.asList(new ACL(Perms.READ, otherSaslUser)), CreateMode.PERSISTENT); + zk.create("/sasl_read/sub", null, Arrays.asList(new ACL(Perms.READ, otherSaslUser)), CreateMode.PERSISTENT); + zk.delete("/digest_read/sub", -1); + zk.delete("/digest_read", -1); + zk.delete("/sasl_read/sub", -1); + zk.delete("/sasl_read", -1); + //If the test failes it will most likely fail with a NoAuth exception before it ever gets to this assertion + Assert.assertEquals(authFailed.get(), 0); + } finally { + zk.close(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/test/SessionInvalidationTest.java b/src/java/test/org/apache/zookeeper/test/SessionInvalidationTest.java index 3a0b2ee6018..22a1518d0a8 100644 --- a/src/java/test/org/apache/zookeeper/test/SessionInvalidationTest.java +++ b/src/java/test/org/apache/zookeeper/test/SessionInvalidationTest.java @@ -23,7 +23,6 @@ import java.io.OutputStream; import java.net.Socket; -import junit.framework.Assert; import org.apache.jute.BinaryOutputArchive; import org.apache.zookeeper.ZooDefs; @@ -33,6 +32,7 @@ import org.apache.zookeeper.proto.ConnectRequest; import org.apache.zookeeper.proto.CreateRequest; import org.apache.zookeeper.proto.RequestHeader; +import org.junit.Assert; import org.junit.Test; public class SessionInvalidationTest extends ClientBase { diff --git a/src/java/test/org/apache/zookeeper/test/SessionTest.java b/src/java/test/org/apache/zookeeper/test/SessionTest.java index 098fea95b73..06f58466f48 100644 --- a/src/java/test/org/apache/zookeeper/test/SessionTest.java +++ b/src/java/test/org/apache/zookeeper/test/SessionTest.java @@ -22,7 +22,6 @@ import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -235,72 +234,6 @@ public void processResult(int rc, String p, Object c, byte[] b, Stat s) { Assert.assertEquals(KeeperException.Code.SESSIONEXPIRED.toString(), cb.toString()); } - private List findThreads(String name) { - int threadCount = Thread.activeCount(); - Thread threads[] = new Thread[threadCount*2]; - threadCount = Thread.enumerate(threads); - ArrayList list = new ArrayList(); - for(int i = 0; i < threadCount; i++) { - if (threads[i].getName().indexOf(name) != -1) { - list.add(threads[i]); - } - } - return list; - } - - /** - * Make sure ephemerals get cleaned up when a session times out. - */ - @SuppressWarnings("deprecation") - @Test - public void testSessionTimeout() throws Exception { - final int TIMEOUT = 5000; - List etBefore = findThreads("EventThread"); - List stBefore = findThreads("SendThread"); - DisconnectableZooKeeper zk = createClient(TIMEOUT); - zk.create("/stest", new byte[0], Ids.OPEN_ACL_UNSAFE, - CreateMode.EPHEMERAL); - - // Find the new event and send threads - List etAfter = findThreads("EventThread"); - List stAfter = findThreads("SendThread"); - Thread eventThread = null; - Thread sendThread = null; - for(Thread t: etAfter) { - if (!etBefore.contains(t)) { - eventThread = t; - break; - } - } - for(Thread t: stAfter) { - if (!stBefore.contains(t)) { - sendThread = t; - break; - } - } - sendThread.suspend(); - //zk.disconnect(); - - Thread.sleep(TIMEOUT*2); - sendThread.resume(); - eventThread.join(TIMEOUT); - Assert.assertFalse("EventThread is still running", eventThread.isAlive()); - - zk = createClient(TIMEOUT); - zk.create("/stest", new byte[0], Ids.OPEN_ACL_UNSAFE, - CreateMode.EPHEMERAL); - tearDown(); - zk.close(); - zk.disconnect(); - setUp(); - - zk = createClient(TIMEOUT); - Assert.assertTrue(zk.exists("/stest", false) != null); - Thread.sleep(TIMEOUT*2); - Assert.assertTrue(zk.exists("/stest", false) == null); - zk.close(); - } - /** * Make sure that we cannot have two connections with the same * session id. diff --git a/src/java/test/org/apache/zookeeper/test/SessionTimeoutTest.java b/src/java/test/org/apache/zookeeper/test/SessionTimeoutTest.java new file mode 100644 index 00000000000..09badae12a1 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/SessionTimeoutTest.java @@ -0,0 +1,129 @@ +/** + * 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.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.Stat; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class SessionTimeoutTest extends ClientBase { + protected static final Logger LOG = LoggerFactory.getLogger(SessionTimeoutTest.class); + + private TestableZooKeeper zk; + + @Before + public void setUp() throws Exception { + super.setUp(); + zk = createClient(); + } + + @Test + public void testSessionExpiration() throws InterruptedException, + KeeperException { + final CountDownLatch expirationLatch = new CountDownLatch(1); + Watcher watcher = new Watcher() { + @Override + public void process(WatchedEvent event) { + if ( event.getState() == Event.KeeperState.Expired ) { + expirationLatch.countDown(); + } + } + }; + zk.exists("/foo", watcher); + + zk.getTestable().injectSessionExpiration(); + Assert.assertTrue(expirationLatch.await(5, TimeUnit.SECONDS)); + + boolean gotException = false; + try { + zk.exists("/foo", false); + Assert.fail("Should have thrown a SessionExpiredException"); + } catch (KeeperException.SessionExpiredException e) { + // correct + gotException = true; + } + Assert.assertTrue(gotException); + } + + /** + * Make sure ephemerals get cleaned up when session disconnects. + */ + @Test + public void testSessionDisconnect() throws KeeperException, InterruptedException, IOException { + zk.create("/sdisconnect", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.EPHEMERAL); + assertNotNull("Ephemeral node has not been created", zk.exists("/sdisconnect", null)); + + zk.close(); + + zk = createClient(); + assertNull("Ephemeral node shouldn't exist after client disconnect", zk.exists("/sdisconnect", null)); + } + + /** + * Make sure ephemerals are kept when session restores. + */ + @Test + public void testSessionRestore() throws KeeperException, InterruptedException, IOException { + zk.create("/srestore", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.EPHEMERAL); + assertNotNull("Ephemeral node has not been created", zk.exists("/srestore", null)); + + zk.disconnect(); + zk.close(); + + zk = createClient(); + assertNotNull("Ephemeral node should be present when session is restored", zk.exists("/srestore", null)); + } + + /** + * Make sure ephemerals are kept when server restarts. + */ + @Test + public void testSessionSurviveServerRestart() throws Exception { + zk.create("/sdeath", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.EPHEMERAL); + assertNotNull("Ephemeral node has not been created", zk.exists("/sdeath", null)); + + zk.disconnect(); + stopServer(); + startServer(); + zk = createClient(); + + assertNotNull("Ephemeral node should be present when server restarted", zk.exists("/sdeath", null)); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/SessionTrackerCheckTest.java b/src/java/test/org/apache/zookeeper/test/SessionTrackerCheckTest.java index 1e993fd6997..b4844525dce 100644 --- a/src/java/test/org/apache/zookeeper/test/SessionTrackerCheckTest.java +++ b/src/java/test/org/apache/zookeeper/test/SessionTrackerCheckTest.java @@ -24,6 +24,7 @@ import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.server.SessionTracker.Session; import org.apache.zookeeper.server.SessionTracker.SessionExpirer; +import org.apache.zookeeper.server.ZooKeeperServerListener; import org.apache.zookeeper.server.quorum.LeaderSessionTracker; import org.apache.zookeeper.server.quorum.LearnerSessionTracker; import org.junit.After; @@ -76,7 +77,8 @@ public void testLearnerSessionTracker() throws Exception { Expirer expirer = new Expirer(1); // With local session on LearnerSessionTracker tracker = new LearnerSessionTracker(expirer, - sessionsWithTimeouts, TICK_TIME, expirer.sid, true); + sessionsWithTimeouts, TICK_TIME, expirer.sid, true, + testZKSListener()); // Unknown session long sessionId = 0xb100ded; @@ -114,7 +116,7 @@ public void testLearnerSessionTracker() throws Exception { // With local session off tracker = new LearnerSessionTracker(expirer, sessionsWithTimeouts, - TICK_TIME, expirer.sid, false); + TICK_TIME, expirer.sid, false, testZKSListener()); // Should be noop sessionId = 0xdeadbeef; @@ -131,7 +133,8 @@ public void testLeaderSessionTracker() throws Exception { Expirer expirer = new Expirer(2); // With local session on LeaderSessionTracker tracker = new LeaderSessionTracker(expirer, - sessionsWithTimeouts, TICK_TIME, expirer.sid, true); + sessionsWithTimeouts, TICK_TIME, expirer.sid, true, + testZKSListener()); // Local session from other server long sessionId = ((expirer.sid + 1) << 56) + 1; @@ -179,7 +182,7 @@ public void testLeaderSessionTracker() throws Exception { // With local session off tracker = new LeaderSessionTracker(expirer, sessionsWithTimeouts, - TICK_TIME, expirer.sid, false); + TICK_TIME, expirer.sid, false, testZKSListener()); // Global session sessionId = 0xdeadbeef; @@ -215,4 +218,13 @@ public void testLeaderSessionTracker() throws Exception { } + ZooKeeperServerListener testZKSListener() { + return new ZooKeeperServerListener() { + + @Override + public void notifyStopping(String errMsg, int exitCode) { + + } + }; + } } diff --git a/src/java/test/org/apache/zookeeper/test/SledgeHammer.java b/src/java/test/org/apache/zookeeper/test/SledgeHammer.java index 99ae5fc5c30..1e1a20896e9 100644 --- a/src/java/test/org/apache/zookeeper/test/SledgeHammer.java +++ b/src/java/test/org/apache/zookeeper/test/SledgeHammer.java @@ -24,13 +24,11 @@ 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.ZooDefs.Ids; import org.apache.zookeeper.data.Stat; -public class SledgeHammer extends Thread implements Watcher { +public class SledgeHammer extends Thread{ ZooKeeper zk; int count; @@ -38,8 +36,8 @@ public class SledgeHammer extends Thread implements Watcher { int readsPerWrite; public SledgeHammer(String hosts, int count, int readsPerWrite) - throws IOException { - zk = new ZooKeeper(hosts, 10000, this); + throws Exception { + zk = ClientBase.createZKClient(hosts, 10000); this.count = count; this.readsPerWrite = readsPerWrite; } @@ -98,7 +96,7 @@ public void run() { * @throws NumberFormatException */ public static void main(String[] args) throws NumberFormatException, - IOException { + Exception { if (args.length != 3) { System.err .println("USAGE: SledgeHammer zookeeper_server reps reads_per_rep"); @@ -109,11 +107,4 @@ public static void main(String[] args) throws NumberFormatException, h.start(); System.exit(0); } - - public void process(WatchedEvent event) { - synchronized (this) { - notifyAll(); - } - } - } diff --git a/src/java/test/org/apache/zookeeper/test/StandaloneTest.java b/src/java/test/org/apache/zookeeper/test/StandaloneTest.java index 1701ba35dea..3d57b6c5c10 100644 --- a/src/java/test/org/apache/zookeeper/test/StandaloneTest.java +++ b/src/java/test/org/apache/zookeeper/test/StandaloneTest.java @@ -24,17 +24,20 @@ import java.util.ArrayList; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.Watcher; -import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ZooKeeperServer; import org.apache.zookeeper.server.quorum.QuorumPeerTestBase; import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.admin.ZooKeeperAdmin; +import org.junit.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.junit.Assert; import org.junit.Test; @@ -43,37 +46,82 @@ */ public class StandaloneTest extends QuorumPeerTestBase implements Watcher{ protected static final Logger LOG = - LoggerFactory.getLogger(StandaloneTest.class); - + LoggerFactory.getLogger(StandaloneTest.class); + + @Before + public void setup() { + System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", + "super:D/InIHSb7yEEbrWz8b9l71RjZJU="/* password is 'test'*/); + QuorumPeerConfig.setReconfigEnabled(true); + } + /** - * Ensure that a single standalone server comes up when misconfigured - * with a single server.# line in the configuration. This handles the - * case of HBase, which configures zoo.cfg in this way. Maintain b/w - * compatibility. - * TODO remove in a future version (4.0.0 hopefully) + * This test wouldn't create any dynamic config. + * However, it adds a "clientPort=XXX" in static config file. + * It checks the standard way of standalone mode. */ @Test - public void testStandaloneQuorum() throws Exception { + public void testNoDynamicConfig() throws Exception { ClientBase.setupTestEnv(); - final int CLIENT_PORT_QP1 = PortAssignment.unique(); - - String quorumCfgSection = - "server.1=127.0.0.1:" + (PortAssignment.unique()) - + ":" + (PortAssignment.unique()) + ";" + CLIENT_PORT_QP1 + "\n"; - - MainThread q1 = new MainThread(1, CLIENT_PORT_QP1, quorumCfgSection); - q1.start(); + final int CLIENT_PORT = PortAssignment.unique(); + + MainThread mt = new MainThread( + MainThread.UNSET_MYID, CLIENT_PORT, "", false); + verifyStandalone(mt, CLIENT_PORT); + } + + /** + * This test creates a dynamic config of new format. + * The dynamic config is written in dynamic config file. + * It checks that the client port will be read from the dynamic config. + * + * This handles the case of HBase, which adds a single server line to the config. + * Maintain b/w compatibility. + */ + @Test + public void testClientPortInDynamicFile() throws Exception { + ClientBase.setupTestEnv(); + final int CLIENT_PORT = PortAssignment.unique(); + + String quorumCfgSection = "server.1=127.0.0.1:" + + (PortAssignment.unique()) + ":" + (PortAssignment.unique()) + + ":participant;" + CLIENT_PORT + "\n"; + + MainThread mt = new MainThread(1, quorumCfgSection); + verifyStandalone(mt, CLIENT_PORT); + } + + /** + * This test creates a dynamic config of new format. + * The dynamic config is written in static config file. + * It checks that the client port will be read from the dynamic config. + */ + @Test + public void testClientPortInStaticFile() throws Exception { + ClientBase.setupTestEnv(); + final int CLIENT_PORT = PortAssignment.unique(); + + String quorumCfgSection = "server.1=127.0.0.1:" + + (PortAssignment.unique()) + ":" + (PortAssignment.unique()) + + ":participant;" + CLIENT_PORT + "\n"; + + MainThread mt = new MainThread(1, quorumCfgSection, false); + verifyStandalone(mt, CLIENT_PORT); + } + + void verifyStandalone(MainThread mt, int clientPort) throws InterruptedException { + mt.start(); try { Assert.assertTrue("waiting for server 1 being up", - ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_QP1, - CONNECTION_TIMEOUT)); - } finally { - Assert.assertFalse("Error- MainThread started in Quorum Mode!", - q1.isQuorumPeerRunning()); - q1.shutdown(); + ClientBase.waitForServerUp("127.0.0.1:" + clientPort, + CONNECTION_TIMEOUT)); + } finally { + Assert.assertFalse("Error- MainThread started in Quorum Mode!", + mt.isQuorumPeerRunning()); + mt.shutdown(); } - } - + } + /** * Verify that reconfiguration in standalone mode fails with * KeeperException.UnimplementedException. @@ -95,13 +143,15 @@ public void testStandaloneReconfigFails() throws Exception { CountdownWatcher watcher = new CountdownWatcher(); ZooKeeper zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, watcher); + ZooKeeperAdmin zkAdmin = new ZooKeeperAdmin(HOSTPORT, CONNECTION_TIMEOUT, watcher); watcher.waitForConnected(CONNECTION_TIMEOUT); List joiners = new ArrayList(); joiners.add("server.2=localhost:1234:1235;1236"); // generate some transactions that will get logged try { - zk.reconfig(joiners, null, null, -1, new Stat()); + zkAdmin.addAuthInfo("digest", "super:test".getBytes()); + zkAdmin.reconfigure(joiners, null, null, -1, new Stat()); Assert.fail("Reconfiguration in standalone should trigger " + "UnimplementedException"); } catch (KeeperException.UnimplementedException ex) { diff --git a/src/java/test/org/apache/zookeeper/test/StaticHostProviderTest.java b/src/java/test/org/apache/zookeeper/test/StaticHostProviderTest.java index bf1dcef7fbc..10c6d1c5f03 100644 --- a/src/java/test/org/apache/zookeeper/test/StaticHostProviderTest.java +++ b/src/java/test/org/apache/zookeeper/test/StaticHostProviderTest.java @@ -25,6 +25,7 @@ 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 java.net.InetAddress; @@ -55,9 +56,9 @@ public void testNextGoesRoundAndSleeps() { 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); } @@ -69,24 +70,24 @@ public void testNextDoesNotSleepForZero() { hostProvider.next(0); --size; } - long start = System.currentTimeMillis(); + long start = Time.currentElapsedTime(); hostProvider.next(0); - long stop = System.currentTimeMillis(); + long stop = Time.currentElapsedTime(); assertTrue(5 > stop - start); } @Test(expected = IllegalArgumentException.class) public void testTwoInvalidHostAddresses() { ArrayList list = new ArrayList(); - list.add(new InetSocketAddress("a", 2181)); - list.add(new InetSocketAddress("b", 2181)); + list.add(new InetSocketAddress("a...", 2181)); + list.add(new InetSocketAddress("b...", 2181)); new StaticHostProvider(list); } @Test public void testOneInvalidHostAddresses() { Collection addr = getServerAddresses((byte) 1); - addr.add(new InetSocketAddress("a", 2181)); + addr.add(new InetSocketAddress("a...", 2181)); StaticHostProvider sp = new StaticHostProvider(addr); InetSocketAddress n1 = sp.next(0); @@ -496,7 +497,7 @@ public void testLiteralIPNoReverseNS() throws Exception { assertTrue(!next.isUnresolved()); assertTrue(!next.toString().startsWith("/")); // Do NOT trigger the reverse name service lookup. - String hostname = next.getHostName(); + String hostname = next.getHostString(); // In this case, the hostname equals literal IP address. hostname.equals(next.getAddress().getHostAddress()); } diff --git a/src/java/test/org/apache/zookeeper/test/StringUtilTest.java b/src/java/test/org/apache/zookeeper/test/StringUtilTest.java index dd463ac4391..29d197fd476 100644 --- a/src/java/test/org/apache/zookeeper/test/StringUtilTest.java +++ b/src/java/test/org/apache/zookeeper/test/StringUtilTest.java @@ -18,14 +18,15 @@ package org.apache.zookeeper.test; +import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.common.StringUtils; import org.junit.Test; import java.util.Arrays; -import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertEquals; -public class StringUtilTest { +public class StringUtilTest extends ZKTestCase { @Test public void testStrings() { diff --git a/src/java/test/org/apache/zookeeper/test/TestHammer.java b/src/java/test/org/apache/zookeeper/test/TestHammer.java index 09a678b28c1..3134209cb7d 100644 --- a/src/java/test/org/apache/zookeeper/test/TestHammer.java +++ b/src/java/test/org/apache/zookeeper/test/TestHammer.java @@ -18,12 +18,12 @@ package org.apache.zookeeper.test; -import java.io.IOException; import org.apache.zookeeper.CreateMode; 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,12 +32,11 @@ 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); - } catch (IOException e1) { - // TODO Auto-generated catch block + zk = ClientBase.createZKClient(args[0], 10000); + } catch (Exception e1) { e1.printStackTrace(); throw new RuntimeException(e1); } @@ -51,7 +50,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/TestUtils.java b/src/java/test/org/apache/zookeeper/test/TestUtils.java new file mode 100644 index 00000000000..95f02eeb2e2 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/TestUtils.java @@ -0,0 +1,60 @@ +/** + * 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 org.junit.Assert; + +/** + * This class contains test utility methods + */ +public class TestUtils { + + /** + * deletes a folder recursively + * + * @param file + * folder to be deleted + * @param failOnError + * if true file deletion success is ensured + */ + public static boolean deleteFileRecursively(File file, + final boolean failOnError) { + if (file != null) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + int size = files.length; + for (int i = 0; i < size; i++) { + File f = files[i]; + boolean deleted = deleteFileRecursively(files[i], failOnError); + if(!deleted && failOnError) + { + Assert.fail("file '" + f.getAbsolutePath()+"' deletion failed"); + } + } + } + return file.delete(); + } + return true; + } + + public static boolean deleteFileRecursively(File file) { + return deleteFileRecursively(file, false); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/TruncateTest.java b/src/java/test/org/apache/zookeeper/test/TruncateTest.java index 7124a3287f8..cae2d9f53ae 100644 --- a/src/java/test/org/apache/zookeeper/test/TruncateTest.java +++ b/src/java/test/org/apache/zookeeper/test/TruncateTest.java @@ -27,8 +27,6 @@ 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; @@ -54,7 +52,6 @@ public class TruncateTest extends ZKTestCase { private static final Logger LOG = LoggerFactory.getLogger(TruncateTest.class); File dataDir1, dataDir2, dataDir3; - final int baseHostPort = PortAssignment.unique(); @Before public void setUp() throws IOException { @@ -70,19 +67,14 @@ public void tearDown() { ClientBase.recursiveDelete(dataDir3); } - volatile boolean connected; - Watcher nullWatcher = new Watcher() { - @Override - 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); + // make sure to snapshot, so that we have something there when + // truncateLog reloads the db + snaplog.save(zkdb.getDataTree(), zkdb.getSessionWithTimeOuts()); for (int i = 1; i <= 100; i++) { append(zkdb, i); @@ -123,9 +115,11 @@ public void testTruncationNullLog() throws Exception { 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++) { - logs[i].delete(); + LOG.debug("Deleting: {}", logs[i].getName()); + Assert.assertTrue("Failed to delete log file: " + logs[i].getName(), logs[i].delete()); } try { zkdb.truncateLog(1); @@ -151,13 +145,13 @@ private void append(ZKDatabase zkdb, int i) throws IOException { } @Test - public void testTruncate() throws IOException, InterruptedException, KeeperException { + public void testTruncate() throws Exception { // Prime the server that is going to come in late with 50 txns - String hostPort = "127.0.0.1:" + baseHostPort; + String hostPort = "127.0.0.1:" + PortAssignment.unique(); int maxCnxns = 100; ServerCnxnFactory factory = ClientBase.createNewServerInstance(null, hostPort, maxCnxns); - ClientBase.startServerInstance(dataDir1, factory, hostPort); + ClientBase.startServerInstance(dataDir1, factory, hostPort, 1); ClientBase.shutdownServerInstance(factory, hostPort); // standalone starts with 0 epoch while quorum starts with 1 @@ -166,9 +160,9 @@ public void testTruncate() throws IOException, InterruptedException, KeeperExcep origfile.renameTo(newfile); factory = ClientBase.createNewServerInstance(null, hostPort, maxCnxns); - ClientBase.startServerInstance(dataDir1, factory, hostPort); + ClientBase.startServerInstance(dataDir1, factory, hostPort, 1); - ZooKeeper zk = new ZooKeeper(hostPort, 15000, nullWatcher); + ZooKeeper zk = ClientBase.createZKClient(hostPort, 15000); for(int i = 0; i < 50; i++) { zk.create("/" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } @@ -189,37 +183,37 @@ public void testTruncate() throws IOException, InterruptedException, KeeperExcep int tickTime = 2000; int initLimit = 3; int syncLimit = 3; - int port1 = baseHostPort+1; - int port2 = baseHostPort+2; - int port3 = baseHostPort+3; + + int port1 = PortAssignment.unique(); + int port2 = PortAssignment.unique(); + int port3 = PortAssignment.unique(); // 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), - new InetSocketAddress("127.0.0.1", port1 + 2000), - new InetSocketAddress("127.0.0.1", port1 + 3000))); - peers.put(Long.valueOf(2), new QuorumServer(2, new InetSocketAddress("127.0.0.1", port2 + 1000), - new InetSocketAddress("127.0.0.1", port2 + 2000), - new InetSocketAddress("127.0.0.1", port2 + 3000))); - peers.put(Long.valueOf(3), new QuorumServer(3, new InetSocketAddress("127.0.0.1", port3 + 1000), - new InetSocketAddress("127.0.0.1", port3 + 2000), - new InetSocketAddress("127.0.0.1", port3 + 3000))); + peers.put(Long.valueOf(1), new QuorumServer(1, + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", port1))); + peers.put(Long.valueOf(2), new QuorumServer(2, + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", port2))); + peers.put(Long.valueOf(3), new QuorumServer(3, + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", PortAssignment.unique()), + new InetSocketAddress("127.0.0.1", port3))); QuorumPeer s2 = new QuorumPeer(peers, dataDir2, dataDir2, port2, 0, 2, tickTime, initLimit, syncLimit); s2.start(); QuorumPeer s3 = new QuorumPeer(peers, dataDir3, dataDir3, port3, 0, 3, tickTime, initLimit, syncLimit); s3.start(); - connected = false; - zk = new ZooKeeper("127.0.0.1:" + port2, 15000, nullWatcher); - while(!connected) { - Thread.sleep(1000); - } + zk = ClientBase.createZKClient("127.0.0.1:" + port2, 15000); + for(int i = 0; i < 10; i++) { zk.create("/" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } zk.close(); - - final ZooKeeper zk2 = new ZooKeeper("127.0.0.1:" + port2, 15000, nullWatcher); + final ZooKeeper zk2 = ClientBase.createZKClient("127.0.0.1:" + port2, 15000); zk2.getData("/9", false, new Stat()); try { zk2.getData("/10", false, new Stat()); @@ -229,12 +223,7 @@ public void testTruncate() throws IOException, InterruptedException, KeeperExcep } QuorumPeer s1 = new QuorumPeer(peers, dataDir1, dataDir1, port1, 0, 1, tickTime, initLimit, syncLimit); s1.start(); - - connected = false; - ZooKeeper zk1 = new ZooKeeper("127.0.0.1:" + port1, 15000, nullWatcher); - while(!connected) { - Thread.sleep(1000); - } + ZooKeeper zk1 = ClientBase.createZKClient("127.0.0.1:" + port1, 15000); zk1.getData("/9", false, new Stat()); try { // /10 wont work because the session expiration diff --git a/src/java/test/org/apache/zookeeper/test/WatchEventWhenAutoReset.java b/src/java/test/org/apache/zookeeper/test/WatchEventWhenAutoResetTest.java similarity index 95% rename from src/java/test/org/apache/zookeeper/test/WatchEventWhenAutoReset.java rename to src/java/test/org/apache/zookeeper/test/WatchEventWhenAutoResetTest.java index eed02c5845b..a7604638173 100644 --- a/src/java/test/org/apache/zookeeper/test/WatchEventWhenAutoReset.java +++ b/src/java/test/org/apache/zookeeper/test/WatchEventWhenAutoResetTest.java @@ -29,16 +29,17 @@ import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.apache.zookeeper.ZKTestCase; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import junit.framework.TestCase; -public class WatchEventWhenAutoReset extends TestCase { +public class WatchEventWhenAutoResetTest extends ZKTestCase { protected static final Logger LOG = LoggerFactory - .getLogger(WatchEventWhenAutoReset.class); + .getLogger(WatchEventWhenAutoResetTest.class); // waiting time for expected condition private static final int TIMEOUT = 30000; @@ -79,7 +80,7 @@ private ZooKeeper createClient(QuorumUtil qu, int id, EventsWatcher watcher) } catch (InterruptedException e) { // ignoring the interrupt } catch (TimeoutException e) { - fail("can not connect to " + hostPort); + Assert.fail("can not connect to " + hostPort); } return zk; } @@ -88,6 +89,11 @@ private ZooKeeper createClient(QuorumUtil qu, int id) throws IOException { return createClient(qu, id, new EventsWatcher()); } + @Before + public void setUp() { + System.setProperty("zookeeper.admin.enableServer", "false"); + } + @Test public void testNodeDataChanged() throws Exception { QuorumUtil qu = new QuorumUtil(1); diff --git a/src/java/test/org/apache/zookeeper/test/WatcherTest.java b/src/java/test/org/apache/zookeeper/test/WatcherTest.java index 899099dc409..0419125ecab 100644 --- a/src/java/test/org/apache/zookeeper/test/WatcherTest.java +++ b/src/java/test/org/apache/zookeeper/test/WatcherTest.java @@ -25,7 +25,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.zookeeper.ClientCnxn; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.TestableZooKeeper; @@ -36,6 +35,7 @@ import org.apache.zookeeper.Watcher.Event; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.client.ZKClientConfig; import org.apache.zookeeper.data.Stat; import org.junit.Assert; import org.junit.Before; @@ -44,6 +44,8 @@ public class WatcherTest extends ClientBase { protected static final Logger LOG = LoggerFactory.getLogger(WatcherTest.class); + private long timeOfLastWatcherInvocation; + private final static class MyStatCallback implements StatCallback { int rc; public void processResult(int rc, String path, Object ctx, Stat stat) { @@ -59,6 +61,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) { @@ -73,7 +76,7 @@ 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); + System.setProperty(ZKClientConfig.DISABLE_AUTO_WATCH_RESET, "false"); } /** @@ -172,7 +175,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. @@ -206,7 +208,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()); } @@ -221,6 +223,18 @@ public void testWatchAutoResetWithPending() throws Exception { 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; @Test @@ -243,13 +257,16 @@ public void testWatcherAutoResetWithLocal() throws Exception { @Test public void testWatcherAutoResetDisabledWithGlobal() throws Exception { - ClientCnxn.setDisableAutoResetWatch(true); + /** + * When ZooKeeper is created this property will get used. + */ + System.setProperty(ZKClientConfig.DISABLE_AUTO_WATCH_RESET, "true"); testWatcherAutoResetWithGlobal(); } @Test public void testWatcherAutoResetDisabledWithLocal() throws Exception { - ClientCnxn.setDisableAutoResetWatch(true); + System.setProperty(ZKClientConfig.DISABLE_AUTO_WATCH_RESET, "true"); testWatcherAutoResetWithLocal(); } @@ -278,7 +295,8 @@ private void testWatcherAutoReset(ZooKeeper zk, MyWatcher globalWatcher, localWatcher.waitForDisconnected(500); startServer(); globalWatcher.waitForConnected(3000); - if (!isGlobal && !ClientCnxn.getDisableAutoResetWatch()) { + boolean disableAutoWatchReset = zk.getClientConfig().getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET); + if (!isGlobal && !disableAutoWatchReset) { localWatcher.waitForConnected(500); } @@ -288,7 +306,7 @@ private void testWatcherAutoReset(ZooKeeper zk, MyWatcher globalWatcher, CreateMode.PERSISTENT); WatchedEvent e; - if (!ClientCnxn.getDisableAutoResetWatch()) { + if (!disableAutoWatchReset) { e = localWatcher.events.poll(TIMEOUT, TimeUnit.MILLISECONDS); Assert.assertEquals(e.getPath(), EventType.NodeDataChanged, e.getType()); Assert.assertEquals("/watchtest/child", e.getPath()); @@ -297,7 +315,7 @@ private void testWatcherAutoReset(ZooKeeper zk, MyWatcher globalWatcher, // why waste the time on poll } - if (!ClientCnxn.getDisableAutoResetWatch()) { + if (!disableAutoWatchReset) { e = localWatcher.events.poll(TIMEOUT, TimeUnit.MILLISECONDS); // The create will trigger the get children and the exist // watches @@ -308,7 +326,7 @@ private void testWatcherAutoReset(ZooKeeper zk, MyWatcher globalWatcher, // why waste the time on poll } - if (!ClientCnxn.getDisableAutoResetWatch()) { + if (!disableAutoWatchReset) { e = localWatcher.events.poll(TIMEOUT, TimeUnit.MILLISECONDS); Assert.assertEquals(EventType.NodeChildrenChanged, e.getType()); Assert.assertEquals("/watchtest", e.getPath()); @@ -323,11 +341,11 @@ private void testWatcherAutoReset(ZooKeeper zk, MyWatcher globalWatcher, try { try { localWatcher.waitForDisconnected(500); - if (!isGlobal && !ClientCnxn.getDisableAutoResetWatch()) { + if (!isGlobal && !disableAutoWatchReset) { Assert.fail("Got an event when I shouldn't have"); } } catch(TimeoutException toe) { - if (ClientCnxn.getDisableAutoResetWatch()) { + if (disableAutoWatchReset) { Assert.fail("Didn't get an event when I should have"); } // Else what we are expecting since there are no outstanding watches @@ -368,14 +386,14 @@ private void testWatcherAutoReset(ZooKeeper zk, MyWatcher globalWatcher, localWatcher.waitForDisconnected(500); startServer(); globalWatcher.waitForConnected(TIMEOUT); - if (!isGlobal && !ClientCnxn.getDisableAutoResetWatch()) { + if (!isGlobal && !disableAutoWatchReset) { localWatcher.waitForConnected(500); } zk.delete("/watchtest/child", -1); zk.delete("/watchtest", -1); - if (!ClientCnxn.getDisableAutoResetWatch()) { + if (!disableAutoWatchReset) { e = localWatcher.events.poll(TIMEOUT, TimeUnit.MILLISECONDS); Assert.assertEquals(EventType.NodeDeleted, e.getType()); Assert.assertEquals("/watchtest/child", e.getPath()); @@ -387,7 +405,6 @@ private void testWatcherAutoReset(ZooKeeper zk, MyWatcher globalWatcher, // Make sure nothing is straggling! Thread.sleep(1000); Assert.assertTrue(localWatcher.events.isEmpty()); - } } diff --git a/src/java/test/org/apache/zookeeper/test/X509AuthTest.java b/src/java/test/org/apache/zookeeper/test/X509AuthTest.java new file mode 100644 index 00000000000..4982cf3391b --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/X509AuthTest.java @@ -0,0 +1,290 @@ +/** + * 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.math.BigInteger; +import java.net.Socket; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Date; +import java.util.Set; + +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; + + +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZKTestCase; +import org.apache.zookeeper.server.MockServerCnxn; +import org.apache.zookeeper.server.auth.X509AuthenticationProvider; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class X509AuthTest extends ZKTestCase { + private static TestCertificate clientCert; + private static TestCertificate superCert; + private static TestCertificate unknownCert; + + @Before + public void setUp() { + System.setProperty("zookeeper.X509AuthenticationProvider.superUser", + "CN=SUPER"); + System.setProperty("zookeeper.ssl.keyManager", + "org.apache.zookeeper.test.X509AuthTest.TestKeyManager"); + System.setProperty("zookeeper.ssl.trustManager", + "org.apache.zookeeper.test.X509AuthTest.TestTrustManager"); + + clientCert = new TestCertificate("CLIENT"); + superCert = new TestCertificate("SUPER"); + unknownCert = new TestCertificate("UNKNOWN"); + } + + @Test + public void testTrustedAuth() { + X509AuthenticationProvider provider = createProvider(clientCert); + MockServerCnxn cnxn = new MockServerCnxn(); + cnxn.clientChain = new X509Certificate[] { clientCert }; + Assert.assertEquals(KeeperException.Code.OK, provider.handleAuthentication(cnxn, null)); + } + + @Test + public void testSuperAuth() { + X509AuthenticationProvider provider = createProvider(superCert); + MockServerCnxn cnxn = new MockServerCnxn(); + cnxn.clientChain = new X509Certificate[] { superCert }; + Assert.assertEquals(KeeperException.Code.OK, provider.handleAuthentication(cnxn, null)); + Assert.assertEquals("super", cnxn.getAuthInfo().get(0).getScheme()); + } + + @Test + public void testUntrustedAuth() { + X509AuthenticationProvider provider = createProvider(clientCert); + MockServerCnxn cnxn = new MockServerCnxn(); + cnxn.clientChain = new X509Certificate[] { unknownCert }; + Assert.assertEquals(KeeperException.Code.AUTHFAILED, provider.handleAuthentication(cnxn, null)); + } + + private static class TestPublicKey implements PublicKey { + private static final long serialVersionUID = 1L; + @Override + public String getAlgorithm() { + return null; + } + @Override + public String getFormat() { + return null; + } + @Override + public byte[] getEncoded() { + return null; + } + } + private static class TestCertificate extends X509Certificate { + private byte[] encoded; + private X500Principal principal; + private PublicKey publicKey; + public TestCertificate(String name) { + encoded = name.getBytes(); + principal = new X500Principal("CN=" + name); + publicKey = new TestPublicKey(); + } + @Override + public boolean hasUnsupportedCriticalExtension() { + return false; + } + @Override + public Set getCriticalExtensionOIDs() { + return null; + } + @Override + public Set getNonCriticalExtensionOIDs() { + return null; + } + @Override + public byte[] getExtensionValue(String oid) { + return null; + } + @Override + public void checkValidity() throws CertificateExpiredException, + CertificateNotYetValidException { + } + @Override + public void checkValidity(Date date) + throws CertificateExpiredException, + CertificateNotYetValidException { + } + @Override + public int getVersion() { + return 0; + } + @Override + public BigInteger getSerialNumber() { + return null; + } + @Override + public Principal getIssuerDN() { + return null; + } + @Override + public Principal getSubjectDN() { + return null; + } + @Override + public Date getNotBefore() { + return null; + } + @Override + public Date getNotAfter() { + return null; + } + @Override + public byte[] getTBSCertificate() throws CertificateEncodingException { + return null; + } + @Override + public byte[] getSignature() { + return null; + } + @Override + public String getSigAlgName() { + return null; + } + @Override + public String getSigAlgOID() { + return null; + } + @Override + public byte[] getSigAlgParams() { + return null; + } + @Override + public boolean[] getIssuerUniqueID() { + return null; + } + @Override + public boolean[] getSubjectUniqueID() { + return null; + } + @Override + public boolean[] getKeyUsage() { + return null; + } + @Override + public int getBasicConstraints() { + return 0; + } + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return encoded; + } + @Override + public void verify(PublicKey key) throws CertificateException, + NoSuchAlgorithmException, InvalidKeyException, + NoSuchProviderException, SignatureException { + } + @Override + public void verify(PublicKey key, String sigProvider) + throws CertificateException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchProviderException, + SignatureException { + } + @Override + public String toString() { + return null; + } + @Override + public PublicKey getPublicKey() { + return publicKey; + } + @Override + public X500Principal getSubjectX500Principal() { + return principal; + } + } + public static class TestKeyManager implements X509KeyManager { + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket) { + return null; + } + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, + Socket socket) { + return null; + } + @Override + public X509Certificate[] getCertificateChain(String alias) { + return null; + } + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return null; + } + @Override + public PrivateKey getPrivateKey(String alias) { + return null; + } + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return null; + } + } + public static class TestTrustManager implements X509TrustManager { + X509Certificate cert; + public TestTrustManager(X509Certificate testCert) { + cert = testCert; + } + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (!Arrays.equals(cert.getEncoded(), chain[0].getEncoded())) { + throw new CertificateException("Client cert not trusted"); + } + } + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (!Arrays.equals(cert.getEncoded(), chain[0].getEncoded())) { + throw new CertificateException("Server cert not trusted"); + } + } + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + } + + protected X509AuthenticationProvider createProvider(X509Certificate trustedCert) { + return new X509AuthenticationProvider( + new TestTrustManager(trustedCert), + new TestKeyManager()); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/ZkDatabaseCorruptionTest.java b/src/java/test/org/apache/zookeeper/test/ZkDatabaseCorruptionTest.java index c213b2a26f4..c757fc3ea31 100644 --- a/src/java/test/org/apache/zookeeper/test/ZkDatabaseCorruptionTest.java +++ b/src/java/test/org/apache/zookeeper/test/ZkDatabaseCorruptionTest.java @@ -25,12 +25,11 @@ import org.apache.zookeeper.AsyncCallback; import org.apache.zookeeper.CreateMode; -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.server.SyncRequestProcessor; +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; @@ -41,6 +40,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.junit.Assert.assertEquals; + public class ZkDatabaseCorruptionTest extends ZKTestCase { protected static final Logger LOG = LoggerFactory.getLogger(ZkDatabaseCorruptionTest.class); public static final long CONNECTION_TIMEOUT = ClientTest.CONNECTION_TIMEOUT; @@ -84,9 +85,7 @@ public void processResult(int rc, String path, Object ctx, public void testCorruption() throws Exception { ClientBase.waitForServerUp(qb.hostPort, 10000); ClientBase.waitForServerUp(qb.hostPort, 10000); - ZooKeeper zk = new ZooKeeper(qb.hostPort, 10000, new Watcher() { - public void process(WatchedEvent event) { - }}); + ZooKeeper zk = ClientBase.createZKClient(qb.hostPort, 10000); SyncRequestProcessor.setSnapCount(100); for (int i = 0; i < 2000; i++) { zk.create("/0-" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, @@ -157,5 +156,15 @@ public void process(WatchedEvent event) { if (leaderSid != 5)QuorumBase.shutdown(qb.s5); } + @Test + public void testAbsentRecentSnapshot() throws IOException { + ZKDatabase zkDatabase = new ZKDatabase(new FileTxnSnapLog(new File("foo"), new File("bar")){ + @Override + public File findMostRecentSnapshot() throws IOException { + return null; + } + }); + assertEquals(0, zkDatabase.calculateTxnLogSizeLimit()); + } } diff --git a/src/java/test/org/apache/zookeeper/test/ZooKeeperTestClient.java b/src/java/test/org/apache/zookeeper/test/ZooKeeperTestClient.java index 5386a7a9106..a0cba912cfa 100644 --- a/src/java/test/org/apache/zookeeper/test/ZooKeeperTestClient.java +++ b/src/java/test/org/apache/zookeeper/test/ZooKeeperTestClient.java @@ -33,6 +33,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.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ZooKeeperServer; @@ -43,7 +44,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 deleted file mode 100644 index d0f7216deed..00000000000 --- a/src/packages/deb/init.d/zookeeper +++ /dev/null @@ -1,140 +0,0 @@ -#! /usr/bin/env bash - -# 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. - -### BEGIN INIT INFO -# Provides: zookeeper -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: -# Short-Description: Apache ZooKeeper server -### END INIT INFO - -set -e - -# /etc/init.d/zookeeper: start and stop the Apache ZooKeeper daemon - -umask 022 - -. /usr/libexec/zkEnv.sh - -. /lib/lsb/init-functions - -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() { - ([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ] -} - -check_for_no_start() { - # forget it if we're trying to start, and /etc/zookeeper/zookeeper_not_to_be_run exists - if [ -e /etc/zookeeper/zookeeper_not_to_be_run ]; then - if [ "$1" = log_end_msg ]; then - log_end_msg 0 - fi - if ! run_by_init; then - log_action_msg "Apache ZooKeeper server not in use (/etc/zookeeper/zookeeper_not_to_be_run)" - fi - exit 0 - fi -} - -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 - else - log_end_msg 1 - fi - ;; - stop) - log_daemon_msg "Stopping Apache ZooKeeper server" "zookeeper" - if start-stop-daemon --stop --quiet --oknodo --pidfile ${ZOOPIDFILE}; then - log_end_msg 0 - else - log_end_msg 1 - fi - ;; - - restart) - check_privsep_dir - 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}/sbin/zkServer.sh start; then - log_end_msg 0 - else - log_end_msg 1 - fi - ;; - - try-restart) - check_privsep_dir - log_daemon_msg "Restarting Apache ZooKeeper server" "zookeeper" - set +e - start-stop-daemon --stop --quiet --retry 30 --pidfile ${ZOOPIDFILE} - RET="$?" - set -e - case $RET 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}/sbin/zkServer.sh start; then - log_end_msg 0 - else - log_end_msg 1 - fi - ;; - 1) - # daemon not running - log_progress_msg "(not running)" - log_end_msg 0 - ;; - *) - # failed to stop - log_progress_msg "(failed to stop)" - log_end_msg 1 - ;; - esac - ;; - - status) - status_of_proc -p ${ZOOPIDFILE} ${JAVA_HOME}/bin/java zookeeper && exit 0 || exit $? - ;; - - *) - log_action_msg "Usage: /etc/init.d/zookeeper {start|stop|restart|try-restart|status}" - exit 1 -esac - -exit 0 diff --git a/src/packages/deb/zookeeper.control/conffile b/src/packages/deb/zookeeper.control/conffile deleted file mode 100644 index 6fdd65be113..00000000000 --- a/src/packages/deb/zookeeper.control/conffile +++ /dev/null @@ -1,15 +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. -/etc/zookeeper diff --git a/src/packages/deb/zookeeper.control/control b/src/packages/deb/zookeeper.control/control deleted file mode 100644 index c746906024a..00000000000 --- a/src/packages/deb/zookeeper.control/control +++ /dev/null @@ -1,23 +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: zookeeper -Version: @version@ -Section: misc -Priority: optional -Architecture: all -Depends: sun-java6-jre -Maintainer: Apache Software Foundation -Description: ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. -Distribution: development diff --git a/src/packages/deb/zookeeper.control/postinst b/src/packages/deb/zookeeper.control/postinst deleted file mode 100644 index a6cbd22d143..00000000000 --- a/src/packages/deb/zookeeper.control/postinst +++ /dev/null @@ -1,23 +0,0 @@ -#!/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. - -bash /usr/sbin/update-zookeeper-env.sh \ - --prefix=/usr \ - --conf-dir=/etc/zookeeper \ - --log-dir=/var/log/zookeeper \ - --pid-dir=/var/run/zookeeper \ - --var-dir=/var/lib/zookeeper diff --git a/src/packages/deb/zookeeper.control/postrm b/src/packages/deb/zookeeper.control/postrm deleted file mode 100644 index 27842b7564b..00000000000 --- a/src/packages/deb/zookeeper.control/postrm +++ /dev/null @@ -1,19 +0,0 @@ -#!/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. - -/usr/sbin/userdel zookeeper 2> /dev/null >/dev/null -exit 0 diff --git a/src/packages/deb/zookeeper.control/prerm b/src/packages/deb/zookeeper.control/prerm deleted file mode 100644 index c986688a7b3..00000000000 --- a/src/packages/deb/zookeeper.control/prerm +++ /dev/null @@ -1,24 +0,0 @@ -#!/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. - -/etc/init.d/zookeeper stop 2>/dev/null >/dev/null -bash /usr/sbin/update-zookeeper-env.sh \ - --prefix=/usr \ - --conf-dir=/etc/zookeeper \ - --log-dir=/var/log/zookeeper \ - --pid-dir=/var/run/zookeeper \ - --uninstal diff --git a/src/packages/rpm/init.d/zookeeper b/src/packages/rpm/init.d/zookeeper deleted file mode 100644 index 624e1a58845..00000000000 --- a/src/packages/rpm/init.d/zookeeper +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bash - -# 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. - -# -# ZooKeeper -# -# chkconfig: 2345 89 9 -# description: zookeeper - -source /etc/rc.d/init.d/functions -source /usr/libexec/zkEnv.sh - -RETVAL=0 -PIDFILE="${ZOOPIDFILE}" -desc="ZooKeeper daemon" - -start() { - echo -n $"Starting $desc (zookeeper): " - daemon --user zookeeper zkServer.sh start - RETVAL=$? - echo - [ $RETVAL -eq 0 ] && touch /var/lock/subsys/zookeeper - return $RETVAL -} - -stop() { - echo -n $"Stopping $desc (zookeeper): " - daemon --user zookeeper zkServer.sh stop - RETVAL=$? - sleep 5 - echo - [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/zookeeper $PIDFILE -} - -restart() { - stop - start -} - -checkstatus(){ - status -p $PIDFILE ${JAVA_HOME}/bin/java - RETVAL=$? -} - -condrestart(){ - [ -e /var/lock/subsys/zookeeper ] && restart || : -} - -case "$1" in - start) - start - ;; - stop) - stop - ;; - status) - checkstatus - ;; - restart) - restart - ;; - condrestart) - condrestart - ;; - *) - echo $"Usage: $0 {start|stop|status|restart|condrestart}" - exit 1 -esac - -exit $RETVAL diff --git a/src/packages/rpm/spec/zookeeper.spec b/src/packages/rpm/spec/zookeeper.spec deleted file mode 100644 index 8e195c7165e..00000000000 --- a/src/packages/rpm/spec/zookeeper.spec +++ /dev/null @@ -1,166 +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. - -# -# RPM Spec file for ZooKeeper version @version@ -# - -%define name zookeeper -%define version @version@ -%define release @package.release@ - -# Installation Locations -%define _prefix @package.prefix@ -%define _bin_dir %{_prefix}/bin -%define _conf_dir @package.conf.dir@ -%define _include_dir %{_prefix}/include -%define _lib_dir %{_prefix}/lib -%define _lib64_dir %{_prefix}/lib64 -%define _libexec_dir %{_prefix}/libexec -%define _log_dir @package.log.dir@ -%define _man_dir %{_prefix}/man -%define _pid_dir @package.pid.dir@ -%define _sbin_dir %{_prefix}/sbin -%define _share_dir %{_prefix}/share/zookeeper -%define _src_dir %{_prefix}/src -%define _var_dir @package.var.dir@ - -# Build time settings -%define _build_dir @package.build.dir@ -%define _final_name @final.name@ -%define _c_lib @c.lib@ -%define debug_package %{nil} - -# Disable brp-java-repack-jars for aspect J -%define __os_install_post \ - /usr/lib/rpm/redhat/brp-compress \ - %{!?__debug_package:/usr/lib/rpm/redhat/brp-strip %{__strip}} \ - /usr/lib/rpm/redhat/brp-strip-static-archive %{__strip} \ - /usr/lib/rpm/redhat/brp-strip-comment-note %{__strip} %{__objdump} \ - /usr/lib/rpm/brp-python-bytecompile %{nil} - -# RPM searches perl files for dependancies and this breaks for non packaged perl lib -# like thrift so disable this -%define _use_internal_dependency_generator 0 - -Summary: ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. -License: Apache License, Version 2.0 -URL: http://zookeeper.apache.org/ -Vendor: Apache Software Foundation -Group: Development/Libraries -Name: %{name} -Version: %{version} -Release: %{release} -Source0: %{_final_name}.tar.gz -Source1: %{_final_name}-lib.tar.gz -Prefix: %{_prefix} -Prefix: %{_conf_dir} -Prefix: %{_log_dir} -Prefix: %{_pid_dir} -Prefix: %{_var_dir} -Requires: sh-utils, textutils, /usr/sbin/useradd, /usr/sbin/usermod, /sbin/chkconfig, /sbin/service, jdk >= 1.6 -AutoReqProv: no -Provides: zookeeper - -%description -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. - -%package lib -Summary: ZooKeeper C binding library -Group: System/Libraries -#Requires: %{name} == %{version} -Provides: zookeeper-lib - -%description lib -ZooKeeper C client library for communicating with ZooKeeper Server. - -%prep -%setup -D -b 1 -n %{_final_name} -%setup -D -a 0 -n %{_final_name} - -%build -mkdir -p ${RPM_BUILD_DIR}%{_prefix} -mkdir -p ${RPM_BUILD_DIR}%{_bin_dir} -mkdir -p ${RPM_BUILD_DIR}%{_include_dir} -mkdir -p ${RPM_BUILD_DIR}%{_lib_dir} -%ifarch amd64 x86_64 -mkdir -p ${RPM_BUILD_DIR}%{_lib64_dir} -%endif -mkdir -p ${RPM_BUILD_DIR}%{_libexec_dir} -mkdir -p ${RPM_BUILD_DIR}%{_log_dir} -mkdir -p ${RPM_BUILD_DIR}%{_conf_dir} -mkdir -p ${RPM_BUILD_DIR}%{_man_dir} -mkdir -p ${RPM_BUILD_DIR}%{_pid_dir} -mkdir -p ${RPM_BUILD_DIR}%{_sbin_dir} -mkdir -p ${RPM_BUILD_DIR}%{_share_dir} -mkdir -p ${RPM_BUILD_DIR}%{_var_dir} -mkdir -p ${RPM_BUILD_DIR}/etc/init.d - -cp ${RPM_BUILD_DIR}/%{_final_name}/src/packages/rpm/init.d/zookeeper ${RPM_BUILD_DIR}/etc/init.d/zookeeper -cp ${RPM_BUILD_DIR}/%{_final_name}/src/packages/update-zookeeper-env.sh ${RPM_BUILD_DIR}/%{_final_name}/sbin/update-zookeeper-env.sh -chmod 0755 ${RPM_BUILD_DIR}/%{_final_name}/sbin/* -chmod 0755 ${RPM_BUILD_DIR}/etc/init.d/zookeeper - -######################### -#### INSTALL SECTION #### -######################### -%install -pushd ${RPM_BUILD_DIR} -mv ${RPM_BUILD_DIR}/%{_final_name}/bin/* ${RPM_BUILD_DIR}%{_bin_dir} -mv ${RPM_BUILD_DIR}/%{_final_name}/libexec/* ${RPM_BUILD_DIR}%{_libexec_dir} -mv ${RPM_BUILD_DIR}/%{_final_name}/share/zookeeper/* ${RPM_BUILD_DIR}%{_share_dir} -mv ${RPM_BUILD_DIR}/%{_final_name}/conf/* ${RPM_BUILD_DIR}%{_conf_dir} -mv ${RPM_BUILD_DIR}/%{_final_name}/sbin/* ${RPM_BUILD_DIR}%{_sbin_dir} -cp -f ${RPM_BUILD_DIR}%{_conf_dir}/zoo_sample.cfg ${RPM_BUILD_DIR}%{_conf_dir}/zoo.cfg -popd ${RPM_BUILD_DIR} -rm -rf ${RPM_BUILD_DIR}/%{_final_name} - -%pre -getent group hadoop 2>/dev/null >/dev/null || /usr/sbin/groupadd -r hadoop - -/usr/sbin/useradd --comment "ZooKeeper" --shell /bin/bash -M -r --groups hadoop --home %{_share_dir} zookeeper 2> /dev/null || : - -%post -bash ${RPM_INSTALL_PREFIX0}/sbin/update-zookeeper-env.sh \ - --prefix=${RPM_INSTALL_PREFIX0} \ - --conf-dir=${RPM_INSTALL_PREFIX1} \ - --log-dir=${RPM_INSTALL_PREFIX2} \ - --pid-dir=${RPM_INSTALL_PREFIX3} \ - --var-dir=${RPM_INSTALL_PREFIX4} - -%preun -bash ${RPM_INSTALL_PREFIX0}/sbin/update-zookeeper-env.sh \ - --prefix=${RPM_INSTALL_PREFIX0} \ - --conf-dir=${RPM_INSTALL_PREFIX1} \ - --log-dir=${RPM_INSTALL_PREFIX2} \ - --pid-dir=${RPM_INSTALL_PREFIX3} \ - --var-dir=${RPM_INSTALL_PREFIX4} \ - --uninstall - -%files -%defattr(-,root,root) -%attr(0755,root,hadoop) %{_log_dir} -%attr(0775,root,hadoop) %{_pid_dir} -%attr(0775,root,hadoop) /etc/init.d/zookeeper -%config(noreplace) %{_conf_dir}/* -%{_prefix} - -%post lib -/sbin/ldconfig - -%files lib -%defattr(-,root,root) -%{_prefix}/lib/* -%{_prefix}/bin diff --git a/src/packages/templates/conf/zookeeper-env.sh b/src/packages/templates/conf/zookeeper-env.sh deleted file mode 100644 index a9efb63fb0a..00000000000 --- a/src/packages/templates/conf/zookeeper-env.sh +++ /dev/null @@ -1,16 +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. -export JAVA_HOME=${JAVA_HOME} -export ZOO_LOG_DIR=${LOG_DIR} diff --git a/src/packages/update-zookeeper-env.sh b/src/packages/update-zookeeper-env.sh deleted file mode 100644 index 84284874186..00000000000 --- a/src/packages/update-zookeeper-env.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env bash - -# 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 script configures zookeeper-env.sh and zoo.cfg. - -usage() { - echo " -usage: $0 - Required parameters: - --prefix=PREFIX path to install into - - Optional parameters: - --arch=i386 OS Architecture - --conf-dir=/etc/zookeeper Configuration directory - --log-dir=/var/log/zookeeper Log directory - --pid-dir=/var/run PID file location - " - exit 1 -} - -template_generator() { - REGEX='(\$\{[a-zA-Z_][a-zA-Z_0-9]*\})' - cat $1 | - while read line ; do - while [[ "$line" =~ $REGEX ]] ; do - LHS=${BASH_REMATCH[1]} - RHS="$(eval echo "\"$LHS\"")" - line=${line//$LHS/$RHS} - done - echo $line >> $2 - done -} - -OPTS=$(getopt \ - -n $0 \ - -o '' \ - -l 'arch:' \ - -l 'prefix:' \ - -l 'conf-dir:' \ - -l 'log-dir:' \ - -l 'pid-dir:' \ - -l 'var-dir:' \ - -l 'uninstall' \ - -- "$@") - -if [ $? != 0 ] ; then - usage -fi - -eval set -- "${OPTS}" -while true ; do - case "$1" in - --arch) - ARCH=$2 ; shift 2 - ;; - --prefix) - PREFIX=$2 ; shift 2 - ;; - --log-dir) - LOG_DIR=$2 ; shift 2 - ;; - --lib-dir) - LIB_DIR=$2 ; shift 2 - ;; - --conf-dir) - CONF_DIR=$2 ; shift 2 - ;; - --pid-dir) - PID_DIR=$2 ; shift 2 - ;; - --uninstall) - UNINSTALL=1; shift - ;; - --var-dir) - VAR_DIR=$2 ; shift 2 - ;; - --) - shift ; break - ;; - *) - echo "Unknown option: $1" - usage - exit 1 - ;; - esac -done - -for var in PREFIX; do - if [ -z "$(eval "echo \$$var")" ]; then - echo Missing param: $var - usage - fi -done - -ARCH=${ARCH:-i386} -CONF_DIR=${CONF_DIR:-$PREFIX/etc/zookeeper} -LIB_DIR=${LIB_DIR:-$PREFIX/lib} -LOG_DIR=${LOG_DIR:-$PREFIX/var/log} -PID_DIR=${PID_DIR:-$PREFIX/var/run} -VAR_DIR=${VAR_DIR:-$PREFIX/var/lib} -UNINSTALL=${UNINSTALL:-0} - -if [ "${ARCH}" != "i386" ]; then - LIB_DIR=${LIB_DIR}64 -fi - -if [ "${UNINSTALL}" -eq "1" ]; then - # Remove symlinks - if [ -e ${PREFIX}/etc/zookeeper ]; then - rm -f ${PREFIX}/etc/zookeeper - fi -else - # Create symlinks - if [ ${CONF_DIR} != ${PREFIX}/etc/zookeeper ]; then - mkdir -p ${PREFIX}/etc - ln -sf ${CONF_DIR} ${PREFIX}/etc/zookeeper - fi - - mkdir -p ${LOG_DIR} - chown zookeeper:hadoop ${LOG_DIR} - chmod 755 ${LOG_DIR} - - if [ ! -d ${PID_DIR} ]; then - mkdir -p ${PID_DIR} - chown zookeeper:hadoop ${PID_DIR} - chmod 755 ${PID_DIR} - fi - - if [ ! -d ${VAR_DIR} ]; then - mkdir -p ${VAR_DIR}/data - chown -R zookeeper:hadoop ${VAR_DIR} - chmod -R 755 ${VAR_DIR} - fi - - TFILE="/tmp/$(basename $0).$$.tmp" - if [ -z "${JAVA_HOME}" ]; then - if [ -e /etc/debian_version ]; then - JAVA_HOME=/usr/lib/jvm/java-6-sun/jre - else - JAVA_HOME=/usr/java/default - fi - fi - template_generator ${PREFIX}/share/zookeeper/templates/conf/zookeeper-env.sh $TFILE - cp ${TFILE} ${CONF_DIR}/zookeeper-env.sh - rm -f ${TFILE} - template_generator ${PREFIX}/share/zookeeper/templates/conf/zoo.cfg $TFILE - cp ${TFILE} ${CONF_DIR}/zoo.cfg - rm -f ${TFILE} -fi 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 dff659df9d2..2aad55a446c 100644 --- a/src/recipes/build-recipes.xml +++ b/src/recipes/build-recipes.xml @@ -36,7 +36,7 @@ - + @@ -111,7 +111,7 @@ @@ -126,7 +126,7 @@ - + @@ -139,22 +139,6 @@ - - - - - - - - - - - - - - - - diff --git a/src/recipes/build.xml b/src/recipes/build.xml index cd73104785d..559d5a53858 100644 --- a/src/recipes/build.xml +++ b/src/recipes/build.xml @@ -58,22 +58,4 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/recipes/election/build.xml b/src/recipes/election/build.xml index 3e1bcb8545f..8e1d00a11c3 100644 --- a/src/recipes/election/build.xml +++ b/src/recipes/election/build.xml @@ -57,7 +57,7 @@ + debug="on" encoding="${build.encoding}"> diff --git a/src/recipes/election/test/org/apache/zookeeper/recipes/leader/LeaderElectionSupportTest.java b/src/recipes/election/test/org/apache/zookeeper/recipes/leader/LeaderElectionSupportTest.java index 9781f277aad..7e19dc7c4fa 100644 --- a/src/recipes/election/test/org/apache/zookeeper/recipes/leader/LeaderElectionSupportTest.java +++ b/src/recipes/election/test/org/apache/zookeeper/recipes/leader/LeaderElectionSupportTest.java @@ -23,7 +23,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import junit.framework.Assert; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; @@ -32,6 +31,7 @@ import org.apache.zookeeper.recipes.leader.LeaderElectionSupport.EventType; import org.apache.zookeeper.test.ClientBase; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; diff --git a/src/recipes/lock/build.xml b/src/recipes/lock/build.xml index 841cc018145..1fa7b22d3b8 100644 --- a/src/recipes/lock/build.xml +++ b/src/recipes/lock/build.xml @@ -57,7 +57,7 @@ + debug="on" encoding="${build.encoding}"> diff --git a/src/recipes/lock/src/c/src/zoo_lock.c b/src/recipes/lock/src/c/src/zoo_lock.c index 8a6d81763f4..74a115f79cf 100644 --- a/src/recipes/lock/src/c/src/zoo_lock.c +++ b/src/recipes/lock/src/c/src/zoo_lock.c @@ -93,7 +93,7 @@ ZOOAPI int zkr_lock_unlock(zkr_lock_mutex_t *mutex) { while (ret == ZCONNECTIONLOSS && (count < 3)) { ret = zoo_delete(zh, buf, -1); if (ret == ZCONNECTIONLOSS) { - LOG_DEBUG(("connectionloss while deleting the node")); + LOG_DEBUG(LOGCALLBACK(zh), ("connectionloss while deleting the node")); nanosleep(&ts, 0); count++; } @@ -109,7 +109,7 @@ ZOOAPI int zkr_lock_unlock(zkr_lock_mutex_t *mutex) { pthread_mutex_unlock(&(mutex->pmutex)); return 0; } - LOG_WARN(("not able to connect to server - giving up")); + LOG_WARN(LOGCALLBACK(zh), ("not able to connect to server - giving up")); pthread_mutex_unlock(&(mutex->pmutex)); return ZCONNECTIONLOSS; } @@ -176,7 +176,7 @@ static int retry_getchildren(zhandle_t *zh, char* path, struct String_vector *ve while (ret == ZCONNECTIONLOSS && count < retry) { ret = zoo_get_children(zh, path, 0, vector); if (ret == ZCONNECTIONLOSS) { - LOG_DEBUG(("connection loss to the server")); + LOG_DEBUG(LOGCALLBACK(zh), ("connection loss to the server")); nanosleep(ts, 0); count++; } @@ -212,7 +212,7 @@ static int retry_zoowexists(zhandle_t *zh, char* path, watcher_fn watcher, void* while (ret == ZCONNECTIONLOSS && count < retry) { ret = zoo_wexists(zh, path, watcher, ctx, stat); if (ret == ZCONNECTIONLOSS) { - LOG_DEBUG(("connectionloss while setting watch on my predecessor")); + LOG_DEBUG(LOGCALLBACK(zh), ("connectionloss while setting watch on my predecessor")); nanosleep(ts, 0); count++; } @@ -267,7 +267,7 @@ static int zkr_lock_operation(zkr_lock_mutex_t *mutex, struct timespec *ts) { // do not want to retry the create since // we would end up creating more than one child if (ret != ZOK) { - LOG_WARN(("could not create zoo node %s", buf)); + LOG_WARN(LOGCALLBACK(zh), ("could not create zoo node %s", buf)); return ret; } mutex->id = getName(retbuf); @@ -277,7 +277,7 @@ static int zkr_lock_operation(zkr_lock_mutex_t *mutex, struct timespec *ts) { ret = ZCONNECTIONLOSS; ret = retry_getchildren(zh, path, vector, ts, retry); if (ret != ZOK) { - LOG_WARN(("could not connect to server")); + LOG_WARN(LOGCALLBACK(zh), ("could not connect to server")); return ret; } //sort this list @@ -299,7 +299,7 @@ static int zkr_lock_operation(zkr_lock_mutex_t *mutex, struct timespec *ts) { // will keep waiting if (ret != ZOK) { free_String_vector(vector); - LOG_WARN(("unable to watch my predecessor")); + LOG_WARN(LOGCALLBACK(zh), ("unable to watch my predecessor")); ret = zkr_lock_unlock(mutex); while (ret == 0) { //we have to give up our leadership @@ -315,7 +315,7 @@ static int zkr_lock_operation(zkr_lock_mutex_t *mutex, struct timespec *ts) { // this is the case when we are the owner // of the lock if (strcmp(mutex->id, owner_id) == 0) { - LOG_DEBUG(("got the zoo lock owner - %s", mutex->id)); + LOG_DEBUG(LOGCALLBACK(zh), ("got the zoo lock owner - %s", mutex->id)); mutex->isOwner = 1; if (mutex->completion != NULL) { mutex->completion(0, mutex->cbdata); diff --git a/src/recipes/lock/src/java/org/apache/zookeeper/recipes/lock/ZNodeName.java b/src/recipes/lock/src/java/org/apache/zookeeper/recipes/lock/ZNodeName.java index 99b6616f931..2e32e597fd3 100644 --- a/src/recipes/lock/src/java/org/apache/zookeeper/recipes/lock/ZNodeName.java +++ b/src/recipes/lock/src/java/org/apache/zookeeper/recipes/lock/ZNodeName.java @@ -74,15 +74,17 @@ public int hashCode() { return name.hashCode() + 37; } + /** + * Compare znodes based on their sequence number + * @param that other znode to compare to + * @return the difference between their sequence numbers: a positive value if this + * znode has a larger sequence number, 0 if they have the same sequence number + * or a negative number if this znode has a lower sequence number + */ public int compareTo(ZNodeName that) { - int answer = this.prefix.compareTo(that.prefix); + int answer = this.sequence - that.sequence; if (answer == 0) { - int s1 = this.sequence; - int s2 = that.sequence; - if (s1 == -1 && s2 == -1) { - return this.name.compareTo(that.name); - } - answer = s1 == -1 ? 1 : s2 == -1 ? -1 : s1 - s2; + return this.prefix.compareTo(that.prefix); } return answer; } diff --git a/src/recipes/lock/test/org/apache/zookeeper/recipes/lock/ZNodeNameTest.java b/src/recipes/lock/test/org/apache/zookeeper/recipes/lock/ZNodeNameTest.java index 6c3bdacf645..72813845751 100644 --- a/src/recipes/lock/test/org/apache/zookeeper/recipes/lock/ZNodeNameTest.java +++ b/src/recipes/lock/test/org/apache/zookeeper/recipes/lock/ZNodeNameTest.java @@ -17,17 +17,17 @@ */ package org.apache.zookeeper.recipes.lock; -import junit.framework.TestCase; + +import org.junit.Assert; +import org.junit.Test; import java.util.SortedSet; import java.util.TreeSet; -import org.junit.Test; - /** * test for znodenames */ -public class ZNodeNameTest extends TestCase { +public class ZNodeNameTest { @Test public void testOrderWithSamePrefix() throws Exception { String[] names = { "x-3", "x-5", "x-11", "x-1" }; @@ -37,22 +37,34 @@ public void testOrderWithSamePrefix() throws Exception { @Test public void testOrderWithDifferentPrefixes() throws Exception { String[] names = { "r-3", "r-2", "r-1", "w-2", "w-1" }; - String[] expected = { "r-1", "r-2", "r-3", "w-1", "w-2" }; + String[] expected = { "r-1", "w-1", "r-2", "w-2", "r-3" }; + assertOrderedNodeNames(names, expected); + } + @Test + public void testOrderWithDifferentPrefixIncludingSessionId() throws Exception { + String[] names = { "x-242681582799028564-0000000002", "x-170623981976748329-0000000003", "x-98566387950223723-0000000001" }; + String[] expected = { "x-98566387950223723-0000000001", "x-242681582799028564-0000000002", "x-170623981976748329-0000000003" }; + assertOrderedNodeNames(names, expected); + } + @Test + public void testOrderWithExtraPrefixes() throws Exception { + String[] names = { "r-1-3-2", "r-2-2-1", "r-3-1-3" }; + String[] expected = { "r-2-2-1", "r-1-3-2", "r-3-1-3" }; assertOrderedNodeNames(names, expected); } protected void assertOrderedNodeNames(String[] names, String[] expected) { int size = names.length; - assertEquals("The two arrays should be the same size!", names.length, expected.length); SortedSet nodeNames = new TreeSet(); for (String name : names) { nodeNames.add(new ZNodeName(name)); } + Assert.assertEquals("The SortedSet does not have the expected size!", nodeNames.size(), expected.length); int index = 0; for (ZNodeName nodeName : nodeNames) { String name = nodeName.getName(); - assertEquals("Node " + index, expected[index++], name); + Assert.assertEquals("Node " + index, expected[index++], name); } } diff --git a/src/recipes/queue/build.xml b/src/recipes/queue/build.xml index 0f3505ab9ad..12ec0e186c4 100644 --- a/src/recipes/queue/build.xml +++ b/src/recipes/queue/build.xml @@ -57,7 +57,7 @@ + debug="on" encoding="${build.encoding}"> diff --git a/src/recipes/queue/src/c/include/zoo_queue.h b/src/recipes/queue/src/c/include/zoo_queue.h index ccc4602f6b9..dccc7635d77 100644 --- a/src/recipes/queue/src/c/include/zoo_queue.h +++ b/src/recipes/queue/src/c/include/zoo_queue.h @@ -100,7 +100,7 @@ ZOOAPI int zkr_queue_remove(zkr_queue_t *queue, char *buffer, int *buffer_len); * \param buffer_len a pointer to the length of the buffer * \return returns 0 (ZOK) and sets *buffer_len to the length of data written if successful. Otherwise it will set *buffer_len to -1 and return a zookeeper error code. */ -ZOOAPI int zkr_queue_take(zkr_queue_t *queue, char *buffer, int *buffer_len); +ZOOAPI int zkr_queue_take(zhandle_t *zh, zkr_queue_t *queue, char *buffer, int *buffer_len); /** * \brief destroys a zookeeper queue structure diff --git a/src/recipes/queue/src/c/src/zoo_queue.c b/src/recipes/queue/src/c/src/zoo_queue.c index 89ec24b6eda..d7cc5707ec0 100644 --- a/src/recipes/queue/src/c/src/zoo_queue.c +++ b/src/recipes/queue/src/c/src/zoo_queue.c @@ -292,7 +292,7 @@ static void take_latch_destroy_synchronized(take_latch_t *latch){ pthread_mutex_unlock(mutex); } -static void take_latch_setter_trigger_latch(take_latch_t *latch){ +static void take_latch_setter_trigger_latch(zhandle_t *zh, take_latch_t *latch){ pthread_mutex_t *mutex = &(latch->queue->pmutex); pthread_mutex_lock(mutex); switch(latch->state){ @@ -303,7 +303,7 @@ static void take_latch_setter_trigger_latch(take_latch_t *latch){ take_latch_destroy_unsafe(latch); break; case take_triggered: - LOG_DEBUG(("Error! Latch was triggered twice.")); + LOG_DEBUG(LOGCALLBACK(zh), ("Error! Latch was triggered twice.")); break; case take_waiting: pthread_cond_signal(&(latch->latch_condition)); @@ -312,7 +312,7 @@ static void take_latch_setter_trigger_latch(take_latch_t *latch){ pthread_mutex_unlock(mutex); } -static void take_latch_waiter_await(take_latch_t *latch){ +static void take_latch_waiter_await(zhandle_t *zh, take_latch_t *latch){ pthread_mutex_t *mutex = &(latch->queue->pmutex); pthread_mutex_lock(mutex); switch(latch->state){ @@ -323,10 +323,10 @@ static void take_latch_waiter_await(take_latch_t *latch){ take_latch_destroy_unsafe(latch); break; case take_waiting: - LOG_DEBUG(("Error! Called await twice.")); + LOG_DEBUG(LOGCALLBACK(zh), ("Error! Called await twice.")); break; case take_not_needed: - LOG_DEBUG(("Error! Waiting after marking not needed.")); + LOG_DEBUG(LOGCALLBACK(zh), ("Error! Waiting after marking not needed.")); break; case take_triggered: take_latch_destroy_unsafe(latch); @@ -335,7 +335,7 @@ static void take_latch_waiter_await(take_latch_t *latch){ pthread_mutex_unlock(mutex); } -static void take_latch_waiter_mark_unneeded(take_latch_t *latch){ +static void take_latch_waiter_mark_unneeded(zhandle_t *zh, take_latch_t *latch){ pthread_mutex_t *mutex = &(latch->queue->pmutex); pthread_mutex_lock(mutex); switch(latch->state){ @@ -343,10 +343,10 @@ static void take_latch_waiter_mark_unneeded(take_latch_t *latch){ latch->state = take_not_needed; break; case take_waiting: - LOG_DEBUG(("Error! Can't mark unneeded after waiting.")); + LOG_DEBUG(LOGCALLBACK(zh), ("Error! Can't mark unneeded after waiting.")); break; case take_not_needed: - LOG_DEBUG(("Marked unneeded twice.")); + LOG_DEBUG(LOGCALLBACK(zh), ("Marked unneeded twice.")); break; case take_triggered: take_latch_destroy_unsafe(latch); @@ -357,12 +357,12 @@ static void take_latch_waiter_mark_unneeded(take_latch_t *latch){ static void take_watcher(zhandle_t *zh, int type, int state, const char *path, void *watcherCtx){ take_latch_t *latch = (take_latch_t *) watcherCtx; - take_latch_setter_trigger_latch(latch); + take_latch_setter_trigger_latch(zh, latch); } -ZOOAPI int zkr_queue_take(zkr_queue_t *queue, char *buffer, int *buffer_len){ +ZOOAPI int zkr_queue_take(zhandle_t *zh, zkr_queue_t *queue, char *buffer, int *buffer_len){ int path_length = strlen(queue->path); take_attempt: for(;;){ @@ -392,9 +392,9 @@ ZOOAPI int zkr_queue_take(zkr_queue_t *queue, char *buffer, int *buffer_len){ return get_children_rc; } if(stvector.count == 0){ - take_latch_waiter_await(take_latch); + take_latch_waiter_await(zh, take_latch); }else{ - take_latch_waiter_mark_unneeded(take_latch); + take_latch_waiter_mark_unneeded(zh, take_latch); } sort_children(vector); diff --git a/src/zookeeper.jute b/src/zookeeper.jute index 709e935bdef..2533ddf8d11 100644 --- a/src/zookeeper.jute +++ b/src/zookeeper.jute @@ -127,11 +127,12 @@ module org.apache.zookeeper.proto { vector acl; int flags; } - class Create2Request { + class CreateTTLRequest { ustring path; buffer data; vector acl; int flags; + long ttl; } class DeleteRequest { ustring path; @@ -235,6 +236,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 { @@ -266,6 +272,19 @@ module org.apache.zookeeper.txn { boolean ephemeral; int parentCVersion; } + class CreateTTLTxn { + ustring path; + buffer data; + vector acl; + int parentCVersion; + long ttl; + } + class CreateContainerTxn { + ustring path; + buffer data; + vector acl; + int parentCVersion; + } class DeleteTxn { ustring path; }