diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d507550 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + target-branch: "main" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/cred-scan.yml b/.github/workflows/cred-scan.yml new file mode 100644 index 0000000..f2c7a04 --- /dev/null +++ b/.github/workflows/cred-scan.yml @@ -0,0 +1,15 @@ +name: Credential Scanner + +on: + pull_request: + branches: [ main ] + +jobs: + cred-scan: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Scan credentials + uses: outscale/cred-scan@main + with: + scan_path: "./" \ No newline at end of file diff --git a/.github/workflows/github-sanity-scan.yml b/.github/workflows/github-sanity-scan.yml new file mode 100644 index 0000000..da55894 --- /dev/null +++ b/.github/workflows/github-sanity-scan.yml @@ -0,0 +1,15 @@ +name: Github sanity scanner + +on: + pull_request: + branches: [ main ] + +jobs: + github-sanity-scan: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Github sanity scanner + uses: outscale/github-sanity-scan@main + with: + no-pull-request-target: true \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..6fca0cc --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,21 @@ +name: bsud crate.io publishing +on: + release: + types: [released] + +jobs: + publish: + environment: publish + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Stable Rust with rustfmt + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: rustfmt + - name: Cargo publish + run: cargo publish + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9905cd6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,76 @@ +name: release + +on: + push: + tags: + - v* + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Create Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + tag_name: ${{ github.ref_name }} + name: Release ${{ github.ref_name }} + draft: true + prerelease: false + generate_release_notes: true + artefact: + strategy: + matrix: + include: + - name: Linux x86_64 + os: ubuntu-latest + target: x86_64-unknown-linux-musl + suffix: '' + - name: macOS x86_64 + os: macos-latest + target: x86_64-apple-darwin + suffix: '' + - name: macOS M1 + os: macos-latest + target: aarch64-apple-darwin + suffix: '' + - name: Windows + os: windows-latest + target: x86_64-pc-windows-msvc + suffix: .exe + runs-on: ${{ matrix.os }} + name: Create artefact for ${{ matrix.name }} + needs: [release] + steps: + - uses: actions/checkout@v3 + - name: Stable Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + target: ${{ matrix.target }} + toolchain: stable + - name: Install musl-tools + run: sudo apt update -y && sudo apt install musl-tools -y + if: matrix.os == 'ubuntu-latest' + - name: Build + run: cargo build --target ${{ matrix.target }} --release + - name: Move binary + run: mv target/${{ matrix.target }}/release/bsud${{ matrix.suffix }} bsud-${{ github.ref_name }}-${{ matrix.target }}${{ matrix.suffix }} + - name: Upload Artefact to release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + tag_name: ${{ github.ref_name }} + name: Release ${{ github.ref_name }} + draft: true + prerelease: false + files: bsud-${{ github.ref_name }}-${{ matrix.target }}${{ matrix.suffix }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e3fe4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,127 @@ +.vscode +logs.txt + +# Created by https://www.toptal.com/developers/gitignore/api/vim,rust,emacs,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=vim,rust,emacs,macos + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# End of https://www.toptal.com/developers/gitignore/api/vim,rust,emacs,macos diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d4b1926 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3644 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336d835910fab747186c56586562cb46f42809c2843ef3a84f47509009522838" +dependencies = [ + "concurrent-queue", + "event-listener 3.0.1", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-io" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da8f3146014722c89e7859e1d7bb97873125d7346d10ca642ffab794355828" +dependencies = [ + "async-lock 2.8.0", + "cfg-if 1.0.0", + "concurrent-queue", + "futures-io", + "futures-lite 1.13.0", + "parking", + "polling", + "rustix 0.38.21", + "slab", + "tracing", + "waker-fn", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e900cdcd39bb94a14487d3f7ef92ca222162e6c7c3fe7cb3550ea75fb486ed" +dependencies = [ + "event-listener 3.0.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "492cd91ac1bad2bb910e38cb324891bf3d71845f64ae503a2ddcfa64fe7f03b0" +dependencies = [ + "async-channel 2.0.0", + "async-io", + "async-lock 3.0.0", + "async-signal", + "blocking", + "cfg-if 1.0.0", + "event-listener 3.0.1", + "futures-lite 2.0.0", + "rustix 0.38.21", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-signal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +dependencies = [ + "async-io", + "async-lock 2.8.0", + "atomic-waker", + "cfg-if 1.0.0", + "futures-core", + "futures-io", + "rustix 0.38.21", + "signal-hook-registry", + "slab", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-task" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "aws-sigv4" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2d486e1bc89b62a50ab74a6c626aa6e34923e4afdd12be26b4d173bb68a4d66" +dependencies = [ + "aws-smithy-http", + "form_urlencoded", + "hex", + "http 0.2.9", + "once_cell", + "percent-encoding 2.3.0", + "regex", + "ring", + "time 0.3.24", + "tracing", +] + +[[package]] +name = "aws-smithy-http" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a4b8e568f284def4d1edfb0705e7058928f355ae264c44359b80f021e24d419" +dependencies = [ + "aws-smithy-types", + "bytes 1.4.0", + "bytes-utils", + "futures-core", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", + "percent-encoding 2.3.0", + "pin-project", + "tracing", +] + +[[package]] +name = "aws-smithy-types" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420eb4743434a99ce9ba270bec3ef242884d407bff0bca7d16b5b0457a7d3782" +dependencies = [ + "itoa 0.4.8", + "num-integer", + "ryu", + "time 0.3.24", +] + +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "blocking" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" +dependencies = [ + "async-channel 1.9.0", + "async-lock 2.8.0", + "async-task", + "fastrand", + "futures-io", + "futures-lite 1.13.0", + "piper", + "tracing", +] + +[[package]] +name = "bstr" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bsud" +version = "0.1.0" +dependencies = [ + "async-process", + "clap", + "cucumber", + "datetime", + "easy-error", + "env_logger", + "futures 0.3.28", + "jsonrpc-core", + "jsonrpc-derive", + "jsonrpc-http-server", + "lazy_static", + "lfs-core", + "log", + "outscale_api", + "proc-mounts", + "rand 0.8.5", + "reqwest 0.11.18", + "secrecy", + "serde", + "serde_derive", + "serde_json", + "signal-hook", + "threadpool", + "tokio 1.33.0", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "either", + "iovec", +] + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "bytes-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9" +dependencies = [ + "bytes 1.4.0", + "either", +] + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "concurrent-queue" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +dependencies = [ + "crossbeam-utils 0.8.16", +] + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + +[[package]] +name = "cookie" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" +dependencies = [ + "time 0.1.45", + "url 1.7.2", +] + +[[package]] +name = "cookie_store" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" +dependencies = [ + "cookie", + "failure", + "idna 0.1.5", + "log", + "publicsuffix", + "serde", + "serde_json", + "time 0.1.45", + "try_from", + "url 1.7.2", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg 1.1.0", + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if 0.1.10", + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg 1.1.0", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "ct-logs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113" +dependencies = [ + "sct 0.6.1", +] + +[[package]] +name = "cucumber" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ead33440b8c33da870588443d745a0f23de153013fe847ae71073d176f75293" +dependencies = [ + "anyhow", + "async-trait", + "clap", + "console", + "cucumber-codegen", + "cucumber-expressions", + "derive_more", + "drain_filter_polyfill", + "either", + "futures 0.3.28", + "gherkin", + "globwalk", + "humantime", + "inventory", + "itertools", + "lazy-regex 3.0.2", + "linked-hash-map", + "once_cell", + "pin-project", + "regex", + "sealed", + "smart-default", +] + +[[package]] +name = "cucumber-codegen" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8e4a7c46b4b5c81894087a5e3a95138bdd26e2e46fcca258513e07b880a369" +dependencies = [ + "cucumber-expressions", + "inflections", + "itertools", + "proc-macro2", + "quote", + "regex", + "syn 2.0.28", + "synthez", +] + +[[package]] +name = "cucumber-expressions" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d794fed319eea24246fb5f57632f7ae38d61195817b7eb659455aa5bdd7c1810" +dependencies = [ + "derive_more", + "either", + "nom 7.1.3", + "nom_locate", + "regex", + "regex-syntax", +] + +[[package]] +name = "datetime" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc" +dependencies = [ + "iso8601", + "libc", + "locale", + "pad", + "redox_syscall 0.1.57", + "winapi 0.3.9", +] + +[[package]] +name = "deranged" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "easy-error" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cc9717c61d2908f50d16ebb5677c7e82ea2bdf7cb52f66b30fe079f3212e16" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cec0252c2afff729ee6f00e903d479fba81784c8e2bd77447673471fdfaea1" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" +dependencies = [ + "event-listener 3.0.1", + "pin-project-lite", +] + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding 2.3.0", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags 1.3.2", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-cpupool" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" +dependencies = [ + "futures 0.1.31", + "num_cpus", +] + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-lite" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1155db57329dca6d018b61e76b1488ce9a2e5e44028cac420a5898f4fcef63" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gherkin" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8f8f49b2b547ec22cc4d99f3bf30d4889ef0dbaa231c0736eeaf20efb5a38e" +dependencies = [ + "heck", + "peg", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "textwrap", + "thiserror", + "typed-builder", +] + +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "globset" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aca8bbd8e0707c1887a8bbb7e6b40e228f251ff5d62c8220a4a7a53c73aff006" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + +[[package]] +name = "h2" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" +dependencies = [ + "byteorder", + "bytes 0.4.12", + "fnv", + "futures 0.1.31", + "http 0.1.21", + "indexmap", + "log", + "slab", + "string", + "tokio-io", +] + +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +dependencies = [ + "bytes 1.4.0", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.9", + "indexmap", + "slab", + "tokio 1.33.0", + "tokio-util 0.7.8", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "http" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" +dependencies = [ + "bytes 0.4.12", + "fnv", + "itoa 0.4.8", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes 1.4.0", + "fnv", + "itoa 1.0.9", +] + +[[package]] +name = "http-body" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "http 0.1.21", + "tokio-buf", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes 1.4.0", + "http 0.2.9", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.12.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c843caf6296fc1f93444735205af9ed4e109a539005abb2564ae1d6fad34c52" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "futures-cpupool", + "h2 0.1.26", + "http 0.1.21", + "http-body 0.1.0", + "httparse", + "iovec", + "itoa 0.4.8", + "log", + "net2", + "rustc_version", + "time 0.1.45", + "tokio 0.1.22", + "tokio-buf", + "tokio-executor", + "tokio-io", + "tokio-reactor", + "tokio-tcp", + "tokio-threadpool", + "tokio-timer", + "want 0.2.0", +] + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes 1.4.0", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.20", + "http 0.2.9", + "http-body 0.4.5", + "httparse", + "httpdate", + "itoa 1.0.9", + "pin-project-lite", + "socket2 0.4.9", + "tokio 1.33.0", + "tower-service", + "tracing", + "want 0.3.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719d85c7df4a7f309a77d145340a063ea929dcb2e025bae46a80345cffec2952" +dependencies = [ + "bytes 0.4.12", + "ct-logs", + "futures 0.1.31", + "hyper 0.12.36", + "rustls 0.16.0", + "tokio-io", + "tokio-rustls 0.10.3", + "webpki 0.21.4", + "webpki-roots 0.17.0", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +dependencies = [ + "futures-util", + "http 0.2.9", + "hyper 0.14.27", + "rustls 0.21.5", + "tokio 1.33.0", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg 1.1.0", + "hashbrown", +] + +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "inventory" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1be380c410bf0595e94992a648ea89db4dd3f3354ba54af206fd2a68cf5ac8e" + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix 0.38.21", + "windows-sys 0.48.0", +] + +[[package]] +name = "iso8601" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e86914a73535f3f541a765adea0a9fafcf53fa6adb73662c4988fd9233766f" +dependencies = [ + "nom 4.2.3", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc-core" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" +dependencies = [ + "futures 0.3.28", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "jsonrpc-derive" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b939a78fa820cdfcb7ee7484466746a7377760970f6f9c6fe19f9edcc8a38d2" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "jsonrpc-http-server" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" +dependencies = [ + "futures 0.3.28", + "hyper 0.14.27", + "jsonrpc-core", + "jsonrpc-server-utils", + "log", + "net2", + "parking_lot 0.11.2", + "unicase", +] + +[[package]] +name = "jsonrpc-server-utils" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" +dependencies = [ + "bytes 1.4.0", + "futures 0.3.28", + "globset", + "jsonrpc-core", + "lazy_static", + "log", + "tokio 1.33.0", + "tokio-stream", + "tokio-util 0.6.10", + "unicase", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy-regex" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff63c423c68ea6814b7da9e88ce585f793c87ddd9e78f646970891769c8235d4" +dependencies = [ + "lazy-regex-proc_macros 2.4.1", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e723bd417b2df60a0f6a2b6825f297ea04b245d4ba52b5a22cb679bdf58b05fa" +dependencies = [ + "lazy-regex-proc_macros 3.0.1", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8edfc11b8f56ce85e207e62ea21557cfa09bb24a8f6b04ae181b086ff8611c22" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0a1d9139f0ee2e862e08a9c5d0ba0470f2aa21cd1e1aa1b1562f83116c725f" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.28", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lfs-core" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4441d37490f59d2fdc631e6be973ce8712435e4bfebe9615125b11603cd41f" +dependencies = [ + "lazy-regex 2.5.0", + "libc", + "snafu", +] + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "locale" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd" +dependencies = [ + "libc", +] + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg 1.1.0", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check 0.1.5", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom_locate" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" +dependencies = [ + "bytecount", + "memchr", + "nom 7.1.3", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg 1.1.0", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "outscale_api" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5873499fb222f1e0e5965ead9147724eaba1ec67afa634dac73021316d77b3" +dependencies = [ + "aws-sigv4", + "home", + "http 0.2.9", + "reqwest 0.9.24", + "secrecy", + "serde", + "serde_derive", + "serde_json", + "url 2.4.0", +] + +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api 0.3.4", + "parking_lot_core 0.6.3", + "rustc_version", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api 0.4.10", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66b810a62be75176a80873726630147a5ca780cd33921e0b5709033e66b0a" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "libc", + "redox_syscall 0.1.57", + "rustc_version", + "smallvec 0.6.14", + "winapi 0.3.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec 1.11.0", + "winapi 0.3.9", +] + +[[package]] +name = "partition-identity" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa925f9becb532d758b0014b472c576869910929cf4c3f8054b386f19ab9e21" +dependencies = [ + "thiserror", +] + +[[package]] +name = "peg" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "polling" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53b6af1f60f36f8c2ac2aad5459d75a5a9b4be1e8cdd40264f315d78193e531" +dependencies = [ + "cfg-if 1.0.0", + "concurrent-queue", + "pin-project-lite", + "rustix 0.38.21", + "tracing", + "windows-sys 0.48.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-mounts" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d652f8435d0ab70bf4f3590a6a851d59604831a458086541b95238cc51ffcf2" +dependencies = [ + "partition-identity", +] + +[[package]] +name = "publicsuffix" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f" +dependencies = [ + "idna 0.2.3", + "url 2.4.0", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi 0.3.9", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "reqwest" +version = "0.9.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f88643aea3c1343c804950d7bf983bd2067f5ab59db6d613a08e05572f2714ab" +dependencies = [ + "base64 0.10.1", + "bytes 0.4.12", + "cookie", + "cookie_store", + "encoding_rs", + "flate2", + "futures 0.1.31", + "http 0.1.21", + "hyper 0.12.36", + "hyper-rustls 0.17.1", + "log", + "mime", + "mime_guess", + "rustls 0.16.0", + "serde", + "serde_json", + "serde_urlencoded 0.5.5", + "time 0.1.45", + "tokio 0.1.22", + "tokio-executor", + "tokio-io", + "tokio-rustls 0.10.3", + "tokio-threadpool", + "tokio-timer", + "url 1.7.2", + "uuid", + "webpki-roots 0.17.0", + "winreg 0.6.2", +] + +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64 0.21.2", + "bytes 1.4.0", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.20", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", + "hyper-rustls 0.24.1", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding 2.3.0", + "pin-project-lite", + "rustls 0.21.5", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded 0.7.1", + "tokio 1.33.0", + "tokio-rustls 0.24.1", + "tower-service", + "url 2.4.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.22.6", + "winreg 0.10.1", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys 0.4.10", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustls" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" +dependencies = [ + "base64 0.10.1", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct 0.7.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.2", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sealed" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "serde_json" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +dependencies = [ + "itoa 1.0.9", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" +dependencies = [ + "dtoa", + "itoa 0.4.8", + "serde", + "url 1.7.2", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.9", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "smart-default" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "string" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" +dependencies = [ + "bytes 0.4.12", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "synthez" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d2c2202510a1e186e63e596d9318c91a8cbe85cd1a56a7be0c333e5f59ec8d" +dependencies = [ + "syn 2.0.28", + "synthez-codegen", + "synthez-core", +] + +[[package]] +name = "synthez-codegen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f724aa6d44b7162f3158a57bccd871a77b39a4aef737e01bcdff41f4772c7746" +dependencies = [ + "syn 2.0.28", + "synthez-core", +] + +[[package]] +name = "synthez-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bfa6ec52465e2425fd43ce5bbbe0f0b623964f7c63feb6b10980e816c654ea" +dependencies = [ + "proc-macro2", + "quote", + "sealed", + "syn 2.0.28", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +dependencies = [ + "rustix 0.37.23", + "windows-sys 0.48.0", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" +dependencies = [ + "deranged", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "mio 0.6.23", + "num_cpus", + "tokio-current-thread", + "tokio-executor", + "tokio-io", + "tokio-reactor", + "tokio-tcp", + "tokio-threadpool", + "tokio-timer", +] + +[[package]] +name = "tokio" +version = "1.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +dependencies = [ + "backtrace", + "bytes 1.4.0", + "libc", + "mio 0.8.8", + "num_cpus", + "pin-project-lite", + "socket2 0.5.4", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-buf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" +dependencies = [ + "bytes 0.4.12", + "either", + "futures 0.1.31", +] + +[[package]] +name = "tokio-current-thread" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" +dependencies = [ + "futures 0.1.31", + "tokio-executor", +] + +[[package]] +name = "tokio-executor" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" +dependencies = [ + "crossbeam-utils 0.7.2", + "futures 0.1.31", +] + +[[package]] +name = "tokio-io" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "log", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" +dependencies = [ + "crossbeam-utils 0.7.2", + "futures 0.1.31", + "lazy_static", + "log", + "mio 0.6.23", + "num_cpus", + "parking_lot 0.9.0", + "slab", + "tokio-executor", + "tokio-io", + "tokio-sync", +] + +[[package]] +name = "tokio-rustls" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7cf08f990090abd6c6a73cab46fed62f85e8aef8b99e4b918a9f4a637f0676" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "iovec", + "rustls 0.16.0", + "tokio-io", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.5", + "tokio 1.33.0", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio 1.33.0", +] + +[[package]] +name = "tokio-sync" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" +dependencies = [ + "fnv", + "futures 0.1.31", +] + +[[package]] +name = "tokio-tcp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.31", + "iovec", + "mio 0.6.23", + "tokio-io", + "tokio-reactor", +] + +[[package]] +name = "tokio-threadpool" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" +dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils 0.7.2", + "futures 0.1.31", + "lazy_static", + "log", + "num_cpus", + "slab", + "tokio-executor", +] + +[[package]] +name = "tokio-timer" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" +dependencies = [ + "crossbeam-utils 0.7.2", + "futures 0.1.31", + "slab", + "tokio-executor", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes 1.4.0", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio 1.33.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes 1.4.0", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio 1.33.0", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "try_from" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "typed-builder" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check 0.9.4", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna 0.4.0", + "percent-encoding 2.3.0", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +dependencies = [ + "rand 0.6.5", +] + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" +dependencies = [ + "futures 0.1.31", + "log", + "try-lock", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a262ae37dd9d60f60dd473d1158f9fbebf110ba7b6a5051c8160460f6043718b" +dependencies = [ + "webpki 0.21.4", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki 0.22.0", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ab6d4f8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "bsud" +version = "0.1.0" +edition = "2021" +authors = ["OpenSource Team "] +license = "BSD-3-Clause" +description = "bsud auto-scale BSU volumes on Outscale's cloud" +repository = "https://github.com/outscale/bsud" +readme = "docs/README.md" + +[lib] +name = "bsudlib" +path = "src/lib.rs" + +[[bin]] +name = "bsud" +path = "src/main.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.2.7", features = ["derive"] } +datetime = "0.5.2" +easy-error = "1.0.0" +env_logger = "0.10.0" +jsonrpc-core = "18.0.0" +jsonrpc-derive = "18.0.0" +jsonrpc-http-server = "18.0.0" +lazy_static = "1.4.0" +lfs-core = "0.11.1" +log = "0.4.17" +outscale_api = { version = "1.6.0", default-features = false, features = ["rustls-tls"] } +proc-mounts = "0.3.0" +reqwest = { version = "0.11.17", default-features = false, features = ["blocking", "rustls-tls"] } +secrecy = { version = "0.8.0", features = ["alloc", "serde"] } +serde = { version = "1.0.163", features = ["derive"] } +serde_derive = "1.0.163" +serde_json = "1.0.96" +signal-hook = "0.3.15" +threadpool = "1.8.1" + +[dev-dependencies] +cucumber = "0.20" +futures = "0.3" +env_logger = "0.10.0" +rand = "0.8.5" +tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread", "time"] } +async-process = "2.0.0" + +[[test]] +name = "bsud-tests" +harness = false # allows Cucumber to print output instead of libtest \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7886747 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2024, Outscale SAS +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..3aa2a3a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,38 @@ +``` +🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 +🔥 WORK IN PROGRESS 🔥 +🔥 DO NOT USE IN PRODUCTION 🔥 +🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 +``` + +# BSUd + +BSUd creates a virtual drive on your linux machine on [Outscale's cloud](https://outscale.com/). The drive is composed of aggregated cloud block devices ([Block Storage Units](https://docs.outscale.com/en/userguide/Block-Storage-Unit-BSU.html)). +BSUd will dynamically add and remove BSUs accordingly to real file occupation on the drive while limiting the number of attached BSUs. +Scaling up and down is done without putting the drive offline. + +BSUd directly run system commands such as [LVM](https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux)) to manage block aggregation. All commands are logged and administrators can easily inspect the system with or without BSUd running. No dark magic. + +Here is an example of what a BSUd drive could look like under LVM perspective: +```bash +$ sudo pvdisplay -S vgname=example -C --separator ' | ' -o pv_name,vg_name,pv_size,vg_size; + PV | VG | PSize | VSize + /dev/xvdb | example | <70.00g | 1.44t + /dev/xvdc | example | <101.00g | 1.44t + /dev/xvdd | example | <84.00g | 1.44t + /dev/xvde | example | <122.00g | 1.44t + /dev/xvdg | example | <147.00g | 1.44t + /dev/xvdh | example | <177.00g | 1.44t + /dev/xvdi | example | <213.00g | 1.44t + /dev/xvdj | example | <256.00g | 1.44t + /dev/xvdk | example | <308.00g | 1.44t +``` + +- 🎁 [Install](install.md) +- 🚀 [Use](use.md) +- 🔧 [Develop](develop.md) +- 💡 [Contribute](CONTRIBUTING.md) + +# License + +BSUd is licensed under BSD-3-Clause. See [LICENSE](../LICENSE) for more details. \ No newline at end of file diff --git a/docs/bsud-online.svg b/docs/bsud-online.svg new file mode 100644 index 0000000..cb48eca --- /dev/null +++ b/docs/bsud-online.svg @@ -0,0 +1,3 @@ + + +
start
start
no
no
yes
yes
Are all BSU attached ?
Are all BSU a...
Attach missing BSU
Attach missing BSU
yes
yes
no
no
BSU count
= 0 ?
BSU count...
Create first BSU
Create first BSU
no
no
Are PV
init ?
Are PV...
Init missing PV on BSU
Init missing PV on BSU
no
no
Is VG
created ?
Is VG...
Create VG
Create VG
no
no
yes
yes
Is VG
extended ?
Is VG...
Extend VG
Extend VG
yes
yes
no
no
Is LV
created ?
Is LV...
Create LV
Create LV
yes
yes
no
no
Is FS
formated ?
Is FS...
yes
yes
no
no
Is FS
mounted ?
Is FS...
yes
yes
no
no
BSUs
=
max
BSUs...
Remove smallest BSU
from drive
Remove smallest BSU...
Format FS
Format FS
Mount FS
Mount FS
yes
yes
no
no
Is drive
low space
left ?
Is drive...
yes
yes
no
no
max allowed
space ?
max allowed...
Create larger BSU
Create larger BSU
end
end
no
no
yes
yes
Is drive
high space
left ?
Is drive...
yes
yes
no
no
BSU count
= 1 ?
BSU count...
yes
yes
no
no
Has
minimal
size ?
Has...
end
end
Remove largest BSU
from drive
Remove largest BSU...
Create ideal sized BSU
Create ideal sized BSU
end
end
yes
yes
no
no
BSUs
=
max - 1
BSUs...
Create smaller BSU
Create smaller BSU
no
no
yes
yes
Already
have smallest
BSU ?
Already...
yes
yes
VG scan
VG scan
yes
yes
Enable VG
Enable VG
Extend LV
Extend LV
Enable LV
Enable LV
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/bsud.drawio b/docs/bsud.drawio new file mode 100644 index 0000000..df888f9 --- /dev/null +++ b/docs/bsud.drawio @@ -0,0 +1,506 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/config.json b/docs/config.json new file mode 100644 index 0000000..7ccac44 --- /dev/null +++ b/docs/config.json @@ -0,0 +1,19 @@ +{ + "authentication": { + "access-key": "YOUR_ACCESS_KEY", + "secret-key": "YOUR_SECRET_KEY" + }, + "drives": [ + { + "name": "example", + "target": "online", + "mount-path": "/mnt", + "disk-type": "gp2", + "initial-size-gib": 10, + "max-bsu-count": 10, + "max-used-space-perc": 85, + "min-used-space-perc": 40, + "disk-scale-factor-perc": 20 + } + ] +} \ No newline at end of file diff --git a/docs/develop.md b/docs/develop.md new file mode 100644 index 0000000..c0646d7 --- /dev/null +++ b/docs/develop.md @@ -0,0 +1,107 @@ +# Architecture + +A drive is composed of one or more [BSU](https://docs.outscale.com/en/userguide/Block-Storage-Unit-BSU.html) which are dynamically created and attached to the VM where `bsud` is running. + +## Online drive + +When drive's target is set to "online", BSUd will maintain the drive available: +1. BSUd starts by populating existing BSU on Outscale's cloud with tag `osc.bsud.drive-name`. +2. BSU matching a drive name will be attached to the VM (if not already attached). +3. Once all BSU belonging to a drive are attached, BSUd will initialise each of them as a [LVM](https://en.wikipedia.org/wiki/Logical_Volume_Manager_%28Linux%29) Physical Volumes (PV) (if not already initialized). +4. All PV belonging to a specific drive are then aggregated in a Volume Group (VG) (if not already aggregated). +5. A single Logical Volume (LV) will be created (e.g. `/dev/mydrive/bsud`) from the VG and will take 100% of the space (if not already expanded). +6. A [btrfs](https://btrfs.readthedocs.io/en/latest/Administration.html) filesystem will be created on top of the LV (if not already formated). +7. Once the filesystem ready, it will be mounted where the user asked (if not already mounted). +8. BSU will be created if needed and the whole loop restarts + +BSUd is stateless. It will just read configuration and try to converge to desired description using a [controller pattern](https://kubernetes.io/docs/concepts/architecture/controller/). + +Here is a flow chart visualisation of BSUd execution (see [drive.rs](../src/drive.rs) for more details): + +![flow chart of online drive logic](bsud-online.svg) + +## Offline drive + +Make a drive offline is a basic reconcile loop with the following steps: +1. While fs mounted -> try unmount +2. Disable LV +3. Disable VG +4. Fetch all BSU on Outscale API +5. Detach until no BSU is attached + +## Delete drive + +To delete a drive, the reconcile loop is pretty basic too: +1. Run Offline reconcile: no drive is now attached +2. Fetch all BSU on Outscale API +3. Delete all BSU corresponding to the drive on Outscale API + +# Build project + +You will need [rustlang](https://www.rust-lang.org/) installed. + +You can easily build with just `cargo build` or build with musl: +```bash +rustup target add x86_64-unknown-linux-musl +cargo build --release --target x86_64-unknown-linux-musl +./target/x86_64-unknown-linux-musl/release/bsud +``` + +# Run in debug mode + +If you want to only focus on bsud traces: +``` +sudo RUST_LOG=bsud=trace ./target/debug/bsud -c config.json +``` + +For complete traces: +``` +sudo RUST_LOG=trace ./target/debug/bsud -c config.json +``` + +If you want to pass your credentials through env variables as root: +``` +sudo OSC_ACCESS_KEY=$OSC_ACCESS_KEY OSC_SECRET_KEY=$OSC_SECRET_KEY RUST_LOG=bsud=trace ./target/debug/bsud -c config.json +``` + +# Packaging + +TODO + +# License checking + +```bash +cargo sbom +``` + +# Cleanup all BSU created by BSUd + +During dev, it is useful to delete BSU which are previously created. + +```bash +./tests/delete-drive.sh example +./tests/delete-drive.sh --all +``` + +# Tests + +Check [test documentation](testing.md). + +# Few useful commands + +Watch drive: +```bash +watch df -B G /dev/mapper/example-bsud +sudo watch vgdisplay example +sudo watch vgs example +``` + +Write some data: +```bash +while true; do sudo bash -c 'dd if=/dev/urandom bs=1G count=10 | pv | dd of=/mnt/random${RANDOM}${RANDOM}${RANDOM}${RANDOM}.data'; done +``` + +See PV details: +```bash +while true; do sudo pvdisplay -S vgname=example -C --separator ' | ' -o pv_name,vg_name,pv_size,vg_size; sleep 2; clear; done +``` diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..32cce90 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1 @@ +# FAQ \ No newline at end of file diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..33833b6 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,7 @@ +# Install BSUd + +For now, BSUd is just provided as a stand-alone binary. + +Go to [Github's releases](https://github.com/outscale/bsud/releases) to get the latest BSUd version. + +Once downloaded or installed, check [Use section](https://github.com/outscale/bsud/blob/main/docs/use.md) to get more details on how to run it. \ No newline at end of file diff --git a/docs/maybe.gif b/docs/maybe.gif new file mode 100644 index 0000000..9977a90 Binary files /dev/null and b/docs/maybe.gif differ diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..45526e4 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,64 @@ +# Test Coverage + +Is bsud fully tested? + +![maybe-test-are-complete](maybe.gif) + +# Basic Check + +```bash +cargo clippy +``` + +# E2E Tests + +E2E tests run real life scenario on actual platform, attaching real BSUs, making I/Os, growing, shrinking, stressing, ... + +This mean you will need the following pre-requisites to run those tests: +- Run tests on one Outscale's VM. +- Have a pair of [Access Key and Secret Key](https://docs.outscale.com/en/userguide/About-Access-Keys.html) ready. +- Some disk quota (gp2 and io1) +- bsud binary +- Some software installed: + - curl >= v7.75 + +E2E tests take time as they will actually write and read real data do drives. + +## Running E2E Tests + +Set credentials and test log levels. You may need `RUST_LOG=bsud_test=trace,bsudlib=trace` level of details to debug your tests. +```bash +export OSC_ACCESS_KEY=XXX +export OSC_SECRET_KEY=YYY +export RUST_LOG=off +``` + +Build & run all tests: +```bash +find ./target/debug/deps/ -type f -regex '.*bsud_tests-[a-z0-9]+' -exec rm ./{} \; &&\ +cargo test --no-run &&\ +time sudo bash -c "find ./target/debug/deps/ -type f -regex '.*bsud_tests-[a-z0-9]+' | RUST_LOG=$RUST_LOG OSC_ACCESS_KEY=$OSC_ACCESS_KEY OSC_SECRET_KEY=$OSC_SECRET_KEY xargs -i sh -c \"./{} --fail-fast --concurrency 1\"" 2> logs.txt +``` + +This will: +1. delete any compiled tests +2. build tests as current user +3. find compiled test and run it as root with some test options + +Note: We don't want to build as root as file rights will be messed up and there is no simple way yet to just run tests without building for now. + +Tips: +- If you want to run a specific feature file, add `--input basic-lifecycle.feature` (after `--fail-fast`). +- Remove `--concurrency` option to avoid the limit. Test output will look weird as all tests are running in parallel but this should be faster. + +## Developping E2E tests + +Tests are based on Behavior-Based Driven using [cucumber-rs](https://cucumber-rs.github.io/cucumber/current/) with [Gherkin](https://cucumber.io/docs/gherkin/reference/) syntax. + +All features are located in [/tests/features](./features/) folder. + +# Cleaning + +When successful, test drives are removed but in case of failure some drive could remain. + +Use `./tests/delete-drive.sh` to manually remove drives \ No newline at end of file diff --git a/docs/use.md b/docs/use.md new file mode 100644 index 0000000..49c5c41 --- /dev/null +++ b/docs/use.md @@ -0,0 +1,73 @@ +# Run Dependencies + +BSUd is only for Linux-based OS on Outscale's cloud for now. + +BSUd will need to run those external commands: +- lvm +- btrfs +- [Outscale's API Access Key and Secret Key](https://docs.outscale.com/en/userguide/About-Access-Keys.html) + +# Configuration + +## config.json + +BSUd is configured through a json configuration file. +The following section describe a simple configuration (see [config.json](config.json)): + +- `authentification` + - `access-key`: optional if OSC_ACCESS_KEY env var is set. + - `secret-key`: optional if OSC_SECRET_KEY env var is set. +- `drives` + - `name`: unique drive's name. Be sure to use an unique name across your Outscale account otherwise, BSUd cannot differentiate drives and will try to attach them. + - `target`: between "online" (default), "offline" and "delete". + - `mount-path`: absolute path where BSUd will mount the scaled file system. + - `disk-iops-per-gib`: BSU iops to allocate per GibiBytes (for io1 disks). + - `max-total-size-gib`: Limit the maximal size a drive can offer. + - `disk-scale-factor-perc`: Controls the size of the next BSU to be created regarding the size of the largest or smallest existing BSU in the drive. + - `min-used-space-perc` controls when to scale down (remove a BSU) accordingly to the used percentage in the drive. + - `max-bsu-count`: maximal allowed number of BSU in the drive. + +## Environment variables + +- OSC_ACCESS_KEY +- OSC_SECRET_KEY + +# Usage + +- Get version: `bsud --version` +- Manually run bsud: `bsud -c docs/config.json` + +`bsud` will look for `/etc/osc/bsud.json` configuration file path by default. + +# Creating or updating a drive + +Just add or edit drive in BSUd configuration and restart daemon. +Note that changing drive name is not supported for now and will just create a new fresh drive. + +# About drive targets + +When drive target is configured to "online" (default), all BSU are attached and the drive is maintained available to user. + +Drives which have a "offline" target are unmounted and all its BSUs are detached from the VM. + +Drives which have a "delete" target are umounted, all its BSU are detached from the VM and all its BSU are deleted. + +## About auto-scaling + +BSUd is using exponential auto-scaling. It will create an exponentially larger BSU to expand the drive while limiting the number of attached BSU. The size of created BSU will be adjusted depending of a scaling factor (see `disk-scale-factor-perc`). + +Example: If the largest BSU in the drive is 20Gib and the scale factor is set to 10%, then the next BSU to be added to the drive will be will be 20 * 1.1 = 22Gib. + +BSUd may also create BSU which are 10% smaller in some particular cases in order to balance drive size repartition. More details on how this can occur in [dev documentation](develop.md). + +A minimal and maximal space usage can be defined in order to trigger scaling (see `min-used-space-perc` and `max-used-space-perc`). + +Example: If the drive is 89% full and the `max-used-space-perc` is set to 85%, then the drive will scale up (adding a BSU). +Example: If the drive is 19% full and the `min-used-space-perc` is set to 20%, then the drive will scale down (remove a BSU). + +VMs cannot attach an infinite number of disks. `max-bsu-count` will limit the number of attached BSU without limiting drive's maximal size. BSUd will scale up and migrate any data before removing a BSU. +BSUd will maintain `max-bsu-count` minus 1 in order to be able to add one more disk to scale up. Once `max-bsu-count` BSU reached, BSUd will try to remove the smallest disk. + +## Question? + +Check the [FAQ](faq.md) \ No newline at end of file diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..580a093 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,12 @@ +use clap::Parser; + +pub fn parse() -> Args { + Args::parse() +} + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about=None)] +pub struct Args { + #[arg(long = "config", short = 'c', default_value_t = String::from("/etc/osc/bsud/config.json"))] + pub config_path: String, +} diff --git a/src/bsu.rs b/src/bsu.rs new file mode 100644 index 0000000..b0860d8 --- /dev/null +++ b/src/bsu.rs @@ -0,0 +1,362 @@ +use crate::config::{DiskType, CLOUD_CONFIG, SUBREGION, VM_ID}; +use crate::utils::gib_to_bytes; +use easy_error::format_err; +use log::{debug, error}; +use outscale_api::apis::tag_api::create_tags; +use outscale_api::apis::volume_api::{ + create_volume, delete_volume, link_volume, read_volumes, unlink_volume, +}; +use outscale_api::models::{ + CreateTagsRequest, CreateVolumeRequest, DeleteVolumeRequest, FiltersVolume, LinkVolumeRequest, + ReadVolumesRequest, ResourceTag, UnlinkVolumeRequest, Volume, +}; +use std::error::Error; +use std::path::PathBuf; + +use datetime::{Duration, Instant}; +use lazy_static::lazy_static; +use std::sync::Mutex; +use std::thread::sleep; +use std::time; + +const API_LIMITER_S: u64 = 3; +const BSU_TAG_KEY: &str = "osc.bsud.drive-name"; +const MAX_IOPS_PER_VOLUMES: usize = 13000; +const DEFAULT_IO1_IOPS_PER_GB: usize = 100; + +lazy_static! { + pub static ref API_LIMITER: Mutex = + Mutex::new(Instant::now() - Duration::of(API_LIMITER_S as i64)); +} + +#[derive(Debug, Default, Clone)] +pub struct Bsu { + pub vm_id: Option, + pub drive_name: String, + pub id: String, + pub size_bytes: usize, + pub size_gib: usize, + pub device_path: Option, +} + +impl Bsu { + pub fn new(volume: &Volume) -> Result> { + let Some(bsu_id) = volume.volume_id.clone() else { + return Err(Box::new(format_err!("BSU {:?} does not have an id", volume))); + }; + let Some(bsu_size_gib) = volume.size else { + return Err(Box::new(format_err!("BSU {:?} does not have a size", volume))); + }; + let vm_id = Bsu::get_drive_linked_vm_id(volume); + let Some(drive_name) = Bsu::get_drive_name(volume) else { + Err(format_err!("Cannot extract drive name from BSU id {}", bsu_id))? + }; + let device_path = Bsu::get_drive_device_path(volume); + + Ok(Bsu { + vm_id, + drive_name, + id: bsu_id, + size_bytes: gib_to_bytes(bsu_size_gib as usize), + size_gib: bsu_size_gib as usize, + device_path, + }) + } + + fn get_drive_linked_vm_id(volume: &Volume) -> Option { + let Some(linked_volumes) = &volume.linked_volumes else { + return None; + }; + for linked_volume in linked_volumes { + let Some(state) = &linked_volume.state else { + continue; + }; + let Some(vm_id) = &linked_volume.vm_id else { + continue; + }; + match state.as_str() { + "attaching" | "attached" => return Some(vm_id.clone()), + _ => return None, + }; + } + None + } + + fn get_drive_name(volume: &Volume) -> Option { + let Some(tags) = &volume.tags else { + return None; + }; + for tag in tags { + if tag.key == *BSU_TAG_KEY.to_string() { + return Some(tag.value.clone()); + } + } + None + } + + fn get_drive_device_path(volume: &Volume) -> Option { + let Some(linked_volumes) = &volume.linked_volumes else { + return None; + }; + linked_volumes.iter().next()?.device_name.clone() + } + + pub fn fetch_drive(drive_name: &String) -> Result, Box> { + debug!("\"{}\" drive: fetching all bsu", drive_name); + api_limiter()?; + let mut request = ReadVolumesRequest::new(); + let mut filter = FiltersVolume::default(); + let tag = format!("{}={}", BSU_TAG_KEY, drive_name); + filter.tags = Some(vec![tag]); + filter.volume_states = Some(vec![ + "creating".to_string(), + "available".to_string(), + "in-use".to_string(), + ]); + request.filters = Some(Box::new(filter)); + let response = read_volumes(&*CLOUD_CONFIG.read()?, Some(request)); + if response.is_err() { + error!("read volume response: {:?}", response); + } + let response = response?; + let volumes = response.volumes.unwrap_or_default(); + // Check state filtering + let volumes: Vec = volumes + .into_iter() + .filter(|vol| { + let Some(state) = &vol.state else { + return false; + }; + matches!(state.as_str(), "creating" | "available" | "in-use") + }) + .collect(); + let bsu_list = volumes.iter().map(Bsu::new).collect(); + bsu_list + } + + pub fn detach(&self) -> Result<(), Box> { + debug!("detaching BSU {} on vm {:?}", self.id, self.vm_id); + api_limiter()?; + let request = UnlinkVolumeRequest::new(self.id.clone()); + let response = unlink_volume(&*CLOUD_CONFIG.read()?, Some(request)); + if response.is_err() { + error!("unlink volume response: {:?}", response); + response?; + } + Bsu::wait_state(&self.id, "available")?; + Ok(()) + } + + pub fn multiple_attach(vm_id: &String, bsus: &Vec) -> Result<(), Box> { + for bsu in bsus { + debug!("attaching BSU {} on vm {:?}", bsu.id, vm_id); + api_limiter()?; + let Some(device_name) = Bsu::find_next_available_device() else { + return Err(Box::new(format_err!("cannot find available device to attach {} BSU on {} VM", bsu.id, vm_id))); + }; + let request = LinkVolumeRequest::new(device_name, vm_id.clone(), bsu.id.clone()); + let response = link_volume(&*CLOUD_CONFIG.read()?, Some(request)); + if response.is_err() { + error!("link volume response: {:?}", response); + response?; + } + } + Bsu::wait_states(bsus, "in-use")?; + Ok(()) + } + + pub fn multiple_detach(bsus: &Vec) -> Result<(), Box> { + let vm_id: String = VM_ID.try_read()?.clone(); + let mut unlinked_volumes = Vec::new(); + for bsu in bsus { + debug!("detaching BSU {} on vm {}", bsu.id, vm_id); + let Some(ref bsu_vm_id) = bsu.vm_id else { + debug!("BSU id {} seems not to be attached, ignore detaching", bsu.id); + continue + }; + if vm_id != *bsu_vm_id { + debug!( + "BSU {} id seems attached to vm {}, not on vm {}, ignore detaching", + bsu.id, bsu_vm_id, vm_id + ); + continue; + } + api_limiter()?; + let request = UnlinkVolumeRequest::new(bsu.id.clone()); + let response = unlink_volume(&*CLOUD_CONFIG.read()?, Some(request)); + if response.is_err() { + error!("unlink volume response: {:?}", response); + response?; + } + unlinked_volumes.push(bsu.clone()); + } + Bsu::wait_states(&unlinked_volumes, "available")?; + Ok(()) + } + + pub fn delete(&self) -> Result<(), Box> { + debug!("deleting BSU {}", self.id); + api_limiter()?; + let request = DeleteVolumeRequest::new(self.id.clone()); + let response = delete_volume(&*CLOUD_CONFIG.read()?, Some(request)); + if response.is_err() { + error!("delete volume response: {:?}", response); + response?; + } + Ok(()) + } + + pub fn wait_state(bsu_id: &String, desired_state: &str) -> Result<(), Box> { + loop { + let volume_state = Bsu::get_state(bsu_id)?; + debug!( + "volume {} state: {}, desired state: {}", + bsu_id, volume_state, desired_state + ); + if volume_state == desired_state { + return Ok(()); + } + } + } + + pub fn wait_states(bsus: &[Bsu], desired_state: &str) -> Result<(), Box> { + let bsu_ids: Vec = bsus.iter().map(|bsu| bsu.id.clone()).collect(); + debug!("fetching multiple BSU states {:?}", &bsu_ids); + let mut request = ReadVolumesRequest::new(); + let filter = FiltersVolume { + volume_ids: Some(bsu_ids), + ..Default::default() + }; + request.filters = Some(Box::new(filter)); + loop { + api_limiter()?; + let response = read_volumes(&*CLOUD_CONFIG.read()?, Some(request.clone())); + if response.is_err() { + error!("read volume response: {:?}", response); + continue; + } + let volumes = response?.volumes.unwrap_or_default(); + if !volumes + .iter() + .filter_map(|volume| volume.state.clone()) + .any(|state| state != desired_state) + { + return Ok(()); + } + } + } + + pub fn get_state(bsu_id: &String) -> Result> { + debug!("fetching BSU {} state", bsu_id); + api_limiter()?; + let mut request = ReadVolumesRequest::new(); + let filter = FiltersVolume { + volume_ids: Some(vec![bsu_id.clone()]), + ..Default::default() + }; + request.filters = Some(Box::new(filter)); + let response = read_volumes(&*CLOUD_CONFIG.read()?, Some(request)); + if response.is_err() { + error!("read volume response: {:?}", response); + } + let response = response?; + let volumes = response.volumes.unwrap_or_default(); + let Some(volume) = volumes.into_iter().next() else { + return Err(Box::new(format_err!("cannot find BSU {}", bsu_id))); + }; + let Some(state) = volume.state else { + return Err(Box::new(format_err!("cannot find state in BSU {}", bsu_id))); + }; + Ok(state) + } + + fn find_next_available_device() -> Option { + for c1 in b'b'..=b'z' { + let device = format!("/dev/xvd{}", c1 as char); + let path = PathBuf::from(device.clone()); + if !path.exists() { + return Some(device); + } + } + for c1 in b'b'..=b'z' { + for c2 in b'a'..=b'z' { + let device = format!("/dev/xvd{}{}", c1 as char, c2 as char); + let path = PathBuf::from(device.clone()); + if !path.exists() { + return Some(device); + } + } + } + None + } + + pub fn create_gib( + drive_name: &String, + disk_type: &DiskType, + disk_iops_per_gib: Option, + disk_size_gib: usize, + ) -> Result<(), Box> { + debug!( + "\"{}\" drive: creating BSU of type {}, size {} GiB", + drive_name, + disk_type.to_string(), + disk_size_gib + ); + api_limiter()?; + let mut creation_request = CreateVolumeRequest::new(SUBREGION.read()?.clone()); + creation_request.volume_type = Some(disk_type.to_string()); + creation_request.iops = match disk_type { + DiskType::Io1 => match disk_iops_per_gib { + Some(disk_iops_per_gib) => { + Some((disk_size_gib * disk_iops_per_gib).max(MAX_IOPS_PER_VOLUMES) as i32) + } + None => { + Some((DEFAULT_IO1_IOPS_PER_GB * disk_size_gib).max(MAX_IOPS_PER_VOLUMES) as i32) + } + }, + _ => None, + }; + creation_request.size = Some(disk_size_gib as i32); + let create_result = match create_volume(&*CLOUD_CONFIG.read()?, Some(creation_request)) { + Ok(create) => create, + Err(err) => { + debug!("\"{}\" drive: during bsu creation: {:?}", drive_name, err); + return Err(Box::new(err)); + } + }; + let Some(bsu) = create_result.volume else { + return Err(Box::new(format_err!("volume creation did not provide a volume object"))); + }; + let Some(bsu_id) = bsu.volume_id else { + return Err(Box::new(format_err!("volume creation did provide a volume object but not volume id"))); + }; + debug!("\"{}\" drive: created BSU id {}", drive_name, bsu_id); + debug!("\"{}\" drive: adding tag to BSU {}", drive_name, bsu_id); + api_limiter()?; + let tag = ResourceTag::new(BSU_TAG_KEY.to_string(), drive_name.clone()); + let tag_request = CreateTagsRequest::new(vec![bsu_id.clone()], vec![tag]); + if let Err(err) = create_tags(&*CLOUD_CONFIG.read()?, Some(tag_request)) { + debug!( + "\"{}\" drive: during bsu tag creation: {:?}", + drive_name, err + ); + return Err(Box::new(err)); + } + Bsu::wait_state(&bsu_id, "available")?; + Ok(()) + } +} + +pub fn api_limiter() -> Result<(), Box> { + let mut limiter = API_LIMITER.lock()?; + let waited_time_s = Instant::now().seconds() - limiter.seconds(); + let time_left = (API_LIMITER_S as i64 - waited_time_s).max(0) as u64; + + if time_left > 0 { + debug!("api limiter sleeps for {} seconds", time_left); + sleep(time::Duration::from_secs(time_left)); + } + + *limiter = Instant::now(); + Ok(()) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..24861be --- /dev/null +++ b/src/config.rs @@ -0,0 +1,179 @@ +use easy_error::format_err; +use lazy_static::lazy_static; +use log::debug; +use outscale_api::apis::configuration::AWSv4Key; +use secrecy::Secret; +use secrecy::SecretString; +use serde::Deserialize; +use std::env; +use std::error::Error; +use std::fs::read_to_string; +use std::str::FromStr; +use std::sync::RwLock; + +type CloudConfig = outscale_api::apis::configuration::Configuration; + +const METADATA_SUBREGION_URL: &str = + "http://169.254.169.254/latest/meta-data/placement/availability-zone"; +const METADATA_VMID_URL: &str = "http://169.254.169.254/latest/meta-data/instance-id"; + +lazy_static! { + pub static ref CLOUD_CONFIG: RwLock = RwLock::new(CloudConfig::new()); + pub static ref REGION: RwLock = RwLock::new(String::new()); + pub static ref SUBREGION: RwLock = RwLock::new(String::new()); + pub static ref VM_ID: RwLock = RwLock::new(String::new()); +} +#[derive(Deserialize, Debug)] +pub struct Config { + pub drives: Vec, +} + +pub fn discover_vm_config() -> Result<(), Box> { + debug!("getting subregion from metadata"); + let subregion = reqwest::blocking::get(METADATA_SUBREGION_URL)?.text()?; + let mut region = subregion.clone(); + region.pop(); + { + *SUBREGION.write()? = subregion; + *REGION.write()? = region; + } + debug!("get vm id"); + let vm_id = reqwest::blocking::get(METADATA_VMID_URL)?.text()?; + { + *VM_ID.write()? = vm_id; + } + Ok(()) +} + +pub fn region() -> Result> { + Ok(String::from(&(*REGION.read()?))) +} + +pub fn load(path: String) -> Result> { + debug!("trying to read \"{}\"", path); + let data = read_to_string(path)?; + let config_file: ConfigFile = serde_json::from_str(&data)?; + + let config_file_auth = match config_file.authentication { + Some(c) => c, + None => { + debug!("cannot get credentials through configuration file, trying to get credentials through env"); + let Ok(access_key) = env::var("OSC_ACCESS_KEY") else { + return Err(Box::new(format_err!("Cannot get OSC_ACCESS_KEY env variable"))); + }; + let Ok(secret_key) = env::var("OSC_SECRET_KEY") else { + return Err(Box::new(format_err!("Cannot get OSC_SECRET_KEY env variable"))); + }; + ConfigFileAuth { + access_key, + secret_key: SecretString::new(secret_key), + } + } + }; + discover_vm_config()?; + + debug!("forge cloud configuration"); + let mut cloud_config = CloudConfig::new(); + cloud_config.aws_v4_key = Some(AWSv4Key { + access_key: config_file_auth.access_key, + secret_key: config_file_auth.secret_key, + region: region()?, + service: "oapi".to_string(), + }); + { + *CLOUD_CONFIG.write()? = cloud_config; + } + + Ok(Config { + drives: config_file.drives, + }) +} + +#[derive(Deserialize, Debug)] +struct ConfigFile { + authentication: Option, + drives: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct ConfigFileAuth { + access_key: String, + secret_key: Secret, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct ConfigFileDrive { + pub name: String, + pub target: DriveTarget, + pub mount_path: String, + pub disk_type: Option, + pub disk_iops_per_gib: Option, + pub max_total_size_gib: Option, + pub initial_size_gib: Option, + pub max_bsu_count: Option, + pub max_used_space_perc: Option, + pub min_used_space_perc: Option, + pub disk_scale_factor_perc: Option, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "kebab-case")] +pub enum DriveTarget { + Online, // normal drive flow, drive is available + Offline, // unmount + detach from VM + Delete, // unmount + detach from VM + delete data +} + +impl FromStr for DriveTarget { + type Err = (); + fn from_str(input: &str) -> Result { + match input.to_lowercase().as_str() { + "online" => Ok(DriveTarget::Online), + "offline" => Ok(DriveTarget::Offline), + "delete" => Ok(DriveTarget::Delete), + _ => Err(()), + } + } +} + +impl ToString for DriveTarget { + fn to_string(&self) -> String { + match self { + DriveTarget::Online => String::from("online"), + DriveTarget::Offline => String::from("offline"), + DriveTarget::Delete => String::from("delete"), + } + } +} + +#[derive(Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "kebab-case")] +pub enum DiskType { + Standard, + Gp2, + Io1, +} + +impl FromStr for DiskType { + type Err = (); + fn from_str(input: &str) -> Result { + match input.to_lowercase().as_str() { + "standard" => Ok(Self::Standard), + "gp2" => Ok(Self::Gp2), + "io1" => Ok(Self::Io1), + _ => Err(()), + } + } +} + +impl ToString for DiskType { + fn to_string(&self) -> String { + match self { + Self::Standard => "standard".to_string(), + Self::Gp2 => "gp2".to_string(), + Self::Io1 => "io1".to_string(), + } + } +} diff --git a/src/drive.rs b/src/drive.rs new file mode 100644 index 0000000..b28476f --- /dev/null +++ b/src/drive.rs @@ -0,0 +1,950 @@ +use crate::bsu::Bsu; +use crate::config::{self, Config, ConfigFileDrive, DriveTarget, VM_ID}; +use crate::fs; +use crate::lvm; +use crate::utils::{bytes_to_gib, bytes_to_gib_rounded, gib_to_bytes}; +use datetime::{Duration, Instant}; +use easy_error::format_err; +use log::info; +use log::{debug, error}; +use std::cmp::Ordering; +use std::cmp::{max, min}; +use std::collections::{HashMap, HashSet}; +use std::error::Error; +use std::path::Path; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::thread::sleep; +use std::time; +use threadpool::ThreadPool; + +const RECONCILE_COOLDOWN_S: u64 = 30; +const DEFAULT_INITIAL_DISK_GIB: usize = 10; +const DEFAULT_MAX_DISKS: usize = 10; +const DEFAULT_MAX_USED_PERC: usize = 85; +const DEFAULT_MIN_USED_PERC: usize = 40; +const DEFAULT_SCALE_FACTOR_PERC: usize = 20; +const DEFAULT_DISK_TYPE: config::DiskType = config::DiskType::Gp2; +// https://docs.outscale.com/api#createvolume +const MAX_BSU_SIZE_GIB: usize = 14901; + +type DriveName = String; + +#[derive(Debug, Default)] +pub struct Drives { + drives_cmd: HashMap>, + drives_threads: ThreadPool, +} + +impl Drives { + pub fn run(config: Config) -> Result> { + let mut drives_cmd = HashMap::>::new(); + let mut drive_list = Vec::::new(); + + for drive_config in config.drives { + let name = drive_config.name.clone(); + + let (sender, receiver) = channel::(); + let drive = Drive::new(drive_config, receiver); + drives_cmd.insert(name.clone(), sender); + drive_list.push(drive); + } + + for (sender, drive) in Drives::discover_local_drives()? { + let name: String = drive.name.clone(); + if drives_cmd.get(&name).is_some() { + continue; + } + drives_cmd.insert(name.clone(), sender); + drive_list.push(drive); + } + + let drives_threads = ThreadPool::new(drive_list.len()); + for mut drive in drive_list { + drives_threads.execute(move || drive.run()); + } + + Ok(Drives { + drives_cmd, + drives_threads, + }) + } + + pub fn stop(&mut self) -> Result<(), Box> { + for (name, sender) in self.drives_cmd.iter() { + info!("asking drive {} to stop", name); + sender.send(DriveCmd::Stop)?; + } + info!("waiting for drives to stop"); + self.drives_threads.join(); + info!("all drives stopped"); + Ok(()) + } + + pub fn discover_local_drives() -> Result> { + // TODO + Ok(vec![]) + } +} + +type DriveDiscovery = Vec<(Sender, Drive)>; +type DevicePath = String; + +#[derive(Debug)] +pub enum DriveCmd { + Stop, +} + +#[derive(Debug)] +pub struct Drive { + last_reconcile: Instant, + all_bsu: Vec, + drive_cmd: Receiver, + exit: bool, + pv_to_be_initialized: Vec, + pv_to_add_to_vg: Vec, + pub name: String, + pub target: DriveTarget, + pub mount_path: String, + pub disk_type: config::DiskType, + pub disk_iops_per_gib: Option, + pub max_total_size_gib: Option, + pub initial_size_gib: usize, + pub max_bsu_count: usize, + pub max_used_space_perc: f32, + pub min_used_space_perc: f32, + pub disk_scale_factor_perc: f32, +} + +impl Drive { + pub fn new(config: ConfigFileDrive, drive_cmd: Receiver) -> Self { + Drive { + last_reconcile: Instant::now() - Duration::of(RECONCILE_COOLDOWN_S as i64), + all_bsu: Vec::default(), + drive_cmd, + exit: false, + pv_to_be_initialized: Vec::new(), + pv_to_add_to_vg: Vec::new(), + name: config.name, + target: config.target, + mount_path: config.mount_path, + disk_type: config.disk_type.unwrap_or(DEFAULT_DISK_TYPE), + initial_size_gib: config.initial_size_gib.unwrap_or(DEFAULT_INITIAL_DISK_GIB), + max_bsu_count: config.max_bsu_count.unwrap_or(DEFAULT_MAX_DISKS), + max_used_space_perc: config.max_used_space_perc.unwrap_or(DEFAULT_MAX_USED_PERC) as f32 + / 100.0, + min_used_space_perc: config.min_used_space_perc.unwrap_or(DEFAULT_MIN_USED_PERC) as f32 + / 100.0, + disk_scale_factor_perc: config + .disk_scale_factor_perc + .unwrap_or(DEFAULT_SCALE_FACTOR_PERC) as f32 + / 100.0, + disk_iops_per_gib: config.disk_iops_per_gib, + max_total_size_gib: config.max_total_size_gib, + } + } + + pub fn run(&mut self) { + loop { + if Instant::now().seconds() - self.last_reconcile.seconds() + <= RECONCILE_COOLDOWN_S as i64 + { + sleep(time::Duration::from_millis(10)); + if self.early_exit().is_err() { + break; + } + continue; + } + if let Err(err) = self.reconcile() { + error!("\"{}\" drive: {}", self.name, err); + } else { + info!("\"{}\" drive: reconcile loop over with success", self.name); + } + self.last_reconcile = Instant::now(); + if self.exit { + break; + } + } + info!("\"{}\" drive: stopped", self.name); + } + + pub fn early_exit(&mut self) -> Result<(), Box> { + if let Ok(cmd) = self.drive_cmd.try_recv() { + info!("\"{}\" drive received {:?} command", self.name, cmd); + match cmd { + DriveCmd::Stop => { + self.exit = true; + return Err(Box::new(format_err!( + "\"{}\" drive: early exit due to drive stop", + self.name + ))); + } + }; + } + Ok(()) + } + + pub fn reconcile(&mut self) -> Result<(), Box> { + info!( + "\"{}\" drive: entering {:?} drive target", + self.name, self.target + ); + info!( + "\"{}\" drive: start reconcile {}", + self.name, + self.target.to_string() + ); + match self.target { + DriveTarget::Online => self.reconcile_online(), + DriveTarget::Offline => self.reconcile_offline(), + DriveTarget::Delete => self.reconcile_delete(), + } + } + + pub fn reconcile_offline(&mut self) -> Result<(), Box> { + self.early_exit()?; + while self.is_fs_mounted()? { + self.early_exit()?; + self.fs_umount()?; + } + + self.disable_lv().ok(); + self.disable_vg().ok(); + + self.early_exit()?; + self.fetch_all_drive_bsu()?; + if self.bsu_count() == 0 { + return Ok(()); + } + + while self.are_bsu_attached()? { + self.early_exit()?; + self.bsu_detach_all_from_this_vm()?; + self.early_exit()?; + self.fetch_all_drive_bsu()?; + self.early_exit()?; + } + self.vg_scan().ok(); + Ok(()) + } + + pub fn reconcile_delete(&mut self) -> Result<(), Box> { + self.reconcile_offline()?; + self.delete_all_bsu()?; + Ok(()) + } + + pub fn reconcile_online(&mut self) -> Result<(), Box> { + 'start_again: loop { + debug!("\"{}\" drive: reconcile online loop again", self.name); + self.early_exit()?; + self.crash_resume()?; + + self.early_exit()?; + self.fetch_all_drive_bsu()?; + + self.early_exit()?; + while !self.are_bsu_attached()? { + self.bsu_attach_missing()?; + self.fetch_all_drive_bsu()?; + self.early_exit()?; + } + + self.early_exit()?; + if self.bsu_count() == 0 { + self.create_initial_bsu()?; + continue 'start_again; + } + + self.early_exit()?; + while !self.are_pv_initialized()? { + self.pv_initialize_missing()?; + self.early_exit()?; + } + + self.early_exit()?; + self.vg_scan().ok(); + + self.early_exit()?; + while !self.is_vg_created()? { + self.vg_create()?; + self.early_exit()?; + } + + self.early_exit()?; + self.enable_vg().ok(); + + self.early_exit()?; + while !self.is_vg_extended()? { + self.vg_extend()?; + self.early_exit()?; + } + + self.early_exit()?; + while !self.is_lv_created()? { + self.lv_create()?; + self.early_exit()?; + } + + self.early_exit()?; + self.enable_lv().ok(); + + self.early_exit()?; + self.lv_extend()?; + + self.early_exit()?; + while !self.is_fs_formated()? { + self.fs_format()?; + self.early_exit()?; + } + + self.early_exit()?; + while !self.is_mount_path_created() { + self.create_mount_path()?; + } + + self.early_exit()?; + while !self.is_fs_mounted()? { + self.fs_mount()?; + self.early_exit()?; + } + + self.early_exit()?; + while !self.is_fs_extended()? { + self.fs_extend()?; + self.early_exit()?; + } + + self.early_exit()?; + if self.is_drive_reached_max_attached_bsu()? { + self.remove_smallest_bsu()?; + self.early_exit()?; + continue 'start_again; + } + + self.early_exit()?; + if self.is_drive_low_space_left()? { + if self.is_max_space_reached() { + return Ok(()); + } + if !self.is_drive_reached_max_attached_bsu_minus_one()? + && !self.is_drive_contains_smallest_bsu() + { + self.create_smaller_bsu()?; + } else { + self.create_larger_bsu()?; + } + continue 'start_again; + } + + self.early_exit()?; + if self.is_drive_high_space_left()? { + if self.bsu_count() > 1 { + self.remove_largest_bsu()?; + } else { + if self.has_minimal_size() { + return Ok(()); + } + self.create_ideal_bsu()?; + } + self.early_exit()?; + continue 'start_again; + } + return Ok(()); + } + } + + pub fn crash_resume(&mut self) -> Result<(), Box> { + // Run pvmove alone to restart eventual pvmove actions + // https://www.man7.org/linux/man-pages/man8/pvmove.8.html + lvm::pv_move_no_arg()?; + Ok(()) + } + + pub fn fetch_all_drive_bsu(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: fetch all bsu", self.name); + self.all_bsu = Bsu::fetch_drive(&self.name)?; + info!( + "\"{}\" drive: fetched {} BSU", + self.name, + self.all_bsu.len() + ); + Ok(()) + } + + pub fn are_bsu_attached(&mut self) -> Result> { + let mut ret = true; + debug!("\"{}\" drive: are bsu attached ?", self.name); + let vm_id = VM_ID.try_read()?; + for bsu in self.all_bsu.iter() { + let Some(bsu_vm_id) = &bsu.vm_id else { + debug!("\"{}\" drive: BSU id {} not attached to any VM", self.name, bsu.id); + ret = false; + continue; + }; + if *bsu_vm_id != *vm_id { + debug!( + "\"{}\" drive: BSU id {} is attached to vm {} instead of vm {}", + self.name, bsu.id, bsu_vm_id, vm_id + ); + ret = false; + continue; + } + let Some(device_name) = &bsu.device_path else { + debug!("\"{}\" drive: BSU id {} does not have a device path", self.name, bsu.id); + ret = false; + continue; + }; + let device_path = Path::new(device_name); + if !device_path.exists() { + debug!( + "\"{}\" drive: BSU id {} seems not to exist yet on {}", + self.name, bsu.id, device_name + ); + ret = false; + continue; + } + debug!( + "\"{}\" drive: bsu id {} of size {}B ({}GiB) is attached", + self.name, + bsu.id, + bsu.size_bytes, + bytes_to_gib(bsu.size_bytes) + ); + } + info!("\"{}\" drive: are bsu attached ? -> {}", self.name, ret); + Ok(ret) + } + + pub fn bsu_attach_missing(&mut self) -> Result<(), Box> { + let vm_id: String = VM_ID.try_read()?.clone(); + let bsus: Vec = self + .all_bsu + .iter() + .filter(|bsu| bsu.vm_id.is_none()) + .cloned() + .collect(); + Bsu::multiple_attach(&vm_id, &bsus) + } + + pub fn bsu_detach_all_from_this_vm(&mut self) -> Result<(), Box> { + info!( + "\"{}\" drive: detach all {} BSU", + self.name, + self.all_bsu.len() + ); + Bsu::multiple_detach(&self.all_bsu) + } + + pub fn delete_all_bsu(&mut self) -> Result<(), Box> { + info!( + "\"{}\" drive: delete all {} BSU", + self.name, + self.all_bsu.len() + ); + for bsu in self.all_bsu.iter() { + bsu.delete()?; + } + Ok(()) + } + + pub fn bsu_count(&mut self) -> usize { + let count = self.all_bsu.len(); + debug!("\"{}\" drive: bsu count = {}", self.name, count); + count + } + + pub fn create_initial_bsu(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: create initial BSU", self.name); + Bsu::create_gib( + &self.name, + &self.disk_type, + self.disk_iops_per_gib, + self.initial_size_gib, + ) + } + + pub fn are_pv_initialized(&mut self) -> Result> { + let mut ret = true; + self.pv_to_be_initialized.clear(); + let mut found_devices = HashSet::::new(); + if let Some(report_with_no_vg) = lvm::get_report_with_no_vg()? { + for device in report_with_no_vg.devices() { + found_devices.insert(device); + } + } + if let Some(report) = lvm::get_report(&self.name)? { + for device in report.devices() { + found_devices.insert(device); + } + } + for bsu in self.all_bsu.iter() { + let Some(device_path) = &bsu.device_path else { + error!("\"{}\" drive: BSU {} should have loca path, please report error", self.name, bsu.id); + continue; + }; + if !found_devices.contains(device_path) { + info!( + "\"{}\" drive: BSU {} ({}) seems not to be pv initialized", + self.name, bsu.id, device_path + ); + self.pv_to_be_initialized.push(device_path.clone()); + ret = false; + } + } + info!("\"{}\" drive: are pv initialized -> {}", self.name, ret); + Ok(ret) + } + + pub fn pv_initialize_missing(&mut self) -> Result<(), Box> { + for device in self.pv_to_be_initialized.iter() { + lvm::init_pv(device)?; + } + Ok(()) + } + + pub fn is_vg_created(&mut self) -> Result> { + let lvm = lvm::get_report(&self.name)?; + info!( + "\"{}\" drive: is vg created -> {}", + self.name, + lvm.is_some() + ); + Ok(lvm.is_some()) + } + + pub fn vg_create(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: create vg", self.name); + let mut found_devices = HashSet::::new(); + if let Some(report_with_no_vg) = lvm::get_report_with_no_vg()? { + for device in report_with_no_vg.devices() { + found_devices.insert(device); + } + } + for bsu in self.all_bsu.iter() { + let Some(device_path) = &bsu.device_path else { + error!("\"{}\" drive: BSU {} should have local path, please report error", self.name, bsu.id); + continue; + }; + if found_devices.contains(device_path) { + return lvm::vg_create(&self.name, device_path); + } + } + Err(Box::new(format_err!( + "\"{}\" drive: no PV found to init VG, please report this error", + self.name + ))) + } + + pub fn is_vg_extended(&mut self) -> Result> { + let mut ret = true; + self.pv_to_add_to_vg.clear(); + let mut found_devices = HashSet::::new(); + if let Some(report_with_no_vg) = lvm::get_report_with_no_vg()? { + for device in report_with_no_vg.devices() { + found_devices.insert(device); + } + } + for bsu in self.all_bsu.iter() { + let Some(device_path) = &bsu.device_path else { + error!("\"{}\" drive: BSU {} should have local path, please report error", self.name, bsu.id); + continue; + }; + if found_devices.contains(device_path) { + info!( + "\"{}\" drive: pv {} can be added to vg", + self.name, device_path + ); + self.pv_to_add_to_vg.push(device_path.clone()); + ret = false; + } + } + info!("\"{}\" drive: is vg extended -> {}", self.name, ret); + Ok(ret) + } + + pub fn vg_extend(&mut self) -> Result<(), Box> { + for pv_device_path in self.pv_to_add_to_vg.iter() { + lvm::extend_vg(&self.name, pv_device_path)?; + } + Ok(()) + } + + pub fn is_lv_created(&mut self) -> Result> { + let Some(lvm) = lvm::get_report(&self.name)? else { + return Err(Box::new(format_err!("\"{}\" drive: lvm details cannot be found, please report issue", self.name))); + }; + let Some(_lv) = lvm.lv.into_iter().next() else { + debug!("\"{}\" drive: is lv created -> false", self.name); + return Ok(false); + }; + info!("\"{}\" drive: is lv created -> true", self.name); + Ok(true) + } + + pub fn lv_create(&mut self) -> Result<(), Box> { + lvm::create_lv(&self.name) + } + + pub fn lv_extend(&mut self) -> Result<(), Box> { + let vg_size = lvm::get_vg_size_bytes(&self.name)?; + let lv_size = lvm::get_lv_size_bytes(&self.name)?; + match vg_size.cmp(&lv_size) { + Ordering::Greater => { + debug!("\"{}\" drive: lv can be extended", self.name); + let lv_path = lvm::lv_path(&self.name); + lvm::lv_extend_full(&lv_path)?; + } + Ordering::Equal => debug!("\"{}\" drive: lv fit vg", self.name), + Ordering::Less => { + return Err(Box::new(format_err!( + "\"{}\" drive: vg_size ({}) < lv_size ({})", + self.name, + vg_size, + lv_size + ))); + } + }; + Ok(()) + } + + pub fn enable_lv(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: disabling lv {}", self.name, self.name); + lvm::lv_activate(true, &self.name) + } + + pub fn disable_lv(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: disabling lv {}", self.name, self.name); + lvm::lv_activate(false, &self.name) + } + + pub fn enable_vg(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: enabling vg {}", self.name, self.name); + lvm::vg_activate(true, &self.name) + } + + pub fn disable_vg(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: disabling vg {}", self.name, self.name); + lvm::vg_activate(false, &self.name) + } + + pub fn vg_scan(&self) -> Result<(), Box> { + debug!("\"{}\" drive: vgscan", self.name); + lvm::vg_scan() + } + + pub fn is_fs_formated(&mut self) -> Result> { + let lv_path = lvm::lv_path(&self.name); + let ret = fs::device_seems_formated(&lv_path)?; + info!("\"{}\" drive: is fs formated -> {}", self.name, ret); + Ok(ret) + } + + pub fn fs_format(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: fs format", self.name); + let lv_path = lvm::lv_path(&self.name); + fs::format(&lv_path) + } + + pub fn is_mount_path_created(&mut self) -> bool { + let ret = fs::is_folder(&self.mount_path); + debug!("\"{}\" drive: is mount target created ? -> {}", self.name, ret); + ret + } + + pub fn create_mount_path(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: try creating folder in {}", self.name, self.mount_path); + fs::create_folder(&self.mount_path) + } + + pub fn is_fs_mounted(&mut self) -> Result> { + let lv_path = lvm::lv_path(&self.name); + let ret = fs::is_mounted(&lv_path, &self.mount_path)?; + info!("\"{}\" drive: is fs mounted ? -> {}", self.name, ret); + Ok(ret) + } + + pub fn fs_mount(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: fs mount", self.name); + let lv_path = lvm::lv_path(&self.name); + fs::mount(&lv_path, &self.mount_path) + } + + pub fn fs_umount(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: fs umount", self.name); + let lv_path = lvm::lv_path(&self.name); + fs::umount(&lv_path) + } + + pub fn is_fs_extended(&mut self) -> Result> { + let lv_size = lvm::get_lv_size_bytes(&self.name)?; + let lv_path = lvm::lv_path(&self.name); + let fs_size = fs::size_bytes(&lv_path)?; + debug!( + "\"{}\" drive: lv size: {}B ({}GiB), fs size: {}B ({}GiB)", + self.name, + lv_size, + bytes_to_gib(lv_size), + fs_size, + bytes_to_gib(fs_size) + ); + let ret = match fs_size.cmp(&lv_size) { + Ordering::Equal => true, + Ordering::Less => false, + Ordering::Greater => { + return Err(Box::new(format_err!( + "\"{}\" drive: fs_size > lv_size", + self.name + ))) + } + }; + info!("\"{}\" drive: is fs extended ? -> {}", self.name, ret); + Ok(ret) + } + + pub fn fs_extend(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: fs extend", self.name); + fs::extend_fs_max(&self.mount_path) + } + + pub fn is_drive_reached_max_attached_bsu(&mut self) -> Result> { + let count = self.bsu_count(); + let ret = count >= self.max_bsu_count; + info!( + "\"{}\" drive: is drive reached max attached BSU: (count: {}, max: {}) -> {}", + self.name, count, self.max_bsu_count, ret + ); + Ok(ret) + } + + pub fn is_drive_reached_max_attached_bsu_minus_one(&mut self) -> Result> { + let ret = self.bsu_count() == self.max_bsu_count - 1; + info!( + "\"{}\" drive: is drive reached max attached BSU minus ONE (count: {}, max: {}) -> {}", + self.name, + self.all_bsu.len(), + self.max_bsu_count, + ret + ); + Ok(ret) + } + + pub fn is_drive_contains_smallest_bsu(&mut self) -> bool { + let ret = self.smallest_bsu().size_gib <= self.initial_size_gib; + debug!( + "\"{}\" drive: is_drive_contains_smallest_bsu ? -> {}", + self.name, ret + ); + ret + } + + pub fn remove_smallest_bsu(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: remove smallest BSU", self.name); + let bsu = self.smallest_bsu(); + self.remove_bsu(&bsu) + } + + pub fn is_drive_low_space_left(&mut self) -> Result> { + let lv_path = lvm::lv_path(&self.name); + let usage_per = fs::used_perc(&lv_path)?; + let ret = usage_per >= self.max_used_space_perc; + debug!( + "\"{}\" drive: used space perc: {}, max_used_space_perc: {}", + self.name, usage_per, self.max_used_space_perc + ); + info!( + "\"{}\" drive: is drive low space left -> {}", + self.name, ret + ); + Ok(ret) + } + + pub fn is_max_space_reached(&mut self) -> bool { + let Some(max_total_size_gib) = self.max_total_size_gib else { + return false; + }; + let total_gib = self.all_bsu_size_gib(); + let ret = total_gib >= max_total_size_gib; + info!("\"{}\" drive: is max space reached -> {} ({}/{}Gib)", self.name, ret, total_gib, max_total_size_gib); + ret + } + + pub fn all_bsu_size_gib(&self) -> usize { + let mut total_size: usize = 0; + for bsu in self.all_bsu.iter() { + total_size += bsu.size_bytes; + } + bytes_to_gib_rounded(total_size) + } + + pub fn create_larger_bsu(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: create larger BSU", self.name); + let largest_size_gib = self.largest_bsu().size_gib as f32; + let new_bsu_size_gib = + (largest_size_gib + largest_size_gib * self.disk_scale_factor_perc).ceil() as usize; + let final_bsu_size = min(MAX_BSU_SIZE_GIB, new_bsu_size_gib); + Bsu::create_gib( + &self.name, + &self.disk_type, + self.disk_iops_per_gib, + final_bsu_size, + ) + } + + pub fn create_smaller_bsu(&mut self) -> Result<(), Box> { + debug!("\"{}\" drive: create smaller BSU", self.name); + let largest_size_gib = self.smallest_bsu().size_gib as f32; + let new_bsu_size_gib = + (largest_size_gib - largest_size_gib * self.disk_scale_factor_perc).ceil() as usize; + let final_bsu_size = max(self.initial_size_gib, new_bsu_size_gib); + Bsu::create_gib( + &self.name, + &self.disk_type, + self.disk_iops_per_gib, + final_bsu_size, + ) + } + + pub fn largest_bsu(&self) -> Bsu { + let mut largest_bsu_size = 0; + let mut largest_bsu: Option<&Bsu> = None; + for bsu in self.all_bsu.iter() { + if bsu.size_bytes > largest_bsu_size { + largest_bsu = Some(bsu); + largest_bsu_size = bsu.size_bytes; + } + } + largest_bsu + .expect("largest BSU should exit, please report this") + .clone() + } + + pub fn smallest_bsu(&self) -> Bsu { + let mut smallest_bsu_size = usize::MAX; + let mut smallest_bsu: Option<&Bsu> = None; + for bsu in self.all_bsu.iter() { + if bsu.size_bytes < smallest_bsu_size { + smallest_bsu = Some(bsu); + smallest_bsu_size = bsu.size_bytes; + } + } + smallest_bsu + .expect("smallest BSU should exit, please report this") + .clone() + } + + pub fn is_drive_high_space_left(&mut self) -> Result> { + let lv_path = lvm::lv_path(&self.name); + let usage_per = fs::used_perc(&lv_path)?; + let ret = usage_per <= self.min_used_space_perc; + debug!( + "\"{}\" drive: used space perc: {}, low space perc: {}", + self.name, usage_per, self.min_used_space_perc + ); + info!( + "\"{}\" drive: is drive high space left -> {}", + self.name, ret + ); + Ok(ret) + } + + pub fn has_minimal_size(&self) -> bool { + let total_size_gib = self.all_bsu_size_gib(); + let ret = total_size_gib == self.initial_size_gib; + info!("\"{}\" drive: has minimal size -> {}", self.name, ret); + ret + } + + pub fn ideal_size_bytes(&mut self) -> Result> { + let lv_path = lvm::lv_path(&self.name); + let used_size_bytes = fs::used_bytes(&lv_path)? as f32; + let middle_perc = (self.min_used_space_perc + self.max_used_space_perc) / 2.0; + let ideal_size_bytes = (used_size_bytes / middle_perc).ceil() as usize; + let ideal_size_bytes = max(ideal_size_bytes, gib_to_bytes(self.initial_size_gib)); + let ideal_size_bytes = min(ideal_size_bytes, fs::size_bytes(&lv_path)?); + Ok(ideal_size_bytes) + } + + pub fn create_ideal_bsu(&mut self) -> Result<(), Box> { + let ideal_size_gib = bytes_to_gib_rounded(self.ideal_size_bytes()?); + info!( + "\"{}\" drive: create fit BSU of size {}GiB", + self.name, ideal_size_gib + ); + Bsu::create_gib( + &self.name, + &self.disk_type, + self.disk_iops_per_gib, + ideal_size_gib, + )?; + Ok(()) + } + + pub fn remove_largest_bsu(&mut self) -> Result<(), Box> { + info!("\"{}\" drive: remove largest BSU", self.name); + let bsu = self.largest_bsu(); + self.remove_bsu(&bsu) + } + + pub fn remove_bsu(&mut self, bsu: &Bsu) -> Result<(), Box> { + info!( + "removing BSU {} of size {}B ({}GiB)", + bsu.id, + bsu.size_bytes, + bytes_to_gib(bsu.size_bytes) + ); + let lv_path = lvm::lv_path(&self.name); + let free_space_bytes = fs::available_bytes(&lv_path)?; + if free_space_bytes < bsu.size_bytes { + return Err(Box::new(format_err!( + "\"{}\" drive: cannot remove BSU. free space left: {}B ({}GiB), bsu size to remove: {} ({}GiB)", + self.name, + free_space_bytes, + bytes_to_gib(free_space_bytes), + bsu.size_bytes, + bytes_to_gib(bsu.size_bytes) + ))); + } + let Some(device_path) = &bsu.device_path else { + return Err(Box::new(format_err!("\"{}\" drive: cannot find device path for BSU {}", self.name, bsu.id))); + }; + + let ideal_size_bytes = self.ideal_size_bytes()?; + let fs_size_bytes = fs::size_bytes(&lv_path)?; + let largest_possible_new_fs_size = fs_size_bytes - bsu.size_bytes; + // trying (when possible) to lower more than required to delete the BSU will drastically help pvmove not to move useless fs data. + let new_fs_size_bytes = min(largest_possible_new_fs_size, ideal_size_bytes); + + debug!( + "\"{}\" drive: resising fs & lv to {}B ({}GiB)", + self.name, + new_fs_size_bytes, + bytes_to_gib(new_fs_size_bytes) + ); + debug!( + "\"{}\" drive: ideal_size_bytes was {}B ({}GiB)", + self.name, + ideal_size_bytes, + bytes_to_gib(ideal_size_bytes) + ); + debug!( + "\"{}\" drive: largest_possible_new_fs_size was {}B ({}GiB)", + self.name, + largest_possible_new_fs_size, + bytes_to_gib(largest_possible_new_fs_size) + ); + + fs::resize(&self.mount_path, new_fs_size_bytes)?; + let lv_path = lvm::lv_path(&self.name); + lvm::lv_reduce(&lv_path, new_fs_size_bytes)?; + lvm::pv_move(device_path)?; + lvm::vg_reduce(&self.name, device_path)?; + lvm::pv_remove(device_path)?; + // Once pv moved, be sure we can expand back lv and fs. + self.lv_extend()?; + self.fs_extend()?; + + bsu.detach()?; + bsu.delete()?; + Ok(()) + } +} diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..6965b34 --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,167 @@ +use crate::utils::bytes_to_gib; +use crate::utils::exec; +use easy_error::format_err; +use lfs_core::{self, Stats}; +use log::debug; +use proc_mounts::MountList; +use std::error::Error; +use std::fs::File; +use std::io::Read; +use std::path::Path; +use std::path::PathBuf; +use std::fs::create_dir; + +pub fn device_seems_formated(device_path: &String) -> Result> { + debug!("does device {} seems formated ?", device_path); + // Read fs header, consider unformated if reading only zeros + let mut buffer = [0; 1_000_000]; + let mut file = File::open(device_path)?; + let n = file.read(&mut buffer[..])?; + for byte in &buffer[..n] { + if *byte != 0 { + debug!("does device {} seems formated ? -> true", device_path); + return Ok(true); + } + } + debug!("does device {} seems formated ? -> false", device_path); + Ok(false) +} + +pub fn format(device_path: &String) -> Result<(), Box> { + exec("mkfs.btrfs", &[device_path])?; + Ok(()) +} + +pub fn is_folder(path: &String) -> bool { + PathBuf::from(path).is_dir() +} + +pub fn create_folder(path: &String) -> Result<(), Box> { + Ok(create_dir(path)?) +} + +pub fn is_mounted(device_path: &String, mount_target: &String) -> Result> { + let mount_list = MountList::new()?; + let source = Path::new(device_path.as_str()); + let Some(mount_info)= mount_list.get_mount_by_source(source) else { + debug!("{} is not mounted", device_path); + return Ok(false); + }; + let dest = PathBuf::from(mount_target.clone()); + if mount_info.dest != dest { + return Err(Box::new(format_err!( + "{:?} seems to be mounted on {:?}, not in {}", + source, + mount_info.dest, + mount_target + ))); + } + debug!( + "{:?} is mounted on {:?}, all good", + mount_info.source, mount_info.dest + ); + Ok(true) +} + +pub fn mount(device_path: &String, mount_target: &String) -> Result<(), Box> { + exec("mount", &[device_path, mount_target])?; + Ok(()) +} + +pub fn umount(device_path: &String) -> Result<(), Box> { + exec("umount", &[device_path])?; + Ok(()) +} + +fn get_stats(device_path: &String) -> Result, Box> { + let mut read_options = lfs_core::ReadOptions::default(); + read_options.remote_stats(false); + for mount in lfs_core::read_mounts(&read_options)? { + if mount.info.fs == *device_path { + let stats = mount.stats?; + return Ok(Some(stats)); + } + } + Ok(None) +} + +pub fn used_bytes(device_path: &String) -> Result> { + debug!("used_bytes"); + let Some(stats)= get_stats(device_path)? else { + return Err(Box::new(format_err!( + "used_bytes cannot get fs stats from {}", + device_path + ))); + }; + let used_bytes = stats.used() as usize; + debug!( + "used_bytes on {}: {}B ({}GiB)", + device_path, + used_bytes, + bytes_to_gib(used_bytes) + ); + Ok(used_bytes) +} + +pub fn size_bytes(device_path: &String) -> Result> { + debug!("size_bytes"); + let Some(stats)= get_stats(device_path)? else { + return Err(Box::new(format_err!( + "size_bytes cannot get fs stats from {}", + device_path + ))); + }; + let size_bytes = stats.size() as usize; + debug!( + "size_bytes on {}: {}B ({}GiB)", + device_path, + size_bytes, + bytes_to_gib(size_bytes) + ); + Ok(size_bytes) +} + +pub fn available_bytes(device_path: &String) -> Result> { + debug!("available_bytes"); + let Some(stats)= get_stats(device_path)? else { + return Err(Box::new(format_err!( + "available_bytes cannot get fs stats from {}", + device_path + ))); + }; + let available_bytes = stats.available() as usize; + debug!( + "available_bytes on {}: {}B ({}GiB)", + device_path, + available_bytes, + bytes_to_gib(available_bytes) + ); + Ok(available_bytes) +} + +pub fn used_perc(device_path: &String) -> Result> { + debug!("available_perc"); + let Some(stats)= get_stats(device_path)? else { + return Err(Box::new(format_err!( + "available_perc cannot get fs stats from {}", + device_path + ))); + }; + let available_perc = stats.used() as f32 / stats.size() as f32; + debug!("available_perc on {}: {}", device_path, available_perc); + Ok(available_perc) +} + +pub fn extend_fs_max(mount_target: &String) -> Result<(), Box> { + exec("btrfs", &["filesystem", "resize", "max", mount_target])?; + Ok(()) +} + +pub fn resize(mount_path: &str, new_size_bytes: usize) -> Result<(), Box> { + let new_size = format!("{}", new_size_bytes); + exec( + "btrfs", + &["filesystem", "resize", new_size.as_str(), mount_path], + )?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..11b1b11 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,7 @@ +pub mod args; +pub mod bsu; +pub mod config; +pub mod drive; +pub mod fs; +pub mod lvm; +pub mod utils; diff --git a/src/lvm.rs b/src/lvm.rs new file mode 100644 index 0000000..ff006f6 --- /dev/null +++ b/src/lvm.rs @@ -0,0 +1,454 @@ +use crate::utils::bytes_to_gib; +use crate::utils::exec; +use crate::utils::exec_bool; +use easy_error::format_err; +use log::debug; +use serde_derive::Deserialize; +use serde_derive::Serialize; +use std::error::Error; + +const LV_NAME: &str = "bsud"; + +pub fn lv_path(drive_name: &str) -> String { + let drive_name = drive_name.replace('-', "--"); + format!("/dev/mapper/{}-{}", drive_name, LV_NAME) +} + +pub fn get_reports() -> Result, Box> { + let output = exec( + "lvm", + &[ + "fullreport", + "--all", + "--units", + "B", + "--reportformat", + "json", + ], + )?; + let desc: JsonDesc = serde_json::from_str(output.stdout.as_str())?; + Ok(desc.report) +} + +pub fn get_report(name: &String) -> Result, Box> { + let all_lvm = get_reports()?; + for lvm in all_lvm { + let Some(vg) = lvm.vg.first() else { + continue; + }; + if vg.vg_name == *name { + return Ok(Some(lvm)); + } + } + Ok(None) +} + +pub fn get_report_with_no_vg() -> Result, Box> { + let all_lvm = get_reports()?; + for lvm in all_lvm { + if lvm.vg.is_empty() { + return Ok(Some(lvm)); + } + } + Ok(None) +} + +pub fn get_vg(name: &String) -> Result> { + let Some(lvm) = get_report(name)? else { + return Err(Box::new(format_err!("\"{}\" drive: Cannot get LVM description", name))) + }; + let Some(vg) = lvm.vg.into_iter().next() else { + return Err(Box::new(format_err!("\"{}\" drive: Cannot get VG description", name))) + }; + Ok(vg) +} + +pub fn get_lv(name: &String) -> Result> { + let Some(lvm) = get_report(name)? else { + return Err(Box::new(format_err!("\"{}\" drive: Cannot get LVM description", name))) + }; + let Some(lv) = lvm.lv.into_iter().next() else { + return Err(Box::new(format_err!("\"{}\" drive: Cannot get LV description", name))) + }; + Ok(lv) +} + +pub fn init_pv(path: &String) -> Result<(), Box> { + exec("lvm", &["pvcreate", path])?; + Ok(()) +} + +pub fn vg_create(vg_name: &String, initial_pv_path: &String) -> Result<(), Box> { + exec( + "lvm", + &["vgcreate", "--alloc", "normal", vg_name, initial_pv_path], + )?; + Ok(()) +} + +pub fn vg_activate(activate: bool, vg_name: &String) -> Result<(), Box> { + if activate { + exec("vgchange", &["-ay", vg_name])?; + } else { + exec("vgchange", &["-an", vg_name])?; + } + Ok(()) +} + +pub fn extend_vg(vg_name: &String, pv_device_path: &String) -> Result<(), Box> { + exec("lvm", &["vgextend", vg_name, pv_device_path])?; + Ok(()) +} + +pub fn create_lv(vg_name: &String) -> Result<(), Box> { + exec( + "lvm", + &["lvcreate", "--extents", "100%FREE", "-n", LV_NAME, vg_name], + )?; + Ok(()) +} + +pub fn get_vg_size_bytes(vg_name: &String) -> Result> { + let mut vg = get_vg(vg_name)?; + vg.vg_size.pop(); + let vg_size_bytes = vg.vg_size.parse::()?; + Ok(vg_size_bytes) +} + +pub fn get_lv_size_bytes(vg_name: &String) -> Result> { + let mut lv = get_lv(vg_name)?; + lv.lv_size.pop(); + let lv_size_bytes = lv.lv_size.parse::()?; + Ok(lv_size_bytes) +} + +pub fn lv_extend_full(lv_path: &String) -> Result<(), Box> { + exec("lvm", &["lvextend", "--extents", "+100%FREE", lv_path])?; + Ok(()) +} + +pub fn lv_activate(activate: bool, lv_name: &String) -> Result<(), Box> { + if activate { + exec("lvchange", &["-ay", lv_name])?; + } else { + exec("lvchange", &["-an", lv_name])?; + } + Ok(()) +} + +pub fn vg_scan() -> Result<(), Box> { + exec("vgscan", &[])?; + Ok(()) +} + +pub fn pv_move(pv_path: &String) -> Result<(), Box> { + exec_bool("lvm", &["pvmove", pv_path])?; + Ok(()) +} + +pub fn pv_move_no_arg() -> Result<(), Box> { + exec_bool("lvm", &["pvmove"])?; + Ok(()) +} + +pub fn lv_reduce(lv_path: &String, new_fs_size_bytes: usize) -> Result<(), Box> { + debug!( + "lv_reduce {} of size {}B ({}GiB)", + lv_path, + new_fs_size_bytes, + bytes_to_gib(new_fs_size_bytes) + ); + exec( + "lvm", + &[ + "lvreduce", + "--yes", + "--size", + format!("{}B", new_fs_size_bytes).as_str(), + lv_path, + ], + )?; + Ok(()) +} + +pub fn vg_reduce(name: &str, device_path: &str) -> Result<(), Box> { + exec("lvm", &["vgreduce", name, device_path])?; + Ok(()) +} + +pub fn pv_remove(device_path: &str) -> Result<(), Box> { + exec("lvm", &["pvremove", device_path])?; + Ok(()) +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct JsonDesc { + pub report: Vec, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct Lvm { + pub vg: Vec, + pub pv: Vec, + pub lv: Vec, + pub pvseg: Vec, + pub seg: Vec, +} + +impl Lvm { + pub fn devices(&self) -> Vec { + let mut all_devices = Vec::new(); + for pv in self.pv.iter() { + all_devices.push(pv.pv_name.clone()); + } + all_devices + } +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct Vg { + pub vg_fmt: String, + pub vg_uuid: String, + pub vg_name: String, + pub vg_attr: String, + pub vg_permissions: String, + pub vg_extendable: String, + pub vg_exported: String, + pub vg_autoactivation: String, + pub vg_partial: String, + pub vg_allocation_policy: String, + pub vg_clustered: String, + pub vg_shared: String, + pub vg_size: String, + pub vg_free: String, + pub vg_sysid: String, + pub vg_systemid: String, + pub vg_lock_type: String, + pub vg_lock_args: String, + pub vg_extent_size: String, + pub vg_extent_count: String, + pub vg_free_count: String, + pub max_lv: String, + pub max_pv: String, + pub pv_count: String, + pub vg_missing_pv_count: String, + pub lv_count: String, + pub snap_count: String, + pub vg_seqno: String, + pub vg_tags: String, + pub vg_profile: String, + pub vg_mda_count: String, + pub vg_mda_used_count: String, + pub vg_mda_free: String, + pub vg_mda_size: String, + pub vg_mda_copies: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct Pv { + pub pv_fmt: String, + pub pv_uuid: String, + pub dev_size: String, + pub pv_name: String, + pub pv_major: String, + pub pv_minor: String, + pub pv_mda_free: String, + pub pv_mda_size: String, + pub pv_ext_vsn: String, + pub pe_start: String, + pub pv_size: String, + pub pv_free: String, + pub pv_used: String, + pub pv_attr: String, + pub pv_allocatable: String, + pub pv_exported: String, + pub pv_missing: String, + pub pv_pe_count: String, + pub pv_pe_alloc_count: String, + pub pv_tags: String, + pub pv_mda_count: String, + pub pv_mda_used_count: String, + pub pv_ba_start: String, + pub pv_ba_size: String, + pub pv_in_use: String, + pub pv_duplicate: String, + pub pv_device_id: String, + pub pv_device_id_type: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct Lv { + pub lv_uuid: String, + pub lv_name: String, + pub lv_full_name: String, + pub lv_path: String, + pub lv_dm_path: String, + pub lv_parent: String, + pub lv_layout: String, + pub lv_role: String, + pub lv_initial_image_sync: String, + pub lv_image_synced: String, + pub lv_merging: String, + pub lv_converting: String, + pub lv_allocation_policy: String, + pub lv_allocation_locked: String, + pub lv_fixed_minor: String, + pub lv_skip_activation: String, + pub lv_autoactivation: String, + pub lv_when_full: String, + pub lv_active: String, + pub lv_active_locally: String, + pub lv_active_remotely: String, + pub lv_active_exclusively: String, + pub lv_major: String, + pub lv_minor: String, + pub lv_read_ahead: String, + pub lv_size: String, + pub lv_metadata_size: String, + pub seg_count: String, + pub origin: String, + pub origin_uuid: String, + pub origin_size: String, + pub lv_ancestors: String, + pub lv_full_ancestors: String, + pub lv_descendants: String, + pub lv_full_descendants: String, + pub raid_mismatch_count: String, + pub raid_sync_action: String, + pub raid_write_behind: String, + pub raid_min_recovery_rate: String, + pub raid_max_recovery_rate: String, + pub raidintegritymode: String, + pub raidintegrityblocksize: String, + pub integritymismatches: String, + pub move_pv: String, + pub move_pv_uuid: String, + pub convert_lv: String, + pub convert_lv_uuid: String, + pub mirror_log: String, + pub mirror_log_uuid: String, + pub data_lv: String, + pub data_lv_uuid: String, + pub metadata_lv: String, + pub metadata_lv_uuid: String, + pub pool_lv: String, + pub pool_lv_uuid: String, + pub lv_tags: String, + pub lv_profile: String, + pub lv_lockargs: String, + pub lv_time: String, + pub lv_time_removed: String, + pub lv_host: String, + pub lv_modules: String, + pub lv_historical: String, + pub writecache_block_size: String, + pub lv_kernel_major: String, + pub lv_kernel_minor: String, + pub lv_kernel_read_ahead: String, + pub lv_permissions: String, + pub lv_suspended: String, + pub lv_live_table: String, + pub lv_inactive_table: String, + pub lv_device_open: String, + pub data_percent: String, + pub snap_percent: String, + pub metadata_percent: String, + pub copy_percent: String, + pub sync_percent: String, + pub cache_total_blocks: String, + pub cache_used_blocks: String, + pub cache_dirty_blocks: String, + pub cache_read_hits: String, + pub cache_read_misses: String, + pub cache_write_hits: String, + pub cache_write_misses: String, + pub kernel_cache_settings: String, + pub kernel_cache_policy: String, + pub kernel_metadata_format: String, + pub lv_health_status: String, + pub kernel_discards: String, + pub lv_check_needed: String, + pub lv_merge_failed: String, + pub lv_snapshot_invalid: String, + pub vdo_operating_mode: String, + pub vdo_compression_state: String, + pub vdo_index_state: String, + pub vdo_used_size: String, + pub vdo_saving_percent: String, + pub writecache_total_blocks: String, + pub writecache_free_blocks: String, + pub writecache_writeback_blocks: String, + pub writecache_error: String, + pub lv_attr: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct Pvseg { + pub pvseg_start: String, + pub pvseg_size: String, + pub pv_uuid: String, + pub lv_uuid: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct Seg { + pub segtype: String, + pub stripes: String, + pub data_stripes: String, + pub reshape_len: String, + pub reshape_len_le: String, + pub data_copies: String, + pub data_offset: String, + pub new_data_offset: String, + pub parity_chunks: String, + pub stripe_size: String, + pub region_size: String, + pub chunk_size: String, + pub thin_count: String, + pub discards: String, + pub cache_metadata_format: String, + pub cache_mode: String, + pub zero: String, + pub transaction_id: String, + pub thin_id: String, + pub seg_start: String, + pub seg_start_pe: String, + pub seg_size: String, + pub seg_size_pe: String, + pub seg_tags: String, + pub seg_pe_ranges: String, + pub seg_le_ranges: String, + pub seg_metadata_le_ranges: String, + pub devices: String, + pub metadata_devices: String, + pub seg_monitor: String, + pub cache_policy: String, + pub cache_settings: String, + pub vdo_compression: String, + pub vdo_deduplication: String, + pub vdo_use_metadata_hints: String, + pub vdo_minimum_io_size: String, + pub vdo_block_map_cache_size: String, + pub vdo_block_map_era_length: String, + pub vdo_use_sparse_index: String, + pub vdo_index_memory_size: String, + pub vdo_slab_size: String, + pub vdo_ack_threads: String, + pub vdo_bio_threads: String, + pub vdo_bio_rotation: String, + pub vdo_cpu_threads: String, + pub vdo_hash_zone_threads: String, + pub vdo_logical_threads: String, + pub vdo_physical_threads: String, + pub vdo_max_discard: String, + pub vdo_write_policy: String, + pub vdo_header_size: String, + pub lv_uuid: String, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d4fd158 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,75 @@ +mod args; +mod bsu; +mod config; +mod drive; +mod fs; +mod lvm; +mod utils; + +use drive::Drives; +use log::{debug, error, info, warn}; +use signal_hook::consts::{SIGINT, SIGTERM}; +use signal_hook::iterator::Signals; +use std::process; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn main() { + env_logger::init(); + info!("starting bsud v{}", VERSION); + + let mut signals = Signals::new([SIGINT, SIGTERM]).expect("cannot init signals"); + + let args = args::parse(); + debug!("args: {:?}", args); + + let config = config::load(args.config_path).unwrap_or_else(|err| { + error!("cannot init configuration: {}", err); + exit(1) + }); + debug!("config: {:?}", config); + + if !pre_flight_check() { + exit(1); + } + + let mut drives = Drives::run(config).unwrap_or_else(|err| { + error!("cannot run drives: {}", err); + exit(1); + }); + + loop { + for sig in signals.forever() { + warn!("received signal {:?}", sig); + match sig { + SIGINT | SIGTERM => { + if let Err(err) = drives.stop() { + error!("error while stopping: {}", err); + } + exit(0); + } + _unmanaged_sig => { + error!("unmanaged signal {}", _unmanaged_sig); + } + } + } + } +} + +fn pre_flight_check() -> bool { + let mut ret = true; + if utils::exec("lvm", &["fullreport"]).is_err() { + error!("cannot get lvm fullreport, check installation and permissions"); + ret = false; + } + if utils::exec("btrfs", &["filesystem", "show"]).is_err() { + error!("cannot get run btrfs, check installation and permissions"); + ret = false; + } + ret +} + +fn exit(code: i32) -> ! { + info!("exiting bsud v{} with code {}", VERSION, code); + process::exit(code) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..e42c753 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,73 @@ +use easy_error::format_err; +use log::trace; +use std::error::Error; +use std::process::Command; +use std::process::Stdio; + +const NB_OF_BYTES_IN_GIB: usize = 1024_usize.pow(3); + +pub fn bytes_to_gib(bytes: usize) -> f32 { + bytes as f32 / NB_OF_BYTES_IN_GIB as f32 +} + +pub fn bytes_to_gib_rounded(bytes: usize) -> usize { + bytes_to_gib(bytes).ceil() as usize +} + +pub fn gib_to_bytes(gib: usize) -> usize { + gib * NB_OF_BYTES_IN_GIB +} + +pub struct ExecOutput { + pub success: bool, + pub stdout: String, + pub stderr: String, +} + +fn cmd_str(cmd: &str, args: &[&str]) -> String { + let mut concatenated_arg = String::from(cmd); + for arg in args { + concatenated_arg += " "; + concatenated_arg += arg; + } + concatenated_arg +} + +fn exec_raw(cmd: &str, args: &[&str]) -> Result> { + let cmd_str = cmd_str(cmd, args); + trace!("exec {}", cmd_str); + let output = Command::new(cmd) + .args(args) + .stdout(Stdio::piped()) + .stdout(Stdio::piped()) + .output()?; + let stdout = String::from_utf8(output.stdout)?; + let stderr = String::from_utf8(output.stderr)?; + let success = output.status.success(); + if !success { + if !stdout.is_empty() { + trace!("{} stdout: {}", cmd_str, stdout); + } + if !stderr.is_empty() { + trace!("{} stderr: {}", cmd_str, stderr); + } + } + Ok(ExecOutput { + success, + stdout, + stderr, + }) +} + +pub fn exec(cmd: &str, args: &[&str]) -> Result> { + let output = exec_raw(cmd, args)?; + if !output.success { + return Err(Box::new(format_err!("{} {:?} exited non zero", cmd, args))); + } + Ok(output) +} + +pub fn exec_bool(cmd: &str, args: &[&str]) -> Result> { + let output = exec_raw(cmd, args)?; + Ok(output.success) +} diff --git a/tests/bsud-tests.rs b/tests/bsud-tests.rs new file mode 100644 index 0000000..7b5a1c7 --- /dev/null +++ b/tests/bsud-tests.rs @@ -0,0 +1,262 @@ +use bsudlib::config::{ + discover_vm_config, region, ConfigFileDrive, DiskType, DriveTarget, CLOUD_CONFIG, +}; +use bsudlib::drive::{Drive, DriveCmd}; +use bsudlib::{fs, lvm}; +use bsudlib::utils::bytes_to_gib; +use cucumber::{given, then, when, writer, World, WriterExt}; +use log::debug; +use std::error::Error; +use outscale_api::apis::configuration::AWSv4Key; +use secrecy::SecretString; +use std::cmp::Ordering; +use std::env; +use std::str::FromStr; +use std::sync::mpsc::{channel, Sender}; +use std::fs::remove_file; +use std::time::Duration; +use tokio::time::sleep; +use rand::{distributions::Alphanumeric, Rng}; +use std::fs::read_dir; +use std::path::PathBuf; +use std::io; +use tokio::task::block_in_place; +use async_process::Command; + +fn setup_creds() { + let mut global_cloud_config = CLOUD_CONFIG.write().expect("cloud config setting"); + if global_cloud_config.aws_v4_key.is_some() { + debug!("credentials already set"); + return; + } + debug!("get credentials through env"); + let access_key = env::var("OSC_ACCESS_KEY").expect("OSC_ACCESS_KEY must be set"); + let secret_key = + SecretString::new(env::var("OSC_SECRET_KEY").expect("OSC_SECRET_KEY must be set")); + // This avoid async to crash with blocking request + block_in_place(move || { + discover_vm_config().expect("discover vm config"); + }); + global_cloud_config.aws_v4_key = Some(AWSv4Key { + access_key, + secret_key, + region: region().expect("read region"), + service: "oapi".to_string(), + }); +} + +#[derive(Debug, World)] +#[world(init = Self::new)] +pub struct DriveEnv { + drive: Drive, + _cmd: Sender, +} + +impl DriveEnv { + fn new() -> Self { + setup_creds(); + let (sender, receiver) = channel::(); + Self { + drive: Drive::new(DriveEnv::drive_config(), receiver), + _cmd: sender, + } + } + + fn drive_config() -> ConfigFileDrive { + let random_name = random_name(); + ConfigFileDrive { + name: format!("test-{}", random_name), + target: DriveTarget::Online, + mount_path: format!("/media/bsud-{}/", random_name), + disk_type: Some(DiskType::Gp2), + disk_iops_per_gib: None, + max_bsu_count: Some(10), + max_total_size_gib: None, + initial_size_gib: Some(10), + max_used_space_perc: Some(85), + min_used_space_perc: Some(20), + disk_scale_factor_perc: Some(20), + } + } +} + +#[given(expr = "drive target is {word}")] +async fn drive_config_target(drive_env: &mut DriveEnv, target: String) { + drive_env.drive.target = DriveTarget::from_str(&target).expect("drive target"); +} + +#[given(expr = "drive disk type is {word}")] +async fn drive_config_disk_type(drive_env: &mut DriveEnv, disk_type: String) { + drive_env.drive.disk_type = DiskType::from_str(&disk_type).expect("disk type"); +} + +#[given(expr = "drive max bsu count is {int}")] +async fn drive_config_max_bsu_count(drive_env: &mut DriveEnv, count: usize) { + drive_env.drive.max_bsu_count = count; +} + +#[given(expr = "drive max total size is unlimited")] +async fn drive_config_max_total_size_unlimited(drive_env: &mut DriveEnv) { + drive_env.drive.max_total_size_gib = None; +} + +#[given(expr = "drive max total size is {int}Gib")] +async fn drive_config_max_total_size_gib(drive_env: &mut DriveEnv, max_gib: usize) { + drive_env.drive.max_total_size_gib = Some(max_gib); +} + +#[given(expr = "drive initial size is {int}Gib")] +async fn drive_config_initial_size_gib(drive_env: &mut DriveEnv, size_gib: usize) { + drive_env.drive.initial_size_gib = size_gib; +} + +#[given(expr = "drive max used space is {int}%")] +async fn drive_config_max_used_space_perc(drive_env: &mut DriveEnv, max_per: usize) { + drive_env.drive.max_used_space_perc = max_per as f32 / 100.0; +} + +#[given(expr = "drive min used space is {int}%")] +async fn drive_config_min_used_space_perc(drive_env: &mut DriveEnv, min_per: usize) { + drive_env.drive.min_used_space_perc = min_per as f32 / 100.0; +} + +#[given(expr = "drive scale factor is {int}%")] +async fn drive_config_disk_scale_factor_perc(drive_env: &mut DriveEnv, scale_per: usize) { + drive_env.drive.disk_scale_factor_perc = scale_per as f32 / 100.0; +} + +#[given(expr = "reconcile runs")] +#[when(expr = "reconcile runs")] +fn feed_cat(drive_env: &mut DriveEnv) { + drive_env.drive.reconcile().expect("reconcile should not fail"); +} + +#[given(expr = "drive has no BSU")] +#[then(expr = "cleanup")] +async fn drive_has_no_bsu(drive_env: &mut DriveEnv) { + drive_env + .drive + .reconcile_delete() + .expect("reconcile deletion should be ok"); + drive_env + .drive + .fetch_all_drive_bsu() + .expect("should be able to fetch drives"); + assert_eq!(drive_env.drive.bsu_count(), 0); +} + +#[given(expr = "drive target is set to {word}")] +async fn drive_target_is_set_to(drive_env: &mut DriveEnv, target: String) { + let target = DriveTarget::from_str(target.as_str()).expect("bad target for drive"); + drive_env.drive.target = target; +} + +#[given(expr = "drive usage is {int}Gib")] +async fn drive_set_usage(drive_env: &mut DriveEnv, target_gib: usize) -> Result<(), Box> { + let lv_path = lvm::lv_path(&drive_env.drive.name); + loop { + wait_for_stabilized_usage(&drive_env.drive).await; + let current_drive_usage_bytes = fs::used_bytes(&lv_path).expect("get drive usage"); + let current_drive_usage_gib = bytes_to_gib(current_drive_usage_bytes).round() as usize; + match current_drive_usage_gib.cmp(&target_gib) { + Ordering::Equal => { + debug!("current_drive_usage_gib ({}) = target_gib ({})", current_drive_usage_gib, target_gib); + return Ok(()) + }, + Ordering::Less => { + debug!("current_drive_usage_gib ({}) < target_gib ({})", current_drive_usage_gib, target_gib); + debug!("creating 1gib file"); + create_1_gib_file(&drive_env.drive.mount_path).await.expect("create file"); + }, + Ordering::Greater => { + debug!("current_drive_usage_gib ({}) > target_gib ({})", current_drive_usage_gib, target_gib); + debug!("removing 1gib file",); + delete_1_gib_file(&drive_env.drive.mount_path).expect("remove file"); + }, + }; + } +} + +fn random_name() -> String { + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(8) + .map(char::from) + .collect() +} + +async fn create_1_gib_file(folder: &str) -> Result<(), Box> { + let output_file = format!("of={}/{}.zero", folder, random_name()); + debug!("writing 1gib file to {}", output_file); + let count = format!("count={}", 1024_usize.pow(2)); + let out = Command::new("dd") + .args(["if=/dev/zero", output_file.as_str(), "bs=1024", count.as_str(), "conv=fsync"]) + .output().await?; + assert!(out.status.success()); + Ok(()) +} + +fn delete_1_gib_file(folder: &str) -> Result<(), Box> { + let read = read_dir(PathBuf::from(folder))?; + for entry in read { + let entry = entry?; + if entry.file_type()?.is_file() { + debug!("removing file {}", &entry.path().as_os_str().to_str().unwrap()); + remove_file(&entry.path())?; + } + } + Ok(()) +} + +#[given(expr = "drive has {int} BSU")] +#[then(expr = "drive has {int} BSU")] +async fn drive_has_x_bsu(drive_env: &mut DriveEnv, bsu_count: usize) { + drive_env + .drive + .fetch_all_drive_bsu() + .expect("fetch all BSU from drive"); + assert_eq!(drive_env.drive.bsu_count(), bsu_count); +} + +#[given(expr = "drive is mounted")] +#[then(expr = "drive is mounted")] +async fn drive_is_mounted(drive_env: &mut DriveEnv) { + let lv_path = lvm::lv_path(&drive_env.drive.name); + assert!(fs::is_mounted(&lv_path, &drive_env.drive.mount_path).expect("fs::is_mounted")) +} + +#[given(expr = "drive size is {int}Gib")] +#[then(expr = "drive size is {int}Gib")] +async fn drive_has_x_gib(drive_env: &mut DriveEnv, supposed_capa_gib: usize) { + let lv_path = lvm::lv_path(&drive_env.drive.name); + let fs_size_bytes = fs::size_bytes(&lv_path).expect("get fs size"); + let fs_size_gib = bytes_to_gib(fs_size_bytes).round() as usize; + assert_eq!(fs_size_gib, supposed_capa_gib); +} + +async fn wait_for_stabilized_usage(drive: &Drive) { + let lv_path = lvm::lv_path(&drive.name); + let mut usage = fs::used_bytes(&lv_path).expect("get fs usage"); + loop { + debug!("wait for file usage to stabilize"); + sleep(Duration::from_millis(10_000)).await; + let new_usage = fs::used_bytes(&lv_path).expect("get fs usage"); + if usage == new_usage { + return; + } + usage = new_usage; + } +} + +#[tokio::main] +async fn main() { + env_logger::init(); + DriveEnv::cucumber() + .with_writer( + writer::Basic::raw(io::stdout(), writer::Coloring::Never, 0) + .summarized() + .assert_normalized(), + ) + .run("tests/features/") + .await; +} diff --git a/tests/delete-drive.sh b/tests/delete-drive.sh new file mode 100755 index 0000000..110a867 --- /dev/null +++ b/tests/delete-drive.sh @@ -0,0 +1,139 @@ +#!/bin/bash +arg="$1" +set -eu + +function test_command { + local cmd=${*:1} + set +e + if ! $cmd &> /dev/null; then + echo "error: command $cmd not found" + exit 1 + fi + set -e +} + +function test_commands { + test_command curl --version + test_command mktemp --help +} + +function print_help { + >&2 echo "usage:" + >&2 echo " delete-drive.sh DRIVE_NAME" + >&2 echo " delete-drive.sh --all" +} + +function api { + local call_name=$1 + local query=$2 + local stdout=$(mktemp) + local stderr=$(mktemp) + if ! curl "https://${OSC_ENDPOINT_API}/${call_name}" -H 'Content-Type: application/json' -d "$query" --user $OSC_ACCESS_KEY:$OSC_SECRET_KEY --aws-sigv4 "osc" > "$stdout" 2> "$stderr"; then + >&2 echo "error while performing API call $call_name" + >&2 echo "query: $query" + >&2 echo "stdout: $(cat "$stdout")" + >&2 echo "stderr: $(cat "$stderr")" + fi + cat "$stdout" + rm "$stdout" + rm "$stderr" +} + +function list_bsu_of_drive { + local drive_name=$1 + api ReadVolumes "{\"Filters\": {\"TagKeys\":[\"osc.bsud.drive-name\"], \"TagValues\":[\"${drive_name}\"]}}" | jq .Volumes[].VolumeId | xargs +} + +function list_all_drives { + api ReadVolumes "{\"Filters\": {\"TagKeys\":[\"osc.bsud.drive-name\"]}}" | jq .Volumes[].Tags[].Value | xargs +} + +function detach_and_delete_bsu { + local bsu_id=$1 + detach_bsu "$bsu_id" || true + sleep 10 + delete_bsu "$bsu_id" || true +} + +function detach_bsu { + local bsu_id=$1 + echo "detaching BSU $bsu_id..." + api UnlinkVolume "{\"VolumeId\": \"$bsu_id\"}" > /dev/null || true +} + +function delete_bsu { + local bsu_id=$1 + echo "deleting BSU $bsu_id..." + api DeleteVolume "{\"VolumeId\": \"$bsu_id\"}" > /dev/null || true +} + +function delete_drive { + local drive_name=$1 + echo "# deleting BSUd drive $drive_name..." + + local lv_path="/dev/${drive_name}/bsud" + if [ -e $lv_path ]; then + echo "umounting $lv_path... " + sudo umount "$lv_path" || true + echo "disable lv $lv_path... " + sudo lvchange --activate n "$lv_path" || true + # In case of forced detach + echo "dmsetup remove $lv_path... " + sudo dmsetup remove /dev/${drive_name}/bsud || true + else + echo "$lv_path seems not to exist" + fi + + echo "listing BSU of $drive_name drive..." + local bsus=$(list_bsu_of_drive "$drive_name") + if [ -z "$bsus" ]; then + echo "no BSU to delete" + exit 0 + fi + for bsu_id in $bsus; do + detach_and_delete_bsu "$bsu_id" & + done + wait +} + +ROOT=$(cd "$(dirname "$0")/.." && pwd) + +success=true +if [ -z "${OSC_ACCESS_KEY:-}" ]; then + >&2 echo 'error: OSC_ACCESS_KEY not set' + success=false +fi +if [ -z "${OSC_SECRET_KEY:-}" ]; then + >&2 echo 'error: OSC_SECRET_KEY not set' + success=false +fi +if [ -z "${OSC_ENDPOINT_API=:-}" ]; then + >&2 echo 'error: OSC_ENDPOINT_API not set' + >&2 echo 'please set under the form api.$OSC_REGION.outscale.com/api/v1' + success=false +fi + +if ! $success; then + exit 1 +fi + +test_commands +if [ -z "${arg=:-}" ]; then + print_help + echo -n "existing BSU drives: " + list_all_drives + exit 1 +fi + +if [ "$arg" = "--all" ]; then + all_drives="$(list_all_drives)" + if [ -z "${all_drives}" ]; then + >&2 echo 'no drive found to be delete' + exit 0 + fi + for drive_name in ${all_drives}; do + delete_drive $drive_name + done +else + delete_drive $drive_name +fi diff --git a/tests/features/basic-lifecycle.feature b/tests/features/basic-lifecycle.feature new file mode 100644 index 0000000..9eb0da7 --- /dev/null +++ b/tests/features/basic-lifecycle.feature @@ -0,0 +1,19 @@ +Feature: Basic lifecycle + + Background: + Given drive target is online + And drive disk type is Gp2 + And drive max bsu count is 10 + And drive max total size is unlimited + And drive initial size is 10Gib + And drive max used space is 85% + And drive min used space is 20% + And drive scale factor is 20% + + Scenario: Initial BSU should be created and mounted + Given drive has no BSU + When reconcile runs + Then drive has 1 BSU + And drive is mounted + And drive size is 10Gib + And cleanup \ No newline at end of file diff --git a/tests/features/scale-up.feature b/tests/features/scale-up.feature new file mode 100644 index 0000000..3de90cd --- /dev/null +++ b/tests/features/scale-up.feature @@ -0,0 +1,33 @@ +Feature: Scale-up + + Background: + Given drive target is online + And drive disk type is Gp2 + And drive max bsu count is 10 + And drive max total size is unlimited + And drive initial size is 10Gib + And drive max used space is 85% + And drive min used space is 20% + And drive scale factor is 20% + And reconcile runs + And drive is mounted + And drive has 1 BSU + And drive size is 10Gib + + Scenario: Writing 50% of disk capacity don't trigger scale-up + Given drive usage is 5Gib + When reconcile runs + Then drive has 1 BSU + And drive size is 10Gib + And cleanup + + Scenario: Writing 90% of disk capacity trigger scale-up + Given drive usage is 9Gib + When reconcile runs + Then drive has 2 BSU + And drive size is 22Gib + And cleanup + +# different feature ? +# TODO: test limit -> total size +# TODO: test limit -> max disks \ No newline at end of file