diff --git a/.travis.yml b/.travis.yml index d2da893..d4f5f44 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()) - } -}