├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README.tpl ├── TODO.md ├── renovate.json ├── scripts └── ci-start-zookeeper └── src ├── error.rs ├── lib.rs ├── proto ├── active_packetizer.rs ├── error.rs ├── mod.rs ├── packetizer.rs ├── request.rs ├── response.rs └── watch.rs ├── transform.rs └── types ├── acl.rs ├── mod.rs ├── multi.rs └── watch.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "*" 9 | pull_request: 10 | merge_group: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | rust: [stable, beta, nightly] 18 | zookeeper: [3.9.2, 3.8.4, 3.7.2, 3.6.4, 3.5.10] 19 | steps: 20 | - name: Check out repository code 21 | uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 22 | - run: rustup default ${{ matrix.rust }} 23 | - run: wget -O zookeeper.tar.gz https://archive.apache.org/dist/zookeeper/zookeeper-${{ matrix.zookeeper }}/apache-zookeeper-${{ matrix.zookeeper }}-bin.tar.gz 24 | - run: mkdir zookeeper 25 | - run: tar -zxvf zookeeper.tar.gz -C zookeeper --strip-components 1 26 | - run: ./scripts/ci-start-zookeeper 27 | - run: cargo test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [Unreleased] 6 | 7 | ## [0.4.0] - 2024-05-08 8 | 9 | ### Changed 10 | 11 | - [BREAKING] Replace `snafu::Whatever` types with `tokio_zookeeper::error::Error` ([#44]). 12 | 13 | ### Bugfixes 14 | 15 | - Errors are now thread-safe (`Send + Sync`) again ([#44]). 16 | 17 | ## [0.3.0] - 2024-05-07 18 | 19 | ### Changed 20 | 21 | - [BREAKING] Migrated errors from Failure to SNAFU ([#39]). 22 | - [BREAKING] Migrated from `slog` to `tracing` ([#40]). 23 | - Updated ZooKeeper versions we test against (now 3.9.2, 3.8.4, 3.7.2, 3.6.4, 3.5.10) ([#39]). 24 | 25 | [#39]: https://github.com/stackabletech/tokio-zookeeper/pull/39 26 | [#40]: https://github.com/stackabletech/tokio-zookeeper/pull/40 27 | [#44]: https://github.com/stackabletech/tokio-zookeeper/pull/44 28 | 29 | ## [0.2.1] - 2023-02-13 30 | 31 | ### Changed 32 | 33 | - Don't try to reconnect during exit ([#30]). 34 | 35 | [#30]: https://github.com/stackabletech/tokio-zookeeper/pull/30 36 | 37 | ## [0.2.0] - 2023-02-10 38 | 39 | ### Highlights 40 | 41 | tokio-zookeeper now uses futures 0.3 and Tokio 1, which means that it is 42 | now compatible with Rust's async/await syntax! 43 | 44 | #### Migration from 0.1.x 45 | 46 | 1. Upgrade the rest of your app to Tokio 1.x (you can use a compatibility wrapper for code such as tokio-zk that still 47 | uses Tokio 0.1, see 48 | [Cargo.toml](https://github.com/stackabletech/zookeeper-operator/blob/a682dcc3c7dc841917e968ba0e9fa9d33a4fabf5/rust/operator-binary/Cargo.toml#L22-L23) 49 | and 50 | [`WithTokio01Executor`](https://github.com/stackabletech/zookeeper-operator/blob/a682dcc3c7dc841917e968ba0e9fa9d33a4fabf5/rust/operator-binary/src/utils.rs#L6-L38)). 51 | 2. Upgrade tokio-zookeeper to v0.2. 52 | 3. Migrate async calls that thread the `ZooKeeper` instance to instead borrow it (for example, 53 | `zk.exists(path).and_then(|(zk, stat)| /* do stuff */);` becomes 54 | `let stat = zk.exists(path).await?;`). 55 | 4. Remove Tokio 0.1 and the compatibility wrapper if they are no longer required. 56 | 57 | ### Added 58 | 59 | - Support all-or-nothing multi-operations ([#15]). 60 | 61 | ### Changed 62 | 63 | - [BREAKING] Updated to futures 0.3 and Tokio 1, which are compatible with async/await ([#19]). 64 | 65 | [#15]: https://github.com/stackabletech/tokio-zookeeper/pull/15 66 | [#19]: https://github.com/stackabletech/tokio-zookeeper/pull/19 67 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "async-trait" 22 | version = "0.1.80" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" 25 | dependencies = [ 26 | "proc-macro2", 27 | "quote", 28 | "syn", 29 | ] 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.3.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 36 | 37 | [[package]] 38 | name = "backtrace" 39 | version = "0.3.71" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 42 | dependencies = [ 43 | "addr2line", 44 | "cc", 45 | "cfg-if", 46 | "libc", 47 | "miniz_oxide", 48 | "object", 49 | "rustc-demangle", 50 | ] 51 | 52 | [[package]] 53 | name = "byteorder" 54 | version = "1.5.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 57 | 58 | [[package]] 59 | name = "cc" 60 | version = "1.0.97" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" 63 | 64 | [[package]] 65 | name = "cfg-if" 66 | version = "1.0.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 69 | 70 | [[package]] 71 | name = "futures" 72 | version = "0.3.30" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 75 | dependencies = [ 76 | "futures-channel", 77 | "futures-core", 78 | "futures-executor", 79 | "futures-io", 80 | "futures-sink", 81 | "futures-task", 82 | "futures-util", 83 | ] 84 | 85 | [[package]] 86 | name = "futures-channel" 87 | version = "0.3.30" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 90 | dependencies = [ 91 | "futures-core", 92 | "futures-sink", 93 | ] 94 | 95 | [[package]] 96 | name = "futures-core" 97 | version = "0.3.30" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 100 | 101 | [[package]] 102 | name = "futures-executor" 103 | version = "0.3.30" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 106 | dependencies = [ 107 | "futures-core", 108 | "futures-task", 109 | "futures-util", 110 | ] 111 | 112 | [[package]] 113 | name = "futures-io" 114 | version = "0.3.30" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 117 | 118 | [[package]] 119 | name = "futures-macro" 120 | version = "0.3.30" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "syn", 127 | ] 128 | 129 | [[package]] 130 | name = "futures-sink" 131 | version = "0.3.30" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 134 | 135 | [[package]] 136 | name = "futures-task" 137 | version = "0.3.30" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 140 | 141 | [[package]] 142 | name = "futures-util" 143 | version = "0.3.30" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 146 | dependencies = [ 147 | "futures-channel", 148 | "futures-core", 149 | "futures-io", 150 | "futures-macro", 151 | "futures-sink", 152 | "futures-task", 153 | "memchr", 154 | "pin-project-lite", 155 | "pin-utils", 156 | "slab", 157 | ] 158 | 159 | [[package]] 160 | name = "gimli" 161 | version = "0.28.1" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 164 | 165 | [[package]] 166 | name = "heck" 167 | version = "0.4.1" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 170 | 171 | [[package]] 172 | name = "lazy_static" 173 | version = "1.4.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 176 | 177 | [[package]] 178 | name = "libc" 179 | version = "0.2.154" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 182 | 183 | [[package]] 184 | name = "log" 185 | version = "0.4.21" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 188 | 189 | [[package]] 190 | name = "memchr" 191 | version = "2.7.2" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 194 | 195 | [[package]] 196 | name = "miniz_oxide" 197 | version = "0.7.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 200 | dependencies = [ 201 | "adler", 202 | ] 203 | 204 | [[package]] 205 | name = "mio" 206 | version = "0.8.11" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 209 | dependencies = [ 210 | "libc", 211 | "wasi", 212 | "windows-sys 0.48.0", 213 | ] 214 | 215 | [[package]] 216 | name = "nu-ansi-term" 217 | version = "0.46.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 220 | dependencies = [ 221 | "overload", 222 | "winapi", 223 | ] 224 | 225 | [[package]] 226 | name = "object" 227 | version = "0.32.2" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 230 | dependencies = [ 231 | "memchr", 232 | ] 233 | 234 | [[package]] 235 | name = "once_cell" 236 | version = "1.19.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 239 | 240 | [[package]] 241 | name = "overload" 242 | version = "0.1.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 245 | 246 | [[package]] 247 | name = "pin-project" 248 | version = "1.1.5" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 251 | dependencies = [ 252 | "pin-project-internal", 253 | ] 254 | 255 | [[package]] 256 | name = "pin-project-internal" 257 | version = "1.1.5" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 260 | dependencies = [ 261 | "proc-macro2", 262 | "quote", 263 | "syn", 264 | ] 265 | 266 | [[package]] 267 | name = "pin-project-lite" 268 | version = "0.2.14" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 271 | 272 | [[package]] 273 | name = "pin-utils" 274 | version = "0.1.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 277 | 278 | [[package]] 279 | name = "proc-macro2" 280 | version = "1.0.82" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 283 | dependencies = [ 284 | "unicode-ident", 285 | ] 286 | 287 | [[package]] 288 | name = "quote" 289 | version = "1.0.36" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 292 | dependencies = [ 293 | "proc-macro2", 294 | ] 295 | 296 | [[package]] 297 | name = "rustc-demangle" 298 | version = "0.1.24" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 301 | 302 | [[package]] 303 | name = "sharded-slab" 304 | version = "0.1.7" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 307 | dependencies = [ 308 | "lazy_static", 309 | ] 310 | 311 | [[package]] 312 | name = "slab" 313 | version = "0.4.9" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 316 | dependencies = [ 317 | "autocfg", 318 | ] 319 | 320 | [[package]] 321 | name = "smallvec" 322 | version = "1.13.2" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 325 | 326 | [[package]] 327 | name = "snafu" 328 | version = "0.8.2" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "75976f4748ab44f6e5332102be424e7c2dc18daeaf7e725f2040c3ebb133512e" 331 | dependencies = [ 332 | "snafu-derive", 333 | ] 334 | 335 | [[package]] 336 | name = "snafu-derive" 337 | version = "0.8.2" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "b4b19911debfb8c2fb1107bc6cb2d61868aaf53a988449213959bb1b5b1ed95f" 340 | dependencies = [ 341 | "heck", 342 | "proc-macro2", 343 | "quote", 344 | "syn", 345 | ] 346 | 347 | [[package]] 348 | name = "socket2" 349 | version = "0.5.7" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 352 | dependencies = [ 353 | "libc", 354 | "windows-sys 0.52.0", 355 | ] 356 | 357 | [[package]] 358 | name = "syn" 359 | version = "2.0.61" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" 362 | dependencies = [ 363 | "proc-macro2", 364 | "quote", 365 | "unicode-ident", 366 | ] 367 | 368 | [[package]] 369 | name = "thread_local" 370 | version = "1.1.8" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 373 | dependencies = [ 374 | "cfg-if", 375 | "once_cell", 376 | ] 377 | 378 | [[package]] 379 | name = "tokio" 380 | version = "1.37.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" 383 | dependencies = [ 384 | "backtrace", 385 | "libc", 386 | "mio", 387 | "pin-project-lite", 388 | "socket2", 389 | "tokio-macros", 390 | "windows-sys 0.48.0", 391 | ] 392 | 393 | [[package]] 394 | name = "tokio-macros" 395 | version = "2.2.0" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 398 | dependencies = [ 399 | "proc-macro2", 400 | "quote", 401 | "syn", 402 | ] 403 | 404 | [[package]] 405 | name = "tokio-zookeeper" 406 | version = "0.4.0" 407 | dependencies = [ 408 | "async-trait", 409 | "byteorder", 410 | "futures", 411 | "once_cell", 412 | "pin-project", 413 | "snafu", 414 | "tokio", 415 | "tracing", 416 | "tracing-subscriber", 417 | ] 418 | 419 | [[package]] 420 | name = "tracing" 421 | version = "0.1.40" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 424 | dependencies = [ 425 | "pin-project-lite", 426 | "tracing-attributes", 427 | "tracing-core", 428 | ] 429 | 430 | [[package]] 431 | name = "tracing-attributes" 432 | version = "0.1.27" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 435 | dependencies = [ 436 | "proc-macro2", 437 | "quote", 438 | "syn", 439 | ] 440 | 441 | [[package]] 442 | name = "tracing-core" 443 | version = "0.1.32" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 446 | dependencies = [ 447 | "once_cell", 448 | "valuable", 449 | ] 450 | 451 | [[package]] 452 | name = "tracing-log" 453 | version = "0.2.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 456 | dependencies = [ 457 | "log", 458 | "once_cell", 459 | "tracing-core", 460 | ] 461 | 462 | [[package]] 463 | name = "tracing-subscriber" 464 | version = "0.3.18" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 467 | dependencies = [ 468 | "nu-ansi-term", 469 | "sharded-slab", 470 | "smallvec", 471 | "thread_local", 472 | "tracing-core", 473 | "tracing-log", 474 | ] 475 | 476 | [[package]] 477 | name = "unicode-ident" 478 | version = "1.0.12" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 481 | 482 | [[package]] 483 | name = "valuable" 484 | version = "0.1.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 487 | 488 | [[package]] 489 | name = "wasi" 490 | version = "0.11.0+wasi-snapshot-preview1" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 493 | 494 | [[package]] 495 | name = "winapi" 496 | version = "0.3.9" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 499 | dependencies = [ 500 | "winapi-i686-pc-windows-gnu", 501 | "winapi-x86_64-pc-windows-gnu", 502 | ] 503 | 504 | [[package]] 505 | name = "winapi-i686-pc-windows-gnu" 506 | version = "0.4.0" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 509 | 510 | [[package]] 511 | name = "winapi-x86_64-pc-windows-gnu" 512 | version = "0.4.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 515 | 516 | [[package]] 517 | name = "windows-sys" 518 | version = "0.48.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 521 | dependencies = [ 522 | "windows-targets 0.48.5", 523 | ] 524 | 525 | [[package]] 526 | name = "windows-sys" 527 | version = "0.52.0" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 530 | dependencies = [ 531 | "windows-targets 0.52.5", 532 | ] 533 | 534 | [[package]] 535 | name = "windows-targets" 536 | version = "0.48.5" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 539 | dependencies = [ 540 | "windows_aarch64_gnullvm 0.48.5", 541 | "windows_aarch64_msvc 0.48.5", 542 | "windows_i686_gnu 0.48.5", 543 | "windows_i686_msvc 0.48.5", 544 | "windows_x86_64_gnu 0.48.5", 545 | "windows_x86_64_gnullvm 0.48.5", 546 | "windows_x86_64_msvc 0.48.5", 547 | ] 548 | 549 | [[package]] 550 | name = "windows-targets" 551 | version = "0.52.5" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 554 | dependencies = [ 555 | "windows_aarch64_gnullvm 0.52.5", 556 | "windows_aarch64_msvc 0.52.5", 557 | "windows_i686_gnu 0.52.5", 558 | "windows_i686_gnullvm", 559 | "windows_i686_msvc 0.52.5", 560 | "windows_x86_64_gnu 0.52.5", 561 | "windows_x86_64_gnullvm 0.52.5", 562 | "windows_x86_64_msvc 0.52.5", 563 | ] 564 | 565 | [[package]] 566 | name = "windows_aarch64_gnullvm" 567 | version = "0.48.5" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 570 | 571 | [[package]] 572 | name = "windows_aarch64_gnullvm" 573 | version = "0.52.5" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 576 | 577 | [[package]] 578 | name = "windows_aarch64_msvc" 579 | version = "0.48.5" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 582 | 583 | [[package]] 584 | name = "windows_aarch64_msvc" 585 | version = "0.52.5" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 588 | 589 | [[package]] 590 | name = "windows_i686_gnu" 591 | version = "0.48.5" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 594 | 595 | [[package]] 596 | name = "windows_i686_gnu" 597 | version = "0.52.5" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 600 | 601 | [[package]] 602 | name = "windows_i686_gnullvm" 603 | version = "0.52.5" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 606 | 607 | [[package]] 608 | name = "windows_i686_msvc" 609 | version = "0.48.5" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 612 | 613 | [[package]] 614 | name = "windows_i686_msvc" 615 | version = "0.52.5" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 618 | 619 | [[package]] 620 | name = "windows_x86_64_gnu" 621 | version = "0.48.5" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 624 | 625 | [[package]] 626 | name = "windows_x86_64_gnu" 627 | version = "0.52.5" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 630 | 631 | [[package]] 632 | name = "windows_x86_64_gnullvm" 633 | version = "0.48.5" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 636 | 637 | [[package]] 638 | name = "windows_x86_64_gnullvm" 639 | version = "0.52.5" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 642 | 643 | [[package]] 644 | name = "windows_x86_64_msvc" 645 | version = "0.48.5" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 648 | 649 | [[package]] 650 | name = "windows_x86_64_msvc" 651 | version = "0.52.5" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 654 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-zookeeper" 3 | version = "0.4.0" 4 | 5 | description = "Asynchronous client library for interacting with Apache ZooKeeper" 6 | readme = "README.md" 7 | 8 | authors = ["Jon Gjengset ", "Stackable GmbH "] 9 | 10 | homepage = "https://github.com/jonhoo/tokio-zookeeper" 11 | repository = "https://github.com/jonhoo/tokio-zookeeper.git" 12 | 13 | keywords = ["zookeeper", "tokio", "asynchronous"] 14 | categories = ["api-bindings", "asynchronous", "network-programming"] 15 | 16 | license = "MIT/Apache-2.0" 17 | 18 | edition = "2021" 19 | 20 | [badges] 21 | maintenance = { status = "experimental" } 22 | 23 | [dependencies] 24 | async-trait = "0.1.80" 25 | byteorder = "1.5.0" 26 | futures = "0.3.30" 27 | once_cell = "1.19.0" 28 | pin-project = "1.1.5" 29 | snafu = "0.8.2" 30 | tokio = { version = "1.37.0", features = ["net", "rt", "time"] } 31 | tracing = "0.1.40" 32 | 33 | [dev-dependencies] 34 | tokio = { version = "1.37.0", features = ["macros"] } 35 | tracing-subscriber = "0.3.18" 36 | 37 | [features] 38 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jon Gjengset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tokio-zookeeper 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/tokio-zookeeper.svg)](https://crates.io/crates/tokio-zookeeper) 4 | [![Documentation](https://docs.rs/tokio-zookeeper/badge.svg)](https://docs.rs/tokio-zookeeper/) 5 | [![Build](https://github.com/stackabletech/tokio-zookeeper/actions/workflows/build.yml/badge.svg)](https://github.com/stackabletech/tokio-zookeeper/actions/workflows/build.yml) 6 | 7 | This crate provides a client for interacting with [Apache 8 | ZooKeeper](https://zookeeper.apache.org/), a highly reliable distributed service for 9 | maintaining configuration information, naming, providing distributed synchronization, and 10 | providing group services. 11 | 12 | ## About ZooKeeper 13 | 14 | The [ZooKeeper Overview](https://zookeeper.apache.org/doc/current/zookeeperOver.html) provides 15 | a thorough introduction to ZooKeeper, but we'll repeat the most important points here. At its 16 | [heart](https://zookeeper.apache.org/doc/current/zookeeperOver.html#sc_designGoals), ZooKeeper 17 | is a [hierarchical key-value 18 | store](https://zookeeper.apache.org/doc/current/zookeeperOver.html#sc_dataModelNameSpace) (that 19 | is, keys can have "sub-keys"), which additional mechanisms that guarantee consistent operation 20 | across client and server failures. Keys in ZooKeeper look like paths (e.g., `/key/subkey`), and 21 | every item along a path is called a 22 | "[Znode](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_zkDataModel_znodes)". 23 | Each Znode (including those with children) can also have associated data, which can be queried 24 | and updated like in other key-value stores. Along with its data and children, each Znode stores 25 | meta-information such as [access-control 26 | lists](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_ZooKeeperAccessControl), 27 | [modification 28 | timestamps](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_timeInZk), 29 | and a version number 30 | that allows clients to avoid stepping on each other's toes when accessing values (more on that 31 | later). 32 | 33 | ### Operations 34 | 35 | ZooKeeper's API consists of the same basic operations you would expect to find in a 36 | file-system: [`create`](struct.ZooKeeper.html#method.create) for creating new Znodes, 37 | [`delete`](struct.ZooKeeper.html#method.delete) for removing them, 38 | [`exists`](struct.ZooKeeper.html#method.exists) for checking if a node exists, 39 | [`get_data`](struct.ZooKeeper.html#method.get_data) and 40 | [`set_data`](struct.ZooKeeper.html#method.set_data) for getting and setting a node's associated 41 | data respectively, and [`get_children`](struct.ZooKeeper.html#method.get_children) for 42 | retrieving the children of a given node (i.e., its subkeys). For all of these operations, 43 | ZooKeeper gives [strong 44 | guarantees](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkGuarantees) 45 | about what happens when there are multiple clients interacting with the system, or even what 46 | happens in response to system and network failures. 47 | 48 | ### Ephemeral nodes 49 | 50 | When you create a Znode, you also specify a [`CreateMode`]. Nodes that are created with 51 | [`CreateMode::Persistent`] are the nodes we have discussed thus far. They remain in the server 52 | until you delete them. Nodes that are created with [`CreateMode::Ephemeral`] on the other hand 53 | are special. These [ephemeral 54 | nodes](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#Ephemeral+Nodes) are 55 | automatically deleted by the server when the client that created them disconnects. This can be 56 | handy for implementing lease-like mechanisms, and for detecting faults. Since they are 57 | automatically deleted, and nodes with children cannot be deleted directly, ephemeral nodes are 58 | not allowed to have children. 59 | 60 | ### Watches 61 | 62 | In addition to the methods above, [`ZooKeeper::exists`], [`ZooKeeper::get_data`], and 63 | [`ZooKeeper::get_children`] also support setting 64 | "[watches](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkWatches)" on 65 | a node. A watch is one-time trigger that causes a [`WatchedEvent`] to be sent to the client 66 | that set the watch when the state for which the watch was set changes. For example, for a 67 | watched `get_data`, a one-time notification will be sent the first time the data of the target 68 | node changes following when the response to the original `get_data` call was processed. You 69 | should see the ["Watches" entry in the Programmer's 70 | Guide](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkWatches) for 71 | details. 72 | 73 | ### Getting started 74 | 75 | To get ZooKeeper up and running, follow the official [Getting Started 76 | Guide](https://zookeeper.apache.org/doc/current/zookeeperStarted.html). In most Linux 77 | environments, the procedure for getting a basic setup working is usually just to install the 78 | `zookeeper` package and then run `systemctl start zookeeper`. ZooKeeper will then be running at 79 | `127.0.0.1:2181`. 80 | 81 | ## This implementation 82 | 83 | This library is analogous to the asynchronous API offered by the [official Java 84 | implementation](https://zookeeper.apache.org/doc/current/api/org/apache/zookeeper/ZooKeeper.html), 85 | and for most operations the Java documentation should apply to the Rust implementation. If this 86 | is not the case, it is considered [a bug](https://github.com/jonhoo/tokio-zookeeper/issues), 87 | and we'd love a bug report with as much relevant information as you can offer. 88 | 89 | Note that since this implementation is asynchronous, users of the client must take care to 90 | not re-order operations in their own code. There is some discussion of this in the [official 91 | documentation of the Java 92 | bindings](https://zookeeper.apache.org/doc/r3.4.12/zookeeperProgrammers.html#Java+Binding). 93 | 94 | For more information on ZooKeeper, see the [ZooKeeper Programmer's 95 | Guide](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html) and the [Confluence 96 | ZooKeeper wiki](https://cwiki.apache.org/confluence/display/ZOOKEEPER/Index). There is also a 97 | basic tutorial (that uses the Java client) 98 | [here](https://zookeeper.apache.org/doc/current/zookeeperTutorial.html). 99 | 100 | ### Interaction with Tokio 101 | 102 | The futures in this crate expect to be running under a `tokio::runtime::Runtime`. In the common case, 103 | they would be executed by `.await`ing them in a context that is executed via `#[tokio::main]` 104 | or `#[tokio::test]`. You can also explicitly create a `tokio::runtime::Runtime` and then use 105 | `Runtime::block_on` or `Runtime::spawn`. 106 | 107 | ## A somewhat silly example 108 | 109 | ```rust 110 | use tokio_zookeeper::*; 111 | use futures::prelude::*; 112 | 113 | let (zk, default_watcher) = ZooKeeper::connect(&"127.0.0.1:2181".parse().unwrap()) 114 | .await 115 | .unwrap(); 116 | 117 | // let's first check if /example exists. the .watch() causes us to be notified 118 | // the next time the "exists" status of /example changes after the call. 119 | let stat = zk.watch().exists("/example").await.unwrap(); 120 | // initially, /example does not exist 121 | assert_eq!(stat, None); 122 | // so let's make it! 123 | let path = zk 124 | .create( 125 | "/example", 126 | &b"Hello world"[..], 127 | Acl::open_unsafe(), 128 | CreateMode::Persistent, 129 | ) 130 | .await 131 | .unwrap(); 132 | assert_eq!(path.as_deref(), Ok("/example")); 133 | 134 | // does it exist now? 135 | let stat = zk.watch().exists("/example").await.unwrap(); 136 | // looks like it! 137 | // note that the creation above also triggered our "exists" watch! 138 | assert_eq!(stat.unwrap().data_length as usize, b"Hello world".len()); 139 | 140 | // did the data get set correctly? 141 | let res = zk.get_data("/example").await.unwrap(); 142 | let data = b"Hello world"; 143 | let res = res.unwrap(); 144 | assert_eq!(res.0, data); 145 | assert_eq!(res.1.data_length as usize, data.len()); 146 | 147 | // let's update the data. 148 | let stat = zk 149 | .set_data("/example", Some(res.1.version), &b"Bye world"[..]) 150 | .await 151 | .unwrap(); 152 | assert_eq!(stat.unwrap().data_length as usize, "Bye world".len()); 153 | 154 | // create a child of /example 155 | let path = zk 156 | .create( 157 | "/example/more", 158 | &b"Hello more"[..], 159 | Acl::open_unsafe(), 160 | CreateMode::Persistent, 161 | ) 162 | .await 163 | .unwrap(); 164 | assert_eq!(path.as_deref(), Ok("/example/more")); 165 | 166 | // it should be visible as a child of /example 167 | let children = zk.get_children("/example").await.unwrap(); 168 | assert_eq!(children, Some(vec!["more".to_string()])); 169 | 170 | // it is not legal to delete a node that has children directly 171 | let res = zk.delete("/example", None).await.unwrap(); 172 | assert_eq!(res, Err(error::Delete::NotEmpty)); 173 | // instead we must delete the children first 174 | let res = zk.delete("/example/more", None).await.unwrap(); 175 | assert_eq!(res, Ok(())); 176 | let res = zk.delete("/example", None).await.unwrap(); 177 | assert_eq!(res, Ok(())); 178 | // no /example should no longer exist! 179 | let stat = zk.exists("/example").await.unwrap(); 180 | assert_eq!(stat, None); 181 | 182 | // now let's check that the .watch().exists we did in the very 183 | // beginning actually triggered! 184 | let (event, _w) = default_watcher.into_future().await; 185 | assert_eq!( 186 | event, 187 | Some(WatchedEvent { 188 | event_type: WatchedEventType::NodeCreated, 189 | keeper_state: KeeperState::SyncConnected, 190 | path: String::from("/example"), 191 | }) 192 | ); 193 | ``` 194 | 195 | # Thank you 196 | 197 | This crate was originally developed by [Jon Gjengset (@jonhoo)](https://github.com/jonhoo/) as part of 198 | [his long-standing series of streams](https://www.youtube.com/playlist?list=PLqbS7AVVErFgY2faCIYjJZv_RluGkTlKt), 199 | starting at [this video](https://www.youtube.com/watch?v=mMuk8Rn9HBg&list=PLqbS7AVVErFgY2faCIYjJZv_RluGkTlKt&index=9). 200 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # {{crate}} 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/tokio-zookeeper.svg)](https://crates.io/crates/tokio-zookeeper) 4 | [![Documentation](https://docs.rs/tokio-zookeeper/badge.svg)](https://docs.rs/tokio-zookeeper/) 5 | 6 | {{readme}} 7 | 8 | # Live-coding 9 | 10 | The crate is under development as part of a live-coding stream series 11 | intended for users who are already somewhat familiar with Rust, and who 12 | want to see something larger and more involved be built. For 13 | futures-related stuff, I can also highly recommend @aturon's in-progress 14 | [Async in Rust 15 | book](https://aturon.github.io/apr/async-in-rust/chapter.html). 16 | 17 | You can find the recordings of past sessions in [this YouTube 18 | playlist](https://www.youtube.com/playlist?list=PLqbS7AVVErFgY2faCIYjJZv_RluGkTlKt). 19 | This crate started out in [this 20 | video](https://www.youtube.com/watch?v=mMuk8Rn9HBg), and got fleshed out 21 | more in [this follow-up](https://www.youtube.com/watch?v=0-Fsu-aM0_A), before 22 | we mostly finished it in [part 3](https://www.youtube.com/watch?v=1ADDeB9rqAI). 23 | I recommend you also take a look at the [ZooKeeper Programming 24 | Guide](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html) if 25 | you want to follow along. To get updates about future streams, follow me on 26 | [Patreon](https://www.patreon.com/jonhoo) or 27 | [Twitter](https://twitter.com/jonhoo). 28 | 29 | # Thank you 30 | 31 | For each of the projects I build, I like to thank the people who are 32 | willing and able to take the extra step of supporting me in making these 33 | videos on [Patreon](https://www.patreon.com/jonhoo) or 34 | [Liberapay](https://liberapay.com/jonhoo/). You have my most sincere 35 | gratitude, and I'm so excited that you find what I do interesting enough 36 | that you're willing to give a stranger money to do something they love! 37 | 38 | - Rodrigo Valin 39 | - Pigeon F 40 | - Patrick Allen 41 | - Matthew Knight 42 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Documentation for stat fields: https://zookeeper.apache.org/doc/r3.4.12/zookeeperProgrammers.html#sc_zkStatStructure 2 | Enforce path constraints? https://zookeeper.apache.org/doc/r3.4.12/zookeeperProgrammers.html#ch_zkDataModel 3 | Multi-server connect 4 | Improve ergonomics of the API! 5 | Create new session only when session has expired! 6 | + "session expired" notification 7 | + Herd effect mitigation from upstream ZK library? 8 | + Probably leave it to user to decide they want to establish new? 9 | Watches 10 | + Add non-default ones 11 | + Hmm: https://zookeeper.apache.org/doc/r3.4.12/zookeeperProgrammers.html#sc_WatchGuarantees 12 | "Watches are ordered with respect to other events, other watches, and 13 | asynchronous replies. The ZooKeeper client libraries ensures that 14 | everything is dispatched in order." 15 | 16 | Sounds like something we have do deal with... 17 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>stackabletech/.github:renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /scripts/ci-start-zookeeper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | mkdir /tmp/data 6 | echo -e "tickTime=2000\ndataDir=/tmp/data\nclientPort=2181" > zookeeper/conf/zoo.cfg 7 | ./zookeeper/bin/zkServer.sh start 8 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use snafu::Snafu; 2 | 3 | /// Errors returned by tokio-zookeeper (rather than by the ZooKeeper server) 4 | // Largely a copy of snafu::Whatever, adapted to provide Send+Sync 5 | #[derive(Debug, Snafu)] 6 | #[snafu(whatever, display("{message}"))] 7 | pub struct Error { 8 | #[snafu(source(from(Box, Some)))] 9 | source: Option>, 10 | message: String, 11 | } 12 | 13 | /// Errors that may cause a delete request to fail. 14 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Snafu)] 15 | #[snafu(module)] 16 | pub enum Delete { 17 | /// No node exists with the given `path`. 18 | #[snafu(display("target node does not exist"))] 19 | NoNode, 20 | 21 | /// The target node has a different version than was specified by the call to delete. 22 | #[snafu(display("target node has different version than expected ({expected})"))] 23 | BadVersion { 24 | /// The expected node version. 25 | expected: i32, 26 | }, 27 | 28 | /// The target node has child nodes, and therefore cannot be deleted. 29 | #[snafu(display("target node has children, and cannot be deleted"))] 30 | NotEmpty, 31 | } 32 | 33 | /// Errors that may cause a `set_data` request to fail. 34 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Snafu)] 35 | #[snafu(module)] 36 | pub enum SetData { 37 | /// No node exists with the given `path`. 38 | #[snafu(display("target node does not exist"))] 39 | NoNode, 40 | 41 | /// The target node has a different version than was specified by the call to `set_data`. 42 | #[snafu(display("target node has different version than expected ({expected})"))] 43 | BadVersion { 44 | /// The expected node version. 45 | expected: i32, 46 | }, 47 | 48 | /// The target node's permission does not accept data modification or requires different 49 | /// authentication to be altered. 50 | #[snafu(display("insuficient authentication"))] 51 | NoAuth, 52 | } 53 | 54 | /// Errors that may cause a create request to fail. 55 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Snafu)] 56 | #[snafu(module)] 57 | pub enum Create { 58 | /// A node with the given `path` already exists. 59 | #[snafu(display("target node already exists"))] 60 | NodeExists, 61 | 62 | /// The parent node of the given `path` does not exist. 63 | #[snafu(display("parent node of target does not exist"))] 64 | NoNode, 65 | 66 | /// The parent node of the given `path` is ephemeral, and cannot have children. 67 | #[snafu(display("parent node is ephemeral, and cannot have children"))] 68 | NoChildrenForEphemerals, 69 | 70 | /// The given ACL is invalid. 71 | #[snafu(display("the given ACL is invalid"))] 72 | InvalidAcl, 73 | } 74 | 75 | /// Errors that may cause a `get_acl` request to fail. 76 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Snafu)] 77 | #[snafu(module)] 78 | pub enum GetAcl { 79 | /// No node exists with the given `path`. 80 | #[snafu(display("target node does not exist"))] 81 | NoNode, 82 | } 83 | 84 | /// Errors that may cause a `set_acl` request to fail. 85 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Snafu)] 86 | #[snafu(module)] 87 | pub enum SetAcl { 88 | /// No node exists with the given `path`. 89 | #[snafu(display("target node does not exist"))] 90 | NoNode, 91 | 92 | /// The target node has a different version than was specified by the call to `set_acl`. 93 | #[snafu(display("target node has different version than expected ({expected})"))] 94 | BadVersion { 95 | /// The expected node version. 96 | expected: i32, 97 | }, 98 | 99 | /// The given ACL is invalid. 100 | #[snafu(display("the given ACL is invalid"))] 101 | InvalidAcl, 102 | 103 | /// The target node's permission does not accept acl modification or requires different 104 | /// authentication to be altered. 105 | #[snafu(display("insufficient authentication"))] 106 | NoAuth, 107 | } 108 | 109 | /// Errors that may cause a `check` request to fail. 110 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Snafu)] 111 | #[snafu(module)] 112 | pub enum Check { 113 | /// No node exists with the given `path`. 114 | #[snafu(display("target node does not exist"))] 115 | NoNode, 116 | 117 | /// The target node has a different version than was specified by the call to `check`. 118 | #[snafu(display("target node has different version than expected ({expected})"))] 119 | BadVersion { 120 | /// The expected node version. 121 | expected: i32, 122 | }, 123 | } 124 | 125 | /// The result of a failed `multi` request. 126 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Snafu)] 127 | pub enum Multi { 128 | /// A failed `delete` request. 129 | #[snafu(display("delete failed"), context(false))] 130 | Delete { 131 | /// The source error. 132 | source: Delete, 133 | }, 134 | 135 | /// A failed `set_data` request. 136 | #[snafu(display("set_data failed"), context(false))] 137 | SetData { 138 | /// The source error. 139 | source: SetData, 140 | }, 141 | 142 | /// A failed `create` request. 143 | #[snafu(display("create failed"), context(false))] 144 | Create { 145 | /// The source error. 146 | source: Create, 147 | }, 148 | 149 | /// A failed `check` request. 150 | #[snafu(display("check failed"), context(false))] 151 | Check { 152 | /// The source error. 153 | source: Check, 154 | }, 155 | 156 | /// The request would have succeeded, but a later request in the `multi` 157 | /// batch failed and caused this request to get rolled back. 158 | #[snafu(display("request rolled back due to later failed request"))] 159 | RolledBack, 160 | 161 | /// The request was skipped because an earlier request in the `multi` batch 162 | /// failed. It is unknown whether this request would have succeeded. 163 | #[snafu(display("request failed due to earlier failed request"))] 164 | Skipped, 165 | } 166 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a client for interacting with [Apache 2 | //! ZooKeeper](https://zookeeper.apache.org/), a highly reliable distributed service for 3 | //! maintaining configuration information, naming, providing distributed synchronization, and 4 | //! providing group services. 5 | //! 6 | //! # About ZooKeeper 7 | //! 8 | //! The [ZooKeeper Overview](https://zookeeper.apache.org/doc/current/zookeeperOver.html) provides 9 | //! a thorough introduction to ZooKeeper, but we'll repeat the most important points here. At its 10 | //! [heart](https://zookeeper.apache.org/doc/current/zookeeperOver.html#sc_designGoals), ZooKeeper 11 | //! is a [hierarchical key-value 12 | //! store](https://zookeeper.apache.org/doc/current/zookeeperOver.html#sc_dataModelNameSpace) (that 13 | //! is, keys can have "sub-keys"), which additional mechanisms that guarantee consistent operation 14 | //! across client and server failures. Keys in ZooKeeper look like paths (e.g., `/key/subkey`), and 15 | //! every item along a path is called a 16 | //! "[Znode](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_zkDataModel_znodes)". 17 | //! Each Znode (including those with children) can also have associated data, which can be queried 18 | //! and updated like in other key-value stores. Along with its data and children, each Znode stores 19 | //! meta-information such as [access-control 20 | //! lists](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_ZooKeeperAccessControl), 21 | //! [modification 22 | //! timestamps](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_timeInZk), 23 | //! and a version number 24 | //! that allows clients to avoid stepping on each other's toes when accessing values (more on that 25 | //! later). 26 | //! 27 | //! ## Operations 28 | //! 29 | //! ZooKeeper's API consists of the same basic operations you would expect to find in a 30 | //! file-system: [`create`](struct.ZooKeeper.html#method.create) for creating new Znodes, 31 | //! [`delete`](struct.ZooKeeper.html#method.delete) for removing them, 32 | //! [`exists`](struct.ZooKeeper.html#method.exists) for checking if a node exists, 33 | //! [`get_data`](struct.ZooKeeper.html#method.get_data) and 34 | //! [`set_data`](struct.ZooKeeper.html#method.set_data) for getting and setting a node's associated 35 | //! data respectively, and [`get_children`](struct.ZooKeeper.html#method.get_children) for 36 | //! retrieving the children of a given node (i.e., its subkeys). For all of these operations, 37 | //! ZooKeeper gives [strong 38 | //! guarantees](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkGuarantees) 39 | //! about what happens when there are multiple clients interacting with the system, or even what 40 | //! happens in response to system and network failures. 41 | //! 42 | //! ## Ephemeral nodes 43 | //! 44 | //! When you create a Znode, you also specify a [`CreateMode`]. Nodes that are created with 45 | //! [`CreateMode::Persistent`] are the nodes we have discussed thus far. They remain in the server 46 | //! until you delete them. Nodes that are created with [`CreateMode::Ephemeral`] on the other hand 47 | //! are special. These [ephemeral 48 | //! nodes](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#Ephemeral+Nodes) are 49 | //! automatically deleted by the server when the client that created them disconnects. This can be 50 | //! handy for implementing lease-like mechanisms, and for detecting faults. Since they are 51 | //! automatically deleted, and nodes with children cannot be deleted directly, ephemeral nodes are 52 | //! not allowed to have children. 53 | //! 54 | //! ## Watches 55 | //! 56 | //! In addition to the methods above, [`ZooKeeper::exists`], [`ZooKeeper::get_data`], and 57 | //! [`ZooKeeper::get_children`] also support setting 58 | //! "[watches](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkWatches)" on 59 | //! a node. A watch is one-time trigger that causes a [`WatchedEvent`] to be sent to the client 60 | //! that set the watch when the state for which the watch was set changes. For example, for a 61 | //! watched `get_data`, a one-time notification will be sent the first time the data of the target 62 | //! node changes following when the response to the original `get_data` call was processed. You 63 | //! should see the ["Watches" entry in the Programmer's 64 | //! Guide](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#ch_zkWatches) for 65 | //! details. 66 | //! 67 | //! ## Getting started 68 | //! 69 | //! To get ZooKeeper up and running, follow the official [Getting Started 70 | //! Guide](https://zookeeper.apache.org/doc/current/zookeeperStarted.html). In most Linux 71 | //! environments, the procedure for getting a basic setup working is usually just to install the 72 | //! `zookeeper` package and then run `systemctl start zookeeper`. ZooKeeper will then be running at 73 | //! `127.0.0.1:2181`. 74 | //! 75 | //! # This implementation 76 | //! 77 | //! This library is analogous to the asynchronous API offered by the [official Java 78 | //! implementation](https://zookeeper.apache.org/doc/current/api/org/apache/zookeeper/ZooKeeper.html), 79 | //! and for most operations the Java documentation should apply to the Rust implementation. If this 80 | //! is not the case, it is considered [a bug](https://github.com/jonhoo/tokio-zookeeper/issues), 81 | //! and we'd love a bug report with as much relevant information as you can offer. 82 | //! 83 | //! Note that since this implementation is asynchronous, users of the client must take care to 84 | //! not re-order operations in their own code. There is some discussion of this in the [official 85 | //! documentation of the Java 86 | //! bindings](https://zookeeper.apache.org/doc/r3.4.12/zookeeperProgrammers.html#Java+Binding). 87 | //! 88 | //! For more information on ZooKeeper, see the [ZooKeeper Programmer's 89 | //! Guide](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html) and the [Confluence 90 | //! ZooKeeper wiki](https://cwiki.apache.org/confluence/display/ZOOKEEPER/Index). There is also a 91 | //! basic tutorial (that uses the Java client) 92 | //! [here](https://zookeeper.apache.org/doc/current/zookeeperTutorial.html). 93 | //! 94 | //! ## Interaction with Tokio 95 | //! 96 | //! The futures in this crate expect to be running under a `tokio::Runtime`. In the common case, 97 | //! you cannot resolve them solely using `.wait()`, but should instead use `tokio::run` or 98 | //! explicitly create a `tokio::Runtime` and then use `Runtime::block_on`. 99 | //! 100 | //! # A somewhat silly example 101 | //! 102 | //! ```no_run 103 | //! use tokio_zookeeper::*; 104 | //! use futures::prelude::*; 105 | //! 106 | //! # #[tokio::main(flavor = "current_thread")] 107 | //! # async fn main() { 108 | //! let (zk, default_watcher) = ZooKeeper::connect(&"127.0.0.1:2181".parse().unwrap()) 109 | //! .await 110 | //! .unwrap(); 111 | //! 112 | //! // let's first check if /example exists. the .watch() causes us to be notified 113 | //! // the next time the "exists" status of /example changes after the call. 114 | //! let stat = zk.watch().exists("/example").await.unwrap(); 115 | //! // initially, /example does not exist 116 | //! assert_eq!(stat, None); 117 | //! // so let's make it! 118 | //! let path = zk 119 | //! .create( 120 | //! "/example", 121 | //! &b"Hello world"[..], 122 | //! Acl::open_unsafe(), 123 | //! CreateMode::Persistent, 124 | //! ) 125 | //! .await 126 | //! .unwrap(); 127 | //! assert_eq!(path.as_deref(), Ok("/example")); 128 | //! 129 | //! // does it exist now? 130 | //! let stat = zk.watch().exists("/example").await.unwrap(); 131 | //! // looks like it! 132 | //! // note that the creation above also triggered our "exists" watch! 133 | //! assert_eq!(stat.unwrap().data_length as usize, b"Hello world".len()); 134 | //! 135 | //! // did the data get set correctly? 136 | //! let res = zk.get_data("/example").await.unwrap(); 137 | //! let data = b"Hello world"; 138 | //! let res = res.unwrap(); 139 | //! assert_eq!(res.0, data); 140 | //! assert_eq!(res.1.data_length as usize, data.len()); 141 | //! 142 | //! // let's update the data. 143 | //! let stat = zk 144 | //! .set_data("/example", Some(res.1.version), &b"Bye world"[..]) 145 | //! .await 146 | //! .unwrap(); 147 | //! assert_eq!(stat.unwrap().data_length as usize, "Bye world".len()); 148 | //! 149 | //! // create a child of /example 150 | //! let path = zk 151 | //! .create( 152 | //! "/example/more", 153 | //! &b"Hello more"[..], 154 | //! Acl::open_unsafe(), 155 | //! CreateMode::Persistent, 156 | //! ) 157 | //! .await 158 | //! .unwrap(); 159 | //! assert_eq!(path.as_deref(), Ok("/example/more")); 160 | //! 161 | //! // it should be visible as a child of /example 162 | //! let children = zk.get_children("/example").await.unwrap(); 163 | //! assert_eq!(children, Some(vec!["more".to_string()])); 164 | //! 165 | //! // it is not legal to delete a node that has children directly 166 | //! let res = zk.delete("/example", None).await.unwrap(); 167 | //! assert_eq!(res, Err(error::Delete::NotEmpty)); 168 | //! // instead we must delete the children first 169 | //! let res = zk.delete("/example/more", None).await.unwrap(); 170 | //! assert_eq!(res, Ok(())); 171 | //! let res = zk.delete("/example", None).await.unwrap(); 172 | //! assert_eq!(res, Ok(())); 173 | //! // no /example should no longer exist! 174 | //! let stat = zk.exists("/example").await.unwrap(); 175 | //! assert_eq!(stat, None); 176 | //! 177 | //! // now let's check that the .watch().exists we did in the very 178 | //! // beginning actually triggered! 179 | //! let (event, _w) = default_watcher.into_future().await; 180 | //! assert_eq!( 181 | //! event, 182 | //! Some(WatchedEvent { 183 | //! event_type: WatchedEventType::NodeCreated, 184 | //! keeper_state: KeeperState::SyncConnected, 185 | //! path: String::from("/example"), 186 | //! }) 187 | //! ); 188 | //! # } 189 | //! ``` 190 | 191 | #![deny(missing_docs)] 192 | #![deny(missing_debug_implementations)] 193 | #![deny(missing_copy_implementations)] 194 | 195 | use error::Error; 196 | use futures::{channel::oneshot, Stream}; 197 | use snafu::{whatever as bail, ResultExt}; 198 | use std::borrow::Cow; 199 | use std::net::SocketAddr; 200 | use std::time; 201 | use tracing::{debug, instrument, trace}; 202 | 203 | /// Per-operation ZooKeeper error types. 204 | pub mod error; 205 | mod proto; 206 | mod transform; 207 | mod types; 208 | 209 | use crate::proto::{Watch, ZkError}; 210 | pub use crate::types::{ 211 | Acl, CreateMode, KeeperState, MultiResponse, Permission, Stat, WatchedEvent, WatchedEventType, 212 | }; 213 | 214 | macro_rules! format_err { 215 | ($($x:tt)*) => { 216 | ::without_source(format!($($x)*)) 217 | }; 218 | } 219 | pub(crate) use format_err; 220 | 221 | /// A connection to ZooKeeper. 222 | /// 223 | /// All interactions with ZooKeeper are performed by calling the methods of a `ZooKeeper` instance. 224 | /// All clones of the same `ZooKeeper` instance use the same underlying connection. Once a 225 | /// connection to a server is established, a session ID is assigned to the client. The client will 226 | /// send heart beats to the server periodically to keep the session valid. 227 | /// 228 | /// The application can call ZooKeeper APIs through a client as long as the session ID of the 229 | /// client remains valid. If for some reason, the client fails to send heart beats to the server 230 | /// for a prolonged period of time (exceeding the session timeout value, for instance), the server 231 | /// will expire the session, and the session ID will become invalid. The `ZooKeeper` instance will 232 | /// then no longer be usable, and all futures will resolve with a protocol-level error. To make 233 | /// further ZooKeeper API calls, the application must create a new `ZooKeeper` instance. 234 | /// 235 | /// If the ZooKeeper server the client currently connects to fails or otherwise does not respond, 236 | /// the client will automatically try to connect to another server before its session ID expires. 237 | /// If successful, the application can continue to use the client. 238 | /// 239 | /// Some successful ZooKeeper API calls can leave watches on the "data nodes" in the ZooKeeper 240 | /// server. Other successful ZooKeeper API calls can trigger those watches. Once a watch is 241 | /// triggered, an event will be delivered to the client which left the watch at the first place. 242 | /// Each watch can be triggered only once. Thus, up to one event will be delivered to a client for 243 | /// every watch it leaves. 244 | #[derive(Debug, Clone)] 245 | pub struct ZooKeeper { 246 | #[allow(dead_code)] 247 | connection: proto::Enqueuer, 248 | } 249 | 250 | /// Builder that allows customizing options for ZooKeeper connections. 251 | #[derive(Debug, Copy, Clone)] 252 | pub struct ZooKeeperBuilder { 253 | session_timeout: time::Duration, 254 | } 255 | 256 | impl Default for ZooKeeperBuilder { 257 | fn default() -> Self { 258 | ZooKeeperBuilder { 259 | session_timeout: time::Duration::new(0, 0), 260 | } 261 | } 262 | } 263 | 264 | impl ZooKeeperBuilder { 265 | /// Connect to a ZooKeeper server instance at the given address. 266 | /// 267 | /// A `ZooKeeper` instance is returned, along with a "watcher" that will provide notifications 268 | /// of any changes in state. 269 | /// 270 | /// If the connection to the server fails, the client will automatically try to re-connect. 271 | /// Only if re-connection fails is an error returned to the client. Requests that are in-flight 272 | /// during a disconnect may fail and have to be retried. 273 | pub async fn connect( 274 | self, 275 | addr: &SocketAddr, 276 | ) -> Result<(ZooKeeper, impl Stream), Error> { 277 | let (tx, rx) = futures::channel::mpsc::unbounded(); 278 | let stream = tokio::net::TcpStream::connect(addr) 279 | .await 280 | .whatever_context("connect failed")?; 281 | Ok((self.handshake(*addr, stream, tx).await?, rx)) 282 | } 283 | 284 | /// Set the ZooKeeper [session expiry 285 | /// timeout](https://zookeeper.apache.org/doc/r3.4.12/zookeeperProgrammers.html#ch_zkSessions). 286 | /// 287 | /// The default timeout is dictated by the server. 288 | pub fn set_timeout(&mut self, t: time::Duration) { 289 | self.session_timeout = t; 290 | } 291 | 292 | async fn handshake( 293 | self, 294 | addr: SocketAddr, 295 | stream: tokio::net::TcpStream, 296 | default_watcher: futures::channel::mpsc::UnboundedSender, 297 | ) -> Result { 298 | let request = proto::Request::Connect { 299 | protocol_version: 0, 300 | last_zxid_seen: 0, 301 | timeout: (self.session_timeout.as_secs() * 1_000) as i32 302 | + self.session_timeout.subsec_millis() as i32, 303 | session_id: 0, 304 | passwd: vec![], 305 | read_only: false, 306 | }; 307 | debug!("about to perform handshake"); 308 | 309 | let enqueuer = proto::Packetizer::new(addr, stream, default_watcher); 310 | enqueuer.enqueue(request).await.map(move |response| { 311 | trace!(?response, "Got response"); 312 | ZooKeeper { 313 | connection: enqueuer, 314 | } 315 | }) 316 | } 317 | } 318 | 319 | impl ZooKeeper { 320 | /// Connect to a ZooKeeper server instance at the given address with default parameters. 321 | /// 322 | /// See [`ZooKeeperBuilder::connect`]. 323 | pub async fn connect( 324 | addr: &SocketAddr, 325 | ) -> Result<(Self, impl Stream), Error> { 326 | ZooKeeperBuilder::default().connect(addr).await 327 | } 328 | 329 | /// Create a node with the given `path` with `data` as its contents. 330 | /// 331 | /// The `mode` argument specifies additional options for the newly created node. 332 | /// 333 | /// If `mode` is set to [`CreateMode::Ephemeral`] (or [`CreateMode::EphemeralSequential`]), the 334 | /// node will be removed by the ZooKeeper automatically when the session associated with the 335 | /// creation of the node expires. 336 | /// 337 | /// If `mode` is set to [`CreateMode::PersistentSequential`] or 338 | /// [`CreateMode::EphemeralSequential`], the actual path name of a sequential node will be the 339 | /// given `path` plus a suffix `i` where `i` is the current sequential number of the node. The 340 | /// sequence number is always fixed length of 10 digits, 0 padded. Once such a node is created, 341 | /// the sequential number will be incremented by one. The newly created node's full name is 342 | /// returned when the future is resolved. 343 | /// 344 | /// If a node with the same actual path already exists in the ZooKeeper, the returned future 345 | /// resolves with an error of [`error::Create::NodeExists`]. Note that since a different actual 346 | /// path is used for each invocation of creating sequential nodes with the same `path` 347 | /// argument, calls with sequential modes will never return `NodeExists`. 348 | /// 349 | /// Ephemeral nodes cannot have children in ZooKeeper. Therefore, if the parent node of the 350 | /// given `path` is ephemeral, the return future resolves to 351 | /// [`error::Create::NoChildrenForEphemerals`]. 352 | /// 353 | /// If a node is created successfully, the ZooKeeper server will trigger the watches on the 354 | /// `path` left by `exists` calls, and the watches on the parent of the node by `get_children` 355 | /// calls. 356 | /// 357 | /// The maximum allowable size of the data array is 1 MB (1,048,576 bytes). 358 | #[instrument(skip(data, acl))] 359 | pub async fn create( 360 | &self, 361 | path: &str, 362 | data: D, 363 | acl: A, 364 | mode: CreateMode, 365 | ) -> Result, Error> 366 | where 367 | D: Into>, 368 | A: Into>, 369 | { 370 | let data = data.into(); 371 | tracing::Span::current().record("dlen", data.len()); 372 | self.connection 373 | .enqueue(proto::Request::Create { 374 | path: path.to_string(), 375 | data, 376 | acl: acl.into(), 377 | mode, 378 | }) 379 | .await 380 | .and_then(transform::create) 381 | } 382 | 383 | /// Set the data for the node at the given `path`. 384 | /// 385 | /// The call will succeed if such a node exists, and the given `version` matches the version of 386 | /// the node (if the given `version` is `None`, it matches any version). On success, the 387 | /// updated [`Stat`] of the node is returned. 388 | /// 389 | /// This operation, if successful, will trigger all the watches on the node of the given `path` 390 | /// left by `get_data` calls. 391 | /// 392 | /// The maximum allowable size of the data array is 1 MB (1,048,576 bytes). 393 | #[instrument(skip(data))] 394 | pub async fn set_data( 395 | &self, 396 | path: &str, 397 | version: Option, 398 | data: D, 399 | ) -> Result, Error> 400 | where 401 | D: Into>, 402 | { 403 | let data = data.into(); 404 | tracing::Span::current().record("dlen", data.len()); 405 | let version = version.unwrap_or(-1); 406 | self.connection 407 | .enqueue(proto::Request::SetData { 408 | path: path.to_string(), 409 | version, 410 | data, 411 | }) 412 | .await 413 | .and_then(move |r| transform::set_data(version, r)) 414 | } 415 | 416 | /// Delete the node at the given `path`. 417 | /// 418 | /// The call will succeed if such a node exists, and the given `version` matches the node's 419 | /// version (if the given `version` is `None`, it matches any versions). 420 | /// 421 | /// This operation, if successful, will trigger all the watches on the node of the given `path` 422 | /// left by `exists` API calls, and the watches on the parent node left by `get_children` API 423 | /// calls. 424 | #[instrument] 425 | pub async fn delete( 426 | &self, 427 | path: &str, 428 | version: Option, 429 | ) -> Result, Error> { 430 | let version = version.unwrap_or(-1); 431 | self.connection 432 | .enqueue(proto::Request::Delete { 433 | path: path.to_string(), 434 | version, 435 | }) 436 | .await 437 | .and_then(move |r| transform::delete(version, r)) 438 | } 439 | 440 | /// Return the [ACL](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_ZooKeeperAccessControl) 441 | /// and Stat of the node at the given `path`. 442 | /// 443 | /// If no node exists for the given path, the returned future resolves with an error of 444 | /// [`error::GetAcl::NoNode`]. 445 | #[instrument] 446 | pub async fn get_acl( 447 | &self, 448 | path: &str, 449 | ) -> Result, Stat), error::GetAcl>, Error> { 450 | self.connection 451 | .enqueue(proto::Request::GetAcl { 452 | path: path.to_string(), 453 | }) 454 | .await 455 | .and_then(transform::get_acl) 456 | } 457 | 458 | /// Set the [ACL](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_ZooKeeperAccessControl) 459 | /// for the node of the given `path`. 460 | /// 461 | /// The call will succeed if such a node exists and the given `version` matches the ACL version 462 | /// of the node. On success, the updated [`Stat`] of the node is returned. 463 | /// 464 | /// If no node exists for the given path, the returned future resolves with an error of 465 | /// [`error::SetAcl::NoNode`]. If the given `version` does not match the ACL version, the 466 | /// returned future resolves with an error of [`error::SetAcl::BadVersion`]. 467 | #[instrument(skip(acl))] 468 | pub async fn set_acl( 469 | &self, 470 | path: &str, 471 | acl: A, 472 | version: Option, 473 | ) -> Result, Error> 474 | where 475 | A: Into>, 476 | { 477 | let version = version.unwrap_or(-1); 478 | self.connection 479 | .enqueue(proto::Request::SetAcl { 480 | path: path.to_string(), 481 | acl: acl.into(), 482 | version, 483 | }) 484 | .await 485 | .and_then(move |r| transform::set_acl(version, r)) 486 | } 487 | } 488 | 489 | impl ZooKeeper { 490 | /// Add a global watch for the next chained operation. 491 | pub fn watch(&self) -> WatchGlobally { 492 | WatchGlobally(self) 493 | } 494 | 495 | /// Add a watch for the next chained operation, and return a future for any received event 496 | /// along with the operation's (successful) result. 497 | pub fn with_watcher(&self) -> WithWatcher { 498 | WithWatcher(self) 499 | } 500 | 501 | #[instrument(name = "exists")] 502 | async fn exists_w(&self, path: &str, watch: Watch) -> Result, Error> { 503 | self.connection 504 | .enqueue(proto::Request::Exists { 505 | path: path.to_string(), 506 | watch, 507 | }) 508 | .await 509 | .and_then(transform::exists) 510 | } 511 | 512 | /// Return the [`Stat`] of the node of the given `path`, or `None` if the node does not exist. 513 | pub async fn exists(&self, path: &str) -> Result, Error> { 514 | self.exists_w(path, Watch::None).await 515 | } 516 | 517 | #[instrument] 518 | async fn get_children_w(&self, path: &str, watch: Watch) -> Result>, Error> { 519 | self.connection 520 | .enqueue(proto::Request::GetChildren { 521 | path: path.to_string(), 522 | watch, 523 | }) 524 | .await 525 | .and_then(transform::get_children) 526 | } 527 | 528 | /// Return the names of the children of the node at the given `path`, or `None` if the node 529 | /// does not exist. 530 | /// 531 | /// The returned list of children is not sorted and no guarantee is provided as to its natural 532 | /// or lexical order. 533 | pub async fn get_children(&self, path: &str) -> Result>, Error> { 534 | self.get_children_w(path, Watch::None).await 535 | } 536 | 537 | #[instrument] 538 | async fn get_data_w(&self, path: &str, watch: Watch) -> Result, Stat)>, Error> { 539 | self.connection 540 | .enqueue(proto::Request::GetData { 541 | path: path.to_string(), 542 | watch, 543 | }) 544 | .await 545 | .and_then(transform::get_data) 546 | } 547 | 548 | /// Return the data and the [`Stat`] of the node at the given `path`, or `None` if it does not 549 | /// exist. 550 | pub async fn get_data(&self, path: &str) -> Result, Stat)>, Error> { 551 | self.get_data_w(path, Watch::None).await 552 | } 553 | 554 | /// Start building a multi request. Multi requests batch several operations 555 | /// into one atomic unit. 556 | pub fn multi(&self) -> MultiBuilder { 557 | MultiBuilder { 558 | zk: self, 559 | requests: Vec::new(), 560 | } 561 | } 562 | } 563 | 564 | /// Proxy for [`ZooKeeper`] that adds watches for initiated operations. 565 | /// 566 | /// Triggered watches produce events on the global watcher stream. 567 | #[derive(Debug, Clone)] 568 | pub struct WatchGlobally<'a>(&'a ZooKeeper); 569 | 570 | impl<'a> WatchGlobally<'a> { 571 | /// Return the [`Stat`] of the node of the given `path`, or `None` if the node does not exist. 572 | /// 573 | /// If no errors occur, a watch is left on the node at the given `path`. The watch is triggered 574 | /// by any successful operation that creates or deletes the node, or sets the node's data. When 575 | /// the watch triggers, an event is sent to the global watcher stream. 576 | pub async fn exists(&self, path: &str) -> Result, Error> { 577 | self.0.exists_w(path, Watch::Global).await 578 | } 579 | 580 | /// Return the names of the children of the node at the given `path`, or `None` if the node 581 | /// does not exist. 582 | /// 583 | /// The returned list of children is not sorted and no guarantee is provided as to its natural 584 | /// or lexical order. 585 | /// 586 | /// If no errors occur, a watch is left on the node at the given `path`. The watch is triggered 587 | /// by any successful operation that deletes the node at the given `path`, or creates or 588 | /// deletes a child of that node. When the watch triggers, an event is sent to the global 589 | /// watcher stream. 590 | pub async fn get_children(&self, path: &str) -> Result>, Error> { 591 | self.0.get_children_w(path, Watch::Global).await 592 | } 593 | 594 | /// Return the data and the [`Stat`] of the node at the given `path`, or `None` if it does not 595 | /// exist. 596 | /// 597 | /// If no errors occur, a watch is left on the node at the given `path`. The watch is triggered 598 | /// by any successful operation that sets the node's data, or deletes it. When the watch 599 | /// triggers, an event is sent to the global watcher stream. 600 | pub async fn get_data(&self, path: &str) -> Result, Stat)>, Error> { 601 | self.0.get_data_w(path, Watch::Global).await 602 | } 603 | } 604 | 605 | /// Proxy for [`ZooKeeper`] that adds non-global watches for initiated operations. 606 | /// 607 | /// Events from triggered watches are yielded through returned `oneshot` channels. All events are 608 | /// also produced on the global watcher stream. 609 | #[derive(Debug, Clone)] 610 | pub struct WithWatcher<'a>(&'a ZooKeeper); 611 | 612 | impl<'a> WithWatcher<'a> { 613 | /// Return the [`Stat`] of the node of the given `path`, or `None` if the node does not exist. 614 | /// 615 | /// If no errors occur, a watch will be left on the node at the given `path`. The watch is 616 | /// triggered by any successful operation that creates or deletes the node, or sets the data on 617 | /// the node, and in turn causes the included `oneshot::Receiver` to resolve. 618 | pub async fn exists( 619 | &self, 620 | path: &str, 621 | ) -> Result<(oneshot::Receiver, Option), Error> { 622 | let (tx, rx) = oneshot::channel(); 623 | self.0 624 | .exists_w(path, Watch::Custom(tx)) 625 | .await 626 | .map(|r| (rx, r)) 627 | } 628 | 629 | /// Return the names of the children of the node at the given `path`, or `None` if the node 630 | /// does not exist. 631 | /// 632 | /// The returned list of children is not sorted and no guarantee is provided as to its natural 633 | /// or lexical order. 634 | /// 635 | /// If no errors occur, a watch is left on the node at the given `path`. The watch is triggered 636 | /// by any successful operation that deletes the node at the given `path`, or creates or 637 | /// deletes a child of that node, and in turn causes the included `oneshot::Receiver` to 638 | /// resolve. 639 | pub async fn get_children( 640 | &self, 641 | path: &str, 642 | ) -> Result, Vec)>, Error> { 643 | let (tx, rx) = oneshot::channel(); 644 | self.0 645 | .get_children_w(path, Watch::Custom(tx)) 646 | .await 647 | .map(|r| (r.map(move |c| (rx, c)))) 648 | } 649 | 650 | /// Return the data and the [`Stat`] of the node at the given `path`, or `None` if it does not 651 | /// exist. 652 | /// 653 | /// If no errors occur, a watch is left on the node at the given `path`. The watch is triggered 654 | /// by any successful operation that sets the node's data, or deletes it, and in turn causes 655 | /// the included `oneshot::Receiver` to resolve. 656 | pub async fn get_data( 657 | &self, 658 | path: &str, 659 | ) -> Result, Vec, Stat)>, Error> { 660 | let (tx, rx) = oneshot::channel(); 661 | self.0 662 | .get_data_w(path, Watch::Custom(tx)) 663 | .await 664 | .map(|r| (r.map(move |(b, s)| (rx, b, s)))) 665 | } 666 | } 667 | 668 | /// Proxy for [`ZooKeeper`] that batches operations into an atomic "multi" request. 669 | #[derive(Debug)] 670 | pub struct MultiBuilder<'a> { 671 | zk: &'a ZooKeeper, 672 | requests: Vec, 673 | } 674 | 675 | impl<'a> MultiBuilder<'a> { 676 | /// Attach a create operation to this multi request. 677 | /// 678 | /// See [`ZooKeeper::create`] for details. 679 | pub fn create(mut self, path: &str, data: D, acl: A, mode: CreateMode) -> Self 680 | where 681 | D: Into>, 682 | A: Into>, 683 | { 684 | self.requests.push(proto::Request::Create { 685 | path: path.to_string(), 686 | data: data.into(), 687 | acl: acl.into(), 688 | mode, 689 | }); 690 | self 691 | } 692 | 693 | /// Attach a set data operation to this multi request. 694 | /// 695 | /// See [`ZooKeeper::set_data`] for details. 696 | pub fn set_data(mut self, path: &str, version: Option, data: D) -> Self 697 | where 698 | D: Into>, 699 | { 700 | self.requests.push(proto::Request::SetData { 701 | path: path.to_string(), 702 | version: version.unwrap_or(-1), 703 | data: data.into(), 704 | }); 705 | self 706 | } 707 | 708 | /// Attach a delete operation to this multi request. 709 | /// 710 | /// See [`ZooKeeper::delete`] for details. 711 | pub fn delete(mut self, path: &str, version: Option) -> Self { 712 | self.requests.push(proto::Request::Delete { 713 | path: path.to_string(), 714 | version: version.unwrap_or(-1), 715 | }); 716 | self 717 | } 718 | 719 | /// Attach a check operation to this multi request. 720 | /// 721 | /// There is no equivalent to the check operation outside of a multi 722 | /// request. 723 | pub fn check(mut self, path: &str, version: i32) -> Self { 724 | self.requests.push(proto::Request::Check { 725 | path: path.to_string(), 726 | version, 727 | }); 728 | self 729 | } 730 | 731 | /// Run executes the attached requests in one atomic unit. 732 | pub async fn run(self) -> Result>, Error> { 733 | let (zk, requests) = (self.zk, self.requests); 734 | let reqs_lite: Vec = requests.iter().map(|r| r.into()).collect(); 735 | zk.connection 736 | .enqueue(proto::Request::Multi(requests)) 737 | .await 738 | .and_then(move |r| match r { 739 | Ok(proto::Response::Multi(responses)) => reqs_lite 740 | .iter() 741 | .zip(responses) 742 | .map(|(req, res)| transform::multi(req, res)) 743 | .collect(), 744 | Ok(r) => bail!("got non-multi response to multi: {:?}", r), 745 | Err(e) => Err(format_err!("multi call failed: {:?}", e)), 746 | }) 747 | } 748 | } 749 | 750 | #[cfg(test)] 751 | mod tests { 752 | 753 | use super::*; 754 | 755 | use futures::StreamExt; 756 | use tracing::Level; 757 | 758 | fn init_tracing_subscriber() { 759 | let _ = tracing_subscriber::fmt() 760 | .with_max_level(Level::DEBUG) 761 | .try_init(); 762 | } 763 | 764 | #[tokio::test] 765 | async fn it_works() { 766 | init_tracing_subscriber(); 767 | let builder = ZooKeeperBuilder::default(); 768 | 769 | let (zk, w) = builder 770 | .connect(&"127.0.0.1:2181".parse().unwrap()) 771 | .await 772 | .unwrap(); 773 | let (exists_w, stat) = zk.with_watcher().exists("/foo").await.unwrap(); 774 | assert_eq!(stat, None); 775 | let stat = zk.watch().exists("/foo").await.unwrap(); 776 | assert_eq!(stat, None); 777 | let path = zk 778 | .create( 779 | "/foo", 780 | &b"Hello world"[..], 781 | Acl::open_unsafe(), 782 | CreateMode::Persistent, 783 | ) 784 | .await 785 | .unwrap(); 786 | assert_eq!(path.as_ref().map(String::as_str), Ok("/foo")); 787 | let event = exists_w.await.expect("exists_w failed"); 788 | assert_eq!( 789 | event, 790 | WatchedEvent { 791 | event_type: WatchedEventType::NodeCreated, 792 | keeper_state: KeeperState::SyncConnected, 793 | path: String::from("/foo"), 794 | } 795 | ); 796 | let stat = zk.watch().exists("/foo").await.unwrap(); 797 | assert_eq!(stat.unwrap().data_length as usize, b"Hello world".len()); 798 | let res = zk.get_acl("/foo").await.unwrap(); 799 | let (acl, _) = res.unwrap(); 800 | assert_eq!(acl, Acl::open_unsafe()); 801 | let res = zk.get_data("/foo").await.unwrap(); 802 | let data = b"Hello world"; 803 | let res = res.unwrap(); 804 | assert_eq!(res.0, data); 805 | assert_eq!(res.1.data_length as usize, data.len()); 806 | let stat = zk 807 | .set_data("/foo", Some(res.1.version), &b"Bye world"[..]) 808 | .await 809 | .unwrap(); 810 | assert_eq!(stat.unwrap().data_length as usize, "Bye world".len()); 811 | let res = zk.get_data("/foo").await.unwrap(); 812 | let data = b"Bye world"; 813 | let res = res.unwrap(); 814 | assert_eq!(res.0, data); 815 | assert_eq!(res.1.data_length as usize, data.len()); 816 | let path = zk 817 | .create( 818 | "/foo/bar", 819 | &b"Hello bar"[..], 820 | Acl::open_unsafe(), 821 | CreateMode::Persistent, 822 | ) 823 | .await 824 | .unwrap(); 825 | assert_eq!(path.as_deref(), Ok("/foo/bar")); 826 | let children = zk.get_children("/foo").await.unwrap(); 827 | assert_eq!(children, Some(vec!["bar".to_string()])); 828 | let res = zk.get_data("/foo/bar").await.unwrap(); 829 | let data = b"Hello bar"; 830 | let res = res.unwrap(); 831 | assert_eq!(res.0, data); 832 | assert_eq!(res.1.data_length as usize, data.len()); 833 | // add a new exists watch so we'll get notified of delete 834 | let _ = zk.watch().exists("/foo").await.unwrap(); 835 | let res = zk.delete("/foo", None).await.unwrap(); 836 | assert_eq!(res, Err(error::Delete::NotEmpty)); 837 | let res = zk.delete("/foo/bar", None).await.unwrap(); 838 | assert_eq!(res, Ok(())); 839 | let res = zk.delete("/foo", None).await.unwrap(); 840 | assert_eq!(res, Ok(())); 841 | let stat = zk.watch().exists("/foo").await.unwrap(); 842 | assert_eq!(stat, None); 843 | let (event, w) = w.into_future().await; 844 | assert_eq!( 845 | event, 846 | Some(WatchedEvent { 847 | event_type: WatchedEventType::NodeCreated, 848 | keeper_state: KeeperState::SyncConnected, 849 | path: String::from("/foo"), 850 | }) 851 | ); 852 | let (event, w) = w.into_future().await; 853 | assert_eq!( 854 | event, 855 | Some(WatchedEvent { 856 | event_type: WatchedEventType::NodeDataChanged, 857 | keeper_state: KeeperState::SyncConnected, 858 | path: String::from("/foo"), 859 | }) 860 | ); 861 | let (event, w) = w.into_future().await; 862 | assert_eq!( 863 | event, 864 | Some(WatchedEvent { 865 | event_type: WatchedEventType::NodeDeleted, 866 | keeper_state: KeeperState::SyncConnected, 867 | path: String::from("/foo"), 868 | }) 869 | ); 870 | 871 | drop(zk); // make Packetizer idle 872 | assert_eq!(w.count().await, 0); 873 | } 874 | 875 | #[tokio::test] 876 | async fn example() { 877 | let (zk, default_watcher) = ZooKeeper::connect(&"127.0.0.1:2181".parse().unwrap()) 878 | .await 879 | .unwrap(); 880 | 881 | // let's first check if /example exists. the .watch() causes us to be notified 882 | // the next time the "exists" status of /example changes after the call. 883 | let stat = zk.watch().exists("/example").await.unwrap(); 884 | // initially, /example does not exist 885 | assert_eq!(stat, None); 886 | // so let's make it! 887 | let path = zk 888 | .create( 889 | "/example", 890 | &b"Hello world"[..], 891 | Acl::open_unsafe(), 892 | CreateMode::Persistent, 893 | ) 894 | .await 895 | .unwrap(); 896 | assert_eq!(path.as_deref(), Ok("/example")); 897 | 898 | // does it exist now? 899 | let stat = zk.watch().exists("/example").await.unwrap(); 900 | // looks like it! 901 | // note that the creation above also triggered our "exists" watch! 902 | assert_eq!(stat.unwrap().data_length as usize, b"Hello world".len()); 903 | 904 | // did the data get set correctly? 905 | let res = zk.get_data("/example").await.unwrap(); 906 | let data = b"Hello world"; 907 | let res = res.unwrap(); 908 | assert_eq!(res.0, data); 909 | assert_eq!(res.1.data_length as usize, data.len()); 910 | 911 | // let's update the data. 912 | let stat = zk 913 | .set_data("/example", Some(res.1.version), &b"Bye world"[..]) 914 | .await 915 | .unwrap(); 916 | assert_eq!(stat.unwrap().data_length as usize, "Bye world".len()); 917 | 918 | // create a child of /example 919 | let path = zk 920 | .create( 921 | "/example/more", 922 | &b"Hello more"[..], 923 | Acl::open_unsafe(), 924 | CreateMode::Persistent, 925 | ) 926 | .await 927 | .unwrap(); 928 | assert_eq!(path.as_deref(), Ok("/example/more")); 929 | 930 | // it should be visible as a child of /example 931 | let children = zk.get_children("/example").await.unwrap(); 932 | assert_eq!(children, Some(vec!["more".to_string()])); 933 | 934 | // it is not legal to delete a node that has children directly 935 | let res = zk.delete("/example", None).await.unwrap(); 936 | assert_eq!(res, Err(error::Delete::NotEmpty)); 937 | // instead we must delete the children first 938 | let res = zk.delete("/example/more", None).await.unwrap(); 939 | assert_eq!(res, Ok(())); 940 | let res = zk.delete("/example", None).await.unwrap(); 941 | assert_eq!(res, Ok(())); 942 | // no /example should no longer exist! 943 | let stat = zk.exists("/example").await.unwrap(); 944 | assert_eq!(stat, None); 945 | 946 | // now let's check that the .watch().exists we did in the very 947 | // beginning actually triggered! 948 | let (event, _w) = default_watcher.into_future().await; 949 | assert_eq!( 950 | event, 951 | Some(WatchedEvent { 952 | event_type: WatchedEventType::NodeCreated, 953 | keeper_state: KeeperState::SyncConnected, 954 | path: String::from("/example"), 955 | }) 956 | ); 957 | } 958 | 959 | #[tokio::test] 960 | async fn acl_test() { 961 | init_tracing_subscriber(); 962 | let builder = ZooKeeperBuilder::default(); 963 | 964 | let (zk, _) = (builder.connect(&"127.0.0.1:2181".parse().unwrap())) 965 | .await 966 | .unwrap(); 967 | let _ = zk 968 | .create( 969 | "/acl_test", 970 | &b"foo"[..], 971 | Acl::open_unsafe(), 972 | CreateMode::Ephemeral, 973 | ) 974 | .await 975 | .unwrap(); 976 | 977 | let res = zk.get_acl("/acl_test").await.unwrap(); 978 | let res = res.unwrap(); 979 | assert_eq!(res.0, Acl::open_unsafe()); 980 | 981 | let res = zk 982 | .set_acl("/acl_test", Acl::creator_all(), Some(res.1.version)) 983 | .await 984 | .unwrap(); 985 | // a not authenticated user is not able to set `auth` scheme acls. 986 | assert_eq!(res, Err(error::SetAcl::InvalidAcl)); 987 | 988 | let stat = zk 989 | .set_acl("/acl_test", Acl::read_unsafe(), None) 990 | .await 991 | .unwrap(); 992 | // successfully change node acl to `read_unsafe` 993 | assert_eq!(stat.unwrap().data_length as usize, b"foo".len()); 994 | 995 | let res = zk.get_acl("/acl_test").await.unwrap(); 996 | let res = res.unwrap(); 997 | assert_eq!(res.0, Acl::read_unsafe()); 998 | 999 | let res = zk.set_data("/acl_test", None, &b"bar"[..]).await.unwrap(); 1000 | // cannot set data on a read only node 1001 | assert_eq!(res, Err(error::SetData::NoAuth)); 1002 | 1003 | let res = zk 1004 | .set_acl("/acl_test", Acl::open_unsafe(), None) 1005 | .await 1006 | .unwrap(); 1007 | // cannot change a read only node's acl 1008 | assert_eq!(res, Err(error::SetAcl::NoAuth)); 1009 | 1010 | drop(zk); // make Packetizer idle 1011 | } 1012 | 1013 | #[tokio::test] 1014 | async fn multi_test() { 1015 | init_tracing_subscriber(); 1016 | let builder = ZooKeeperBuilder::default(); 1017 | 1018 | async fn check_exists(zk: &ZooKeeper, paths: &[&str]) -> Result, Error> { 1019 | let mut res = Vec::new(); 1020 | for p in paths { 1021 | let exists = zk.exists(p).await?; 1022 | res.push(exists.is_some()); 1023 | } 1024 | Result::<_, Error>::Ok(res) 1025 | } 1026 | 1027 | let (zk, _) = builder 1028 | .connect(&"127.0.0.1:2181".parse().unwrap()) 1029 | .await 1030 | .unwrap(); 1031 | 1032 | let res = zk 1033 | .multi() 1034 | .create("/b", &b"a"[..], Acl::open_unsafe(), CreateMode::Persistent) 1035 | .create("/c", &b"b"[..], Acl::open_unsafe(), CreateMode::Persistent) 1036 | .run() 1037 | .await 1038 | .unwrap(); 1039 | assert_eq!( 1040 | res, 1041 | [ 1042 | Ok(MultiResponse::Create("/b".into())), 1043 | Ok(MultiResponse::Create("/c".into())) 1044 | ] 1045 | ); 1046 | 1047 | let res = check_exists(&zk, &["/a", "/b", "/c", "/d"]).await.unwrap(); 1048 | assert_eq!(res, &[false, true, true, false]); 1049 | 1050 | let res = zk 1051 | .multi() 1052 | .create("/a", &b"a"[..], Acl::open_unsafe(), CreateMode::Persistent) 1053 | .create("/b", &b"b"[..], Acl::open_unsafe(), CreateMode::Persistent) 1054 | .create("/c", &b"b"[..], Acl::open_unsafe(), CreateMode::Persistent) 1055 | .create("/d", &b"a"[..], Acl::open_unsafe(), CreateMode::Persistent) 1056 | .run() 1057 | .await 1058 | .unwrap(); 1059 | assert_eq!( 1060 | res, 1061 | &[ 1062 | Err(error::Multi::RolledBack), 1063 | Err(error::Multi::Create { 1064 | source: error::Create::NodeExists 1065 | }), 1066 | Err(error::Multi::Skipped), 1067 | Err(error::Multi::Skipped), 1068 | ] 1069 | ); 1070 | 1071 | let res = check_exists(&zk, &["/a", "/b", "/c", "/d"]).await.unwrap(); 1072 | assert_eq!(res, &[false, true, true, false]); 1073 | 1074 | let res = zk 1075 | .multi() 1076 | .set_data("/b", None, &b"garbaggio"[..]) 1077 | .run() 1078 | .await 1079 | .unwrap(); 1080 | match res[0] { 1081 | Ok(MultiResponse::SetData(stat)) => { 1082 | assert_eq!(stat.data_length as usize, "garbaggio".len()) 1083 | } 1084 | _ => panic!("unexpected response: {res:?}"), 1085 | } 1086 | 1087 | let res = zk 1088 | .multi() 1089 | .check("/b", 0) 1090 | .delete("/c", None) 1091 | .run() 1092 | .await 1093 | .unwrap(); 1094 | assert_eq!( 1095 | res, 1096 | [ 1097 | Err(error::Multi::Check { 1098 | source: error::Check::BadVersion { expected: 0 } 1099 | }), 1100 | Err(error::Multi::Skipped), 1101 | ] 1102 | ); 1103 | 1104 | let res = check_exists(&zk, &["/a", "/b", "/c", "/d"]).await.unwrap(); 1105 | assert_eq!(res, &[false, true, true, false]); 1106 | let res = zk.multi().check("/a", 0).run().await.unwrap(); 1107 | assert_eq!( 1108 | res, 1109 | &[Err(error::Multi::Check { 1110 | source: error::Check::NoNode 1111 | }),] 1112 | ); 1113 | 1114 | let res = zk 1115 | .multi() 1116 | .check("/b", 1) 1117 | .delete("/b", None) 1118 | .check("/c", 0) 1119 | .delete("/c", None) 1120 | .run() 1121 | .await 1122 | .unwrap(); 1123 | assert_eq!( 1124 | res, 1125 | [ 1126 | Ok(MultiResponse::Check), 1127 | Ok(MultiResponse::Delete), 1128 | Ok(MultiResponse::Check), 1129 | Ok(MultiResponse::Delete), 1130 | ] 1131 | ); 1132 | 1133 | let res = check_exists(&zk, &["/a", "/b", "/c", "/d"]).await.unwrap(); 1134 | assert_eq!(res, [false, false, false, false]); 1135 | 1136 | drop(zk); // make Packetizer idle 1137 | } 1138 | } 1139 | -------------------------------------------------------------------------------- /src/proto/active_packetizer.rs: -------------------------------------------------------------------------------- 1 | use super::{request, watch::WatchType, Request, Response}; 2 | use crate::{error::Error as DynError, WatchedEvent, WatchedEventType, ZkError}; 3 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 4 | use futures::{ 5 | channel::{mpsc, oneshot}, 6 | ready, 7 | }; 8 | use pin_project::pin_project; 9 | use snafu::Snafu; 10 | use std::collections::HashMap; 11 | use std::{ 12 | future::Future, 13 | mem, 14 | pin::Pin, 15 | task::{Context, Poll}, 16 | time, 17 | }; 18 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 19 | use tracing::{debug, info, instrument, trace}; 20 | 21 | #[derive(Debug, Snafu)] 22 | pub enum Error { 23 | #[snafu(transparent)] 24 | Io { source: std::io::Error }, 25 | #[snafu(transparent)] 26 | Whatever { source: DynError }, 27 | 28 | #[snafu(display("connection closed with {len} bytes left in buffer: {buf:x?}", len = buf.len()))] 29 | ConnectionClosed { buf: Vec }, 30 | 31 | #[snafu(display("Not exiting, but server closed connection"))] 32 | ServerClosedConnection, 33 | #[snafu(display("outstanding requests, but response channel closed"))] 34 | ResponseChannelClosedPrematurely, 35 | 36 | #[snafu(display("bad response to ping: {error_code:?}"))] 37 | BadPing { error_code: ZkError }, 38 | #[snafu(display("failed to close session: {error_code:?}"))] 39 | CloseSession { error_code: ZkError }, 40 | } 41 | 42 | #[pin_project] 43 | pub(super) struct ActivePacketizer { 44 | #[pin] 45 | stream: S, 46 | 47 | /// Heartbeat timer, 48 | #[pin] 49 | timer: tokio::time::Sleep, 50 | timeout: time::Duration, 51 | 52 | /// Bytes we have not yet set. 53 | pub(super) outbox: Vec, 54 | 55 | /// Prefix of outbox that has been sent. 56 | outstart: usize, 57 | 58 | /// Bytes we have not yet deserialized. 59 | inbox: Vec, 60 | 61 | /// Prefix of inbox that has been sent. 62 | instart: usize, 63 | 64 | /// What operation are we waiting for a response for? 65 | reply: HashMap>)>, 66 | 67 | /// Custom registered watchers (path -> watcher) 68 | watchers: HashMap, WatchType)>>, 69 | 70 | /// Custom registered watchers (xid -> watcher to add when ok) 71 | pub(super) pending_watchers: HashMap, WatchType)>, 72 | 73 | first: bool, 74 | 75 | /// Fields for re-connection 76 | pub(super) last_zxid_seen: i64, 77 | pub(super) session_id: i64, 78 | pub(super) password: Vec, 79 | } 80 | 81 | type ReplySender = oneshot::Sender>; 82 | 83 | impl ActivePacketizer 84 | where 85 | S: AsyncRead + AsyncWrite, 86 | { 87 | pub(super) fn new(stream: S) -> Self { 88 | ActivePacketizer { 89 | stream, 90 | timer: tokio::time::sleep(time::Duration::from_secs(86_400)), 91 | timeout: time::Duration::new(86_400, 0), 92 | outbox: Vec::new(), 93 | outstart: 0, 94 | inbox: Vec::new(), 95 | instart: 0, 96 | reply: Default::default(), 97 | watchers: Default::default(), 98 | pending_watchers: Default::default(), 99 | first: true, 100 | 101 | last_zxid_seen: 0, 102 | session_id: 0, 103 | password: Vec::new(), 104 | } 105 | } 106 | 107 | pub(super) fn outbox(self: Pin<&mut Self>) -> &mut Vec { 108 | self.project().outbox 109 | } 110 | pub(super) fn take_password(self: Pin<&mut Self>) -> Vec { 111 | self.project().password.split_off(0) 112 | } 113 | pub(super) fn pending_watchers( 114 | self: Pin<&mut Self>, 115 | ) -> &mut HashMap, WatchType)> { 116 | self.project().pending_watchers 117 | } 118 | 119 | fn outlen(&self) -> usize { 120 | self.outbox.len() - self.outstart 121 | } 122 | 123 | fn inlen(&self) -> usize { 124 | self.inbox.len() - self.instart 125 | } 126 | 127 | #[allow(clippy::type_complexity)] 128 | fn enqueue_impl( 129 | outbox: &mut Vec, 130 | reply: &mut HashMap, 131 | xid: i32, 132 | item: Request, 133 | tx: oneshot::Sender>, 134 | ) { 135 | let lengthi = outbox.len(); 136 | // dummy length 137 | outbox.push(0); 138 | outbox.push(0); 139 | outbox.push(0); 140 | outbox.push(0); 141 | 142 | let old = reply.insert(xid, (item.opcode(), tx)); 143 | assert!(old.is_none()); 144 | 145 | if let Request::Connect { .. } = item { 146 | } else { 147 | // xid 148 | outbox 149 | .write_i32::(xid) 150 | .expect("Vec::write should never fail"); 151 | // opcode 152 | outbox 153 | .write_i32::(item.opcode() as i32) 154 | .expect("Vec::write should never fail"); 155 | } 156 | 157 | // type and payload 158 | item.serialize_into(outbox) 159 | .expect("Vec::write should never fail"); 160 | // set true length 161 | let written = outbox.len() - lengthi - 4; 162 | let mut length = &mut outbox[lengthi..lengthi + 4]; 163 | length 164 | .write_i32::(written as i32) 165 | .expect("Vec::write should never fail"); 166 | } 167 | pub(super) fn enqueue( 168 | self: Pin<&mut Self>, 169 | xid: i32, 170 | item: Request, 171 | tx: oneshot::Sender>, 172 | ) { 173 | let this = self.project(); 174 | Self::enqueue_impl(this.outbox, this.reply, xid, item, tx) 175 | } 176 | pub(super) fn enqueue_unpin( 177 | &mut self, 178 | xid: i32, 179 | item: Request, 180 | tx: oneshot::Sender>, 181 | ) { 182 | Self::enqueue_impl(&mut self.outbox, &mut self.reply, xid, item, tx) 183 | } 184 | 185 | #[instrument(skip(self, cx))] 186 | fn poll_write( 187 | mut self: Pin<&mut Self>, 188 | cx: &mut Context, 189 | exiting: bool, 190 | ) -> Poll> 191 | where 192 | S: AsyncWrite, 193 | { 194 | let mut wrote = false; 195 | while self.outlen() != 0 { 196 | let mut this = self.as_mut().project(); 197 | let n = ready!(this 198 | .stream 199 | .as_mut() 200 | .poll_write(cx, &this.outbox[*this.outstart..])?); 201 | wrote = true; 202 | *this.outstart += n; 203 | if *this.outstart == this.outbox.len() { 204 | this.outbox.clear(); 205 | *this.outstart = 0; 206 | } 207 | } 208 | 209 | let mut this = self.project(); 210 | if wrote { 211 | // heartbeat is since last write traffic! 212 | trace!("resetting heartbeat timer"); 213 | this.timer 214 | .reset(tokio::time::Instant::now() + *this.timeout); 215 | } 216 | 217 | ready!(this.stream.as_mut().poll_flush(cx)?); 218 | 219 | if exiting { 220 | debug!("shutting down writer"); 221 | ready!(this.stream.poll_shutdown(cx)?); 222 | } 223 | 224 | Poll::Ready(Ok(())) 225 | } 226 | 227 | #[instrument(skip(self, cx, default_watcher))] 228 | fn poll_read( 229 | mut self: Pin<&mut Self>, 230 | cx: &mut Context, 231 | default_watcher: &mut mpsc::UnboundedSender, 232 | ) -> Poll> 233 | where 234 | S: AsyncRead, 235 | { 236 | loop { 237 | let mut need = if self.inlen() >= 4 { 238 | let length = (&mut &self.inbox[self.instart..]).read_i32::()? as usize; 239 | length + 4 240 | } else { 241 | 4 242 | }; 243 | trace!("need {need} bytes, have {inlen}", inlen = self.inlen()); 244 | 245 | while self.inlen() < need { 246 | let this = self.as_mut().project(); 247 | let read_from = this.inbox.len(); 248 | this.inbox.resize(*this.instart + need, 0); 249 | let mut inbox_buf = ReadBuf::new(&mut this.inbox[read_from..]); 250 | match this.stream.poll_read(cx, &mut inbox_buf)? { 251 | Poll::Ready(()) => { 252 | let n = inbox_buf.filled().len(); 253 | this.inbox.truncate(read_from + n); 254 | if n == 0 { 255 | if self.inlen() != 0 { 256 | return Poll::Ready( 257 | ConnectionClosedSnafu { 258 | buf: &self.inbox[self.instart..], 259 | } 260 | .fail(), 261 | ); 262 | } else { 263 | // Server closed session with no bytes left in buffer 264 | debug!("server closed connection"); 265 | return Poll::Ready(Ok(())); 266 | } 267 | } 268 | 269 | if self.inlen() >= 4 && need == 4 { 270 | let length = (&mut &self.inbox[self.instart..]) 271 | .read_i32::()? 272 | as usize; 273 | need += length; 274 | } 275 | } 276 | Poll::Pending => { 277 | this.inbox.truncate(read_from); 278 | return Poll::Pending; 279 | } 280 | } 281 | } 282 | 283 | { 284 | let mut this = self.as_mut().project(); 285 | let mut err = None; 286 | let mut buf = &this.inbox[*this.instart + 4..*this.instart + need]; 287 | *this.instart += need; 288 | 289 | let xid = if *this.first { 290 | 0 291 | } else { 292 | let xid = buf.read_i32::()?; 293 | let zxid = buf.read_i64::()?; 294 | if zxid > 0 { 295 | trace!( 296 | "updated zxid from {last_zxid_seen} to {zxid}", 297 | last_zxid_seen = *this.last_zxid_seen 298 | ); 299 | 300 | assert!(zxid >= *this.last_zxid_seen); 301 | *this.last_zxid_seen = zxid; 302 | } 303 | let zk_err: ZkError = buf.read_i32::()?.into(); 304 | if zk_err != ZkError::Ok { 305 | err = Some(zk_err); 306 | } 307 | xid 308 | }; 309 | 310 | if xid == 0 && !*this.first { 311 | // response to shutdown -- empty response 312 | // XXX: in theory, server should now shut down receive end 313 | trace!("got response to CloseSession"); 314 | if let Some(e) = err { 315 | return Poll::Ready(CloseSessionSnafu { error_code: e }.fail()); 316 | } 317 | } else if xid == -1 { 318 | // watch event 319 | use super::response::ReadFrom; 320 | let e = WatchedEvent::read_from(&mut buf)?; 321 | trace!(event = ?e, "got watcher event"); 322 | 323 | let mut remove = false; 324 | if let Some(watchers) = this.watchers.get_mut(&e.path) { 325 | // custom watchers were set by the user -- notify them 326 | let mut i = (watchers.len() - 1) as isize; 327 | trace!( 328 | watcher_count = watchers.len(), 329 | "found potentially waiting custom watchers" 330 | ); 331 | 332 | while i >= 0 { 333 | let triggers = match (&watchers[i as usize].1, e.event_type) { 334 | (WatchType::Child, WatchedEventType::NodeDeleted) 335 | | (WatchType::Child, WatchedEventType::NodeChildrenChanged) => true, 336 | (WatchType::Child, _) => false, 337 | (WatchType::Data, WatchedEventType::NodeDeleted) 338 | | (WatchType::Data, WatchedEventType::NodeDataChanged) => true, 339 | (WatchType::Data, _) => false, 340 | (WatchType::Exist, WatchedEventType::NodeChildrenChanged) => false, 341 | (WatchType::Exist, _) => true, 342 | }; 343 | 344 | if triggers { 345 | // this watcher is no longer active 346 | let w = watchers.swap_remove(i as usize); 347 | // NOTE: ignore the case where the receiver has been dropped 348 | let _ = w.0.send(e.clone()); 349 | } 350 | i -= 1; 351 | } 352 | 353 | if watchers.is_empty() { 354 | remove = true; 355 | } 356 | } 357 | 358 | if remove { 359 | this.watchers 360 | .remove(&e.path) 361 | .expect("tried to remove watcher that didn't exist"); 362 | } 363 | 364 | // NOTE: ignoring error, because the user may not care about events 365 | let _ = default_watcher.unbounded_send(e); 366 | } else if xid == -2 { 367 | // response to ping -- empty response 368 | trace!("got response to heartbeat"); 369 | if let Some(e) = err { 370 | return Poll::Ready(BadPingSnafu { error_code: e }.fail()); 371 | } 372 | } else { 373 | // response to user request 374 | *this.first = false; 375 | 376 | // find the waiting request future 377 | let (opcode, tx) = this.reply.remove(&xid).unwrap(); // TODO: return an error if xid was unknown 378 | 379 | if let Some(w) = this.pending_watchers.remove(&xid) { 380 | // normally, watches are *only* added for successful operations 381 | // the exception to this is if an exists call fails with NoNode 382 | if err.is_none() 383 | || (opcode == request::OpCode::Exists && err == Some(ZkError::NoNode)) 384 | { 385 | trace!(xid, "pending watcher turned into real watcher"); 386 | this.watchers.entry(w.0).or_default().push((w.1, w.2)); 387 | } else { 388 | trace!( 389 | xid, 390 | error = ?err, 391 | "pending watcher not turned into real watcher" 392 | ); 393 | } 394 | } 395 | 396 | if let Some(e) = err { 397 | info!(xid, ?opcode, error = ?e, "handling server error response"); 398 | 399 | let _ = tx.send(Err(e)); 400 | } else { 401 | let mut response = Response::parse(opcode, &mut buf)?; 402 | 403 | debug!(?response, xid, ?opcode, "handling server response"); 404 | 405 | if let Response::Connect { 406 | timeout, 407 | session_id, 408 | ref mut password, 409 | .. 410 | } = response 411 | { 412 | assert!(timeout >= 0); 413 | 414 | *this.timeout = time::Duration::from_millis(2 * timeout as u64 / 3); 415 | trace!( 416 | timeout = ?this.timeout, 417 | "negotiated session timeout", 418 | ); 419 | 420 | this.timer 421 | .as_mut() 422 | .reset(tokio::time::Instant::now() + *this.timeout); 423 | 424 | // keep track of these for consistent re-connect 425 | *this.session_id = session_id; 426 | mem::swap(this.password, password); 427 | } 428 | 429 | let _ = tx.send(Ok(response)); // if receiver doesn't care, we don't either 430 | } 431 | } 432 | } 433 | 434 | if self.instart == self.inbox.len() { 435 | let this = self.as_mut().project(); 436 | this.inbox.clear(); 437 | *this.instart = 0; 438 | } 439 | } 440 | } 441 | 442 | #[instrument(name = "poll_read", skip(self, cx, default_watcher))] 443 | pub(super) fn poll( 444 | mut self: Pin<&mut Self>, 445 | cx: &mut Context, 446 | exiting: bool, 447 | default_watcher: &mut mpsc::UnboundedSender, 448 | ) -> Poll> { 449 | let r = self.as_mut().poll_read(cx, default_watcher)?; 450 | 451 | let mut this = self.as_mut().project(); 452 | if let Poll::Ready(()) = this.timer.as_mut().poll(cx) { 453 | if this.outbox.is_empty() { 454 | // send a ping! 455 | // length is known for pings 456 | this.outbox 457 | .write_i32::(8) 458 | .expect("Vec::write should never fail"); 459 | // xid 460 | this.outbox 461 | .write_i32::(-2) 462 | .expect("Vec::write should never fail"); 463 | // opcode 464 | this.outbox 465 | .write_i32::(request::OpCode::Ping as i32) 466 | .expect("Vec::write should never fail"); 467 | trace!("sending heartbeat"); 468 | } else { 469 | // already request in flight, so no need to also send heartbeat 470 | } 471 | 472 | this.timer 473 | .as_mut() 474 | .reset(tokio::time::Instant::now() + *this.timeout); 475 | } 476 | 477 | let w = self.poll_write(cx, exiting)?; 478 | 479 | match (r, w) { 480 | (Poll::Ready(()), Poll::Ready(())) if exiting => { 481 | debug!("packetizer done"); 482 | Poll::Ready(Ok(())) 483 | } 484 | (Poll::Ready(()), Poll::Ready(())) => Poll::Ready(ServerClosedConnectionSnafu.fail()), 485 | (Poll::Ready(()), _) => Poll::Ready(ResponseChannelClosedPrematurelySnafu.fail()), 486 | _ => Poll::Pending, 487 | } 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /src/proto/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 2 | #[repr(i32)] 3 | pub enum ZkError { 4 | /// This code is never returned from the server. It should not be used other than to indicate a 5 | /// range. Specifically error codes greater than this value are API errors (while values less 6 | /// than this indicate a system error. 7 | APIError = -100, 8 | /// Client authentication failed. 9 | AuthFailed = -115, 10 | /// Invalid arguments. 11 | BadArguments = -8, 12 | /// Version conflict in `set` operation. In case of reconfiguration: reconfig requested from 13 | /// config version X but last seen config has a different version Y. 14 | BadVersion = -103, 15 | /// Connection to the server has been lost. 16 | ConnectionLoss = -4, 17 | /// A data inconsistency was found. 18 | DataInconsistency = -3, 19 | /// Attempt to create ephemeral node on a local session. 20 | EphemeralOnLocalSession = -120, 21 | /// Invalid `Acl` specified. 22 | InvalidACL = -114, 23 | /// Invalid callback specified. 24 | InvalidCallback = -113, 25 | /// Error while marshalling or unmarshalling data. 26 | MarshallingError = -5, 27 | /// Not authenticated. 28 | NoAuth = -102, 29 | /// Ephemeral nodes may not have children. 30 | NoChildrenForEphemerals = -108, 31 | /// Request to create node that already exists. 32 | NodeExists = -110, 33 | /// Attempted to read a node that does not exist. 34 | NoNode = -101, 35 | /// The node has children. 36 | NotEmpty = -111, 37 | /// State-changing request is passed to read-only server. 38 | NotReadOnly = -119, 39 | /// Attempt to remove a non-existing watcher. 40 | NoWatcher = -121, 41 | /// No error occurred. 42 | Ok = 0, 43 | /// Operation timeout. 44 | OperationTimeout = -7, 45 | /// A runtime inconsistency was found. 46 | RuntimeInconsistency = -2, 47 | /// The session has been expired by the server. 48 | SessionExpired = -112, 49 | /// Session moved to another server, so operation is ignored. 50 | SessionMoved = -118, 51 | /// System and server-side errors. This is never thrown by the server, it shouldn't be used 52 | /// other than to indicate a range. Specifically error codes greater than this value, but lesser 53 | /// than `APIError`, are system errors. 54 | SystemError = -1, 55 | /// Operation is unimplemented. 56 | Unimplemented = -6, 57 | } 58 | 59 | impl From for ZkError { 60 | fn from(code: i32) -> Self { 61 | match code { 62 | -100 => ZkError::APIError, 63 | -115 => ZkError::AuthFailed, 64 | -8 => ZkError::BadArguments, 65 | -103 => ZkError::BadVersion, 66 | -4 => ZkError::ConnectionLoss, 67 | -3 => ZkError::DataInconsistency, 68 | -120 => ZkError::EphemeralOnLocalSession, 69 | -114 => ZkError::InvalidACL, 70 | -113 => ZkError::InvalidCallback, 71 | -5 => ZkError::MarshallingError, 72 | -102 => ZkError::NoAuth, 73 | -108 => ZkError::NoChildrenForEphemerals, 74 | -110 => ZkError::NodeExists, 75 | -101 => ZkError::NoNode, 76 | -111 => ZkError::NotEmpty, 77 | -119 => ZkError::NotReadOnly, 78 | -121 => ZkError::NoWatcher, 79 | 0 => ZkError::Ok, 80 | -7 => ZkError::OperationTimeout, 81 | -2 => ZkError::RuntimeInconsistency, 82 | -112 => ZkError::SessionExpired, 83 | -118 => ZkError::SessionMoved, 84 | -1 => ZkError::SystemError, 85 | -6 => ZkError::Unimplemented, 86 | _ => panic!("unknown error code {code}"), 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/proto/mod.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use std::error::Error; 3 | use std::net::SocketAddr; 4 | use tokio::io::{AsyncRead, AsyncWrite}; 5 | 6 | mod active_packetizer; 7 | mod error; 8 | mod packetizer; 9 | mod request; 10 | mod response; 11 | mod watch; 12 | 13 | pub(crate) use self::error::ZkError; 14 | pub(crate) use self::packetizer::{Enqueuer, Packetizer}; 15 | pub(crate) use self::request::Request; 16 | pub(crate) use self::response::Response; 17 | pub(crate) use self::watch::Watch; 18 | 19 | #[async_trait] 20 | pub trait ZooKeeperTransport: AsyncRead + AsyncWrite + Sized + Send + 'static { 21 | type Addr: Send + Clone; 22 | type ConnectError: Error + Send + Sync + 'static; 23 | async fn connect(addr: Self::Addr) -> Result; 24 | } 25 | 26 | #[async_trait] 27 | impl ZooKeeperTransport for tokio::net::TcpStream { 28 | type Addr = SocketAddr; 29 | type ConnectError = tokio::io::Error; 30 | async fn connect(addr: Self::Addr) -> Result { 31 | tokio::net::TcpStream::connect(addr).await 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/proto/packetizer.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | active_packetizer::ActivePacketizer, request, watch::WatchType, Request, Response, 3 | ZooKeeperTransport, 4 | }; 5 | use crate::{error::Error, format_err, Watch, WatchedEvent, ZkError}; 6 | use byteorder::{BigEndian, WriteBytesExt}; 7 | use futures::{ 8 | channel::{mpsc, oneshot}, 9 | future::Either, 10 | ready, FutureExt, StreamExt, TryFutureExt, 11 | }; 12 | use pin_project::pin_project; 13 | use snafu::ResultExt; 14 | use std::{ 15 | future::{self, Future}, 16 | mem, 17 | pin::Pin, 18 | task::{Context, Poll}, 19 | }; 20 | use tokio::io::{AsyncRead, AsyncWrite}; 21 | use tracing::{debug, error, info, instrument, trace}; 22 | 23 | #[pin_project] 24 | pub(crate) struct Packetizer 25 | where 26 | S: ZooKeeperTransport, 27 | { 28 | /// ZooKeeper address 29 | addr: S::Addr, 30 | 31 | /// Current state 32 | #[pin] 33 | state: PacketizerState, 34 | 35 | /// Watcher to send watch events to. 36 | default_watcher: mpsc::UnboundedSender, 37 | 38 | /// Incoming requests 39 | rx: mpsc::UnboundedReceiver<(Request, oneshot::Sender>)>, 40 | 41 | /// Next xid to issue 42 | xid: i32, 43 | 44 | exiting: bool, 45 | } 46 | 47 | impl Packetizer 48 | where 49 | S: ZooKeeperTransport, 50 | { 51 | // Enqueuer is the entry point for submitting data to Packetizer 52 | #[allow(clippy::new_ret_no_self)] 53 | pub(crate) fn new( 54 | addr: S::Addr, 55 | stream: S, 56 | default_watcher: mpsc::UnboundedSender, 57 | ) -> Enqueuer 58 | where 59 | S: Send + 'static + AsyncRead + AsyncWrite, 60 | { 61 | let (tx, rx) = mpsc::unbounded(); 62 | 63 | tokio::spawn( 64 | Packetizer { 65 | addr, 66 | state: PacketizerState::Connected(ActivePacketizer::new(stream)), 67 | xid: 0, 68 | default_watcher, 69 | rx, 70 | exiting: false, 71 | } 72 | .map_err(move |error| { 73 | error!(%error, "packetizer exiting"); 74 | drop(error); 75 | }), 76 | ); 77 | 78 | Enqueuer(tx) 79 | } 80 | } 81 | 82 | #[pin_project(project = PacketizerStateProj)] 83 | enum PacketizerState { 84 | Connected(#[pin] ActivePacketizer), 85 | Reconnecting( 86 | Pin, Error>> + Send + 'static>>, 87 | ), 88 | } 89 | 90 | impl PacketizerState 91 | where 92 | S: AsyncRead + AsyncWrite, 93 | { 94 | fn poll( 95 | mut self: Pin<&mut Self>, 96 | cx: &mut Context, 97 | exiting: bool, 98 | default_watcher: &mut mpsc::UnboundedSender, 99 | ) -> Poll> { 100 | let ap = match self.as_mut().project() { 101 | PacketizerStateProj::Connected(ref mut ap) => { 102 | return ap 103 | .as_mut() 104 | .poll(cx, exiting, default_watcher) 105 | .map(|res| res.whatever_context("active packetizer failed")) 106 | } 107 | PacketizerStateProj::Reconnecting(ref mut c) => ready!(c.as_mut().poll(cx)?), 108 | }; 109 | 110 | // we are now connected! 111 | self.set(PacketizerState::Connected(ap)); 112 | self.poll(cx, exiting, default_watcher) 113 | } 114 | } 115 | 116 | impl Packetizer 117 | where 118 | S: ZooKeeperTransport, 119 | { 120 | #[instrument(skip(self, cx))] 121 | fn poll_enqueue(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { 122 | let mut this = self.project(); 123 | while let PacketizerStateProj::Connected(ref mut ap) = this.state.as_mut().project() { 124 | let (mut item, tx) = match ready!(this.rx.poll_next_unpin(cx)) { 125 | Some((request, response)) => (request, response), 126 | None => return Poll::Ready(Err(())), 127 | }; 128 | debug!(?item, xid = this.xid, "enqueueing request"); 129 | 130 | match item { 131 | Request::GetData { 132 | ref path, 133 | ref mut watch, 134 | .. 135 | } 136 | | Request::GetChildren { 137 | ref path, 138 | ref mut watch, 139 | .. 140 | } 141 | | Request::Exists { 142 | ref path, 143 | ref mut watch, 144 | .. 145 | } => { 146 | if let Watch::Custom(_) = *watch { 147 | // set to Global so that watch will be sent as 1u8 148 | let w = mem::replace(watch, Watch::Global); 149 | if let Watch::Custom(w) = w { 150 | let wtype = match item { 151 | Request::GetData { .. } => WatchType::Data, 152 | Request::GetChildren { .. } => WatchType::Child, 153 | Request::Exists { .. } => WatchType::Exist, 154 | _ => unreachable!(), 155 | }; 156 | trace!(xid = this.xid, path, ?wtype, "adding pending watcher"); 157 | ap.as_mut() 158 | .pending_watchers() 159 | .insert(*this.xid, (path.to_string(), w, wtype)); 160 | } else { 161 | unreachable!(); 162 | } 163 | } 164 | } 165 | _ => {} 166 | } 167 | 168 | ap.as_mut().enqueue(*this.xid, item, tx); 169 | *this.xid += 1; 170 | } 171 | Poll::Pending 172 | } 173 | } 174 | 175 | impl Future for Packetizer 176 | where 177 | S: ZooKeeperTransport, 178 | { 179 | type Output = Result<(), Error>; 180 | 181 | #[instrument(skip(self, cx))] 182 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 183 | if !self.exiting { 184 | match self.as_mut().poll_enqueue(cx) { 185 | Poll::Ready(Ok(())) | Poll::Pending => {} 186 | Poll::Ready(Err(())) => { 187 | let this = self.as_mut().project(); 188 | // no more requests will be enqueued 189 | *this.exiting = true; 190 | 191 | if let PacketizerStateProj::Connected(ref mut ap) = this.state.project() { 192 | // send CloseSession 193 | // length is fixed 194 | ap.as_mut() 195 | .outbox() 196 | .write_i32::(8) 197 | .expect("Vec::write should never fail"); 198 | // xid 199 | ap.as_mut() 200 | .outbox() 201 | .write_i32::(0) 202 | .expect("Vec::write should never fail"); 203 | // opcode 204 | ap.as_mut() 205 | .outbox() 206 | .write_i32::(request::OpCode::CloseSession as i32) 207 | .expect("Vec::write should never fail"); 208 | } else { 209 | unreachable!("poll_enqueue will never return Err() if not connected"); 210 | } 211 | } 212 | } 213 | } 214 | 215 | let mut this = self.as_mut().project(); 216 | match this 217 | .state 218 | .as_mut() 219 | .poll(cx, *this.exiting, this.default_watcher) 220 | { 221 | Poll::Ready(Ok(v)) => Poll::Ready(Ok(v)), 222 | Poll::Pending => Poll::Pending, 223 | Poll::Ready(Err(e)) => { 224 | // if e is disconnect, then purge state and reconnect 225 | // for now, assume all errors are disconnects 226 | // TODO: test this! 227 | 228 | let password = 229 | if let PacketizerStateProj::Connected(ap) = this.state.as_mut().project() { 230 | ap.take_password() 231 | } else { 232 | // XXX: error while connecting -- don't recurse (for now) 233 | return Poll::Ready(Err(e)); 234 | }; 235 | 236 | if *this.exiting { 237 | debug!("connection lost during exit; not reconnecting"); 238 | Poll::Ready(Ok(())) 239 | } else if let PacketizerState::Connected(ActivePacketizer { 240 | last_zxid_seen, 241 | session_id, 242 | .. 243 | }) = *this.state 244 | { 245 | info!( 246 | session_id, 247 | last_zxid = last_zxid_seen, 248 | "connection lost; reconnecting" 249 | ); 250 | 251 | let xid = *this.xid; 252 | *this.xid += 1; 253 | 254 | let retry = S::connect(this.addr.clone()) 255 | .map(|res| res.whatever_context("failed to connect")) 256 | .map_ok(move |stream| { 257 | let request = Request::Connect { 258 | protocol_version: 0, 259 | last_zxid_seen, 260 | timeout: 0, 261 | session_id, 262 | passwd: password, 263 | read_only: false, 264 | }; 265 | trace!("about to handshake (again)"); 266 | 267 | let (tx, rx) = oneshot::channel(); 268 | tokio::spawn(rx.map(move |r| { 269 | trace!(response = ?r, "re-connection response"); 270 | })); 271 | 272 | let mut ap = ActivePacketizer::new(stream); 273 | ap.enqueue_unpin(xid, request, tx); 274 | ap 275 | }); 276 | 277 | // dropping the old state will also cancel in-flight requests 278 | this.state 279 | .set(PacketizerState::Reconnecting(Box::pin(retry))); 280 | self.poll(cx) 281 | } else { 282 | unreachable!(); 283 | } 284 | } 285 | } 286 | } 287 | } 288 | 289 | #[derive(Clone, Debug)] 290 | pub(crate) struct Enqueuer( 291 | mpsc::UnboundedSender<(Request, oneshot::Sender>)>, 292 | ); 293 | 294 | impl Enqueuer { 295 | pub(crate) fn enqueue( 296 | &self, 297 | request: Request, 298 | ) -> impl Future, Error>> { 299 | let (tx, rx) = oneshot::channel(); 300 | match self.0.unbounded_send((request, tx)) { 301 | Ok(()) => { 302 | Either::Left(rx.map_err(|e| format_err!("failed to enqueue new request: {:?}", e))) 303 | } 304 | Err(e) => Either::Right(future::ready(Err(format_err!( 305 | "failed to enqueue new request: {:?}", 306 | e 307 | )))), 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/proto/request.rs: -------------------------------------------------------------------------------- 1 | use super::Watch; 2 | use super::ZkError; 3 | use crate::{Acl, CreateMode}; 4 | use byteorder::{BigEndian, WriteBytesExt}; 5 | use std::borrow::Cow; 6 | use std::io::{self, Write}; 7 | 8 | #[derive(Debug)] 9 | pub(crate) enum Request { 10 | Connect { 11 | protocol_version: i32, 12 | last_zxid_seen: i64, 13 | timeout: i32, 14 | session_id: i64, 15 | passwd: Vec, 16 | read_only: bool, 17 | }, 18 | Exists { 19 | path: String, 20 | watch: Watch, 21 | }, 22 | Delete { 23 | path: String, 24 | version: i32, 25 | }, 26 | SetData { 27 | path: String, 28 | data: Cow<'static, [u8]>, 29 | version: i32, 30 | }, 31 | Create { 32 | path: String, 33 | data: Cow<'static, [u8]>, 34 | acl: Cow<'static, [Acl]>, 35 | mode: CreateMode, 36 | }, 37 | GetChildren { 38 | path: String, 39 | watch: Watch, 40 | }, 41 | GetData { 42 | path: String, 43 | watch: Watch, 44 | }, 45 | GetAcl { 46 | path: String, 47 | }, 48 | SetAcl { 49 | path: String, 50 | acl: Cow<'static, [Acl]>, 51 | version: i32, 52 | }, 53 | Check { 54 | path: String, 55 | version: i32, 56 | }, 57 | Multi(Vec), 58 | } 59 | 60 | #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] 61 | #[repr(i32)] 62 | #[allow(dead_code)] 63 | pub(super) enum OpCode { 64 | Notification = 0, 65 | Create = 1, 66 | Delete = 2, 67 | Exists = 3, 68 | GetData = 4, 69 | SetData = 5, 70 | GetACL = 6, 71 | SetACL = 7, 72 | GetChildren = 8, 73 | Synchronize = 9, 74 | Ping = 11, 75 | GetChildren2 = 12, 76 | Check = 13, 77 | Multi = 14, 78 | Auth = 100, 79 | SetWatches = 101, 80 | Sasl = 102, 81 | CreateSession = -10, 82 | CloseSession = -11, 83 | Error = -1, 84 | } 85 | 86 | impl From for OpCode { 87 | fn from(code: i32) -> Self { 88 | match code { 89 | 0 => OpCode::Notification, 90 | 1 => OpCode::Create, 91 | 2 => OpCode::Delete, 92 | 3 => OpCode::Exists, 93 | 4 => OpCode::GetData, 94 | 5 => OpCode::SetData, 95 | 6 => OpCode::GetACL, 96 | 7 => OpCode::SetACL, 97 | 8 => OpCode::GetChildren, 98 | 9 => OpCode::Synchronize, 99 | 11 => OpCode::Ping, 100 | 12 => OpCode::GetChildren2, 101 | 13 => OpCode::Check, 102 | 14 => OpCode::Multi, 103 | 100 => OpCode::Auth, 104 | 101 => OpCode::SetWatches, 105 | 102 => OpCode::Sasl, 106 | -10 => OpCode::CreateSession, 107 | -11 => OpCode::CloseSession, 108 | -1 => OpCode::Error, 109 | _ => unimplemented!(), 110 | } 111 | } 112 | } 113 | 114 | pub(super) enum MultiHeader { 115 | NextOk(OpCode), 116 | NextErr(ZkError), 117 | Done, 118 | } 119 | 120 | pub trait WriteTo { 121 | fn write_to(&self, writer: W) -> io::Result<()>; 122 | } 123 | 124 | impl WriteTo for Acl { 125 | fn write_to(&self, mut writer: W) -> io::Result<()> { 126 | writer.write_u32::(self.perms.code())?; 127 | self.scheme.write_to(&mut writer)?; 128 | self.id.write_to(writer) 129 | } 130 | } 131 | 132 | impl WriteTo for MultiHeader { 133 | fn write_to(&self, mut writer: W) -> io::Result<()> { 134 | match *self { 135 | MultiHeader::NextOk(opcode) => { 136 | writer.write_i32::(opcode as i32)?; 137 | writer.write_u8(false as u8)?; 138 | writer.write_i32::(-1) 139 | } 140 | MultiHeader::NextErr(_) => { 141 | panic!("client should not serialize MultiHeader::NextErr"); 142 | } 143 | MultiHeader::Done => { 144 | writer.write_i32::(-1)?; 145 | writer.write_u8(true as u8)?; 146 | writer.write_i32::(-1) 147 | } 148 | } 149 | } 150 | } 151 | 152 | impl WriteTo for u8 { 153 | fn write_to(&self, mut writer: W) -> io::Result<()> { 154 | writer.write_u8(*self)?; 155 | Ok(()) 156 | } 157 | } 158 | 159 | impl WriteTo for str { 160 | fn write_to(&self, mut writer: W) -> io::Result<()> { 161 | writer.write_i32::(self.len() as i32)?; 162 | writer.write_all(self.as_ref()) 163 | } 164 | } 165 | 166 | impl WriteTo for [u8] { 167 | fn write_to(&self, mut writer: W) -> io::Result<()> { 168 | writer.write_i32::(self.len() as i32)?; 169 | writer.write_all(self.as_ref()) 170 | } 171 | } 172 | 173 | fn write_list(mut writer: W, ts: &[T]) -> io::Result<()> 174 | where 175 | T: WriteTo, 176 | W: Write, 177 | { 178 | writer.write_i32::(ts.len() as i32)?; 179 | for elem in ts { 180 | elem.write_to(&mut writer)?; 181 | } 182 | Ok(()) 183 | } 184 | 185 | impl Request { 186 | pub(super) fn serialize_into(&self, buffer: &mut Vec) -> Result<(), io::Error> { 187 | match *self { 188 | Request::Connect { 189 | protocol_version, 190 | last_zxid_seen, 191 | timeout, 192 | session_id, 193 | ref passwd, 194 | read_only, 195 | } => { 196 | buffer.write_i32::(protocol_version)?; 197 | buffer.write_i64::(last_zxid_seen)?; 198 | buffer.write_i32::(timeout)?; 199 | buffer.write_i64::(session_id)?; 200 | buffer.write_i32::(passwd.len() as i32)?; 201 | buffer.write_all(passwd)?; 202 | buffer.write_u8(read_only as u8)?; 203 | } 204 | Request::GetData { 205 | ref path, 206 | ref watch, 207 | } 208 | | Request::GetChildren { 209 | ref path, 210 | ref watch, 211 | } 212 | | Request::Exists { 213 | ref path, 214 | ref watch, 215 | } => { 216 | path.write_to(&mut *buffer)?; 217 | buffer.write_u8(watch.to_u8())?; 218 | } 219 | Request::Delete { ref path, version } => { 220 | path.write_to(&mut *buffer)?; 221 | buffer.write_i32::(version)?; 222 | } 223 | Request::SetData { 224 | ref path, 225 | ref data, 226 | version, 227 | } => { 228 | path.write_to(&mut *buffer)?; 229 | data.write_to(&mut *buffer)?; 230 | buffer.write_i32::(version)?; 231 | } 232 | Request::Create { 233 | ref path, 234 | ref data, 235 | mode, 236 | ref acl, 237 | } => { 238 | path.write_to(&mut *buffer)?; 239 | data.write_to(&mut *buffer)?; 240 | write_list(&mut *buffer, acl)?; 241 | buffer.write_i32::(mode as i32)?; 242 | } 243 | Request::GetAcl { ref path } => { 244 | path.write_to(&mut *buffer)?; 245 | } 246 | Request::SetAcl { 247 | ref path, 248 | ref acl, 249 | version, 250 | } => { 251 | path.write_to(&mut *buffer)?; 252 | write_list(&mut *buffer, acl)?; 253 | buffer.write_i32::(version)?; 254 | } 255 | Request::Check { ref path, version } => { 256 | path.write_to(&mut *buffer)?; 257 | buffer.write_i32::(version)?; 258 | } 259 | Request::Multi(ref requests) => { 260 | for r in requests { 261 | MultiHeader::NextOk(r.opcode()).write_to(&mut *buffer)?; 262 | r.serialize_into(&mut *buffer)?; 263 | } 264 | MultiHeader::Done.write_to(&mut *buffer)?; 265 | } 266 | } 267 | Ok(()) 268 | } 269 | 270 | pub(super) fn opcode(&self) -> OpCode { 271 | match *self { 272 | Request::Connect { .. } => OpCode::CreateSession, 273 | Request::Exists { .. } => OpCode::Exists, 274 | Request::Delete { .. } => OpCode::Delete, 275 | Request::Create { .. } => OpCode::Create, 276 | Request::GetChildren { .. } => OpCode::GetChildren, 277 | Request::SetData { .. } => OpCode::SetData, 278 | Request::GetData { .. } => OpCode::GetData, 279 | Request::GetAcl { .. } => OpCode::GetACL, 280 | Request::SetAcl { .. } => OpCode::SetACL, 281 | Request::Multi { .. } => OpCode::Multi, 282 | Request::Check { .. } => OpCode::Check, 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/proto/response.rs: -------------------------------------------------------------------------------- 1 | use super::error::ZkError; 2 | use super::request::{MultiHeader, OpCode}; 3 | use crate::{Acl, KeeperState, Permission, Stat, WatchedEvent, WatchedEventType}; 4 | use byteorder::{BigEndian, ReadBytesExt}; 5 | use std::io::{self, Read}; 6 | 7 | #[derive(Debug)] 8 | pub(crate) enum Response { 9 | Connect { 10 | _protocol_version: i32, 11 | timeout: i32, 12 | session_id: i64, 13 | password: Vec, 14 | _read_only: bool, 15 | }, 16 | Stat(Stat), 17 | GetData { 18 | bytes: Vec, 19 | stat: Stat, 20 | }, 21 | GetAcl { 22 | acl: Vec, 23 | stat: Stat, 24 | }, 25 | Empty, 26 | Strings(Vec), 27 | String(String), 28 | Multi(Vec>), 29 | } 30 | 31 | pub trait ReadFrom: Sized { 32 | fn read_from(read: &mut R) -> io::Result; 33 | } 34 | 35 | impl ReadFrom for Vec { 36 | fn read_from(read: &mut R) -> io::Result { 37 | let len = read.read_i32::()?; 38 | let mut items = Vec::with_capacity(len as usize); 39 | for _ in 0..len { 40 | items.push(read.read_string()?); 41 | } 42 | Ok(items) 43 | } 44 | } 45 | 46 | impl ReadFrom for Stat { 47 | fn read_from(read: &mut R) -> io::Result { 48 | Ok(Stat { 49 | czxid: read.read_i64::()?, 50 | mzxid: read.read_i64::()?, 51 | ctime: read.read_i64::()?, 52 | mtime: read.read_i64::()?, 53 | version: read.read_i32::()?, 54 | cversion: read.read_i32::()?, 55 | aversion: read.read_i32::()?, 56 | ephemeral_owner: read.read_i64::()?, 57 | data_length: read.read_i32::()?, 58 | num_children: read.read_i32::()?, 59 | pzxid: read.read_i64::()?, 60 | }) 61 | } 62 | } 63 | 64 | impl ReadFrom for WatchedEvent { 65 | fn read_from(read: &mut R) -> io::Result { 66 | let wtype = read.read_i32::()?; 67 | let state = read.read_i32::()?; 68 | let path = read.read_string()?; 69 | Ok(WatchedEvent { 70 | event_type: WatchedEventType::from(wtype), 71 | keeper_state: KeeperState::from(state), 72 | path, 73 | }) 74 | } 75 | } 76 | 77 | impl ReadFrom for Vec { 78 | fn read_from(read: &mut R) -> io::Result { 79 | let len = read.read_i32::()?; 80 | let mut items = Vec::with_capacity(len as usize); 81 | for _ in 0..len { 82 | items.push(Acl::read_from(read)?); 83 | } 84 | Ok(items) 85 | } 86 | } 87 | 88 | impl ReadFrom for Acl { 89 | fn read_from(read: &mut R) -> io::Result { 90 | let perms = Permission::read_from(read)?; 91 | let scheme = read.read_string()?; 92 | let id = read.read_string()?; 93 | Ok(Acl { perms, scheme, id }) 94 | } 95 | } 96 | 97 | impl ReadFrom for Permission { 98 | fn read_from(read: &mut R) -> io::Result { 99 | Ok(Permission::from_raw(read.read_u32::()?)) 100 | } 101 | } 102 | 103 | impl ReadFrom for MultiHeader { 104 | fn read_from(read: &mut R) -> io::Result { 105 | let opcode = read.read_i32::()?; 106 | let done = read.read_u8()? != 0; 107 | let err = read.read_i32::()?; 108 | if done { 109 | Ok(MultiHeader::Done) 110 | } else if opcode == -1 { 111 | Ok(MultiHeader::NextErr(err.into())) 112 | } else { 113 | Ok(MultiHeader::NextOk(opcode.into())) 114 | } 115 | } 116 | } 117 | 118 | pub trait BufferReader: Read { 119 | fn read_buffer(&mut self) -> io::Result>; 120 | } 121 | 122 | impl BufferReader for R { 123 | fn read_buffer(&mut self) -> io::Result> { 124 | let len = self.read_i32::()?; 125 | let len = if len < 0 { 0 } else { len as usize }; 126 | let mut buf = vec![0; len]; 127 | let read = self.read(&mut buf)?; 128 | if read == len { 129 | Ok(buf) 130 | } else { 131 | Err(io::Error::new( 132 | io::ErrorKind::WouldBlock, 133 | "read_buffer failed", 134 | )) 135 | } 136 | } 137 | } 138 | 139 | trait StringReader: Read { 140 | fn read_string(&mut self) -> io::Result; 141 | } 142 | 143 | impl StringReader for R { 144 | fn read_string(&mut self) -> io::Result { 145 | let raw = self.read_buffer()?; 146 | Ok(String::from_utf8(raw).unwrap()) 147 | } 148 | } 149 | 150 | impl Response { 151 | pub(super) fn parse(opcode: OpCode, reader: &mut &[u8]) -> io::Result { 152 | match opcode { 153 | OpCode::CreateSession => Ok(Response::Connect { 154 | _protocol_version: reader.read_i32::()?, 155 | timeout: reader.read_i32::()?, 156 | session_id: reader.read_i64::()?, 157 | password: reader.read_buffer()?, 158 | _read_only: reader.read_u8()? != 0, 159 | }), 160 | OpCode::Exists | OpCode::SetData | OpCode::SetACL => { 161 | Ok(Response::Stat(Stat::read_from(reader)?)) 162 | } 163 | OpCode::GetData => Ok(Response::GetData { 164 | bytes: reader.read_buffer()?, 165 | stat: Stat::read_from(reader)?, 166 | }), 167 | OpCode::Delete => Ok(Response::Empty), 168 | OpCode::GetChildren => Ok(Response::Strings(Vec::::read_from(reader)?)), 169 | OpCode::Create => Ok(Response::String(reader.read_string()?)), 170 | OpCode::GetACL => Ok(Response::GetAcl { 171 | acl: Vec::::read_from(reader)?, 172 | stat: Stat::read_from(reader)?, 173 | }), 174 | OpCode::Check => Ok(Response::Empty), 175 | OpCode::Multi => { 176 | let mut responses = Vec::new(); 177 | loop { 178 | match MultiHeader::read_from(reader)? { 179 | MultiHeader::NextErr(e) => { 180 | responses.push(Err(e)); 181 | let _ = reader.read_i32::()?; 182 | } 183 | MultiHeader::NextOk(opcode) => { 184 | responses.push(Ok(Response::parse(opcode, reader)?)); 185 | } 186 | MultiHeader::Done => break, 187 | } 188 | } 189 | Ok(Response::Multi(responses)) 190 | } 191 | _ => panic!("got unexpected response opcode {opcode:?}"), 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/proto/watch.rs: -------------------------------------------------------------------------------- 1 | use crate::WatchedEvent; 2 | use futures::channel::oneshot; 3 | 4 | #[derive(Debug)] 5 | pub(crate) enum Watch { 6 | None, 7 | Global, 8 | Custom(oneshot::Sender), 9 | } 10 | 11 | impl Watch { 12 | pub(crate) fn to_u8(&self) -> u8 { 13 | if let Watch::None = *self { 14 | 0 15 | } else { 16 | 1 17 | } 18 | } 19 | } 20 | 21 | /// Describes what a `Watch` is looking for. 22 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] 23 | pub(crate) enum WatchType { 24 | /// Watching for changes to children. 25 | Child, 26 | /// Watching for changes to data. 27 | Data, 28 | /// Watching for the creation of a node at the given path. 29 | Exist, 30 | } 31 | -------------------------------------------------------------------------------- /src/transform.rs: -------------------------------------------------------------------------------- 1 | use snafu::whatever as bail; 2 | 3 | use crate::{ 4 | error::{self, Error}, 5 | proto::{Request, Response, ZkError}, 6 | Acl, MultiResponse, Stat, 7 | }; 8 | 9 | pub(crate) fn create( 10 | res: Result, 11 | ) -> Result, Error> { 12 | match res { 13 | Ok(Response::String(s)) => Ok(Ok(s)), 14 | Ok(r) => bail!("got non-string response to create: {:?}", r), 15 | Err(ZkError::NoNode) => Ok(Err(error::Create::NoNode)), 16 | Err(ZkError::NodeExists) => Ok(Err(error::Create::NodeExists)), 17 | Err(ZkError::InvalidACL) => Ok(Err(error::Create::InvalidAcl)), 18 | Err(ZkError::NoChildrenForEphemerals) => Ok(Err(error::Create::NoChildrenForEphemerals)), 19 | Err(e) => bail!("create call failed: {:?}", e), 20 | } 21 | } 22 | 23 | pub(crate) fn set_data( 24 | version: i32, 25 | res: Result, 26 | ) -> Result, Error> { 27 | match res { 28 | Ok(Response::Stat(stat)) => Ok(Ok(stat)), 29 | Ok(r) => bail!("got a non-stat response to a set_data request: {:?}", r), 30 | Err(ZkError::NoNode) => Ok(Err(error::SetData::NoNode)), 31 | Err(ZkError::BadVersion) => Ok(Err(error::SetData::BadVersion { expected: version })), 32 | Err(ZkError::NoAuth) => Ok(Err(error::SetData::NoAuth)), 33 | Err(e) => bail!("set_data call failed: {:?}", e), 34 | } 35 | } 36 | 37 | pub(crate) fn delete( 38 | version: i32, 39 | res: Result, 40 | ) -> Result, Error> { 41 | match res { 42 | Ok(Response::Empty) => Ok(Ok(())), 43 | Ok(r) => bail!("got non-empty response to delete: {:?}", r), 44 | Err(ZkError::NoNode) => Ok(Err(error::Delete::NoNode)), 45 | Err(ZkError::NotEmpty) => Ok(Err(error::Delete::NotEmpty)), 46 | Err(ZkError::BadVersion) => Ok(Err(error::Delete::BadVersion { expected: version })), 47 | Err(e) => bail!("delete call failed: {:?}", e), 48 | } 49 | } 50 | 51 | pub(crate) fn get_acl( 52 | res: Result, 53 | ) -> Result, Stat), error::GetAcl>, Error> { 54 | match res { 55 | Ok(Response::GetAcl { acl, stat }) => Ok(Ok((acl, stat))), 56 | Ok(r) => bail!("got non-acl response to a get_acl request: {:?}", r), 57 | Err(ZkError::NoNode) => Ok(Err(error::GetAcl::NoNode)), 58 | Err(e) => bail!("get_acl call failed: {:?}", e), 59 | } 60 | } 61 | 62 | pub(crate) fn set_acl( 63 | version: i32, 64 | res: Result, 65 | ) -> Result, Error> { 66 | match res { 67 | Ok(Response::Stat(stat)) => Ok(Ok(stat)), 68 | Ok(r) => bail!("got non-stat response to a set_acl request: {:?}", r), 69 | Err(ZkError::NoNode) => Ok(Err(error::SetAcl::NoNode)), 70 | Err(ZkError::BadVersion) => Ok(Err(error::SetAcl::BadVersion { expected: version })), 71 | Err(ZkError::InvalidACL) => Ok(Err(error::SetAcl::InvalidAcl)), 72 | Err(ZkError::NoAuth) => Ok(Err(error::SetAcl::NoAuth)), 73 | Err(e) => bail!("set_acl call failed: {:?}", e), 74 | } 75 | } 76 | 77 | pub(crate) fn exists(res: Result) -> Result, Error> { 78 | match res { 79 | Ok(Response::Stat(stat)) => Ok(Some(stat)), 80 | Ok(r) => bail!("got a non-create response to a create request: {:?}", r), 81 | Err(ZkError::NoNode) => Ok(None), 82 | Err(e) => bail!("exists call failed: {:?}", e), 83 | } 84 | } 85 | 86 | pub(crate) fn get_children(res: Result) -> Result>, Error> { 87 | match res { 88 | Ok(Response::Strings(children)) => Ok(Some(children)), 89 | Ok(r) => bail!("got non-strings response to get-children: {:?}", r), 90 | Err(ZkError::NoNode) => Ok(None), 91 | Err(e) => bail!("get-children call failed: {:?}", e), 92 | } 93 | } 94 | 95 | pub(crate) fn get_data(res: Result) -> Result, Stat)>, Error> { 96 | match res { 97 | Ok(Response::GetData { bytes, stat }) => Ok(Some((bytes, stat))), 98 | Ok(r) => bail!("got non-data response to get-data: {:?}", r), 99 | Err(ZkError::NoNode) => Ok(None), 100 | Err(e) => bail!("get-data call failed: {:?}", e), 101 | } 102 | } 103 | 104 | pub(crate) fn check( 105 | version: i32, 106 | res: Result, 107 | ) -> Result, Error> { 108 | match res { 109 | Ok(Response::Empty) => Ok(Ok(())), 110 | Ok(r) => bail!("got a non-check response to a check request: {:?}", r), 111 | Err(ZkError::NoNode) => Ok(Err(error::Check::NoNode)), 112 | Err(ZkError::BadVersion) => Ok(Err(error::Check::BadVersion { expected: version })), 113 | Err(e) => bail!("check call failed: {:?}", e), 114 | } 115 | } 116 | 117 | /// The subset of [`proto::Request`] that a multi request needs to retain. 118 | /// 119 | /// In order to properly handle errors, a multi request needs to retain the 120 | /// expected version for each constituent set data, delete, or check operation. 121 | /// Unfortunately, executing a multi request requires transferring ownership of 122 | /// the `proto::Request`, which contains this information, to the future. A 123 | /// `RequestMarker` is used to avoid cloning the whole `proto::Request`, which 124 | /// can be rather large, when only the version information is necessary. 125 | #[derive(Debug)] 126 | pub(crate) enum RequestMarker { 127 | Create, 128 | SetData { version: i32 }, 129 | Delete { version: i32 }, 130 | Check { version: i32 }, 131 | } 132 | 133 | impl From<&Request> for RequestMarker { 134 | fn from(r: &Request) -> RequestMarker { 135 | match r { 136 | Request::Create { .. } => RequestMarker::Create, 137 | Request::SetData { version, .. } => RequestMarker::SetData { version: *version }, 138 | Request::Delete { version, .. } => RequestMarker::Delete { version: *version }, 139 | Request::Check { version, .. } => RequestMarker::Check { version: *version }, 140 | _ => unimplemented!(), 141 | } 142 | } 143 | } 144 | 145 | pub(crate) fn multi( 146 | req: &RequestMarker, 147 | res: Result, 148 | ) -> Result, Error> { 149 | // Handle multi-specific errors. 150 | match res { 151 | Err(ZkError::Ok) => return Ok(Err(error::Multi::RolledBack)), 152 | // Confusingly, the ZooKeeper server uses RuntimeInconsistency to 153 | // indicate that a request in a multi batch was skipped because an 154 | // earlier request in the batch failed. 155 | // Source: https://github.com/apache/zookeeper/blob/372e713a9/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java#L945-L946 156 | Err(ZkError::RuntimeInconsistency) => return Ok(Err(error::Multi::Skipped)), 157 | _ => (), 158 | }; 159 | 160 | Ok(match req { 161 | RequestMarker::Create => create(res)? 162 | .map(MultiResponse::Create) 163 | .map_err(|err| err.into()), 164 | RequestMarker::SetData { version } => set_data(*version, res)? 165 | .map(MultiResponse::SetData) 166 | .map_err(|err| err.into()), 167 | RequestMarker::Delete { version } => delete(*version, res)? 168 | .map(|_| MultiResponse::Delete) 169 | .map_err(|err| err.into()), 170 | RequestMarker::Check { version } => check(*version, res)? 171 | .map(|_| MultiResponse::Check) 172 | .map_err(|err| err.into()), 173 | }) 174 | } 175 | -------------------------------------------------------------------------------- /src/types/acl.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops; 3 | 4 | use std::string::ToString; 5 | 6 | use once_cell::sync::Lazy; 7 | 8 | /// Describes the ability of a user to perform a certain action. 9 | /// 10 | /// Permissions can be mixed together like integers with `|` and `&`. 11 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 12 | pub struct Permission(u32); 13 | 14 | impl Permission { 15 | /// No permissions are set (server could have been configured without ACL support). 16 | pub const NONE: Permission = Permission(0b00000); 17 | 18 | /// You can access the data of a node and can list its children. 19 | pub const READ: Permission = Permission(0b00001); 20 | 21 | /// You can set the data of a node. 22 | pub const WRITE: Permission = Permission(0b00010); 23 | 24 | /// You can create a child node. 25 | pub const CREATE: Permission = Permission(0b00100); 26 | 27 | /// You can delete a child node (but not necessarily this one). 28 | pub const DELETE: Permission = Permission(0b01000); 29 | 30 | /// You can alter permissions on this node. 31 | pub const ADMIN: Permission = Permission(0b10000); 32 | 33 | /// You can do anything. 34 | pub const ALL: Permission = Permission(0b11111); 35 | 36 | /// Extract a permission value from raw `bits`. 37 | pub(crate) fn from_raw(bits: u32) -> Permission { 38 | Permission(bits) 39 | } 40 | 41 | pub(crate) fn code(&self) -> u32 { 42 | self.0 43 | } 44 | 45 | /// Check that all `permissions` are set. 46 | pub fn can(self, permissions: Permission) -> bool { 47 | (self & permissions) == permissions 48 | } 49 | } 50 | 51 | impl ops::BitAnd for Permission { 52 | type Output = Self; 53 | 54 | fn bitand(self, rhs: Self) -> Self { 55 | Permission::from_raw(self.0 & rhs.0) 56 | } 57 | } 58 | 59 | impl ops::BitOr for Permission { 60 | type Output = Self; 61 | 62 | fn bitor(self, rhs: Self) -> Self { 63 | Permission::from_raw(self.0 | rhs.0) 64 | } 65 | } 66 | 67 | impl fmt::Display for Permission { 68 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 69 | if *self == Permission::ALL { 70 | write!(f, "ALL") 71 | } else if *self == Permission::NONE { 72 | write!(f, "NONE") 73 | } else { 74 | let mut first = true; 75 | let mut tick = || { 76 | if first { 77 | first = false; 78 | "" 79 | } else { 80 | "|" 81 | } 82 | }; 83 | 84 | if self.can(Permission::READ) { 85 | write!(f, "{}READ", tick())?; 86 | } 87 | if self.can(Permission::WRITE) { 88 | write!(f, "{}WRITE", tick())?; 89 | } 90 | if self.can(Permission::CREATE) { 91 | write!(f, "{}CREATE", tick())?; 92 | } 93 | if self.can(Permission::DELETE) { 94 | write!(f, "{}DELETE", tick())?; 95 | } 96 | if self.can(Permission::ADMIN) { 97 | write!(f, "{}ADMIN", tick())?; 98 | } 99 | Ok(()) 100 | } 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::*; 107 | 108 | #[test] 109 | fn permission_bitor() { 110 | let all = Permission::READ 111 | | Permission::WRITE 112 | | Permission::CREATE 113 | | Permission::DELETE 114 | | Permission::ADMIN; 115 | assert_eq!(Permission::ALL, all); 116 | } 117 | 118 | #[test] 119 | fn permission_can() { 120 | assert!(Permission::ALL.can(Permission::WRITE)); 121 | assert!(!Permission::WRITE.can(Permission::READ)); 122 | } 123 | 124 | #[test] 125 | fn permission_format() { 126 | assert_eq!("ALL", Permission::ALL.to_string()); 127 | assert_eq!("NONE", Permission::NONE.to_string()); 128 | assert_eq!( 129 | "READ|WRITE", 130 | (Permission::READ | Permission::WRITE).to_string() 131 | ); 132 | assert_eq!( 133 | "CREATE|DELETE", 134 | (Permission::CREATE | Permission::DELETE).to_string() 135 | ); 136 | assert_eq!("ADMIN", Permission::ADMIN.to_string()); 137 | } 138 | } 139 | 140 | /// An access control list. 141 | /// 142 | /// In general, the ACL system is similar to UNIX file access permissions, where znodes act as 143 | /// files. Unlike UNIX, each znode can have any number of ACLs to correspond with the potentially 144 | /// limitless (and pluggable) authentication schemes. A more surprising difference is that ACLs are 145 | /// not recursive: If `/path` is only readable by a single user, but `/path/sub` is world-readable, 146 | /// then anyone will be able to read `/path/sub`. 147 | /// 148 | /// See the [ZooKeeper Programmer's Guide](https://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_ZooKeeperAccessControl) 149 | /// for more information. 150 | #[derive(Clone, Debug, PartialEq, Eq)] 151 | pub struct Acl { 152 | /// The permissions associated with this ACL. 153 | pub perms: Permission, 154 | /// The authentication scheme this list is used for. The most common scheme is `"auth"`, which 155 | /// allows any authenticated user to do anything (see `creator_all`). 156 | pub scheme: String, 157 | /// The ID of the user under the `scheme`. For example, with the `"ip"` `scheme`, this is an IP 158 | /// address or CIDR netmask. 159 | pub id: String, 160 | } 161 | 162 | impl Acl { 163 | /// Create a new ACL with the given `permissions`, `scheme`, and `id`. 164 | pub fn new(permissions: Permission, scheme: T, id: U) -> Acl 165 | where 166 | T: ToString, 167 | U: ToString, 168 | { 169 | Acl { 170 | perms: permissions, 171 | scheme: scheme.to_string(), 172 | id: id.to_string(), 173 | } 174 | } 175 | 176 | /// This ACL gives the creators authentication id's all permissions. 177 | pub fn creator_all() -> &'static [Acl] { 178 | &ACL_CREATOR_ALL[..] 179 | } 180 | 181 | /// This is a completely open ACL. 182 | pub fn open_unsafe() -> &'static [Acl] { 183 | &ACL_OPEN_UNSAFE[..] 184 | } 185 | 186 | /// This ACL gives the world the ability to read. 187 | pub fn read_unsafe() -> &'static [Acl] { 188 | &ACL_READ_UNSAFE[..] 189 | } 190 | } 191 | 192 | static ACL_CREATOR_ALL: Lazy<[Acl; 1]> = Lazy::new(|| [Acl::new(Permission::ALL, "auth", "")]); 193 | static ACL_OPEN_UNSAFE: Lazy<[Acl; 1]> = 194 | Lazy::new(|| [Acl::new(Permission::ALL, "world", "anyone")]); 195 | static ACL_READ_UNSAFE: Lazy<[Acl; 1]> = 196 | Lazy::new(|| [Acl::new(Permission::READ, "world", "anyone")]); 197 | 198 | impl fmt::Display for Acl { 199 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 200 | write!(f, "({}:{}, {})", self.scheme, self.id, self.perms) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod acl; 2 | pub use self::acl::*; 3 | 4 | mod watch; 5 | pub use self::watch::*; 6 | 7 | mod multi; 8 | pub use self::multi::*; 9 | 10 | /// Statistics about a znode, similar to the UNIX `stat` structure. 11 | /// 12 | /// # Time in ZooKeeper 13 | /// The concept of time is tricky in distributed systems. ZooKeeper keeps track of time in a number 14 | /// of ways. 15 | /// 16 | /// - **zxid**: Every change to a ZooKeeper cluster receives a stamp in the form of a *zxid* 17 | /// (ZooKeeper Transaction ID). This exposes the total ordering of all changes to ZooKeeper. Each 18 | /// change will have a unique *zxid* -- if *zxid:a* is smaller than *zxid:b*, then the associated 19 | /// change to *zxid:a* happened before *zxid:b*. 20 | /// - **Version Numbers**: Every change to a znode will cause an increase to one of the version 21 | /// numbers of that node. 22 | /// - **Clock Time**: ZooKeeper does not use clock time to make decisions, but it uses it to put 23 | /// timestamps into the `Stat` structure. 24 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 25 | pub struct Stat { 26 | /// The transaction ID that created the znode. 27 | pub czxid: i64, 28 | /// The last transaction that modified the znode. 29 | pub mzxid: i64, 30 | /// Milliseconds since epoch when the znode was created. 31 | pub ctime: i64, 32 | /// Milliseconds since epoch when the znode was last modified. 33 | pub mtime: i64, 34 | /// The number of changes to the data of the znode. 35 | pub version: i32, 36 | /// The number of changes to the children of the znode. 37 | pub cversion: i32, 38 | /// The number of changes to the ACL of the znode. 39 | pub aversion: i32, 40 | /// The session ID of the owner of this znode, if it is an ephemeral entry. 41 | pub ephemeral_owner: i64, 42 | /// The length of the data field of the znode. 43 | pub data_length: i32, 44 | /// The number of children this znode has. 45 | pub num_children: i32, 46 | /// The transaction ID that last modified the children of the znode. 47 | pub pzxid: i64, 48 | } 49 | 50 | /// CreateMode value determines how the znode is created on ZooKeeper. 51 | #[repr(i32)] 52 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 53 | pub enum CreateMode { 54 | /// The znode will not be automatically deleted upon client's disconnect. 55 | Persistent = 0, 56 | /// The znode will be deleted upon the client's disconnect. 57 | Ephemeral = 1, 58 | /// The name of the znode will be appended with a monotonically increasing number. The actual 59 | /// path name of a sequential node will be the given path plus a suffix `"i"` where *i* is the 60 | /// current sequential number of the node. The sequence number is always fixed length of 10 61 | /// digits, 0 padded. Once such a node is created, the sequential number will be incremented by 62 | /// one. 63 | PersistentSequential = 2, 64 | /// The znode will be deleted upon the client's disconnect, and its name will be appended with a 65 | /// monotonically increasing number. 66 | EphemeralSequential = 3, 67 | /// Container nodes are special purpose nodes useful for recipes such as leader, lock, etc. When 68 | /// the last child of a container is deleted, the container becomes a candidate to be deleted by 69 | /// the server at some point in the future. Given this property, you should be prepared to get 70 | /// `ZkError::NoNode` when creating children inside of this container node. 71 | Container = 4, 72 | // 73 | // 421 74 | // 000 75 | // ^----- is it a container? 76 | // ^---- is it sequential? 77 | // ^--- is it ephemeral? 78 | } 79 | -------------------------------------------------------------------------------- /src/types/multi.rs: -------------------------------------------------------------------------------- 1 | use super::Stat; 2 | 3 | /// An individual response in a `multi` request. 4 | #[derive(Debug, PartialEq, Eq)] 5 | pub enum MultiResponse { 6 | /// The response to a `create` request within a `multi` batch. 7 | Create(String), 8 | /// The response to a `set_data` request within a `multi` batch. 9 | SetData(Stat), 10 | /// The response to a `delete` request within a `multi` batch. 11 | Delete, 12 | /// The response to a `check` request within a `multi` batch. 13 | Check, 14 | } 15 | -------------------------------------------------------------------------------- /src/types/watch.rs: -------------------------------------------------------------------------------- 1 | /// Represents a change on the ZooKeeper that a `Watcher` is able to respond to. 2 | /// 3 | /// The `WatchedEvent` includes exactly what happened, the current state of the ZooKeeper, and the 4 | /// path of the znode that was involved in the event. 5 | #[derive(Clone, Debug, PartialEq, Eq)] 6 | pub struct WatchedEvent { 7 | /// The trigger that caused the watch to hit. 8 | pub event_type: WatchedEventType, 9 | /// The current state of ZooKeeper (and the client's connection to it). 10 | pub keeper_state: KeeperState, 11 | /// The path of the znode that was involved. 12 | // This will be `None` for session-related triggers. 13 | pub path: String, 14 | } 15 | 16 | /// Enumeration of states the client may be at a Watcher Event. It represents the state of the 17 | /// server at the time the event was generated. 18 | #[repr(i32)] 19 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 20 | pub enum KeeperState { 21 | /// The client is in the disconnected state - it is not connected to any server in the ensemble. 22 | Disconnected = 0, 23 | /// The client is in the connected state - it is connected to a server in the ensemble (one of 24 | /// the servers specified in the host connection parameter during ZooKeeper client creation). 25 | SyncConnected = 3, 26 | /// Authentication has failed -- connection requires a new `ZooKeeper` instance. 27 | AuthFailed = 4, 28 | /// The client is connected to a read-only server, that is the server which is not currently 29 | /// connected to the majority. The only operations allowed after receiving this state is read 30 | /// operations. This state is generated for read-only clients only since read/write clients 31 | /// aren't allowed to connect to read-only servers. 32 | ConnectedReadOnly = 5, 33 | /// Used to notify clients that they are SASL-authenticated, so that they can perform ZooKeeper 34 | /// actions with their SASL-authorized permissions. 35 | SaslAuthenticated = 6, 36 | /// The serving cluster has expired this session. The ZooKeeper client connection (the session) 37 | /// is no longer valid. You must create a new client connection (instantiate a new `ZooKeeper` 38 | /// instance) if you with to access the ensemble. 39 | Expired = -112, 40 | } 41 | 42 | impl From for KeeperState { 43 | fn from(code: i32) -> Self { 44 | match code { 45 | 0 => KeeperState::Disconnected, 46 | 3 => KeeperState::SyncConnected, 47 | 4 => KeeperState::AuthFailed, 48 | 5 => KeeperState::ConnectedReadOnly, 49 | 6 => KeeperState::SaslAuthenticated, 50 | -112 => KeeperState::Expired, 51 | _ => unreachable!("unknown keeper state {:x}", code), 52 | } 53 | } 54 | } 55 | 56 | /// Enumeration of types of events that may occur on the znode. 57 | #[repr(i32)] 58 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 59 | pub enum WatchedEventType { 60 | /// Nothing known has occurred on the znode. This value is issued as part of a `WatchedEvent` 61 | /// when the `KeeperState` changes. 62 | None = -1, 63 | /// Issued when a znode at a given path is created. 64 | NodeCreated = 1, 65 | /// Issued when a znode at a given path is deleted. 66 | NodeDeleted = 2, 67 | /// Issued when the data of a watched znode are altered. This event value is issued whenever a 68 | /// *set* operation occurs without an actual contents check, so there is no guarantee the data 69 | /// actually changed. 70 | NodeDataChanged = 3, 71 | /// Issued when the children of a watched znode are created or deleted. This event is not issued 72 | /// when the data within children is altered. 73 | NodeChildrenChanged = 4, 74 | /// Issued when the client removes a data watcher. 75 | DataWatchRemoved = 5, 76 | /// Issued when the client removes a child watcher. 77 | ChildWatchRemoved = 6, 78 | } 79 | 80 | impl From for WatchedEventType { 81 | fn from(code: i32) -> Self { 82 | match code { 83 | -1 => WatchedEventType::None, 84 | 1 => WatchedEventType::NodeCreated, 85 | 2 => WatchedEventType::NodeDeleted, 86 | 3 => WatchedEventType::NodeDataChanged, 87 | 4 => WatchedEventType::NodeChildrenChanged, 88 | 5 => WatchedEventType::DataWatchRemoved, 89 | 6 => WatchedEventType::ChildWatchRemoved, 90 | _ => unreachable!("unknown event type {:x}", code), 91 | } 92 | } 93 | } 94 | --------------------------------------------------------------------------------