From fabeaa7a1002d5a042746813d66da4c936cf2efb Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Wed, 7 Jun 2017 21:37:48 -0700 Subject: [PATCH] Extract haret-cli-client into a lib and bin crate This starts pulling apart haret (#115), specifically cli client binary into it's own module. The main reason to do this is for a few reasons. First, it allows us to start framing out a higher level library interface for Haret (haret-client). Second, it allows us to shave off some dependencies if we only need a subset for a particular application. haret-client will need a lot more attention over time, since right now it just responds with string output. Note that I've added a .gitignore to `haret-client`, as the standard practice in the community is to only lock down dependencies in the application crates, but leave it up to the library consumers to decide what dependency versions they want to use. --- .travis.yml | 7 + Cargo.toml | 5 - haret-cli-client/Cargo.lock | 687 ++++++++++++++++++++++++++++++ haret-cli-client/Cargo.toml | 18 + haret-cli-client/src/main.rs | 417 ++++++++++++++++++ haret-client/.gitignore | 1 + haret-client/Cargo.toml | 12 + haret-client/src/lib.rs | 474 +++++++++++++++++++++ src/bin/haret-cli-client.rs | 795 ----------------------------------- 9 files changed, 1616 insertions(+), 800 deletions(-) create mode 100644 haret-cli-client/Cargo.lock create mode 100644 haret-cli-client/Cargo.toml create mode 100644 haret-cli-client/src/main.rs create mode 100644 haret-client/.gitignore create mode 100644 haret-client/Cargo.toml create mode 100644 haret-client/src/lib.rs delete mode 100644 src/bin/haret-cli-client.rs diff --git a/.travis.yml b/.travis.yml index d2da893..40f329f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,10 @@ rust: - stable - beta - nightly +script: + - cargo build --verbose + - cargo test --verbose + - cd haret-client && cargo build --verbose + - cd haret-client && cargo test --verbose + - cd haret-cli-client && cargo build --verbose + - cd haret-cli-client && cargo test --verbose diff --git a/Cargo.toml b/Cargo.toml index 7bb2f39..643c23e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,11 +45,6 @@ name = "haret-admin" path = "src/bin/haret-admin.rs" test = false -[[bin]] -name = "haret-cli-client" -path = "src/bin/haret-cli-client.rs" -test = false - [[bin]] name = "devconfig" path = "src/bin/devconfig.rs" diff --git a/haret-cli-client/Cargo.lock b/haret-cli-client/Cargo.lock new file mode 100644 index 0000000..a3173c6 --- /dev/null +++ b/haret-cli-client/Cargo.lock @@ -0,0 +1,687 @@ +[root] +name = "haret-cli-client" +version = "0.1.0" +dependencies = [ + "haret-client 0.1.0", + "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "amy" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "dbghelp-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "error-chain" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "error-chain" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ferris" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "funfsm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gcc" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "haret" +version = "0.1.0" +dependencies = [ + "amy 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "funfsm 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "orset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "protobuf 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rabble 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "rmp-serde 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-envlogger 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-stdlog 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-term 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "vertree 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "haret-client" +version = "0.1.0" +dependencies = [ + "haret 0.1.0", + "protobuf 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "isatty" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "orset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "protobuf" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rabble" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "amy 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ferris 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "orset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "protobuf 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rmp-serde 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-envlogger 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-stdlog 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-term 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "regex" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rmp" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rmp" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rmp-serde" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rmp 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive_internals" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slog" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slog-envlogger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-stdlog 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-term 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slog-extra" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "slog 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slog-stdlog" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-term 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slog-stream" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "slog 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-extra 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slog-term" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", + "isatty 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-stream 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread-id" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread-id" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "toml" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "uuid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "vertree" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "error-chain 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rmp 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +"checksum amy 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ca2815413de45416b94fad6becdedbb08f24b1ab393cd7ceff71a236b44618b6" +"checksum backtrace 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "346d7644f0b5f9bc73082d3b2236b69a05fd35cce0cfa3724e184e6a5c9e2a2f" +"checksum backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72f9b4182546f4b04ebc4ab7f84948953a118bd6021a1b6a6c909e3e94f6be76" +"checksum backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3a0d842ea781ce92be2bf78a9b38883948542749640b8378b3b2f03d1fd9f1ff" +"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" +"checksum byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304" +"checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8" +"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" +"checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" +"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97" +"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" +"checksum error-chain 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5c82c815138e278b8dcdeffc49f27ea6ffb528403e9dea4194f2e3dd40b143" +"checksum error-chain 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6930e04918388a9a2e41d518c25cf679ccafe26733fb4127dbf21993f2575d46" +"checksum ferris 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d3a065de76ec3693f497af0043fa8f0d753b3fae8ff2b7837587d7e015f1df8" +"checksum funfsm 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8968afe94e750bbc641df9c0b1068f42d84e80161385aa14e1276fecc069650" +"checksum gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)" = "5f837c392f2ea61cb1576eac188653df828c861b7137d74ea4a5caa89621f9e6" +"checksum isatty 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fa500db770a99afe2a0f2229be2a3d09c7ed9d7e4e8440bf71253141994e240f" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417" +"checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" +"checksum libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "e32a70cf75e5846d53a673923498228bbec6a8624708a9ea5645f075d6276122" +"checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e" +"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" +"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum net2 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "bc01404e7568680f1259aa5729539f221cb1e6d047a0d9053cab4be8a73b5d67" +"checksum nix 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a7bb1da2be7da3cbffda73fc681d509ffd9e665af478d2bee1907cee0bc64b2" +"checksum num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "98b15ba84e910ea7a1973bccd3df7b31ae282bf9d8bd2897779950c9b8303d40" +"checksum num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ef1a4bf6f9174aa5783a9b4cc892cacd11aebad6c69ad027a0b65c6ca5f8aa37" +"checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e" +"checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" +"checksum orset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "403a860cfab9be3c99fb8ca860c86bb17b85fd0bb7ff223174456a16645f625c" +"checksum protobuf 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3e2ed6fe8ff3b20b44bb4b4f54de12ac89dc38cb451dce8ae5e9dcd1507f738" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum rabble 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4414d6b460a7b54a2b21daba76bdf73a70e753e12253c7f576323ee901dab498" +"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" +"checksum redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "3041aeb6000db123d2c9c751433f526e1f404b23213bd733167ab770c3989b4d" +"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" +"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" +"checksum rmp 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a2da68cc45d803dfd68724d767363d82c6f76293a2bf5fe6ded34f640ee01447" +"checksum rmp 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7ce560a5728f4eec697f07f8d7fa20608893d44b4f5b8f9f5f51a2987f3cffe2" +"checksum rmp-serde 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f4a4c47e55d5b719a58f75af7454bcfe62a4d2018746f339510d159351ceb5b1" +"checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" +"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" +"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" +"checksum serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f530d36fb84ec48fb7146936881f026cdbf4892028835fd9398475f82c1bb4" +"checksum serde_derive 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "10552fad5500771f3902d0c5ba187c5881942b811b7ba0d8fbbfbf84d80806d3" +"checksum serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37aee4e0da52d801acfbc0cc219eb1eda7142112339726e427926a6f6ee65d3a" +"checksum slog 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bab9d589681f7d6b9ca4ed5cc861779a392bca7beaae2f69f2341617415a78dc" +"checksum slog-envlogger 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfea715bb310c33c8f90e659bce5b95e39851348b9a7e2a77495a069662def78" +"checksum slog-extra 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "511581f4dd1dc90e4eca99b60be8a692d9c975e8757558aa774f16007d27492a" +"checksum slog-stdlog 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56cc08f40c45e0ab41dcfde0a19a22c5b7176d3827fc7d078450ebfdc080a37c" +"checksum slog-stream 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3fac4af71007ddb7338f771e059a46051f18d1454d8ac556f234a0573e719daa" +"checksum slog-term 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb53c0bae0745898fd5a7b75b1c389507333470ac4c645ae431890c0f828b6ca" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" +"checksum thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8df7875b676fddfadffd96deea3b1124e5ede707d4884248931077518cf1f773" +"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" +"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" +"checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3" +"checksum toml 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4cc5dbfb20a481e64b99eb7ae280859ec76730c7191570ba5edaa962394edb0a" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" +"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d0f5103675a280a926ec2f9b7bcc2ef49367df54e8c570c3311fec919f9a8b" +"checksum vertree 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3574c0e8259a200995a94785983ff72ab68183d4f6125e64b8afc6f2137a80da" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" diff --git a/haret-cli-client/Cargo.toml b/haret-cli-client/Cargo.toml new file mode 100644 index 0000000..9dfbcdb --- /dev/null +++ b/haret-cli-client/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "haret-cli-client" +version = "0.1.0" +authors = ["Andrew J. Stone "] +description = "A strongly consistent distributed coordination service" +repository = "https://github.com/vmware/haret" +keywords = ["distributed", "coordination", "strong consistency"] +license = "Apache-2.0" + +[dependencies] +haret-client = { path = "../haret-client" } +uuid = { version = "0.5", features = ["serde", "v4"] } +lazy_static = "0.1" + +[[bin]] +name = "haret-cli-client" +path = "src/main.rs" +test = false diff --git a/haret-cli-client/src/main.rs b/haret-cli-client/src/main.rs new file mode 100644 index 0000000..863c53f --- /dev/null +++ b/haret-cli-client/src/main.rs @@ -0,0 +1,417 @@ +// Copyright © 2016-2017 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +extern crate haret_client; +extern crate uuid; + +#[macro_use] +extern crate lazy_static; + +use std::env; +use std::str; +use std::env::Args; +use std::io; +use std::process::exit; +use std::io::{Result, Error, ErrorKind, Write}; +use uuid::Uuid; +use haret_client::HaretClient; + +lazy_static! { + static ref HELP: String = make_help(); +} + +fn main() { + let mut args = env::args(); + // Skip arg0 + let _ = args.next(); + + let addr: String = match args.next() { + Some(api_addr) => api_addr, + None => { + println!("Missing IP Address\n{}", help()); + exit(-1); + } + }; + + let mut client = HaretClient::new(Uuid::new_v4().to_string()); + client.connect(Some(addr)).unwrap(); + + if let Some(flag) = args.next() { + run_script(&flag, args, client) + } else { + run_interactive(client) + } +} + +fn run_interactive(mut client: HaretClient) { + loop { + prompt(); + let mut command = String::new(); + io::stdin().read_line(&mut command).unwrap(); + if &command == "help\n" { + println!("{}", help()); + } + match run(command, &mut client) { + Ok(result) => println!("{}", result), + Err(err) => println!("{}", err) + } + } +} + +fn run_script(flag: &str, mut args: Args, mut client: HaretClient) { + if flag != "-e" { + println!("Invalid Flag"); + println!("{}", help()); + exit(-1); + } + let command = args.next().unwrap_or(String::new()); + match run(command, &mut client) { + Ok(result) => { + println!("{}", result); + exit(0); + } + Err(err) => { + println!("{}", err); + exit(-1) + } + } +} + +fn run(command: String, mut client: &mut HaretClient) -> Result { + let args: Vec<_> = command.split_whitespace().collect(); + for command in commands() { + if pattern_match(&command.pattern, &args) { + if command.consensus && client.primary.is_none() { + let msg = "This command must be run inside a namespace. Please call `enter \ + `."; + return Err(Error::new(ErrorKind::InvalidInput, msg)); + } + return (command.handler)(args, &mut client); + } + } + Err(Error::new(ErrorKind::InvalidInput, "Invalid Input. Type 'help' for commands")) +} + +fn prompt() { + let mut stdout = io::stdout(); + stdout.write_all(b"haret> ").unwrap(); + stdout.flush().unwrap(); +} + +struct Command { + pattern: &'static str, + description: &'static str, + handler: fn(Vec<&str>, &mut HaretClient) -> Result, + consensus: bool, +} + +fn commands() -> Vec { + vec![ + Command { + pattern: "list-namespaces", + description: "List all namespaces", + handler: list_namespaces, + consensus: false + }, + Command { + pattern: "enter $namespace_id", + description: "Enter a namespace to issue consensus requests", + handler: enter_namespace, + consensus: false + }, + Command { + pattern: "ls", + description: "List all keys in the current namespace", + handler: ls, + consensus: true, + }, + Command { + pattern: "create *blob,set,queue $path", + description: "Create a new node at of type blob, set or queue", + handler: create, + consensus: true, + }, + Command { + pattern: "blob put $key $val", + description: "Put a blob to the given key", + handler: blob_put, + consensus: true, + }, + Command { + pattern: "blob get $key", + description: "Get a blob from the given key", + handler: blob_get, + consensus: true, + }, + Command { + pattern: "blob size $key", + description: "Get the size of the blob at the given key", + handler: blob_size, + consensus: true, + }, + Command { + pattern : "queue push $path $val", + description: "Push a blob onto the back of the queue at ", + handler: queue_push, + consensus: true, + }, + Command { + pattern: "queue pop $path", + description: "Pop a blob off the front of the queue at ", + handler: queue_pop, + consensus: true, + }, + Command { + pattern: "queue front $path", + description: "Get a copy of the blob at the front of the queue without removing it", + handler: queue_front, + consensus: true, + }, + Command { + pattern: "queue back $path", + description: "Get a copy of the blob at the back of the queue without removing it", + handler: queue_back, + consensus: true, + }, + Command { + pattern: "queue len $path", + description: "Get the lenght of the queue at ", + handler: queue_len, + consensus: true, + }, + Command { + pattern : "set insert $path $val", + description: "Insert a blob into the set at ", + handler: set_insert, + consensus: true, + }, + Command { + pattern : "set remove $path $val", + description: "Remove a blob from the set at ", + handler: set_remove, + consensus: true, + }, + Command { + pattern : "set contains $path $val", + description: "Return true if the set at contains ", + handler: set_contains, + consensus: true, + }, + Command { + pattern : "set union +path", + description: "Return the union of the sets at the given paths", + handler: set_union, + consensus: true, + }, + Command { + pattern: "set intersection $path1 $path2", + description: "Return the intersection of the sets at the given paths", + handler: set_intersection, + consensus: true, + }, + ] +} + +fn pattern_to_help_string(pattern: &str) -> String { + let split = pattern.split_whitespace(); + let indent = " ".to_string(); + split.fold(indent, |mut acc, word| { + if word == "" { + return acc; + } + + // Required paramter + if word.starts_with("$") { + acc.push('<'); + acc.push_str(&word[1..]); + acc.push('>'); + acc.push(' '); + return acc; + } + + // Required option list + if word.starts_with("*") { + acc.push('<'); + acc.push_str(&word[1..]); + acc.push('>'); + acc.push(' '); + return acc; + } + + // Remainder of line is space seperated options of the same type + if word.starts_with("+") { + acc.push('<'); + acc.push_str(&word[1..]); + acc.push_str("1"); + acc.push('>'); + acc.push(' '); + acc.push('<'); + acc.push_str(&word[1..]); + acc.push_str("2"); + acc.push('>'); + acc.push(' '); + acc.push_str("..."); + return acc; + } + + acc.push_str(word); + acc.push(' '); + acc + }) +} + +fn make_help() -> String { + let commands = commands(); + let mut s = "Usage: haret-cli-client [-e ]\n\n".to_string(); + s.push_str(" Commands\n"); + let help_patterns: Vec<_> = commands.iter().map(|c| pattern_to_help_string(&c.pattern)).collect(); + let column2_pos = help_patterns.iter().fold(0, |acc, p| { + if p.len() > acc { + return p.len(); + } + acc + }) + 8; + for (command, pattern) in commands.iter().zip(help_patterns) { + s.push_str(&pattern); + s.push_str(str::from_utf8(&vec![' ' as u8; column2_pos - pattern.len()]).unwrap()); + s.push_str(command.description); + s.push('\n'); + } + let rest = +" + Flags: + -e Non-interactive mode + + Node Types: + blob + queue + set +"; + s.push_str(rest); + s +} + +fn pattern_match(pattern: &'static str, argv: &Vec<&str>) -> bool { + let split_pattern = pattern.split_whitespace(); + let argv = argv.iter(); + let mut iter = split_pattern.zip(argv.clone()); + let mut varargs = false; + let matched = iter.all(|(pattern, arg)| { + if pattern == "" && *arg == "" { + return true; + } + if pattern.starts_with("$") { + return true; + } + if pattern.starts_with("+") { + varargs = true; + return true; + } + if pattern.starts_with("*") { + if pattern[1..].split(",").find(|option| arg == option).is_none() { + println!("Argument must be one of: {}", pattern); + return false; + } + return true; + } + pattern == *arg + }); + if matched == false { return false; } + if !varargs && pattern.split_whitespace().count() != argv.len() { + return false; + } + true +} + +fn list_namespaces(_: Vec<&str>, client: &mut HaretClient) -> Result { + client.list_namespaces() +} + +fn enter_namespace(argv: Vec<&str>, client: &mut HaretClient) -> Result { + client.enter_namespace(argv[1]) +} + +fn ls(_: Vec<&str>, client: &mut HaretClient) -> Result { + client.ls() +} + +fn create(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let path = args.pop().unwrap(); + let str_type = args.pop().unwrap(); + client.create(path, str_type) +} + +fn blob_put(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let blob = args.pop().unwrap(); + let path = args.pop().unwrap(); + client.blob_put(blob, path) +} + +fn blob_get(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let path = args.pop().unwrap(); + client.blob_get(path) +} + +fn blob_size(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let path = args.pop().unwrap(); + client.blob_size(path) +} + +fn queue_push(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let blob = args.pop().unwrap(); + let path = args.pop().unwrap(); + client.queue_push(blob, path) +} + +fn queue_pop(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let path = args.pop().unwrap(); + client.queue_pop(path) +} + +fn queue_front(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let path = args.pop().unwrap(); + client.queue_front(path) +} + +fn queue_back(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let path = args.pop().unwrap(); + client.queue_back(path) +} + +fn queue_len(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let path = args.pop().unwrap(); + client.queue_len(path) +} + +fn set_insert(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let blob = args.pop().unwrap(); + let path = args.pop().unwrap(); + client.set_insert(blob, path) +} + +fn set_remove(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let blob = args.pop().unwrap(); + let path = args.pop().unwrap(); + client.set_remove(blob, path) +} + +fn set_contains(mut args: Vec<&str>, client: &mut HaretClient) -> Result { + let blob = args.pop().unwrap(); + let path = args.pop().unwrap(); + client.set_contains(blob, path) +} + +fn set_union(args: Vec<&str>, client: &mut HaretClient) -> Result { + client.set_union(args.into_iter().skip(2)) +} + +fn set_intersection(args: Vec<&str>, client: &mut HaretClient) -> Result { + let mut iter = args.into_iter().skip(2); + let path1 = iter.next().unwrap(); + let path2 = iter.next().unwrap(); + client.set_intersection(path1, path2) +} + +fn help() -> Error { + Error::new(ErrorKind::InvalidInput, HELP.clone()) +} diff --git a/haret-client/.gitignore b/haret-client/.gitignore new file mode 100644 index 0000000..03314f7 --- /dev/null +++ b/haret-client/.gitignore @@ -0,0 +1 @@ +Cargo.lock diff --git a/haret-client/Cargo.toml b/haret-client/Cargo.toml new file mode 100644 index 0000000..696d58c --- /dev/null +++ b/haret-client/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "haret-client" +version = "0.1.0" +authors = ["Andrew J. Stone "] +description = "A strongly consistent distributed coordination service" +repository = "https://github.com/vmware/haret" +keywords = ["distributed", "coordination", "strong consistency"] +license = "Apache-2.0" + +[dependencies] +haret = { path = ".." } +protobuf = "1.0.24" diff --git a/haret-client/src/lib.rs b/haret-client/src/lib.rs new file mode 100644 index 0000000..6eca7c7 --- /dev/null +++ b/haret-client/src/lib.rs @@ -0,0 +1,474 @@ +// Copyright © 2016-2017 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +extern crate haret; +extern crate protobuf; + +use std::io::{Read, Result, Error, ErrorKind, Write}; +use std::mem; +use std::net::TcpStream; +use protobuf::{RepeatedField, parse_from_bytes, Message}; +use haret::api::messages::*; + +/// This struct represents the Haret client implementation in rust. It is a low level client that is +/// useful for building higher level native clients or for building clients in other langauges via +/// FFI. +pub struct HaretClient { + pub client_id: String, + pub api_addr: Option, + pub namespace_id: Option, + pub primary: Option, + sock: Option, + request_num: u64 +} + +impl HaretClient { + pub fn new(client_id: String) -> HaretClient { + HaretClient { + client_id: client_id, + api_addr: None, + namespace_id: None, + primary: None, + sock: None, + request_num: 0 + } + } + + /// Connect to `self.api_addr` + pub fn connect(&mut self, api_addr: Option) -> Result<()> { + if api_addr.is_none() && self.api_addr.is_none() { + return Err(Error::new(ErrorKind::InvalidInput, + "API Address unknown. Please call connect with an api_addr.")); + } + if api_addr.is_some() { + self.api_addr = api_addr; + } + self.sock = Some(TcpStream::connect(&self.api_addr.as_ref().unwrap()[..])?); + Ok(()) + } + + /// Register the client id on this node for the given namespace. + /// + /// This function returns the registration message to be written or an error if the primary is + /// unknown. + pub fn register(&mut self, primary: Option) -> Result { + let request = self.prepare_register(primary)?; + self.exec(request) + } + + /// Register the client id on this node for the given namespace. + /// + /// This function returns the registration message to be written or an error if the primary is + /// unknown. + fn prepare_register(&mut self, primary: Option) -> Result { + if primary.is_none() && self.primary.is_none() { + return Err(Error::new(ErrorKind::InvalidInput, "Primary unknown")); + } + + if primary.is_some() { + self.primary = primary; + self.namespace_id = Some(self.primary.as_ref().unwrap() + .get_group().to_string()); + } + let namespace_id = self.namespace_id.clone(); + let mut msg = RegisterClient::new(); + msg.set_client_id(self.client_id.clone()); + msg.set_namespace_id(namespace_id.as_ref().unwrap().clone()); + let mut request = ApiRequest::new(); + request.set_register_client(msg); + Ok(request) + } + + pub fn list_namespaces(&mut self) -> Result { + let mut request = ApiRequest::new(); + request.set_get_namespaces(true); + self.exec(request) + } + + pub fn enter_namespace(&mut self, namespace_id: T) -> Result + where T: Into, + { + self.reset_primary(); + let mut msg = RegisterClient::new(); + msg.set_client_id(self.client_id.clone()); + msg.set_namespace_id(namespace_id.into()); + let mut request = ApiRequest::new(); + request.set_register_client(msg); + self.exec(request) + } + + pub fn ls(&mut self) -> Result { + let mut list_keys = ListKeys::new(); + list_keys.set_path("/".to_string()); + + let mut tree_op = TreeOp::new(); + tree_op.set_list_keys(list_keys); + + let mut consensus_req = ConsensusRequest::new(); + consensus_req.set_to(self.primary.as_ref().unwrap().clone()); + consensus_req.set_client_id(self.client_id.clone()); + consensus_req.set_client_request_num(self.request_num); + consensus_req.set_tree_op(tree_op); + + let mut request = ApiRequest::new(); + request.set_consensus_request(consensus_req); + self.exec(request) + } + + pub fn create(&mut self, path: &str, str_type: &str) -> Result { + let node_type = match &str_type as &str { + "blob" => NodeType::BLOB, + "queue" => NodeType::QUEUE, + "set" => NodeType::SET, + _ => unreachable!() + }; + let mut create_node = CreateNode::new(); + create_node.set_path(path.to_string()); + create_node.set_node_type(node_type); + let mut tree_op = TreeOp::new(); + tree_op.set_create_node(create_node); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn blob_put(&mut self, blob: &str, path: &str) -> Result { + let mut blob_put = BlobPut::new(); + blob_put.set_path(path.to_string()); + blob_put.set_val(blob.as_bytes().to_vec()); + let mut tree_op = TreeOp::new(); + tree_op.set_blob_put(blob_put); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn blob_get(&mut self, path: &str) -> Result { + let mut blob_get = BlobGet::new(); + blob_get.set_path(path.to_string()); + let mut tree_op = TreeOp::new(); + tree_op.set_blob_get(blob_get); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn blob_size(&mut self, path: &str) -> Result { + let mut blob_size = BlobSize::new(); + blob_size.set_path(path.to_string()); + let mut tree_op = TreeOp::new(); + tree_op.set_blob_size(blob_size); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn queue_push(&mut self, blob: &str, path: &str) -> Result { + let mut queue_push = QueuePush::new(); + queue_push.set_path(path.to_string()); + queue_push.set_val(blob.as_bytes().to_vec()); + let mut tree_op = TreeOp::new(); + tree_op.set_queue_push(queue_push); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn queue_pop(&mut self, path: &str) -> Result { + let mut queue_pop = QueuePop::new(); + queue_pop.set_path(path.to_string()); + let mut tree_op = TreeOp::new(); + tree_op.set_queue_pop(queue_pop); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn queue_front(&mut self, path: &str) -> Result { + let mut queue_front = QueueFront::new(); + queue_front.set_path(path.to_string()); + let mut tree_op = TreeOp::new(); + tree_op.set_queue_front(queue_front); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn queue_back(&mut self, path: &str) -> Result { + let mut queue_back = QueueBack::new(); + queue_back.set_path(path.to_string()); + let mut tree_op = TreeOp::new(); + tree_op.set_queue_back(queue_back); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn queue_len(&mut self, path: &str) -> Result { + let mut queue_len = QueueLen::new(); + queue_len.set_path(path.to_string()); + let mut tree_op = TreeOp::new(); + tree_op.set_queue_len(queue_len); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn set_insert(&mut self, blob: &str, path: &str) -> Result { + let mut set_insert = SetInsert::new(); + set_insert.set_path(path.to_string()); + set_insert.set_val(blob.as_bytes().to_vec()); + let mut tree_op = TreeOp::new(); + tree_op.set_set_insert(set_insert); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn set_remove(&mut self, blob: &str, path: &str) -> Result { + let mut set_remove = SetRemove::new(); + set_remove.set_path(path.to_string()); + set_remove.set_val(blob.as_bytes().to_vec()); + let mut tree_op = TreeOp::new(); + tree_op.set_set_remove(set_remove); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn set_contains(&mut self, blob: &str, path: &str) -> Result { + let mut set_contains = SetContains::new(); + set_contains.set_path(path.to_string()); + set_contains.set_val(blob.as_bytes().to_vec()); + let mut tree_op = TreeOp::new(); + tree_op.set_set_contains(set_contains); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn set_union(&mut self, paths: I) -> Result + where I: Iterator, + T: Into, + { + let paths = paths.map(|s| s.into()).collect(); + let mut set_union = SetUnion::new(); + set_union.set_paths(RepeatedField::from_vec(paths)); + let mut tree_op = TreeOp::new(); + tree_op.set_set_union(set_union); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + pub fn set_intersection(&mut self, path1: &str, path2: &str) -> Result { + let mut set_intersection = SetIntersection::new(); + set_intersection.set_path1(path1.to_string()); + set_intersection.set_path2(path2.to_string()); + let mut tree_op = TreeOp::new(); + tree_op.set_set_intersection(set_intersection); + let request = self.consensus_request(tree_op); + self.exec(request) + } + + fn consensus_request(&mut self, tree_op: TreeOp) -> ApiRequest { + let mut consensus_req = ConsensusRequest::new(); + consensus_req.set_to(self.primary.as_ref().unwrap().clone()); + consensus_req.set_client_id(self.client_id.clone()); + consensus_req.set_client_request_num(self.request_num); + consensus_req.set_tree_op(tree_op); + let mut api_request = ApiRequest::new(); + api_request.set_consensus_request(consensus_req); + api_request + } + + fn exec(&mut self, req: ApiRequest) -> Result { + self.write_msg(req).map_err(|_| { + Error::new(ErrorKind::NotConnected, + "Failed to write to socket. Please restart client and try again".to_string()) + })?; + let mut api_response = self.read_msg().map_err(|_| { + Error::new(ErrorKind::NotConnected, + "Failed to read from socket. Please restart client and try again".to_string()) + })?; + + if api_response.has_consensus_reply() { + let mut consensus_reply = api_response.take_consensus_reply(); + + let mut s = String::new(); + + if consensus_reply.has_ok() { + s.push_str("Ok\n"); + } + + if consensus_reply.has_tree_op_result() { + s.push_str(&tree_op_result_to_string(consensus_reply.take_tree_op_result())); + } + + if consensus_reply.has_tree_cas_result() { + for result in consensus_reply.take_tree_cas_result().take_results().into_iter() { + s.push_str(&tree_op_result_to_string(result)); + } + } + + if consensus_reply.has_path() { + s.push_str(&format!("{}", consensus_reply.take_path())); + } + + if consensus_reply.has_error() { + s.push_str("Error: "); + s.push_str(&api_error_to_string(consensus_reply.take_error())); + s.push('\n'); + } + + s.push_str(&format!("Epoch = {}, View = {}, Client Request Num = {}", + consensus_reply.get_epoch(), + consensus_reply.get_view(), + consensus_reply.get_request_num())); + return Ok(s); + } + + if api_response.has_namespaces() { + let namespaces = api_response.take_namespaces().take_ids().to_vec(); + return Ok(namespaces.iter().fold(String::new(), |mut acc, namespace_id | { + acc.push_str(&namespace_id); + acc.push_str("\n"); + acc + })); + } + + if api_response.has_client_registration() { + self.primary = Some(api_response.take_client_registration().take_primary()); + return Ok(format!("Client registered. Primary = {:?}", self.primary.as_ref().unwrap())); + } + + if api_response.has_redirect() { + let mut redirect = api_response.take_redirect(); + let primary = redirect.take_primary(); + let api_addr = redirect.take_api_addr(); + self.connect(Some(api_addr))?; + let req = self.prepare_register(Some(primary.clone()))?; + /// Todo: Remove this recursion to prevent potential stack overflow + self.exec(req)?; + return Ok(format!("Finished Redirecting. Primary = {:?}, API Address = {}", + self.primary.as_ref().unwrap(), + self.api_addr.as_ref().unwrap())) + } + + if api_response.has_retry() { + let duration = api_response.take_retry().get_milliseconds(); + return Ok(format!("Primary not found. Please retry in {} seconds.", duration*1000)); + } + + if api_response.has_unknown_namespace() { + return Ok("Unknown namespace".to_string()); + } + + if api_response.has_timeout() { + return Ok("Timeout".to_string()); + } + + Ok(format!("unknown message {:?}", api_response)) + } + + fn reset_primary(&mut self) { + self.primary = None; + self.namespace_id = None; + } + + fn write_msg(&mut self, req: ApiRequest) -> Result<()> { + let mut msg = ApiMsg::new(); + msg.set_request(req); + let encoded = msg.write_to_bytes().map_err(|_| { + Error::new(ErrorKind::InvalidInput, "Failed to encode msgpack data") + })?; + let len: u32 = encoded.len() as u32; + // 4 byte len header + let header: [u8; 4] = unsafe { mem::transmute(len.to_be()) }; + self.sock.as_ref().unwrap().write_all(&header)?; + self.sock.as_ref().unwrap().write_all(&encoded)?; + self.request_num += 1; + Ok(()) + } + + fn read_msg(&mut self) -> Result { + let mut header = [0; 4]; + self.sock.as_mut().unwrap().read_exact(&mut header)?; + let len = unsafe { u32::from_be(mem::transmute(header)) }; + let mut buf = vec![0; len as usize]; + self.sock.as_mut().unwrap().read_exact(&mut buf)?; + let mut msg: ApiMsg = parse_from_bytes(&buf[..]).map_err(|e| { + Error::new(ErrorKind::InvalidData, e.to_string()) + })?; + Ok(msg.take_response()) + } +} + +fn tree_op_result_to_string(mut result: TreeOpResult) -> String { + let mut s = String::new(); + + if result.has_ok() { + s.push_str("Ok\n"); + } else if result.has_empty() { + s.push_str("\n"); + } else if result.has_bool() { + s.push_str(&format!("{}\n", result.get_bool())); + } else if result.has_blob() { + s.push_str(&format_blob(result.take_blob())); + } else if result.has_int() { + s.push_str(&format!("{:?}\n", result.get_int())); + } else if result.has_set() { + s.push_str(&format_set(result.take_set())); + } else if result.has_keys() { + for mut key in result.take_keys().take_keys().into_vec() { + s.push_str(&format!("{}\n", key.take_name())); + } + } + + if result.has_optional_version() { + s.push_str(&format!("Version = {} ", result.get_optional_version())); + } + s +} + +fn format_blob(blob: Vec) -> String { + match String::from_utf8(blob) { + Ok(s) => format!("{}\n", s), + Err(e) => format!("{:?}\n", e.into_bytes()) + } +} + +fn format_set(mut set: Set) -> String { + set.take_val().into_vec().into_iter().fold(String::new(), |mut acc, blob| { + acc.push_str(&format_blob(blob)); + acc + }) +} + +fn api_error_to_string(mut error: ApiError) -> String { + if error.has_not_found() { + format!("Path Not found: {}", error.take_not_found().take_path()) + } else if error.has_already_exists() { + format!("Path Already exists: {}", error.take_already_exists().take_path()) + } else if error.has_does_not_exist() { + format!("Path Does not exist: {}", error.take_does_not_exist().take_path()) + } else if error.has_wrong_type() { + "Wrong type".to_string() + } else if error.has_path_must_end_in_directory() { + format!("Path must end in directory: {}", + error.take_path_must_end_in_directory().take_path()) + } else if error.has_path_must_be_absolute() { + "Paths must be absolute".to_string() + } else if error.has_cas_failed() { + "Cas Failed".to_string() + } else if error.has_bad_format() { + format!("Path is malformatted {}", error.take_bad_format().take_msg()) + } else if error.has_io() { + format!("IO error: {}", error.take_io().take_msg()) + } else if error.has_encoding() { + format!("Encoding error: {}", error.take_encoding().take_msg()) + } else if error.has_invalid_cas() { + format!("Invalid CAS: {}", error.take_invalid_cas().take_msg()) + } else if error.has_msg() { + error.take_msg() + } else if error.has_cannot_delete_root() { + "Cannot delete root".to_string() + } else if error.has_invalid_msg() { + "Invalid Message".to_string() + } else if error.has_timeout() { + "Timeout".to_string() + } else if error.has_not_enough_replicas() { + "Not enough replicas".to_string() + } else if error.has_bad_epoch() { + "Bad epoch".to_string() + } else { + "Unknown Error".to_string() + } +} diff --git a/src/bin/haret-cli-client.rs b/src/bin/haret-cli-client.rs deleted file mode 100644 index 7c8db8c..0000000 --- a/src/bin/haret-cli-client.rs +++ /dev/null @@ -1,795 +0,0 @@ -// Copyright © 2016-2017 VMware, Inc. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -extern crate haret; -extern crate uuid; -extern crate protobuf; - -#[macro_use] -extern crate lazy_static; - -use std::env; -use std::str; -use std::env::Args; -use std::io; -use std::process::exit; -use std::io::{Read, Result, Error, ErrorKind, Write}; -use std::net::TcpStream; -use std::mem; -use uuid::Uuid; -use protobuf::{RepeatedField, parse_from_bytes, Message}; -use haret::api::messages::*; - -lazy_static! { - static ref HELP: String = make_help(); -} - -fn main() { - let mut args = env::args(); - // Skip arg0 - let _ = args.next(); - - let addr: String = match args.next() { - Some(api_addr) => api_addr, - None => { - println!("Missing IP Address\n{}", help()); - exit(-1); - } - }; - - let mut client = HaretClient::new(Uuid::new_v4().to_string()); - client.connect(Some(addr)).unwrap(); - - if let Some(flag) = args.next() { - run_script(&flag, args, client) - } else { - run_interactive(client) - } -} - -fn run_interactive(mut client: HaretClient) { - loop { - prompt(); - let mut command = String::new(); - io::stdin().read_line(&mut command).unwrap(); - if &command == "help\n" { - println!("{}", help()); - } - match run(command, &mut client) { - Ok(result) => println!("{}", result), - Err(err) => println!("{}", err) - } - } -} - -fn run_script(flag: &str, mut args: Args, mut client: HaretClient) { - if flag != "-e" { - println!("Invalid Flag"); - println!("{}", help()); - exit(-1); - } - let command = args.next().unwrap_or(String::new()); - match run(command, &mut client) { - Ok(result) => { - println!("{}", result); - exit(0); - } - Err(err) => { - println!("{}", err); - exit(-1) - } - } -} - -fn run(command: String, mut client: &mut HaretClient) -> Result { - let req = parse(command, &mut client)?; - exec(req, client) -} - -fn prompt() { - let mut stdout = io::stdout(); - stdout.write_all(b"haret> ").unwrap(); - stdout.flush().unwrap(); -} - -struct Command { - pattern: &'static str, - description: &'static str, - handler: fn(Vec<&str>, &mut HaretClient) -> ApiRequest, - consensus: bool, -} - -fn commands() -> Vec { - vec![ - Command { - pattern: "list-namespaces", - description: "List all namespaces", - handler: list_namespaces, - consensus: false - }, - Command { - pattern: "enter $namespace_id", - description: "Enter a namespace to issue consensus requests", - handler: enter_namespace, - consensus: false - }, - Command { - pattern: "ls", - description: "List all keys in the current namespace", - handler: ls, - consensus: true, - }, - Command { - pattern: "create *blob,set,queue $path", - description: "Create a new node at of type blob, set or queue", - handler: create, - consensus: true, - }, - Command { - pattern: "blob put $key $val", - description: "Put a blob to the given key", - handler: blob_put, - consensus: true, - }, - Command { - pattern: "blob get $key", - description: "Get a blob from the given key", - handler: blob_get, - consensus: true, - }, - Command { - pattern: "blob size $key", - description: "Get the size of the blob at the given key", - handler: blob_size, - consensus: true, - }, - Command { - pattern : "queue push $path $val", - description: "Push a blob onto the back of the queue at ", - handler: queue_push, - consensus: true, - }, - Command { - pattern: "queue pop $path", - description: "Pop a blob off the front of the queue at ", - handler: queue_pop, - consensus: true, - }, - Command { - pattern: "queue front $path", - description: "Get a copy of the blob at the front of the queue without removing it", - handler: queue_front, - consensus: true, - }, - Command { - pattern: "queue back $path", - description: "Get a copy of the blob at the back of the queue without removing it", - handler: queue_back, - consensus: true, - }, - Command { - pattern: "queue len $path", - description: "Get the lenght of the queue at ", - handler: queue_len, - consensus: true, - }, - Command { - pattern : "set insert $path $val", - description: "Insert a blob into the set at ", - handler: set_insert, - consensus: true, - }, - Command { - pattern : "set remove $path $val", - description: "Remove a blob from the set at ", - handler: set_remove, - consensus: true, - }, - Command { - pattern : "set contains $path $val", - description: "Return true if the set at contains ", - handler: set_contains, - consensus: true, - }, - Command { - pattern : "set union +path", - description: "Return the union of the sets at the given paths", - handler: set_union, - consensus: true, - }, - Command { - pattern: "set intersection $path1 $path2", - description: "Return the intersection of the sets at the given paths", - handler: set_intersection, - consensus: true, - } - ] -} - -fn pattern_to_help_string(pattern: &str) -> String { - let split = pattern.split_whitespace(); - let indent = " ".to_string(); - split.fold(indent, |mut acc, word| { - if word == "" { - return acc; - } - - // Required paramter - if word.starts_with("$") { - acc.push('<'); - acc.push_str(&word[1..]); - acc.push('>'); - acc.push(' '); - return acc; - } - - // Required option list - if word.starts_with("*") { - acc.push('<'); - acc.push_str(&word[1..]); - acc.push('>'); - acc.push(' '); - return acc; - } - - // Remainder of line is space seperated options of the same type - if word.starts_with("+") { - acc.push('<'); - acc.push_str(&word[1..]); - acc.push_str("1"); - acc.push('>'); - acc.push(' '); - acc.push('<'); - acc.push_str(&word[1..]); - acc.push_str("2"); - acc.push('>'); - acc.push(' '); - acc.push_str("..."); - return acc; - } - - acc.push_str(word); - acc.push(' '); - acc - }) -} - -fn make_help() -> String { - let commands = commands(); - let mut s = "Usage: haret-cli-client [-e ]\n\n".to_string(); - s.push_str(" Commands\n"); - let help_patterns: Vec<_> = commands.iter().map(|c| pattern_to_help_string(&c.pattern)).collect(); - let column2_pos = help_patterns.iter().fold(0, |acc, p| { - if p.len() > acc { - return p.len(); - } - acc - }) + 8; - for (command, pattern) in commands.iter().zip(help_patterns) { - s.push_str(&pattern); - s.push_str(str::from_utf8(&vec![' ' as u8; column2_pos - pattern.len()]).unwrap()); - s.push_str(command.description); - s.push('\n'); - } - let rest = -" - Flags: - -e Non-interactive mode - - Node Types: - blob - queue - set -"; - s.push_str(rest); - s -} - -fn pattern_match(pattern: &'static str, argv: &Vec<&str>) -> bool { - let split_pattern = pattern.split_whitespace(); - let argv = argv.iter(); - let mut iter = split_pattern.zip(argv.clone()); - let mut varargs = false; - let matched = iter.all(|(pattern, arg)| { - if pattern == "" && *arg == "" { - return true; - } - if pattern.starts_with("$") { - return true; - } - if pattern.starts_with("+") { - varargs = true; - return true; - } - if pattern.starts_with("*") { - if pattern[1..].split(",").find(|option| arg == option).is_none() { - println!("Argument must be one of: {}", pattern); - return false; - } - return true; - } - pattern == *arg - }); - if matched == false { return false; } - if !varargs && pattern.split_whitespace().count() != argv.len() { - return false; - } - true -} - -fn parse(argv: String, mut client: &mut HaretClient) -> Result { - let args: Vec<_> = argv.split_whitespace().collect(); - for command in commands() { - if pattern_match(&command.pattern, &args) { - if command.consensus && client.primary.is_none() { - let msg = "This command must be run inside a namespace. Please call `enter \ - `."; - return Err(Error::new(ErrorKind::InvalidInput, msg)); - } - return Ok((command.handler)(args, &mut client)) - } - } - Err(Error::new(ErrorKind::InvalidInput, "Invalid Input. Type 'help' for commands")) -} - -fn list_namespaces(_: Vec<&str>, _: &mut HaretClient) -> ApiRequest { - let mut request = ApiRequest::new(); - request.set_get_namespaces(true); - request -} - -fn enter_namespace(argv: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - client.reset_primary(); - let mut msg = RegisterClient::new(); - msg.set_client_id(client.client_id.clone()); - msg.set_namespace_id(argv[1].to_string()); - let mut request = ApiRequest::new(); - request.set_register_client(msg); - request -} - -fn ls(_: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let mut list_keys = ListKeys::new(); - list_keys.set_path("/".to_string()); - - let mut tree_op = TreeOp::new(); - tree_op.set_list_keys(list_keys); - - let mut consensus_req = ConsensusRequest::new(); - consensus_req.set_to(client.primary.as_ref().unwrap().clone()); - consensus_req.set_client_id(client.client_id.clone()); - consensus_req.set_client_request_num(client.request_num); - consensus_req.set_tree_op(tree_op); - - let mut api_request = ApiRequest::new(); - api_request.set_consensus_request(consensus_req); - api_request -} - -fn consensus_request(tree_op: TreeOp, client: &mut HaretClient) -> ApiRequest { - let mut consensus_req = ConsensusRequest::new(); - consensus_req.set_to(client.primary.as_ref().unwrap().clone()); - consensus_req.set_client_id(client.client_id.clone()); - consensus_req.set_client_request_num(client.request_num); - consensus_req.set_tree_op(tree_op); - let mut api_request = ApiRequest::new(); - api_request.set_consensus_request(consensus_req); - api_request -} - -fn create(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let path = args.pop().unwrap(); - let str_type = args.pop().unwrap(); - let node_type = match &str_type as &str { - "blob" => NodeType::BLOB, - "queue" => NodeType::QUEUE, - "set" => NodeType::SET, - _ => unreachable!() - }; - let mut create_node = CreateNode::new(); - create_node.set_path(path.to_string()); - create_node.set_node_type(node_type); - let mut tree_op = TreeOp::new(); - tree_op.set_create_node(create_node); - consensus_request(tree_op, client) -} - -fn blob_put(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let blob = args.pop().unwrap(); - let path = args.pop().unwrap(); - let mut blob_put = BlobPut::new(); - blob_put.set_path(path.to_string()); - blob_put.set_val(blob.as_bytes().to_vec()); - let mut tree_op = TreeOp::new(); - tree_op.set_blob_put(blob_put); - consensus_request(tree_op, client) -} - -fn blob_get(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let path = args.pop().unwrap(); - let mut blob_get = BlobGet::new(); - blob_get.set_path(path.to_string()); - let mut tree_op = TreeOp::new(); - tree_op.set_blob_get(blob_get); - consensus_request(tree_op, client) -} - -fn blob_size(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let path = args.pop().unwrap(); - let mut blob_size = BlobSize::new(); - blob_size.set_path(path.to_string()); - let mut tree_op = TreeOp::new(); - tree_op.set_blob_size(blob_size); - consensus_request(tree_op, client) -} - -fn queue_push(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let blob = args.pop().unwrap(); - let path = args.pop().unwrap(); - let mut queue_push = QueuePush::new(); - queue_push.set_path(path.to_string()); - queue_push.set_val(blob.as_bytes().to_vec()); - let mut tree_op = TreeOp::new(); - tree_op.set_queue_push(queue_push); - consensus_request(tree_op, client) -} - -fn queue_pop(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let path = args.pop().unwrap(); - let mut queue_pop = QueuePop::new(); - queue_pop.set_path(path.to_string()); - let mut tree_op = TreeOp::new(); - tree_op.set_queue_pop(queue_pop); - consensus_request(tree_op, client) -} - -fn queue_front(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let path = args.pop().unwrap(); - let mut queue_front = QueueFront::new(); - queue_front.set_path(path.to_string()); - let mut tree_op = TreeOp::new(); - tree_op.set_queue_front(queue_front); - consensus_request(tree_op, client) -} - -fn queue_back(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let path = args.pop().unwrap(); - let mut queue_back = QueueBack::new(); - queue_back.set_path(path.to_string()); - let mut tree_op = TreeOp::new(); - tree_op.set_queue_back(queue_back); - consensus_request(tree_op, client) -} - -fn queue_len(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let path = args.pop().unwrap(); - let mut queue_len = QueueLen::new(); - queue_len.set_path(path.to_string()); - let mut tree_op = TreeOp::new(); - tree_op.set_queue_len(queue_len); - consensus_request(tree_op, client) -} - -fn set_insert(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let blob = args.pop().unwrap(); - let path = args.pop().unwrap(); - let mut set_insert = SetInsert::new(); - set_insert.set_path(path.to_string()); - set_insert.set_val(blob.as_bytes().to_vec()); - let mut tree_op = TreeOp::new(); - tree_op.set_set_insert(set_insert); - consensus_request(tree_op, client) -} - -fn set_remove(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let blob = args.pop().unwrap(); - let path = args.pop().unwrap(); - let mut set_remove = SetRemove::new(); - set_remove.set_path(path.to_string()); - set_remove.set_val(blob.as_bytes().to_vec()); - let mut tree_op = TreeOp::new(); - tree_op.set_set_remove(set_remove); - consensus_request(tree_op, client) -} - -fn set_contains(mut args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let blob = args.pop().unwrap(); - let path = args.pop().unwrap(); - let mut set_contains = SetContains::new(); - set_contains.set_path(path.to_string()); - set_contains.set_val(blob.as_bytes().to_vec()); - let mut tree_op = TreeOp::new(); - tree_op.set_set_contains(set_contains); - consensus_request(tree_op, client) -} - -fn set_union(args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let paths: Vec = args.into_iter().skip(2).map(|s| s.to_string()).collect(); - let mut set_union = SetUnion::new(); - set_union.set_paths(RepeatedField::from_vec(paths)); - let mut tree_op = TreeOp::new(); - tree_op.set_set_union(set_union); - consensus_request(tree_op, client) -} - -fn set_intersection(args: Vec<&str>, client: &mut HaretClient) -> ApiRequest { - let mut iter = args.into_iter().skip(2); - let path1 = iter.next().unwrap(); - let path2 = iter.next().unwrap(); - let mut set_intersection = SetIntersection::new(); - set_intersection.set_path1(path1.to_string()); - set_intersection.set_path2(path2.to_string()); - let mut tree_op = TreeOp::new(); - tree_op.set_set_intersection(set_intersection); - consensus_request(tree_op, client) -} - -fn tree_op_result_to_string(mut result: TreeOpResult) -> String { - let mut s = String::new(); - - if result.has_ok() { - s.push_str("Ok\n"); - } else if result.has_empty() { - s.push_str("\n"); - } else if result.has_bool() { - s.push_str(&format!("{}\n", result.get_bool())); - } else if result.has_blob() { - s.push_str(&format_blob(result.take_blob())); - } else if result.has_int() { - s.push_str(&format!("{:?}\n", result.get_int())); - } else if result.has_set() { - s.push_str(&format_set(result.take_set())); - } else if result.has_keys() { - for mut key in result.take_keys().take_keys().into_vec() { - s.push_str(&format!("{}\n", key.take_name())); - } - } - - if result.has_optional_version() { - s.push_str(&format!("Version = {} ", result.get_optional_version())); - } - s -} - -fn format_blob(blob: Vec) -> String { - match String::from_utf8(blob) { - Ok(s) => format!("{}\n", s), - Err(e) => format!("{:?}\n", e.into_bytes()) - } -} - -fn format_set(mut set: Set) -> String { - set.take_val().into_vec().into_iter().fold(String::new(), |mut acc, blob| { - acc.push_str(&format_blob(blob)); - acc - }) -} - -fn api_error_to_string(mut error: ApiError) -> String { - if error.has_not_found() { - format!("Path Not found: {}", error.take_not_found().take_path()) - } else if error.has_already_exists() { - format!("Path Already exists: {}", error.take_already_exists().take_path()) - } else if error.has_does_not_exist() { - format!("Path Does not exist: {}", error.take_does_not_exist().take_path()) - } else if error.has_wrong_type() { - "Wrong type".to_string() - } else if error.has_path_must_end_in_directory() { - format!("Path must end in directory: {}", - error.take_path_must_end_in_directory().take_path()) - } else if error.has_path_must_be_absolute() { - "Paths must be absolute".to_string() - } else if error.has_cas_failed() { - "Cas Failed".to_string() - } else if error.has_bad_format() { - format!("Path is malformatted {}", error.take_bad_format().take_msg()) - } else if error.has_io() { - format!("IO error: {}", error.take_io().take_msg()) - } else if error.has_encoding() { - format!("Encoding error: {}", error.take_encoding().take_msg()) - } else if error.has_invalid_cas() { - format!("Invalid CAS: {}", error.take_invalid_cas().take_msg()) - } else if error.has_msg() { - error.take_msg() - } else if error.has_cannot_delete_root() { - "Cannot delete root".to_string() - } else if error.has_invalid_msg() { - "Invalid Message".to_string() - } else if error.has_timeout() { - "Timeout".to_string() - } else if error.has_not_enough_replicas() { - "Not enough replicas".to_string() - } else if error.has_bad_epoch() { - "Bad epoch".to_string() - } else { - "Unknown Error".to_string() - } -} - -fn exec(req: ApiRequest, client: &mut HaretClient) -> Result { - client.write_msg(req).map_err(|_| { - Error::new(ErrorKind::NotConnected, - "Failed to write to socket. Please restart client and try again".to_string()) - })?; - let mut api_response = client.read_msg().map_err(|_| { - Error::new(ErrorKind::NotConnected, - "Failed to read from socket. Please restart client and try again".to_string()) - })?; - - if api_response.has_consensus_reply() { - let mut consensus_reply = api_response.take_consensus_reply(); - - let mut s = String::new(); - - if consensus_reply.has_ok() { - s.push_str("Ok\n"); - } - - if consensus_reply.has_tree_op_result() { - s.push_str(&tree_op_result_to_string(consensus_reply.take_tree_op_result())); - } - - if consensus_reply.has_tree_cas_result() { - for result in consensus_reply.take_tree_cas_result().take_results().into_iter() { - s.push_str(&tree_op_result_to_string(result)); - } - } - - if consensus_reply.has_path() { - s.push_str(&format!("{}", consensus_reply.take_path())); - } - - if consensus_reply.has_error() { - s.push_str("Error: "); - s.push_str(&api_error_to_string(consensus_reply.take_error())); - s.push('\n'); - } - - s.push_str(&format!("Epoch = {}, View = {}, Client Request Num = {}", - consensus_reply.get_epoch(), - consensus_reply.get_view(), - consensus_reply.get_request_num())); - return Ok(s); - } - - if api_response.has_namespaces() { - let namespaces = api_response.take_namespaces().take_ids().to_vec(); - return Ok(namespaces.iter().fold(String::new(), |mut acc, namespace_id | { - acc.push_str(&namespace_id); - acc.push_str("\n"); - acc - })); - } - - if api_response.has_client_registration() { - client.primary = Some(api_response.take_client_registration().take_primary()); - return Ok(format!("Client registered. Primary = {:?}", client.primary.as_ref().unwrap())); - } - - if api_response.has_redirect() { - let mut redirect = api_response.take_redirect(); - let primary = redirect.take_primary(); - let api_addr = redirect.take_api_addr(); - client.connect(Some(api_addr))?; - let req = client.register(Some(primary.clone()))?; - /// Todo: Remove this recursion to prevent potential stack overflow - exec(req, client)?; - return Ok(format!("Finished Redirecting. Primary = {:?}, API Address = {}", - client.primary.as_ref().unwrap(), - client.api_addr.as_ref().unwrap())) - } - - if api_response.has_retry() { - let duration = api_response.take_retry().get_milliseconds(); - return Ok(format!("Primary not found. Please retry in {} seconds.", duration*1000)); - } - - if api_response.has_unknown_namespace() { - return Ok("Unknown namespace".to_string()); - } - - if api_response.has_timeout() { - return Ok("Timeout".to_string()); - } - - Ok(format!("unknown message {:?}", api_response)) -} - -fn help() -> Error { - Error::new(ErrorKind::InvalidInput, HELP.clone()) -} - - -// TODO: Put HaretClient into it's own crate -/// This struct represents the Haret client implementation in rust. It is a low level client that is -/// useful for building higher level native clients or for building clients in other langauges via -/// FFI. -struct HaretClient { - pub client_id: String, - pub api_addr: Option, - pub namespace_id: Option, - pub primary: Option, - sock: Option, - request_num: u64 -} - -impl HaretClient { - pub fn new(client_id: String) -> HaretClient { - HaretClient { - client_id: client_id, - api_addr: None, - namespace_id: None, - primary: None, - sock: None, - request_num: 0 - } - } - - fn reset_primary(&mut self) { - self.primary = None; - self.namespace_id = None; - } - - /// Connect to `self.api_addr` - pub fn connect(&mut self, api_addr: Option) -> Result<()> { - if api_addr.is_none() && self.api_addr.is_none() { - return Err(Error::new(ErrorKind::InvalidInput, - "API Address unknown. Please call connect with an api_addr.")); - } - if api_addr.is_some() { - self.api_addr = api_addr; - } - self.sock = Some(TcpStream::connect(&self.api_addr.as_ref().unwrap()[..])?); - Ok(()) - } - - /// Register the client id on this node for the given namespace. - /// - /// This function returns the registration message to be written or an error if the primary is - /// unknown. - pub fn register(&mut self, primary: Option) -> Result { - if primary.is_none() && self.primary.is_none() { - return Err(Error::new(ErrorKind::InvalidInput, "Primary unknown")); - } - - if primary.is_some() { - self.primary = primary; - self.namespace_id = Some(self.primary.as_ref().unwrap() - .get_group().to_string()); - } - let namespace_id = self.namespace_id.clone(); - let mut msg = RegisterClient::new(); - msg.set_client_id(self.client_id.clone()); - msg.set_namespace_id(namespace_id.as_ref().unwrap().clone()); - let mut request = ApiRequest::new(); - request.set_register_client(msg); - Ok(request) - } - - fn write_msg(&mut self, req: ApiRequest) -> Result<()> { - let mut msg = ApiMsg::new(); - msg.set_request(req); - let encoded = msg.write_to_bytes().map_err(|_| { - Error::new(ErrorKind::InvalidInput, "Failed to encode msgpack data") - })?; - let len: u32 = encoded.len() as u32; - // 4 byte len header - let header: [u8; 4] = unsafe { mem::transmute(len.to_be()) }; - self.sock.as_ref().unwrap().write_all(&header)?; - self.sock.as_ref().unwrap().write_all(&encoded)?; - self.request_num += 1; - Ok(()) - } - - fn read_msg(&mut self) -> Result { - let mut header = [0; 4]; - self.sock.as_mut().unwrap().read_exact(&mut header)?; - let len = unsafe { u32::from_be(mem::transmute(header)) }; - let mut buf = vec![0; len as usize]; - self.sock.as_mut().unwrap().read_exact(&mut buf)?; - let mut msg: ApiMsg = parse_from_bytes(&buf[..]).map_err(|e| { - Error::new(ErrorKind::InvalidData, e.to_string()) - })?; - Ok(msg.take_response()) - } -}