diff options
260 files changed, 30534 insertions, 5797 deletions
@@ -2,68 +2,26 @@ kind: pipeline name: default -workspace: - base: /drone/garage - -volumes: -- name: nix_store - host: - path: /var/lib/drone/nix -- name: nix_config - temp: {} - -environment: - HOME: /drone/garage +node: + nix-daemon: 1 steps: - - name: setup nix - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + - name: check formatting + image: nixpkgs/nix:nixos-22.05 commands: - - cp nix/nix.conf /etc/nix/nix.conf - - nix-build --no-build-output --no-out-link shell.nix --arg release false -A inputDerivation - - - name: code quality - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix - commands: - - nix-shell --arg release false --run "cargo fmt -- --check" - - nix-shell --arg release false --run "cargo clippy -- --deny warnings" + - nix-shell --attr rust --run "cargo fmt -- --check" - name: build - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 commands: - - nix-build --no-build-output --option log-lines 100 --argstr target x86_64-unknown-linux-musl --arg release false --argstr git_version $DRONE_COMMIT + - nix-build --no-build-output --attr clippy.amd64 --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT} - name: unit + func tests - image: nixpkgs/nix:nixos-21.05 + image: nixpkgs/nix:nixos-22.05 environment: GARAGE_TEST_INTEGRATION_EXE: result/bin/garage - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix commands: - - | - nix-build \ - --no-build-output \ - --option log-lines 100 \ - --argstr target x86_64-unknown-linux-musl \ - --argstr compileMode test + - nix-build --no-build-output --attr test.amd64 - ./result/bin/garage_api-* - ./result/bin/garage_model-* - ./result/bin/garage_rpc-* @@ -73,16 +31,11 @@ steps: - ./result/bin/garage-* - ./result/bin/integration-* - - name: smoke-test - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + - name: integration tests + image: nixpkgs/nix:nixos-22.05 commands: - - nix-build --no-build-output --argstr target x86_64-unknown-linux-musl --arg release false --argstr git_version $DRONE_COMMIT - - nix-shell --arg release false --run ./script/test-smoke.sh || (cat /tmp/garage.log; false) + - nix-build --no-build-output --attr clippy.amd64 --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT} + - nix-shell --attr integration --run ./script/test-smoke.sh || (cat /tmp/garage.log; false) trigger: event: @@ -92,78 +45,39 @@ trigger: - tag - cron -node: - nix: 1 - --- kind: pipeline type: docker -name: release-linux-x86_64 - -volumes: -- name: nix_store - host: - path: /var/lib/drone/nix -- name: nix_config - temp: {} +name: release-linux-amd64 -environment: - TARGET: x86_64-unknown-linux-musl +node: + nix-daemon: 1 steps: - - name: setup nix - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix - commands: - - cp nix/nix.conf /etc/nix/nix.conf - - nix-build --no-build-output --no-out-link shell.nix -A inputDerivation - - name: build - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 commands: - - nix-build --no-build-output --argstr target $TARGET --arg release true --argstr git_version $DRONE_COMMIT + - nix-build --no-build-output --attr pkgs.amd64.release --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT} + - nix-shell --attr rust --run "./script/not-dynamic.sh result/bin/garage" - name: integration - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 commands: - - nix-shell --run ./script/test-smoke.sh || (cat /tmp/garage.log; false) + - nix-shell --attr integration --run ./script/test-smoke.sh || (cat /tmp/garage.log; false) - name: push static binary - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 environment: AWS_ACCESS_KEY_ID: from_secret: garagehq_aws_access_key_id AWS_SECRET_ACCESS_KEY: from_secret: garagehq_aws_secret_access_key + TARGET: "x86_64-unknown-linux-musl" commands: - - nix-shell --arg rust false --arg integration false --run "to_s3" + - nix-shell --attr release --run "to_s3" - name: docker build and publish - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 environment: DOCKER_AUTH: from_secret: docker_auth @@ -174,7 +88,7 @@ steps: - mkdir -p /kaniko/.docker - echo $DOCKER_AUTH > /kaniko/.docker/config.json - export CONTAINER_TAG=${DRONE_TAG:-$DRONE_COMMIT} - - nix-shell --arg rust false --arg integration false --run "to_docker" + - nix-shell --attr release --run "to_docker" trigger: @@ -182,78 +96,39 @@ trigger: - promote - cron -node: - nix: 1 - --- kind: pipeline type: docker -name: release-linux-i686 - -volumes: -- name: nix_store - host: - path: /var/lib/drone/nix -- name: nix_config - temp: {} +name: release-linux-i386 -environment: - TARGET: i686-unknown-linux-musl +node: + nix-daemon: 1 steps: - - name: setup nix - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix - commands: - - cp nix/nix.conf /etc/nix/nix.conf - - nix-build --no-build-output --no-out-link shell.nix -A inputDerivation - - name: build - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 commands: - - nix-build --no-build-output --argstr target $TARGET --arg release true --argstr git_version $DRONE_COMMIT + - nix-build --no-build-output --attr pkgs.i386.release --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT} + - nix-shell --attr rust --run "./script/not-dynamic.sh result/bin/garage" - name: integration - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 commands: - - nix-shell --run ./script/test-smoke.sh || (cat /tmp/garage.log; false) + - nix-shell --attr integration --run ./script/test-smoke.sh || (cat /tmp/garage.log; false) - name: push static binary - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 environment: AWS_ACCESS_KEY_ID: from_secret: garagehq_aws_access_key_id AWS_SECRET_ACCESS_KEY: from_secret: garagehq_aws_secret_access_key + TARGET: "i686-unknown-linux-musl" commands: - - nix-shell --arg rust false --arg integration false --run "to_s3" + - nix-shell --attr release --run "to_s3" - name: docker build and publish - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 environment: DOCKER_AUTH: from_secret: docker_auth @@ -264,75 +139,41 @@ steps: - mkdir -p /kaniko/.docker - echo $DOCKER_AUTH > /kaniko/.docker/config.json - export CONTAINER_TAG=${DRONE_TAG:-$DRONE_COMMIT} - - nix-shell --arg rust false --arg integration false --run "to_docker" + - nix-shell --attr release --run "to_docker" trigger: event: - promote - cron -node: - nix: 1 - --- kind: pipeline type: docker -name: release-linux-aarch64 - -volumes: -- name: nix_store - host: - path: /var/lib/drone/nix -- name: nix_config - temp: {} +name: release-linux-arm64 -environment: - TARGET: aarch64-unknown-linux-musl +node: + nix-daemon: 1 steps: - - name: setup nix - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix - commands: - - cp nix/nix.conf /etc/nix/nix.conf - - nix-build --no-build-output --no-out-link ./shell.nix --arg rust false --arg integration false -A inputDerivation - - name: build - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 commands: - - nix-build --no-build-output --argstr target $TARGET --arg release true --argstr git_version $DRONE_COMMIT + - nix-build --no-build-output --attr pkgs.arm64.release --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT} + - nix-shell --attr rust --run "./script/not-dynamic.sh result/bin/garage" - name: push static binary - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 environment: AWS_ACCESS_KEY_ID: from_secret: garagehq_aws_access_key_id AWS_SECRET_ACCESS_KEY: from_secret: garagehq_aws_secret_access_key + TARGET: "aarch64-unknown-linux-musl" commands: - - nix-shell --arg rust false --arg integration false --run "to_s3" + - nix-shell --attr release --run "to_s3" - name: docker build and publish - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 environment: DOCKER_AUTH: from_secret: docker_auth @@ -343,75 +184,41 @@ steps: - mkdir -p /kaniko/.docker - echo $DOCKER_AUTH > /kaniko/.docker/config.json - export CONTAINER_TAG=${DRONE_TAG:-$DRONE_COMMIT} - - nix-shell --arg rust false --arg integration false --run "to_docker" + - nix-shell --attr release --run "to_docker" trigger: event: - promote - cron -node: - nix: 1 - --- kind: pipeline type: docker -name: release-linux-armv6l +name: release-linux-arm -volumes: -- name: nix_store - host: - path: /var/lib/drone/nix -- name: nix_config - temp: {} - -environment: - TARGET: armv6l-unknown-linux-musleabihf +node: + nix-daemon: 1 steps: - - name: setup nix - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix - commands: - - cp nix/nix.conf /etc/nix/nix.conf - - nix-build --no-build-output --no-out-link --arg rust false --arg integration false -A inputDerivation - - name: build - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 commands: - - nix-build --no-build-output --argstr target $TARGET --arg release true --argstr git_version $DRONE_COMMIT + - nix-build --no-build-output --attr pkgs.arm.release --argstr git_version ${DRONE_TAG:-$DRONE_COMMIT} + - nix-shell --attr rust --run "./script/not-dynamic.sh result/bin/garage" - name: push static binary - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 environment: AWS_ACCESS_KEY_ID: from_secret: garagehq_aws_access_key_id AWS_SECRET_ACCESS_KEY: from_secret: garagehq_aws_secret_access_key + TARGET: "armv6l-unknown-linux-musleabihf" commands: - - nix-shell --arg integration false --arg rust false --run "to_s3" + - nix-shell --attr release --run "to_s3" - name: docker build and publish - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix - - name: nix_config - path: /etc/nix + image: nixpkgs/nix:nixos-22.05 environment: DOCKER_AUTH: from_secret: docker_auth @@ -422,32 +229,35 @@ steps: - mkdir -p /kaniko/.docker - echo $DOCKER_AUTH > /kaniko/.docker/config.json - export CONTAINER_TAG=${DRONE_TAG:-$DRONE_COMMIT} - - nix-shell --arg rust false --arg integration false --run "to_docker" + - nix-shell --attr release --run "to_docker" trigger: event: - promote - cron -node: - nix: 1 - --- kind: pipeline type: docker name: refresh-release-page -volumes: -- name: nix_store - host: - path: /var/lib/drone/nix +node: + nix-daemon: 1 steps: + - name: multiarch-docker + image: nixpkgs/nix:nixos-22.05 + environment: + DOCKER_AUTH: + from_secret: docker_auth + HOME: "/root" + commands: + - mkdir -p /root/.docker + - echo $DOCKER_AUTH > /root/.docker/config.json + - export CONTAINER_TAG=${DRONE_TAG:-$DRONE_COMMIT} + - nix-shell --attr release --run "multiarch_docker" - name: refresh-index - image: nixpkgs/nix:nixos-21.05 - volumes: - - name: nix_store - path: /nix + image: nixpkgs/nix:nixos-22.05 environment: AWS_ACCESS_KEY_ID: from_secret: garagehq_aws_access_key_id @@ -455,24 +265,21 @@ steps: from_secret: garagehq_aws_secret_access_key commands: - mkdir -p /etc/nix && cp nix/nix.conf /etc/nix/nix.conf - - nix-shell --arg integration false --arg rust false --run "refresh_index" + - nix-shell --attr release --run "refresh_index" depends_on: - - release-linux-x86_64 - - release-linux-i686 - - release-linux-aarch64 - - release-linux-armv6l + - release-linux-amd64 + - release-linux-i386 + - release-linux-arm64 + - release-linux-arm trigger: event: - promote - cron -node: - nix: 1 - --- kind: signature -hmac: 3fc19d6f9a3555519c8405e3281b2e74289bb802f644740d5481d53df3a01fa4 +hmac: 103a04785c98f5376a63ce22865c2576963019bbc4d828f200d2a470a3c821ea ... @@ -3,6 +3,17 @@ version = 3 [[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] name = "aho-corasick" version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -12,6 +23,15 @@ dependencies = [ ] [[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] name = "anyhow" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -30,6 +50,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] +name = "assert-json-diff" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f1c3703dd33532d7f0ca049168930e9099ecac238e23cf932f3a69c42f06da" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-compression" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00461f243d703f6999c8e7494f077799f1362720a55ae49a90ffe6214032fc0b" +dependencies = [ + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + +[[package]] name = "async-stream" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -74,6 +118,15 @@ dependencies = [ [[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" @@ -124,7 +177,7 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", - "bytes 1.1.0", + "bytes", "http", "md5", "tokio-stream", @@ -154,7 +207,7 @@ checksum = "51d371fb688d909e5b866ff1f297bbec4621eed4f9fcdac566fcc33541f0c6a6" dependencies = [ "aws-smithy-eventstream", "aws-smithy-http", - "bytes 1.1.0", + "bytes", "form_urlencoded", "hex", "http", @@ -188,7 +241,7 @@ dependencies = [ "aws-smithy-http", "aws-smithy-http-tower", "aws-smithy-types", - "bytes 1.1.0", + "bytes", "fastrand", "http", "http-body", @@ -209,7 +262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f972226c639e0dc1eca2cb0220c1b5799e2bfc62eda37845b662c5d0cb972371" dependencies = [ "aws-smithy-types", - "bytes 1.1.0", + "bytes", "crc32fast", ] @@ -221,7 +274,7 @@ checksum = "12c787e24b757634453a60ff05948aa1b450f5b3a7a2094f22acff8a5022635b" dependencies = [ "aws-smithy-eventstream", "aws-smithy-types", - "bytes 1.1.0", + "bytes", "bytes-utils", "futures-core", "http", @@ -241,7 +294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64f80a2c56fc09fc9a2da3c63f286ec2a89465433219f8165e14e522283a5eb8" dependencies = [ "aws-smithy-http", - "bytes 1.1.0", + "bytes", "http", "http-body", "pin-project 1.0.10", @@ -292,6 +345,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -304,7 +366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" dependencies = [ "crypto-mac 0.8.0", - "digest", + "digest 0.9.0", "opaque-debug", ] @@ -318,28 +380,37 @@ dependencies = [ ] [[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] name = "bumpalo" version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] -name = "byteorder" -version = "1.4.3" +name = "bytemuck" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" [[package]] -name = "bytes" -version = "0.6.0" +name = "byteorder" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" [[package]] name = "bytes-utils" @@ -347,11 +418,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1934a3ef9cac8efde4966a92781e77713e1ba329f1d42e446c7d7eba340d8ef1" dependencies = [ - "bytes 1.1.0", + "bytes", "either", ] [[package]] +name = "bytesize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" + +[[package]] name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -362,6 +439,12 @@ dependencies = [ [[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" @@ -393,11 +476,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", - "textwrap", + "textwrap 0.11.0", "unicode-width", ] [[package]] +name = "clap" +version = "3.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "lazy_static", + "strsim", + "termcolor", + "textwrap 0.15.0", +] + +[[package]] +name = "clap_derive" +version = "3.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", +] + +[[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -437,7 +559,7 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -446,8 +568,8 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ - "cfg-if", - "crossbeam-utils", + "cfg-if 1.0.0", + "crossbeam-utils 0.8.8", ] [[package]] @@ -456,25 +578,54 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", + "autocfg 1.1.0", + "cfg-if 1.0.0", + "crossbeam-utils 0.8.8", "lazy_static", "memoffset", "scopeguard", ] [[package]] +name = "crossbeam-queue" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +dependencies = [ + "crossbeam-utils 0.6.6", +] + +[[package]] +name = "crossbeam-utils" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +dependencies = [ + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] name = "crossbeam-utils" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] [[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] name = "crypto-mac" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -486,9 +637,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ "generic-array", "subtle", @@ -544,7 +695,7 @@ version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "num_cpus", ] @@ -569,12 +720,23 @@ dependencies = [ ] [[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + +[[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] @@ -613,7 +775,7 @@ version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -631,9 +793,9 @@ dependencies = [ [[package]] name = "err-derive" -version = "0.2.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4" +checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e" dependencies = [ "proc-macro-error", "proc-macro2", @@ -644,18 +806,16 @@ dependencies = [ ] [[package]] -name = "err-derive" -version = "0.3.1" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "rustversion", - "syn", - "synstructure", -] +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" @@ -819,169 +979,159 @@ dependencies = [ [[package]] name = "garage" -version = "0.7.0" +version = "0.8.0" dependencies = [ + "assert-json-diff", "async-trait", "aws-sdk-s3", - "bytes 1.1.0", + "base64", + "bytes", + "bytesize", "chrono", "futures", "futures-util", - "garage_admin", "garage_api", - "garage_model 0.7.0", - "garage_rpc 0.7.0", - "garage_table 0.7.0", - "garage_util 0.7.0", + "garage_block", + "garage_db", + "garage_model", + "garage_rpc", + "garage_table", + "garage_util", "garage_web", - "git-version", "hex", - "hmac", + "hmac 0.12.1", "http", "hyper", "kuska-sodiumoxide", - "netapp 0.4.2", - "pretty_env_logger", + "netapp", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-prometheus", + "prometheus", "rand 0.8.5", - "rmp-serde 0.15.5", + "rmp-serde", "serde", "serde_bytes", - "sha2", - "sled", + "serde_json", + "sha2 0.10.2", "static_init", "structopt", + "timeago", "tokio", "toml", "tracing", -] - -[[package]] -name = "garage_admin" -version = "0.7.0" -dependencies = [ - "futures", - "futures-util", - "garage_util 0.7.0", - "hex", - "http", - "hyper", - "opentelemetry", - "opentelemetry-otlp", - "opentelemetry-prometheus", - "prometheus", - "tracing", + "tracing-subscriber", ] [[package]] name = "garage_api" -version = "0.7.0" +version = "0.8.0" dependencies = [ + "async-trait", "base64", - "bytes 1.1.0", + "bytes", "chrono", - "crypto-mac 0.10.1", - "err-derive 0.3.1", + "crypto-common", + "err-derive", "form_urlencoded", "futures", "futures-util", "garage_block", - "garage_model 0.7.0", - "garage_table 0.7.0", - "garage_util 0.7.0", + "garage_model", + "garage_rpc", + "garage_table", + "garage_util", "hex", - "hmac", + "hmac 0.12.1", "http", "http-range", "httpdate 0.3.2", "hyper", "idna", - "md-5", + "md-5 0.10.1", "multer", "nom", "opentelemetry", + "opentelemetry-prometheus", "percent-encoding", "pin-project 1.0.10", + "prometheus", "quick-xml", "roxmltree", "serde", "serde_bytes", "serde_json", - "sha2", + "sha2 0.10.2", "tokio", + "tokio-stream", "tracing", "url", ] [[package]] name = "garage_block" -version = "0.7.0" +version = "0.8.0" dependencies = [ + "arc-swap", + "async-compression", "async-trait", - "bytes 1.1.0", + "bytes", "futures", "futures-util", - "garage_rpc 0.7.0", - "garage_table 0.7.0", - "garage_util 0.7.0", + "garage_db", + "garage_rpc", + "garage_table", + "garage_util", "hex", "opentelemetry", "rand 0.8.5", - "rmp-serde 0.15.5", + "rmp-serde", "serde", "serde_bytes", - "sled", "tokio", + "tokio-util 0.6.9", "tracing", "zstd", ] [[package]] -name = "garage_model" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584619e8999713d73761775591ad6f01ff8c9d724f3b20984f5932f1fc7f9988" +name = "garage_db" +version = "0.8.0" dependencies = [ - "arc-swap", - "async-trait", - "futures", - "futures-util", - "garage_rpc 0.5.1", - "garage_table 0.5.1", - "garage_util 0.5.1", - "hex", - "log", - "netapp 0.3.1", - "rand 0.8.5", - "rmp-serde 0.15.5", - "serde", - "serde_bytes", + "clap 3.1.18", + "err-derive", + "heed", + "hexdump", + "mktemp", + "pretty_env_logger", + "rusqlite", "sled", - "tokio", - "zstd", + "tracing", ] [[package]] name = "garage_model" -version = "0.7.0" +version = "0.8.0" dependencies = [ "arc-swap", "async-trait", - "err-derive 0.3.1", + "base64", + "blake2", + "err-derive", "futures", "futures-util", "garage_block", - "garage_model 0.5.1", - "garage_rpc 0.7.0", - "garage_table 0.7.0", - "garage_util 0.7.0", + "garage_db", + "garage_rpc", + "garage_table", + "garage_util", "hex", - "netapp 0.4.2", + "netapp", "opentelemetry", "rand 0.8.5", - "rmp-serde 0.15.5", + "rmp-serde", "serde", "serde_bytes", - "sled", "tokio", "tracing", "zstd", @@ -989,42 +1139,14 @@ dependencies = [ [[package]] name = "garage_rpc" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e693aa4582cfe7a7ce70c07880e3662544b5d0cd68bc4b59c53febfbb8d1ec" -dependencies = [ - "arc-swap", - "async-trait", - "bytes 1.1.0", - "futures", - "futures-util", - "garage_util 0.5.1", - "gethostname", - "hex", - "hyper", - "kuska-sodiumoxide", - "log", - "netapp 0.3.1", - "rand 0.8.5", - "rmp-serde 0.15.5", - "serde", - "serde_bytes", - "serde_json", - "tokio", - "tokio-stream", -] - -[[package]] -name = "garage_rpc" -version = "0.7.0" +version = "0.8.0" dependencies = [ "arc-swap", "async-trait", - "bytes 1.1.0", + "bytes", "futures", "futures-util", - "garage_admin", - "garage_util 0.7.0", + "garage_util", "gethostname", "hex", "hyper", @@ -1032,12 +1154,12 @@ dependencies = [ "k8s-openapi", "kube", "kuska-sodiumoxide", - "netapp 0.4.2", + "netapp", "openssl", "opentelemetry", "pnet_datalink", "rand 0.8.5", - "rmp-serde 0.15.5", + "rmp-serde", "schemars", "serde", "serde_bytes", @@ -1049,92 +1171,51 @@ dependencies = [ [[package]] name = "garage_table" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3557f3757e2acd29eaee86804d4e6c38d2abda81b4b349d8a0d2277044265c" -dependencies = [ - "async-trait", - "bytes 1.1.0", - "futures", - "futures-util", - "garage_rpc 0.5.1", - "garage_util 0.5.1", - "hexdump", - "log", - "rand 0.8.5", - "rmp-serde 0.15.5", - "serde", - "serde_bytes", - "sled", - "tokio", -] - -[[package]] -name = "garage_table" -version = "0.7.0" +version = "0.8.0" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes", "futures", "futures-util", - "garage_rpc 0.7.0", - "garage_util 0.7.0", + "garage_db", + "garage_rpc", + "garage_util", + "hex", "hexdump", "opentelemetry", "rand 0.8.5", - "rmp-serde 0.15.5", + "rmp-serde", "serde", "serde_bytes", - "sled", "tokio", "tracing", ] [[package]] name = "garage_util" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e096994382447431e2f3c70e3685eb8b24c00eceff8667bb22a2a27ff17832f" -dependencies = [ - "blake2", - "chrono", - "err-derive 0.3.1", - "futures", - "hex", - "http", - "hyper", - "log", - "netapp 0.3.1", - "rand 0.8.5", - "rmp-serde 0.15.5", - "serde", - "serde_json", - "sha2", - "sled", - "tokio", - "toml", - "xxhash-rust", -] - -[[package]] -name = "garage_util" -version = "0.7.0" +version = "0.8.0" dependencies = [ + "arc-swap", + "async-trait", "blake2", + "bytes", "chrono", - "err-derive 0.3.1", + "digest 0.10.3", + "err-derive", "futures", + "garage_db", + "git-version", "hex", "http", "hyper", - "netapp 0.4.2", + "lazy_static", + "netapp", "opentelemetry", "rand 0.8.5", - "rmp-serde 0.15.5", + "rmp-serde", "serde", "serde_json", - "sha2", - "sled", + "sha2 0.10.2", "tokio", "toml", "tracing", @@ -1143,14 +1224,14 @@ dependencies = [ [[package]] name = "garage_web" -version = "0.7.0" +version = "0.8.0" dependencies = [ - "err-derive 0.3.1", + "err-derive", "futures", "garage_api", - "garage_model 0.7.0", - "garage_table 0.7.0", - "garage_util 0.7.0", + "garage_model", + "garage_table", + "garage_util", "http", "hyper", "opentelemetry", @@ -1184,7 +1265,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi 0.10.0+wasi-snapshot-preview1", ] @@ -1217,7 +1298,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b" dependencies = [ - "bytes 1.1.0", + "bytes", "fnv", "futures-core", "futures-sink", @@ -1235,6 +1316,18 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown", +] [[package]] name = "heck" @@ -1246,6 +1339,50 @@ dependencies = [ ] [[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "heed" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269c7486ed6def5d7b59a427cec3e87b4d4dd4381d01e21c8c9f2d3985688392" +dependencies = [ + "bytemuck", + "byteorder", + "heed-traits", + "heed-types", + "libc", + "lmdb-rkv-sys", + "once_cell", + "page_size", + "synchronoise", + "url", +] + +[[package]] +name = "heed-traits" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53a94e5b2fd60417e83ffdfe136c39afacff0d4ac1d8d01cd66928ac610e1a2" + +[[package]] +name = "heed-types" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a6cf0a6952fcedc992602d5cddd1e3fff091fbe87d38636e3ec23a31f32acbd" +dependencies = [ + "bincode", + "bytemuck", + "byteorder", + "heed-traits", + "serde", + "serde_json", +] + +[[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1272,12 +1409,21 @@ dependencies = [ [[package]] name = "hmac" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.10.1", - "digest", + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.3", ] [[package]] @@ -1286,7 +1432,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ - "bytes 1.1.0", + "bytes", "fnv", "itoa", ] @@ -1297,7 +1443,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ - "bytes 1.1.0", + "bytes", "http", "pin-project-lite", ] @@ -1341,7 +1487,7 @@ version = "0.14.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-channel", "futures-core", "futures-util", @@ -1394,7 +1540,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.1.0", + "bytes", "hyper", "native-tls", "tokio", @@ -1424,7 +1570,7 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ - "autocfg", + "autocfg 1.1.0", "hashbrown", ] @@ -1434,7 +1580,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1447,6 +1593,16 @@ dependencies = [ ] [[package]] +name = "isolang" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265ef164908329e47e753c769b14cbb27434abf0c41984dca201484022f09ce5" +dependencies = [ + "phf", + "phf_codegen", +] + +[[package]] name = "itertools" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1508,13 +1664,31 @@ dependencies = [ ] [[package]] +name = "k2v-client" +version = "0.0.1" +dependencies = [ + "base64", + "clap 3.1.18", + "garage_util", + "http", + "log", + "rusoto_core", + "rusoto_credential", + "rusoto_signature", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] name = "k8s-openapi" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f8de9873b904e74b3533f77493731ee26742418077503683db44e1b3c54aa5c" dependencies = [ "base64", - "bytes 1.1.0", + "bytes", "chrono", "http", "percent-encoding", @@ -1543,7 +1717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd12d68768b54bbd50547f4c7b57b73cff680ef8da3ba409463ee995cf0d707" dependencies = [ "base64", - "bytes 1.1.0", + "bytes", "chrono", "dirs-next", "either", @@ -1672,12 +1846,34 @@ dependencies = [ ] [[package]] +name = "libsqlite3-sys" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "linked-hash-map" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] +name = "lmdb-rkv-sys" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b9ce6b3be08acefa3003c57b7565377432a89ec24476bbe72e11d101f852fe" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] name = "lock_api" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1692,7 +1888,16 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", ] [[package]] @@ -1707,12 +1912,21 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "opaque-debug", ] [[package]] +name = "md-5" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +dependencies = [ + "digest 0.10.3", +] + +[[package]] name = "md5" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1730,7 +1944,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg", + "autocfg 1.1.0", ] [[package]] @@ -1769,12 +1983,21 @@ dependencies = [ ] [[package]] +name = "mktemp" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975de676448231fcde04b9149d2543077e166b78fc29eae5aa219e7928410da2" +dependencies = [ + "uuid", +] + +[[package]] name = "multer" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f8f35e687561d5c1667590911e6698a8cb714a134a7505718a182e7bc9d3836" dependencies = [ - "bytes 1.1.0", + "bytes", "encoding_rs", "futures-util", "http", @@ -1812,37 +2035,15 @@ dependencies = [ [[package]] name = "netapp" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac7dcd2c6c5a9d5ea88ffc17d3339d49d308e68c4d8190c494b55ddbf78d80ad" -dependencies = [ - "arc-swap", - "async-trait", - "bytes 0.6.0", - "err-derive 0.2.4", - "futures", - "hex", - "kuska-handshake", - "kuska-sodiumoxide", - "log", - "rmp-serde 0.14.4", - "serde", - "tokio", - "tokio-stream", - "tokio-util 0.6.9", -] - -[[package]] -name = "netapp" -version = "0.4.2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a19af9ad24e6cdb166e2c5882a7ff9326cab8d45c9d82379fa638c0cbc84df" +checksum = "4ffe47ac46d3b2ce2f736a70865492df082e042eb2bfdddfca3b8dd66bd9469d" dependencies = [ "arc-swap", "async-trait", - "bytes 0.6.0", - "cfg-if", - "err-derive 0.2.4", + "bytes", + "cfg-if 1.0.0", + "err-derive", "futures", "hex", "kuska-handshake", @@ -1850,12 +2051,13 @@ dependencies = [ "log", "opentelemetry", "opentelemetry-contrib", - "rand 0.5.6", - "rmp-serde 0.14.4", + "pin-project 1.0.10", + "rand 0.8.5", + "rmp-serde", "serde", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.7.0", ] [[package]] @@ -1883,7 +2085,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg", + "autocfg 1.1.0", "num-traits", ] @@ -1893,7 +2095,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg", + "autocfg 1.1.0", ] [[package]] @@ -1934,7 +2136,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" dependencies = [ "bitflags", - "cfg-if", + "cfg-if 1.0.0", "foreign-types", "libc", "once_cell", @@ -1962,7 +2164,7 @@ version = "0.9.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" dependencies = [ - "autocfg", + "autocfg 1.1.0", "cc", "libc", "openssl-src", @@ -2042,6 +2244,22 @@ dependencies = [ ] [[package]] +name = "os_str_bytes" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d8d0b2f198229de29dca79676f2738ff952edf3fde542eb8bf94d8c21b435" + +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] name = "parking_lot" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2068,7 +2286,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall", @@ -2082,7 +2300,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", @@ -2117,6 +2335,44 @@ dependencies = [ ] [[package]] +name = "phf" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" +dependencies = [ + "phf_shared", + "rand 0.6.5", +] + +[[package]] +name = "phf_shared" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +dependencies = [ + "siphasher", +] + +[[package]] name = "pin-project" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2264,7 +2520,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f64969ffd5dd8f39bd57a68ac53c163a095ed9d0fb707146da1b27025a3504" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fnv", "lazy_static", "memchr", @@ -2279,7 +2535,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.1.0", + "bytes", "prost-derive", ] @@ -2289,8 +2545,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.1.0", - "heck", + "bytes", + "heck 0.3.3", "itertools 0.10.3", "lazy_static", "log", @@ -2322,7 +2578,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.1.0", + "bytes", "prost", ] @@ -2359,14 +2615,20 @@ dependencies = [ [[package]] name = "rand" -version = "0.5.6" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ - "cloudabi", - "fuchsia-cprng", + "autocfg 0.1.8", "libc", - "rand_core 0.3.1", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", "winapi", ] @@ -2377,12 +2639,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.3", ] [[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" @@ -2416,6 +2688,77 @@ dependencies = [ ] [[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", +] + +[[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", +] + +[[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.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2447,6 +2790,15 @@ dependencies = [ ] [[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] name = "regex-syntax" version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2488,17 +2840,6 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" -dependencies = [ - "byteorder", - "rmp", - "serde", -] - -[[package]] -name = "rmp-serde" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "723ecff9ad04f4ad92fe1c8ca6c20d2196d9286e9c60727c4cb5511629260e9d" @@ -2518,6 +2859,90 @@ dependencies = [ ] [[package]] +name = "rusoto_core" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2" +dependencies = [ + "async-trait", + "base64", + "bytes", + "crc32fast", + "futures", + "http", + "hyper", + "hyper-tls", + "lazy_static", + "log", + "rusoto_credential", + "rusoto_signature", + "rustc_version", + "serde", + "serde_json", + "tokio", + "xml-rs", +] + +[[package]] +name = "rusoto_credential" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05" +dependencies = [ + "async-trait", + "chrono", + "dirs-next", + "futures", + "hyper", + "serde", + "serde_json", + "shlex", + "tokio", + "zeroize", +] + +[[package]] +name = "rusoto_signature" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272" +dependencies = [ + "base64", + "bytes", + "chrono", + "digest 0.9.0", + "futures", + "hex", + "hmac 0.11.0", + "http", + "hyper", + "log", + "md-5 0.9.1", + "percent-encoding", + "pin-project-lite", + "rusoto_credential", + "rustc_version", + "serde", + "sha2 0.9.9", + "tokio", +] + +[[package]] +name = "rusqlite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + +[[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2653,9 +3078,9 @@ checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] @@ -2681,9 +3106,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -2703,9 +3128,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ "indexmap", "itoa", @@ -2731,14 +3156,40 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", - "cfg-if", + "block-buffer 0.9.0", + "cfg-if 1.0.0", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] [[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] name = "signal-hook-registry" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2748,6 +3199,12 @@ dependencies = [ ] [[package]] +name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" + +[[package]] name = "slab" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2761,7 +3218,7 @@ checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" dependencies = [ "crc32fast", "crossbeam-epoch", - "crossbeam-utils", + "crossbeam-utils 0.8.8", "fs2", "fxhash", "libc", @@ -2860,7 +3317,7 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" dependencies = [ - "clap", + "clap 2.34.0", "lazy_static", "structopt-derive", ] @@ -2871,7 +3328,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro-error", "proc-macro2", "quote", @@ -2886,9 +3343,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.89" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" +checksum = "a07e33e919ebcd69113d5be0e4d70c5707004ff45188910106854f38b960df4a" dependencies = [ "proc-macro2", "quote", @@ -2896,6 +3353,15 @@ dependencies = [ ] [[package]] +name = "synchronoise" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d717ed0efc9d39ab3b642a096bc369a3e02a38a51c41845d7fe31bdad1d6eaeb" +dependencies = [ + "crossbeam-queue", +] + +[[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2913,7 +3379,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fastrand", "libc", "redox_syscall", @@ -2940,19 +3406,25 @@ dependencies = [ ] [[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -2960,6 +3432,15 @@ dependencies = [ ] [[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] name = "time" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2981,6 +3462,16 @@ dependencies = [ ] [[package]] +name = "timeago" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ec32dde57efb15c035ac074118d7f32820451395f28cb0524a01d4e94983b26" +dependencies = [ + "chrono", + "isolang", +] + +[[package]] name = "tinyvec" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3001,7 +3492,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ - "bytes 1.1.0", + "bytes", "libc", "memchr", "mio", @@ -3074,9 +3565,8 @@ version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-core", - "futures-io", "futures-sink", "log", "pin-project-lite", @@ -3090,8 +3580,9 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-core", + "futures-io", "futures-sink", "log", "pin-project-lite", @@ -3116,7 +3607,7 @@ dependencies = [ "async-stream", "async-trait", "base64", - "bytes 1.1.0", + "bytes", "futures-core", "futures-util", "h2", @@ -3177,7 +3668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81eca72647e58054bbfa41e6f297c23436f1c60aff6e5eb38455a0f9ca420bb5" dependencies = [ "base64", - "bytes 1.1.0", + "bytes", "futures-core", "futures-util", "http", @@ -3206,7 +3697,7 @@ version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "log", "pin-project-lite", "tracing-attributes", @@ -3231,6 +3722,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" dependencies = [ "lazy_static", + "valuable", ] [[package]] @@ -3244,6 +3736,35 @@ dependencies = [ ] [[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +dependencies = [ + "ansi_term", + "lazy_static", + "matchers", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] name = "treediff" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3316,6 +3837,21 @@ dependencies = [ ] [[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3366,7 +3902,7 @@ version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] @@ -3520,6 +4056,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" [[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] name = "xmlparser" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3573,4 +4115,5 @@ checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" dependencies = [ "cc", "libc", + "pkg-config", ] @@ -4,15 +4,16 @@ args@{ release ? true, rootFeatures ? [ + "garage_db/default" "garage_util/default" "garage_rpc/default" - "garage_admin/default" "garage_table/default" "garage_block/default" "garage_model/default" "garage_api/default" "garage_web/default" "garage/default" + "k2v-client/default" ], rustPackages, buildRustPackages, @@ -45,16 +46,31 @@ in { cargo2nixVersion = "0.10.0"; workspace = { - garage_util = rustPackages.unknown.garage_util."0.7.0"; - garage_rpc = rustPackages.unknown.garage_rpc."0.7.0"; - garage_admin = rustPackages.unknown.garage_admin."0.7.0"; - garage_table = rustPackages.unknown.garage_table."0.7.0"; - garage_block = rustPackages.unknown.garage_block."0.7.0"; - garage_model = rustPackages.unknown.garage_model."0.7.0"; - garage_api = rustPackages.unknown.garage_api."0.7.0"; - garage_web = rustPackages.unknown.garage_web."0.7.0"; - garage = rustPackages.unknown.garage."0.7.0"; + garage_db = rustPackages.unknown.garage_db."0.8.0"; + garage_util = rustPackages.unknown.garage_util."0.8.0"; + garage_rpc = rustPackages.unknown.garage_rpc."0.8.0"; + garage_table = rustPackages.unknown.garage_table."0.8.0"; + garage_block = rustPackages.unknown.garage_block."0.8.0"; + garage_model = rustPackages.unknown.garage_model."0.8.0"; + garage_api = rustPackages.unknown.garage_api."0.8.0"; + garage_web = rustPackages.unknown.garage_web."0.8.0"; + garage = rustPackages.unknown.garage."0.8.0"; + k2v-client = rustPackages.unknown.k2v-client."0.0.1"; }; + "registry+https://github.com/rust-lang/crates.io-index".ahash."0.7.6" = overridableMkRustCrate (profileName: rec { + name = "ahash"; + version = "0.7.6"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"; }; + dependencies = { + ${ if hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "windows" || hostPlatform.parsed.kernel.name == "darwin" || hostPlatform.parsed.kernel.name == "ios" || hostPlatform.parsed.kernel.name == "freebsd" || hostPlatform.parsed.kernel.name == "openbsd" || hostPlatform.parsed.kernel.name == "netbsd" || hostPlatform.parsed.kernel.name == "dragonfly" || hostPlatform.parsed.kernel.name == "solaris" || hostPlatform.parsed.kernel.name == "illumos" || hostPlatform.parsed.kernel.name == "fuchsia" || hostPlatform.parsed.kernel.name == "redox" || hostPlatform.parsed.kernel.name == "cloudabi" || hostPlatform.parsed.kernel.name == "haiku" || hostPlatform.parsed.kernel.name == "vxworks" || hostPlatform.parsed.kernel.name == "emscripten" || hostPlatform.parsed.kernel.name == "wasi" then "getrandom" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".getrandom."0.2.5" { inherit profileName; }; + ${ if !((hostPlatform.parsed.cpu.name == "armv6l" || hostPlatform.parsed.cpu.name == "armv7l") && hostPlatform.parsed.kernel.name == "none") then "once_cell" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; + }; + buildDependencies = { + version_check = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".version_check."0.9.4" { profileName = "__noProfile"; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".aho-corasick."0.7.18" = overridableMkRustCrate (profileName: rec { name = "aho-corasick"; version = "0.7.18"; @@ -69,6 +85,16 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".ansi_term."0.12.1" = overridableMkRustCrate (profileName: rec { + name = "ansi_term"; + version = "0.12.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"; }; + dependencies = { + ${ if hostPlatform.parsed.kernel.name == "windows" then "winapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".anyhow."1.0.56" = overridableMkRustCrate (profileName: rec { name = "anyhow"; version = "1.0.56"; @@ -98,6 +124,39 @@ in ]; }); + "registry+https://github.com/rust-lang/crates.io-index".assert-json-diff."2.0.1" = overridableMkRustCrate (profileName: rec { + name = "assert-json-diff"; + version = "2.0.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "50f1c3703dd33532d7f0ca049168930e9099ecac238e23cf932f3a69c42f06da"; }; + dependencies = { + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".async-compression."0.3.10" = overridableMkRustCrate (profileName: rec { + name = "async-compression"; + version = "0.3.10"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "00461f243d703f6999c8e7494f077799f1362720a55ae49a90ffe6214032fc0b"; }; + features = builtins.concatLists [ + [ "default" ] + [ "libzstd" ] + [ "tokio" ] + [ "zstd" ] + [ "zstd-safe" ] + ]; + dependencies = { + futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; + memchr = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; + pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; + tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + libzstd = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd."0.9.2+zstd.1.5.1" { inherit profileName; }; + zstd_safe = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd-safe."4.1.3+zstd.1.5.1" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".async-stream."0.3.3" = overridableMkRustCrate (profileName: rec { name = "async-stream"; version = "0.3.3"; @@ -117,7 +176,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -129,7 +188,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -145,6 +204,16 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".autocfg."0.1.8" = overridableMkRustCrate (profileName: rec { + name = "autocfg"; + version = "0.1.8"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"; }; + dependencies = { + autocfg = rustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" = overridableMkRustCrate (profileName: rec { name = "autocfg"; version = "1.1.0"; @@ -205,7 +274,7 @@ in aws_smithy_types = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-types."0.38.0" { inherit profileName; }; aws_smithy_xml = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-xml."0.38.0" { inherit profileName; }; aws_types = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-types."0.8.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; md5 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".md5."0.7.0" { inherit profileName; }; tokio_stream = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-stream."0.1.8" { inherit profileName; }; @@ -228,7 +297,7 @@ in aws_smithy_http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-http."0.38.0" { inherit profileName; }; aws_types = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-types."0.8.0" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; - thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; }; }); @@ -251,7 +320,7 @@ in dependencies = { aws_smithy_eventstream = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-eventstream."0.38.0" { inherit profileName; }; aws_smithy_http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-http."0.38.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; form_urlencoded = rustPackages."registry+https://github.com/rust-lang/crates.io-index".form_urlencoded."1.0.1" { inherit profileName; }; hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; @@ -298,7 +367,7 @@ in aws_smithy_http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-http."0.38.0" { inherit profileName; }; aws_smithy_http_tower = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-http-tower."0.38.0" { inherit profileName; }; aws_smithy_types = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-types."0.38.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; fastrand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".fastrand."1.7.0" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; http_body = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-body."0.4.4" { inherit profileName; }; @@ -320,7 +389,7 @@ in src = fetchCratesIo { inherit name version; sha256 = "f972226c639e0dc1eca2cb0220c1b5799e2bfc62eda37845b662c5d0cb972371"; }; dependencies = { aws_smithy_types = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-types."0.38.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; crc32fast = rustPackages."registry+https://github.com/rust-lang/crates.io-index".crc32fast."1.3.2" { inherit profileName; }; }; }); @@ -340,7 +409,7 @@ in dependencies = { aws_smithy_eventstream = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-eventstream."0.38.0" { inherit profileName; }; aws_smithy_types = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-types."0.38.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; bytes_utils = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes-utils."0.1.2" { inherit profileName; }; futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; @@ -361,7 +430,7 @@ in src = fetchCratesIo { inherit name version; sha256 = "64f80a2c56fc09fc9a2da3c63f286ec2a89465433219f8165e14e522283a5eb8"; }; dependencies = { aws_smithy_http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-smithy-http."0.38.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; http_body = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-body."0.4.4" { inherit profileName; }; pin_project = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.10" { inherit profileName; }; @@ -389,7 +458,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "7aa6c9de6c3f875faabcaaad1fb1f4ef241683bfc22795f731719e3568c3ca9f"; }; dependencies = { - thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; xmlparser = rustPackages."registry+https://github.com/rust-lang/crates.io-index".xmlparser."0.13.3" { inherit profileName; }; }; }); @@ -422,6 +491,16 @@ in ]; }); + "registry+https://github.com/rust-lang/crates.io-index".bincode."1.3.3" = overridableMkRustCrate (profileName: rec { + name = "bincode"; + version = "1.3.3"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"; }; + dependencies = { + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" = overridableMkRustCrate (profileName: rec { name = "bitflags"; version = "1.3.2"; @@ -458,6 +537,16 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".block-buffer."0.10.2" = overridableMkRustCrate (profileName: rec { + name = "block-buffer"; + version = "0.10.2"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"; }; + dependencies = { + generic_array = rustPackages."registry+https://github.com/rust-lang/crates.io-index".generic-array."0.14.5" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".bumpalo."3.9.1" = overridableMkRustCrate (profileName: rec { name = "bumpalo"; version = "3.9.1"; @@ -468,33 +557,33 @@ in ]; }); - "registry+https://github.com/rust-lang/crates.io-index".byteorder."1.4.3" = overridableMkRustCrate (profileName: rec { - name = "byteorder"; - version = "1.4.3"; + "registry+https://github.com/rust-lang/crates.io-index".bytemuck."1.9.1" = overridableMkRustCrate (profileName: rec { + name = "bytemuck"; + version = "1.9.1"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"; }; + src = fetchCratesIo { inherit name version; sha256 = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc"; }; features = builtins.concatLists [ - [ "default" ] - [ "std" ] + [ "extern_crate_alloc" ] + [ "extern_crate_std" ] ]; }); - "registry+https://github.com/rust-lang/crates.io-index".bytes."0.6.0" = overridableMkRustCrate (profileName: rec { - name = "bytes"; - version = "0.6.0"; + "registry+https://github.com/rust-lang/crates.io-index".byteorder."1.4.3" = overridableMkRustCrate (profileName: rec { + name = "byteorder"; + version = "1.4.3"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"; }; + src = fetchCratesIo { inherit name version; sha256 = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"; }; features = builtins.concatLists [ [ "default" ] [ "std" ] ]; }); - "registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" = overridableMkRustCrate (profileName: rec { name = "bytes"; - version = "1.1.0"; + version = "1.2.0"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"; }; + src = fetchCratesIo { inherit name version; sha256 = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e"; }; features = builtins.concatLists [ [ "default" ] [ "std" ] @@ -511,11 +600,18 @@ in [ "std" ] ]; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; either = rustPackages."registry+https://github.com/rust-lang/crates.io-index".either."1.6.1" { inherit profileName; }; }; }); + "registry+https://github.com/rust-lang/crates.io-index".bytesize."1.1.0" = overridableMkRustCrate (profileName: rec { + name = "bytesize"; + version = "1.1.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70"; }; + }); + "registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" = overridableMkRustCrate (profileName: rec { name = "cc"; version = "1.0.73"; @@ -530,6 +626,13 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".cfg-if."0.1.10" = overridableMkRustCrate (profileName: rec { + name = "cfg-if"; + version = "0.1.10"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"; }; + }); + "registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" = overridableMkRustCrate (profileName: rec { name = "cfg-if"; version = "1.0.0"; @@ -550,22 +653,22 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"; }; features = builtins.concatLists [ - [ "clock" ] - [ "default" ] - [ "libc" ] - [ "oldtime" ] - (lib.optional (rootFeatures' ? "garage_rpc") "serde") - [ "std" ] - [ "time" ] - [ "winapi" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "clock") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "libc") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "oldtime") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "serde") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "std") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "time") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "winapi") ]; dependencies = { - libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; - num_integer = rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-integer."0.1.44" { inherit profileName; }; - num_traits = rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" { inherit profileName; }; - ${ if rootFeatures' ? "garage_rpc" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - time = rustPackages."registry+https://github.com/rust-lang/crates.io-index".time."0.1.44" { inherit profileName; }; - ${ if hostPlatform.isWindows then "winapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "num_integer" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-integer."0.1.44" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "num_traits" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "time" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".time."0.1.44" { inherit profileName; }; + ${ if (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") && hostPlatform.isWindows then "winapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }; }; }); @@ -581,6 +684,64 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".clap."3.1.18" = overridableMkRustCrate (profileName: rec { + name = "clap"; + version = "3.1.18"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"; }; + features = builtins.concatLists [ + [ "atty" ] + [ "clap_derive" ] + [ "color" ] + [ "default" ] + [ "derive" ] + [ "env" ] + [ "lazy_static" ] + [ "std" ] + [ "strsim" ] + [ "suggestions" ] + [ "termcolor" ] + ]; + dependencies = { + atty = rustPackages."registry+https://github.com/rust-lang/crates.io-index".atty."0.2.14" { inherit profileName; }; + bitflags = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" { inherit profileName; }; + clap_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".clap_derive."3.1.18" { profileName = "__noProfile"; }; + clap_lex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".clap_lex."0.2.0" { inherit profileName; }; + indexmap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".indexmap."1.8.0" { inherit profileName; }; + lazy_static = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }; + strsim = rustPackages."registry+https://github.com/rust-lang/crates.io-index".strsim."0.10.0" { inherit profileName; }; + termcolor = rustPackages."registry+https://github.com/rust-lang/crates.io-index".termcolor."1.1.3" { inherit profileName; }; + textwrap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".textwrap."0.15.0" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".clap_derive."3.1.18" = overridableMkRustCrate (profileName: rec { + name = "clap_derive"; + version = "3.1.18"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"; }; + features = builtins.concatLists [ + [ "default" ] + ]; + dependencies = { + heck = rustPackages."registry+https://github.com/rust-lang/crates.io-index".heck."0.4.0" { inherit profileName; }; + proc_macro_error = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro-error."1.0.4" { inherit profileName; }; + proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; + quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".clap_lex."0.2.0" = overridableMkRustCrate (profileName: rec { + name = "clap_lex"; + version = "0.2.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"; }; + dependencies = { + os_str_bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".os_str_bytes."6.0.1" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".cloudabi."0.0.3" = overridableMkRustCrate (profileName: rec { name = "cloudabi"; version = "0.0.3"; @@ -676,13 +837,39 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".crossbeam-queue."0.1.2" = overridableMkRustCrate (profileName: rec { + name = "crossbeam-queue"; + version = "0.1.2"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"; }; + dependencies = { + crossbeam_utils = rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-utils."0.6.6" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".crossbeam-utils."0.6.6" = overridableMkRustCrate (profileName: rec { + name = "crossbeam-utils"; + version = "0.6.6"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"; }; + features = builtins.concatLists [ + [ "default" ] + [ "lazy_static" ] + [ "std" ] + ]; + dependencies = { + cfg_if = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."0.1.10" { inherit profileName; }; + lazy_static = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".crossbeam-utils."0.8.8" = overridableMkRustCrate (profileName: rec { name = "crossbeam-utils"; version = "0.8.8"; registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"; }; features = builtins.concatLists [ - [ "default" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "default") [ "lazy_static" ] [ "std" ] ]; @@ -692,6 +879,20 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".crypto-common."0.1.6" = overridableMkRustCrate (profileName: rec { + name = "crypto-common"; + version = "0.1.6"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"; }; + features = builtins.concatLists [ + [ "std" ] + ]; + dependencies = { + generic_array = rustPackages."registry+https://github.com/rust-lang/crates.io-index".generic-array."0.14.5" { inherit profileName; }; + typenum = rustPackages."registry+https://github.com/rust-lang/crates.io-index".typenum."1.15.0" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".crypto-mac."0.8.0" = overridableMkRustCrate (profileName: rec { name = "crypto-mac"; version = "0.8.0"; @@ -706,11 +907,11 @@ in }; }); - "registry+https://github.com/rust-lang/crates.io-index".crypto-mac."0.10.1" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".crypto-mac."0.11.1" = overridableMkRustCrate (profileName: rec { name = "crypto-mac"; - version = "0.10.1"; + version = "0.11.1"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a"; }; + src = fetchCratesIo { inherit name version; sha256 = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"; }; dependencies = { generic_array = rustPackages."registry+https://github.com/rust-lang/crates.io-index".generic-array."0.14.5" { inherit profileName; }; subtle = rustPackages."registry+https://github.com/rust-lang/crates.io-index".subtle."2.4.1" { inherit profileName; }; @@ -757,7 +958,7 @@ in proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; strsim = rustPackages."registry+https://github.com/rust-lang/crates.io-index".strsim."0.10.0" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -769,7 +970,7 @@ in dependencies = { darling_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".darling_core."0.13.1" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -795,7 +996,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -813,6 +1014,27 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".digest."0.10.3" = overridableMkRustCrate (profileName: rec { + name = "digest"; + version = "0.10.3"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"; }; + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "alloc") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "block-buffer") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "core-api") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web") "mac") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "std") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web") "subtle") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "block_buffer" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".block-buffer."0.10.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "crypto_common" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".crypto-common."0.1.6" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "subtle" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".subtle."2.4.1" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".dirs-next."2.0.0" = overridableMkRustCrate (profileName: rec { name = "dirs-next"; version = "2.0.0"; @@ -896,11 +1118,11 @@ in }; }); - "registry+https://github.com/rust-lang/crates.io-index".err-derive."0.2.4" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" = overridableMkRustCrate (profileName: rec { name = "err-derive"; - version = "0.2.4"; + version = "0.3.1"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4"; }; + src = fetchCratesIo { inherit name version; sha256 = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e"; }; features = builtins.concatLists [ [ "default" ] [ "std" ] @@ -909,7 +1131,7 @@ in proc_macro_error = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro-error."1.0.4" { inherit profileName; }; proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; synstructure = rustPackages."registry+https://github.com/rust-lang/crates.io-index".synstructure."0.12.6" { inherit profileName; }; }; buildDependencies = { @@ -917,25 +1139,22 @@ in }; }); - "registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" = overridableMkRustCrate (profileName: rec { - name = "err-derive"; - version = "0.3.1"; + "registry+https://github.com/rust-lang/crates.io-index".fallible-iterator."0.2.0" = overridableMkRustCrate (profileName: rec { + name = "fallible-iterator"; + version = "0.2.0"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e"; }; + src = fetchCratesIo { inherit name version; sha256 = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"; }; features = builtins.concatLists [ [ "default" ] [ "std" ] ]; - dependencies = { - proc_macro_error = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro-error."1.0.4" { inherit profileName; }; - proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; - quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; - synstructure = rustPackages."registry+https://github.com/rust-lang/crates.io-index".synstructure."0.12.6" { inherit profileName; }; - }; - buildDependencies = { - rustversion = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".rustversion."1.0.6" { profileName = "__noProfile"; }; - }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".fallible-streaming-iterator."0.1.9" = overridableMkRustCrate (profileName: rec { + name = "fallible-streaming-iterator"; + version = "0.1.9"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"; }; }); "registry+https://github.com/rust-lang/crates.io-index".fastrand."1.7.0" = overridableMkRustCrate (profileName: rec { @@ -1101,7 +1320,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -1134,32 +1353,32 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"; }; features = builtins.concatLists [ - [ "alloc" ] - [ "async-await" ] - [ "async-await-macro" ] - [ "channel" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "default") - [ "futures-channel" ] - [ "futures-io" ] - [ "futures-macro" ] - [ "futures-sink" ] - [ "io" ] - [ "memchr" ] - [ "sink" ] - [ "slab" ] - [ "std" ] - ]; - dependencies = { - futures_channel = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-channel."0.3.21" { inherit profileName; }; - futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; - futures_io = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-io."0.3.21" { inherit profileName; }; - futures_macro = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-macro."0.3.21" { profileName = "__noProfile"; }; - futures_sink = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-sink."0.3.21" { inherit profileName; }; - futures_task = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-task."0.3.21" { inherit profileName; }; - memchr = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; - pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; - pin_utils = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-utils."0.1.0" { inherit profileName; }; - slab = rustPackages."registry+https://github.com/rust-lang/crates.io-index".slab."0.4.5" { inherit profileName; }; + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "alloc") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "async-await") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "async-await-macro") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "channel") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "futures-channel") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "futures-io") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "futures-macro") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "futures-sink") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "io") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "memchr") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "sink") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "slab") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "std") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_channel" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-channel."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_core" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_io" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-io."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_macro" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-macro."0.3.21" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_sink" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-sink."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_task" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-task."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "memchr" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "pin_project_lite" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "pin_utils" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-utils."0.1.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "slab" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".slab."0.4.5" { inherit profileName; }; }; }); @@ -1173,378 +1392,342 @@ in }; }); - "unknown".garage."0.7.0" = overridableMkRustCrate (profileName: rec { + "unknown".garage."0.8.0" = overridableMkRustCrate (profileName: rec { name = "garage"; - version = "0.7.0"; + version = "0.8.0"; registry = "unknown"; src = fetchCrateLocal (workspaceSrc + "/src/garage"); + features = builtins.concatLists [ + [ "bundled-libs" ] + [ "default" ] + [ "k2v" ] + [ "kubernetes-discovery" ] + [ "lmdb" ] + [ "metrics" ] + [ "opentelemetry-otlp" ] + [ "opentelemetry-prometheus" ] + [ "prometheus" ] + [ "sled" ] + [ "sqlite" ] + [ "system-libs" ] + [ "telemetry-otlp" ] + ]; dependencies = { async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + bytesize = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytesize."1.1.0" { inherit profileName; }; futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - garage_admin = rustPackages."unknown".garage_admin."0.7.0" { inherit profileName; }; - garage_api = rustPackages."unknown".garage_api."0.7.0" { inherit profileName; }; - garage_model = rustPackages."unknown".garage_model."0.7.0" { inherit profileName; }; - garage_rpc = rustPackages."unknown".garage_rpc."0.7.0" { inherit profileName; }; - garage_table = rustPackages."unknown".garage_table."0.7.0" { inherit profileName; }; - garage_util = rustPackages."unknown".garage_util."0.7.0" { inherit profileName; }; - garage_web = rustPackages."unknown".garage_web."0.7.0" { inherit profileName; }; - git_version = rustPackages."registry+https://github.com/rust-lang/crates.io-index".git-version."0.3.5" { inherit profileName; }; + garage_api = rustPackages."unknown".garage_api."0.8.0" { inherit profileName; }; + garage_block = rustPackages."unknown".garage_block."0.8.0" { inherit profileName; }; + garage_db = rustPackages."unknown".garage_db."0.8.0" { inherit profileName; }; + garage_model = rustPackages."unknown".garage_model."0.8.0" { inherit profileName; }; + garage_rpc = rustPackages."unknown".garage_rpc."0.8.0" { inherit profileName; }; + garage_table = rustPackages."unknown".garage_table."0.8.0" { inherit profileName; }; + garage_util = rustPackages."unknown".garage_util."0.8.0" { inherit profileName; }; + garage_web = rustPackages."unknown".garage_web."0.8.0" { inherit profileName; }; hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; sodiumoxide = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; }; - netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.4.2" { inherit profileName; }; - pretty_env_logger = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pretty_env_logger."0.4.0" { inherit profileName; }; + netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.5.2" { inherit profileName; }; + opentelemetry = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; + opentelemetry_otlp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-otlp."0.10.0" { inherit profileName; }; + opentelemetry_prometheus = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-prometheus."0.10.0" { inherit profileName; }; + prometheus = rustPackages."registry+https://github.com/rust-lang/crates.io-index".prometheus."0.13.0" { inherit profileName; }; rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; serde_bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; - sled = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }; structopt = rustPackages."registry+https://github.com/rust-lang/crates.io-index".structopt."0.3.26" { inherit profileName; }; + timeago = rustPackages."registry+https://github.com/rust-lang/crates.io-index".timeago."0.3.1" { inherit profileName; }; tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; toml = rustPackages."registry+https://github.com/rust-lang/crates.io-index".toml."0.5.8" { inherit profileName; }; tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; + tracing_subscriber = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing-subscriber."0.3.11" { inherit profileName; }; }; devDependencies = { + assert_json_diff = rustPackages."registry+https://github.com/rust-lang/crates.io-index".assert-json-diff."2.0.1" { inherit profileName; }; aws_sdk_s3 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aws-sdk-s3."0.8.0" { inherit profileName; }; + base64 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; chrono = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; - hmac = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hmac."0.10.1" { inherit profileName; }; + hmac = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hmac."0.12.1" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; - sha2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.9.9" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + sha2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.10.2" { inherit profileName; }; static_init = rustPackages."registry+https://github.com/rust-lang/crates.io-index".static_init."1.0.2" { inherit profileName; }; }; }); - "unknown".garage_admin."0.7.0" = overridableMkRustCrate (profileName: rec { - name = "garage_admin"; - version = "0.7.0"; - registry = "unknown"; - src = fetchCrateLocal (workspaceSrc + "/src/admin"); - dependencies = { - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - garage_util = rustPackages."unknown".garage_util."0.7.0" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; - hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; - opentelemetry = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; - opentelemetry_otlp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-otlp."0.10.0" { inherit profileName; }; - opentelemetry_prometheus = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-prometheus."0.10.0" { inherit profileName; }; - prometheus = rustPackages."registry+https://github.com/rust-lang/crates.io-index".prometheus."0.13.0" { inherit profileName; }; - tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; - }; - }); - - "unknown".garage_api."0.7.0" = overridableMkRustCrate (profileName: rec { + "unknown".garage_api."0.8.0" = overridableMkRustCrate (profileName: rec { name = "garage_api"; - version = "0.7.0"; + version = "0.8.0"; registry = "unknown"; src = fetchCrateLocal (workspaceSrc + "/src/api"); - dependencies = { - base64 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; - chrono = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; - crypto_mac = rustPackages."registry+https://github.com/rust-lang/crates.io-index".crypto-mac."0.10.1" { inherit profileName; }; - err_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; - form_urlencoded = rustPackages."registry+https://github.com/rust-lang/crates.io-index".form_urlencoded."1.0.1" { inherit profileName; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - garage_block = rustPackages."unknown".garage_block."0.7.0" { inherit profileName; }; - garage_model = rustPackages."unknown".garage_model."0.7.0" { inherit profileName; }; - garage_table = rustPackages."unknown".garage_table."0.7.0" { inherit profileName; }; - garage_util = rustPackages."unknown".garage_util."0.7.0" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - hmac = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hmac."0.10.1" { inherit profileName; }; - http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; - http_range = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-range."0.1.5" { inherit profileName; }; - httpdate = rustPackages."registry+https://github.com/rust-lang/crates.io-index".httpdate."0.3.2" { inherit profileName; }; - hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; - idna = rustPackages."registry+https://github.com/rust-lang/crates.io-index".idna."0.2.3" { inherit profileName; }; - md5 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".md-5."0.9.1" { inherit profileName; }; - multer = rustPackages."registry+https://github.com/rust-lang/crates.io-index".multer."2.0.2" { inherit profileName; }; - nom = rustPackages."registry+https://github.com/rust-lang/crates.io-index".nom."7.1.1" { inherit profileName; }; - opentelemetry = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; - percent_encoding = rustPackages."registry+https://github.com/rust-lang/crates.io-index".percent-encoding."2.1.0" { inherit profileName; }; - pin_project = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.10" { inherit profileName; }; - quick_xml = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quick-xml."0.21.0" { inherit profileName; }; - roxmltree = rustPackages."registry+https://github.com/rust-lang/crates.io-index".roxmltree."0.14.1" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; - sha2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.9.9" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; - url = rustPackages."registry+https://github.com/rust-lang/crates.io-index".url."2.2.2" { inherit profileName; }; - }; - }); - - "unknown".garage_block."0.7.0" = overridableMkRustCrate (profileName: rec { + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api") "k2v") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api") "metrics") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api") "opentelemetry-prometheus") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api") "prometheus") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "async_trait" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "base64" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "chrono" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "crypto_common" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".crypto-common."0.1.6" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "err_derive" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "form_urlencoded" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".form_urlencoded."1.0.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "futures" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "futures_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "garage_block" else null } = rustPackages."unknown".garage_block."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "garage_model" else null } = rustPackages."unknown".garage_model."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "garage_rpc" else null } = rustPackages."unknown".garage_rpc."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "garage_table" else null } = rustPackages."unknown".garage_table."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "garage_util" else null } = rustPackages."unknown".garage_util."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "hex" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "hmac" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hmac."0.12.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "http" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "http_range" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-range."0.1.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "httpdate" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".httpdate."0.3.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "hyper" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "idna" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".idna."0.2.3" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "md5" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".md-5."0.10.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "multer" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".multer."2.0.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "nom" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".nom."7.1.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "opentelemetry" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" then "opentelemetry_prometheus" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-prometheus."0.10.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "percent_encoding" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".percent-encoding."2.1.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "pin_project" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.10" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" then "prometheus" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".prometheus."0.13.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "quick_xml" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quick-xml."0.21.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "roxmltree" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".roxmltree."0.14.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "serde_bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "serde_json" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "sha2" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.10.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "tokio_stream" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-stream."0.1.8" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "tracing" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web" then "url" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".url."2.2.2" { inherit profileName; }; + }; + }); + + "unknown".garage_block."0.8.0" = overridableMkRustCrate (profileName: rec { name = "garage_block"; - version = "0.7.0"; + version = "0.8.0"; registry = "unknown"; src = fetchCrateLocal (workspaceSrc + "/src/block"); + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_block") "system-libs") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "arc_swap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "async_compression" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".async-compression."0.3.10" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "async_trait" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "futures" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "futures_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "garage_db" else null } = rustPackages."unknown".garage_db."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "garage_rpc" else null } = rustPackages."unknown".garage_rpc."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "garage_table" else null } = rustPackages."unknown".garage_table."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "garage_util" else null } = rustPackages."unknown".garage_util."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "hex" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "opentelemetry" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "rand" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "rmp_serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "serde_bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "tokio_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.6.9" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "tracing" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "zstd" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd."0.9.2+zstd.1.5.1" { inherit profileName; }; + }; + }); + + "unknown".garage_db."0.8.0" = overridableMkRustCrate (profileName: rec { + name = "garage_db"; + version = "0.8.0"; + registry = "unknown"; + src = fetchCrateLocal (workspaceSrc + "/src/db"); + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "bundled-libs") + (lib.optional (rootFeatures' ? "garage_db") "clap") + (lib.optional (rootFeatures' ? "garage_db") "cli") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "heed") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "lmdb") + (lib.optional (rootFeatures' ? "garage_db") "pretty_env_logger") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "rusqlite") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "sled") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "sqlite") + ]; dependencies = { - async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - garage_rpc = rustPackages."unknown".garage_rpc."0.7.0" { inherit profileName; }; - garage_table = rustPackages."unknown".garage_table."0.7.0" { inherit profileName; }; - garage_util = rustPackages."unknown".garage_util."0.7.0" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - opentelemetry = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; - rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; - rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; - sled = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage_db" then "clap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".clap."3.1.18" { inherit profileName; }; + err_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "heed" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".heed."0.11.0" { inherit profileName; }; + hexdump = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hexdump."0.1.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage_db" then "pretty_env_logger" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pretty_env_logger."0.4.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "rusqlite" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rusqlite."0.27.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "sled" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }; tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; - zstd = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd."0.9.2+zstd.1.5.1" { inherit profileName; }; }; - }); - - "registry+https://github.com/rust-lang/crates.io-index".garage_model."0.5.1" = overridableMkRustCrate (profileName: rec { - name = "garage_model"; - version = "0.5.1"; - registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "584619e8999713d73761775591ad6f01ff8c9d724f3b20984f5932f1fc7f9988"; }; - dependencies = { - arc_swap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; - async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - garage_rpc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".garage_rpc."0.5.1" { inherit profileName; }; - garage_table = rustPackages."registry+https://github.com/rust-lang/crates.io-index".garage_table."0.5.1" { inherit profileName; }; - garage_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".garage_util."0.5.1" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; - netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.3.1" { inherit profileName; }; - rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; - rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; - sled = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - zstd = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd."0.9.2+zstd.1.5.1" { inherit profileName; }; + devDependencies = { + mktemp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".mktemp."0.4.1" { inherit profileName; }; }; }); - "unknown".garage_model."0.7.0" = overridableMkRustCrate (profileName: rec { + "unknown".garage_model."0.8.0" = overridableMkRustCrate (profileName: rec { name = "garage_model"; - version = "0.7.0"; + version = "0.8.0"; registry = "unknown"; src = fetchCrateLocal (workspaceSrc + "/src/model"); - dependencies = { - arc_swap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; - async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - err_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - garage_block = rustPackages."unknown".garage_block."0.7.0" { inherit profileName; }; - garage_model_050 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".garage_model."0.5.1" { inherit profileName; }; - garage_rpc = rustPackages."unknown".garage_rpc."0.7.0" { inherit profileName; }; - garage_table = rustPackages."unknown".garage_table."0.7.0" { inherit profileName; }; - garage_util = rustPackages."unknown".garage_util."0.7.0" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.4.2" { inherit profileName; }; - opentelemetry = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; - rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; - rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; - sled = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; - zstd = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd."0.9.2+zstd.1.5.1" { inherit profileName; }; - }; - }); - - "registry+https://github.com/rust-lang/crates.io-index".garage_rpc."0.5.1" = overridableMkRustCrate (profileName: rec { - name = "garage_rpc"; - version = "0.5.1"; - registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "81e693aa4582cfe7a7ce70c07880e3662544b5d0cd68bc4b59c53febfbb8d1ec"; }; - dependencies = { - arc_swap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; - async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - garage_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".garage_util."0.5.1" { inherit profileName; }; - gethostname = rustPackages."registry+https://github.com/rust-lang/crates.io-index".gethostname."0.2.3" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; - sodiumoxide = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; }; - log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; - netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.3.1" { inherit profileName; }; - rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; - rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - tokio_stream = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-stream."0.1.8" { inherit profileName; }; - }; - }); - - "unknown".garage_rpc."0.7.0" = overridableMkRustCrate (profileName: rec { + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model") "k2v") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_model") "lmdb") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_model") "sled") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_model") "sqlite") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "arc_swap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "async_trait" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "base64" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "blake2" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".blake2."0.9.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "err_derive" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "futures" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "futures_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "garage_block" else null } = rustPackages."unknown".garage_block."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "garage_db" else null } = rustPackages."unknown".garage_db."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "garage_rpc" else null } = rustPackages."unknown".garage_rpc."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "garage_table" else null } = rustPackages."unknown".garage_table."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "garage_util" else null } = rustPackages."unknown".garage_util."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "hex" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "netapp" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.5.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "opentelemetry" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "rand" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "rmp_serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "serde_bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "tracing" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "zstd" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd."0.9.2+zstd.1.5.1" { inherit profileName; }; + }; + }); + + "unknown".garage_rpc."0.8.0" = overridableMkRustCrate (profileName: rec { name = "garage_rpc"; - version = "0.7.0"; + version = "0.8.0"; registry = "unknown"; src = fetchCrateLocal (workspaceSrc + "/src/rpc"); features = builtins.concatLists [ - (lib.optional (rootFeatures' ? "garage_rpc") "k8s-openapi") - (lib.optional (rootFeatures' ? "garage_rpc") "kube") - (lib.optional (rootFeatures' ? "garage_rpc") "kubernetes-discovery") - (lib.optional (rootFeatures' ? "garage_rpc") "openssl") - (lib.optional (rootFeatures' ? "garage_rpc") "schemars") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "k8s-openapi") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "kube") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "kubernetes-discovery") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "openssl") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "schemars") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "system-libs") ]; dependencies = { ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "arc_swap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "async_trait" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "futures" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "futures_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "garage_admin" else null } = rustPackages."unknown".garage_admin."0.7.0" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "garage_util" else null } = rustPackages."unknown".garage_util."0.7.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "garage_util" else null } = rustPackages."unknown".garage_util."0.8.0" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "gethostname" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".gethostname."0.2.3" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "hex" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "hyper" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; - ${ if rootFeatures' ? "garage_rpc" then "k8s_openapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".k8s-openapi."0.13.1" { inherit profileName; }; - ${ if rootFeatures' ? "garage_rpc" then "kube" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kube."0.62.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "k8s_openapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".k8s-openapi."0.13.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "kube" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kube."0.62.0" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "sodiumoxide" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "netapp" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.4.2" { inherit profileName; }; - ${ if rootFeatures' ? "garage_rpc" then "openssl" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".openssl."0.10.38" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "netapp" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.5.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "openssl" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".openssl."0.10.38" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "opentelemetry" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "pnet_datalink" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pnet_datalink."0.28.0" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "rand" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "rmp_serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - ${ if rootFeatures' ? "garage_rpc" then "schemars" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".schemars."0.8.8" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "schemars" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".schemars."0.8.8" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "serde_bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "serde_json" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "serde_json" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "tokio_stream" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-stream."0.1.8" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "tracing" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; }; }); - "registry+https://github.com/rust-lang/crates.io-index".garage_table."0.5.1" = overridableMkRustCrate (profileName: rec { + "unknown".garage_table."0.8.0" = overridableMkRustCrate (profileName: rec { name = "garage_table"; - version = "0.5.1"; - registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "5c3557f3757e2acd29eaee86804d4e6c38d2abda81b4b349d8a0d2277044265c"; }; - dependencies = { - async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - garage_rpc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".garage_rpc."0.5.1" { inherit profileName; }; - garage_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".garage_util."0.5.1" { inherit profileName; }; - hexdump = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hexdump."0.1.1" { inherit profileName; }; - log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; - rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; - rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; - sled = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - }; - }); - - "unknown".garage_table."0.7.0" = overridableMkRustCrate (profileName: rec { - name = "garage_table"; - version = "0.7.0"; + version = "0.8.0"; registry = "unknown"; src = fetchCrateLocal (workspaceSrc + "/src/table"); dependencies = { async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - garage_rpc = rustPackages."unknown".garage_rpc."0.7.0" { inherit profileName; }; - garage_util = rustPackages."unknown".garage_util."0.7.0" { inherit profileName; }; + garage_db = rustPackages."unknown".garage_db."0.8.0" { inherit profileName; }; + garage_rpc = rustPackages."unknown".garage_rpc."0.8.0" { inherit profileName; }; + garage_util = rustPackages."unknown".garage_util."0.8.0" { inherit profileName; }; + hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; hexdump = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hexdump."0.1.1" { inherit profileName; }; opentelemetry = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; serde_bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_bytes."0.11.5" { inherit profileName; }; - sled = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }; tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; }; }); - "registry+https://github.com/rust-lang/crates.io-index".garage_util."0.5.1" = overridableMkRustCrate (profileName: rec { + "unknown".garage_util."0.8.0" = overridableMkRustCrate (profileName: rec { name = "garage_util"; - version = "0.5.1"; - registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "1e096994382447431e2f3c70e3685eb8b24c00eceff8667bb22a2a27ff17832f"; }; - dependencies = { - blake2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".blake2."0.9.2" { inherit profileName; }; - chrono = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; - err_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; - hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; - log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; - netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.3.1" { inherit profileName; }; - rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; - rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; - sha2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.9.9" { inherit profileName; }; - sled = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - toml = rustPackages."registry+https://github.com/rust-lang/crates.io-index".toml."0.5.8" { inherit profileName; }; - xxhash_rust = rustPackages."registry+https://github.com/rust-lang/crates.io-index".xxhash-rust."0.8.4" { inherit profileName; }; - }; - }); - - "unknown".garage_util."0.7.0" = overridableMkRustCrate (profileName: rec { - name = "garage_util"; - version = "0.7.0"; + version = "0.8.0"; registry = "unknown"; src = fetchCrateLocal (workspaceSrc + "/src/util"); - dependencies = { - blake2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".blake2."0.9.2" { inherit profileName; }; - chrono = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; - err_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; - hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; - netapp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.4.2" { inherit profileName; }; - opentelemetry = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; - rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; - rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; - sha2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.9.9" { inherit profileName; }; - sled = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - toml = rustPackages."registry+https://github.com/rust-lang/crates.io-index".toml."0.5.8" { inherit profileName; }; - tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; - xxhash_rust = rustPackages."registry+https://github.com/rust-lang/crates.io-index".xxhash-rust."0.8.4" { inherit profileName; }; - }; - }); - - "unknown".garage_web."0.7.0" = overridableMkRustCrate (profileName: rec { + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_util") "k2v") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "arc_swap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "async_trait" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "blake2" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".blake2."0.9.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "chrono" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "digest" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".digest."0.10.3" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "err_derive" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "garage_db" else null } = rustPackages."unknown".garage_db."0.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "git_version" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".git-version."0.3.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "hex" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "http" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "hyper" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "lazy_static" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "netapp" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.5.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "opentelemetry" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "rand" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "rmp_serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "serde_json" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "sha2" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.10.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "toml" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".toml."0.5.8" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tracing" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "xxhash_rust" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".xxhash-rust."0.8.4" { inherit profileName; }; + }; + }); + + "unknown".garage_web."0.8.0" = overridableMkRustCrate (profileName: rec { name = "garage_web"; - version = "0.7.0"; + version = "0.8.0"; registry = "unknown"; src = fetchCrateLocal (workspaceSrc + "/src/web"); dependencies = { err_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - garage_api = rustPackages."unknown".garage_api."0.7.0" { inherit profileName; }; - garage_model = rustPackages."unknown".garage_model."0.7.0" { inherit profileName; }; - garage_table = rustPackages."unknown".garage_table."0.7.0" { inherit profileName; }; - garage_util = rustPackages."unknown".garage_util."0.7.0" { inherit profileName; }; + garage_api = rustPackages."unknown".garage_api."0.8.0" { inherit profileName; }; + garage_model = rustPackages."unknown".garage_model."0.8.0" { inherit profileName; }; + garage_table = rustPackages."unknown".garage_table."0.8.0" { inherit profileName; }; + garage_util = rustPackages."unknown".garage_util."0.8.0" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; opentelemetry = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; @@ -1558,6 +1741,9 @@ in version = "0.14.5"; registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"; }; + features = builtins.concatLists [ + [ "more_lengths" ] + ]; dependencies = { typenum = rustPackages."registry+https://github.com/rust-lang/crates.io-index".typenum."1.15.0" { inherit profileName; }; }; @@ -1583,7 +1769,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"; }; features = builtins.concatLists [ - [ "std" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "std") ]; dependencies = { cfg_if = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }; @@ -1612,7 +1798,7 @@ in proc_macro_hack = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro-hack."0.5.19" { profileName = "__noProfile"; }; proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -1622,7 +1808,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b"; }; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; fnv = rustPackages."registry+https://github.com/rust-lang/crates.io-index".fnv."1.0.7" { inherit profileName; }; futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; futures_sink = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-sink."0.3.21" { inherit profileName; }; @@ -1642,8 +1828,24 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"; }; features = builtins.concatLists [ - [ "raw" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "ahash") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "inline-more") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "raw") ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "ahash" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".ahash."0.7.6" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".hashlink."0.7.0" = overridableMkRustCrate (profileName: rec { + name = "hashlink"; + version = "0.7.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"; }; + dependencies = { + hashbrown = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hashbrown."0.11.2" { inherit profileName; }; + }; }); "registry+https://github.com/rust-lang/crates.io-index".heck."0.3.3" = overridableMkRustCrate (profileName: rec { @@ -1656,6 +1858,69 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".heck."0.4.0" = overridableMkRustCrate (profileName: rec { + name = "heck"; + version = "0.4.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"; }; + features = builtins.concatLists [ + [ "default" ] + ]; + }); + + "registry+https://github.com/rust-lang/crates.io-index".heed."0.11.0" = overridableMkRustCrate (profileName: rec { + name = "heed"; + version = "0.11.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "269c7486ed6def5d7b59a427cec3e87b4d4dd4381d01e21c8c9f2d3985688392"; }; + features = builtins.concatLists [ + [ "lmdb" ] + [ "lmdb-rkv-sys" ] + ]; + dependencies = { + bytemuck = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytemuck."1.9.1" { inherit profileName; }; + byteorder = rustPackages."registry+https://github.com/rust-lang/crates.io-index".byteorder."1.4.3" { inherit profileName; }; + heed_traits = rustPackages."registry+https://github.com/rust-lang/crates.io-index".heed-traits."0.8.0" { inherit profileName; }; + heed_types = rustPackages."registry+https://github.com/rust-lang/crates.io-index".heed-types."0.8.0" { inherit profileName; }; + libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + lmdb_sys = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lmdb-rkv-sys."0.11.2" { inherit profileName; }; + once_cell = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; + page_size = rustPackages."registry+https://github.com/rust-lang/crates.io-index".page_size."0.4.2" { inherit profileName; }; + synchronoise = rustPackages."registry+https://github.com/rust-lang/crates.io-index".synchronoise."1.0.0" { inherit profileName; }; + ${ if hostPlatform.isWindows then "url" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".url."2.2.2" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".heed-traits."0.8.0" = overridableMkRustCrate (profileName: rec { + name = "heed-traits"; + version = "0.8.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "a53a94e5b2fd60417e83ffdfe136c39afacff0d4ac1d8d01cd66928ac610e1a2"; }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".heed-types."0.8.0" = overridableMkRustCrate (profileName: rec { + name = "heed-types"; + version = "0.8.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "9a6cf0a6952fcedc992602d5cddd1e3fff091fbe87d38636e3ec23a31f32acbd"; }; + features = builtins.concatLists [ + [ "bincode" ] + [ "default" ] + [ "serde" ] + [ "serde-bincode" ] + [ "serde-json" ] + [ "serde_json" ] + ]; + dependencies = { + bincode = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bincode."1.3.3" { inherit profileName; }; + bytemuck = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytemuck."1.9.1" { inherit profileName; }; + byteorder = rustPackages."registry+https://github.com/rust-lang/crates.io-index".byteorder."1.4.3" { inherit profileName; }; + heed_traits = rustPackages."registry+https://github.com/rust-lang/crates.io-index".heed-traits."0.8.0" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".hermit-abi."0.1.19" = overridableMkRustCrate (profileName: rec { name = "hermit-abi"; version = "0.1.19"; @@ -1692,24 +1957,34 @@ in }; }); - "registry+https://github.com/rust-lang/crates.io-index".hmac."0.10.1" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".hmac."0.11.0" = overridableMkRustCrate (profileName: rec { name = "hmac"; - version = "0.10.1"; + version = "0.11.0"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"; }; + src = fetchCratesIo { inherit name version; sha256 = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"; }; dependencies = { - crypto_mac = rustPackages."registry+https://github.com/rust-lang/crates.io-index".crypto-mac."0.10.1" { inherit profileName; }; + crypto_mac = rustPackages."registry+https://github.com/rust-lang/crates.io-index".crypto-mac."0.11.1" { inherit profileName; }; digest = rustPackages."registry+https://github.com/rust-lang/crates.io-index".digest."0.9.0" { inherit profileName; }; }; }); + "registry+https://github.com/rust-lang/crates.io-index".hmac."0.12.1" = overridableMkRustCrate (profileName: rec { + name = "hmac"; + version = "0.12.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"; }; + dependencies = { + digest = rustPackages."registry+https://github.com/rust-lang/crates.io-index".digest."0.10.3" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" = overridableMkRustCrate (profileName: rec { name = "http"; version = "0.2.6"; registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"; }; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; fnv = rustPackages."registry+https://github.com/rust-lang/crates.io-index".fnv."1.0.7" { inherit profileName; }; itoa = rustPackages."registry+https://github.com/rust-lang/crates.io-index".itoa."1.0.1" { inherit profileName; }; }; @@ -1721,7 +1996,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"; }; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; }; @@ -1775,35 +2050,35 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2"; }; features = builtins.concatLists [ - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "client") - [ "default" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "full") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "h2") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "http1") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "http2") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "runtime") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "server") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "socket2") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "stream") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "tcp") - ]; - dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; - futures_channel = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-channel."0.3.21" { inherit profileName; }; - futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; - futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "h2" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".h2."0.3.12" { inherit profileName; }; - http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; - http_body = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-body."0.4.4" { inherit profileName; }; - httparse = rustPackages."registry+https://github.com/rust-lang/crates.io-index".httparse."1.6.0" { inherit profileName; }; - httpdate = rustPackages."registry+https://github.com/rust-lang/crates.io-index".httpdate."1.0.2" { inherit profileName; }; - itoa = rustPackages."registry+https://github.com/rust-lang/crates.io-index".itoa."1.0.1" { inherit profileName; }; - pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "socket2" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".socket2."0.4.4" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - tower_service = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-service."0.3.1" { inherit profileName; }; - tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; - want = rustPackages."registry+https://github.com/rust-lang/crates.io-index".want."0.3.0" { inherit profileName; }; + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "client") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "default") + (lib.optional (rootFeatures' ? "garage") "full") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "k2v-client") "h2") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "http1") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "k2v-client") "http2") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "runtime") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_web") "server") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "socket2") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "stream") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "tcp") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_channel" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-channel."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_core" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "k2v-client" then "h2" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".h2."0.3.12" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "http" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "http_body" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-body."0.4.4" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "httparse" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".httparse."1.6.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "httpdate" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".httpdate."1.0.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "itoa" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".itoa."1.0.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "pin_project_lite" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "socket2" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".socket2."0.4.4" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tower_service" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-service."0.3.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tracing" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "want" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".want."0.3.0" { inherit profileName; }; }; }); @@ -1851,7 +2126,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"; }; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; native_tls = rustPackages."registry+https://github.com/rust-lang/crates.io-index".native-tls."0.2.8" { inherit profileName; }; tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; @@ -1914,7 +2189,23 @@ in [ "serde" ] ]; dependencies = { - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".isolang."1.0.0" = overridableMkRustCrate (profileName: rec { + name = "isolang"; + version = "1.0.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "265ef164908329e47e753c769b14cbb27434abf0c41984dca201484022f09ce5"; }; + features = builtins.concatLists [ + [ "default" ] + ]; + dependencies = { + phf = rustPackages."registry+https://github.com/rust-lang/crates.io-index".phf."0.7.24" { inherit profileName; }; + }; + buildDependencies = { + phf_codegen = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".phf_codegen."0.7.24" { profileName = "__noProfile"; }; }; }); @@ -1978,8 +2269,8 @@ in [ "treediff" ] ]; dependencies = { - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; treediff = rustPackages."registry+https://github.com/rust-lang/crates.io-index".treediff."3.0.2" { inherit profileName; }; }; }); @@ -1991,8 +2282,34 @@ in src = fetchCratesIo { inherit name version; sha256 = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f"; }; dependencies = { log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + }; + }); + + "unknown".k2v-client."0.0.1" = overridableMkRustCrate (profileName: rec { + name = "k2v-client"; + version = "0.0.1"; + registry = "unknown"; + src = fetchCrateLocal (workspaceSrc + "/src/k2v-client"); + features = builtins.concatLists [ + [ "clap" ] + [ "cli" ] + [ "garage_util" ] + ]; + dependencies = { + base64 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; + clap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".clap."3.1.18" { inherit profileName; }; + garage_util = rustPackages."unknown".garage_util."0.8.0" { inherit profileName; }; + http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; + log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; + rusoto_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rusoto_core."0.48.0" { inherit profileName; }; + rusoto_credential = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rusoto_credential."0.48.0" { inherit profileName; }; + rusoto_signature = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rusoto_signature."0.48.0" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; + tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; }; }); @@ -2011,13 +2328,13 @@ in ]; dependencies = { base64 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; chrono = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; percent_encoding = rustPackages."registry+https://github.com/rust-lang/crates.io-index".percent-encoding."2.1.0" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; serde_value = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde-value."0.7.0" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; url = rustPackages."registry+https://github.com/rust-lang/crates.io-index".url."2.2.2" { inherit profileName; }; }; }); @@ -2082,7 +2399,7 @@ in ]; dependencies = { base64 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; chrono = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; dirs = rustPackages."registry+https://github.com/rust-lang/crates.io-index".dirs-next."2.0.0" { inherit profileName; }; either = rustPackages."registry+https://github.com/rust-lang/crates.io-index".either."1.6.1" { inherit profileName; }; @@ -2098,10 +2415,10 @@ in openssl = rustPackages."registry+https://github.com/rust-lang/crates.io-index".openssl."0.10.38" { inherit profileName; }; pem = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pem."0.8.3" { inherit profileName; }; pin_project = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.10" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; serde_yaml = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_yaml."0.8.23" { inherit profileName; }; - thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; tokio_native_tls = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-native-tls."0.3.0" { inherit profileName; }; tokio_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.6.9" { inherit profileName; }; @@ -2127,9 +2444,9 @@ in json_patch = rustPackages."registry+https://github.com/rust-lang/crates.io-index".json-patch."0.2.6" { inherit profileName; }; k8s_openapi = rustPackages."registry+https://github.com/rust-lang/crates.io-index".k8s-openapi."0.13.1" { inherit profileName; }; once_cell = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; - thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; }; }); @@ -2146,8 +2463,8 @@ in darling = rustPackages."registry+https://github.com/rust-lang/crates.io-index".darling."0.13.1" { inherit profileName; }; proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -2164,8 +2481,8 @@ in k8s_openapi = rustPackages."registry+https://github.com/rust-lang/crates.io-index".k8s-openapi."0.13.1" { inherit profileName; }; kube_client = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kube-client."0.62.0" { inherit profileName; }; pin_project = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.10" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; smallvec = rustPackages."registry+https://github.com/rust-lang/crates.io-index".smallvec."1.8.0" { inherit profileName; }; snafu = rustPackages."registry+https://github.com/rust-lang/crates.io-index".snafu."0.6.10" { inherit profileName; }; tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; @@ -2189,7 +2506,7 @@ in hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; sodiumoxide = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; }; log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; - thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; }; }); @@ -2199,14 +2516,15 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "ae0f8eafdd240b722243787b51fdaf8df6693fb8621d0f7061cdba574214cf88"; }; features = builtins.concatLists [ - [ "default" ] - [ "serde" ] - [ "std" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "serde") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "std") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "use-pkg-config") ]; dependencies = { - libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; - libsodium_sys = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libsodium-sys."0.2.7" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "libsodium_sys" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libsodium-sys."0.2.7" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; }; }); @@ -2233,14 +2551,38 @@ in version = "0.2.7"; registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "6b779387cd56adfbc02ea4a668e704f729be8d6a6abd2c27ca5ee537849a92fd"; }; + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "use-pkg-config") + ]; dependencies = { - libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; }; buildDependencies = { - ${ if !(hostPlatform.parsed.abi.name == "msvc") then "cc" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" { profileName = "__noProfile"; }; - ${ if hostPlatform.parsed.abi.name == "msvc" then "libc" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { profileName = "__noProfile"; }; - pkg_config = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".pkg-config."0.3.24" { profileName = "__noProfile"; }; - walkdir = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".walkdir."2.3.2" { profileName = "__noProfile"; }; + ${ if (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") && !(hostPlatform.parsed.abi.name == "msvc") then "cc" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" { profileName = "__noProfile"; }; + ${ if (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") && hostPlatform.parsed.abi.name == "msvc" then "libc" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "pkg_config" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".pkg-config."0.3.24" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "walkdir" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".walkdir."2.3.2" { profileName = "__noProfile"; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".libsqlite3-sys."0.24.2" = overridableMkRustCrate (profileName: rec { + name = "libsqlite3-sys"; + version = "0.24.2"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"; }; + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "bundled") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "bundled_bindings") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "cc") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "min_sqlite_version_3_6_8") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "pkg-config") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "vcpkg") + ]; + buildDependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" then "cc" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "pkg_config" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".pkg-config."0.3.24" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "vcpkg" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".vcpkg."0.2.15" { profileName = "__noProfile"; }; }; }); @@ -2251,6 +2593,23 @@ in src = fetchCratesIo { inherit name version; sha256 = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"; }; }); + "registry+https://github.com/rust-lang/crates.io-index".lmdb-rkv-sys."0.11.2" = overridableMkRustCrate (profileName: rec { + name = "lmdb-rkv-sys"; + version = "0.11.2"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "61b9ce6b3be08acefa3003c57b7565377432a89ec24476bbe72e11d101f852fe"; }; + features = builtins.concatLists [ + [ "default" ] + ]; + dependencies = { + libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + }; + buildDependencies = { + cc = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" { profileName = "__noProfile"; }; + pkg_config = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".pkg-config."0.3.24" { profileName = "__noProfile"; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".lock_api."0.4.6" = overridableMkRustCrate (profileName: rec { name = "lock_api"; version = "0.4.6"; @@ -2267,13 +2626,23 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"; }; features = builtins.concatLists [ - (lib.optional (rootFeatures' ? "garage") "std") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "std") ]; dependencies = { cfg_if = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }; }; }); + "registry+https://github.com/rust-lang/crates.io-index".matchers."0.1.0" = overridableMkRustCrate (profileName: rec { + name = "matchers"; + version = "0.1.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"; }; + dependencies = { + regex_automata = rustPackages."registry+https://github.com/rust-lang/crates.io-index".regex-automata."0.1.10" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".matches."0.1.9" = overridableMkRustCrate (profileName: rec { name = "matches"; version = "0.1.9"; @@ -2297,6 +2666,20 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".md-5."0.10.1" = overridableMkRustCrate (profileName: rec { + name = "md-5"; + version = "0.10.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582"; }; + features = builtins.concatLists [ + [ "default" ] + [ "std" ] + ]; + dependencies = { + digest = rustPackages."registry+https://github.com/rust-lang/crates.io-index".digest."0.10.3" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".md5."0.7.0" = overridableMkRustCrate (profileName: rec { name = "md5"; version = "0.7.0"; @@ -2361,7 +2744,7 @@ in [ "os-poll" ] ]; dependencies = { - ${ if hostPlatform.parsed.kernel.name == "wasi" || hostPlatform.isUnix then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if hostPlatform.isUnix || hostPlatform.parsed.kernel.name == "wasi" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; ${ if hostPlatform.isWindows then "miow" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".miow."0.3.7" { inherit profileName; }; ${ if hostPlatform.isWindows then "ntapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".ntapi."0.3.7" { inherit profileName; }; @@ -2380,6 +2763,16 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".mktemp."0.4.1" = overridableMkRustCrate (profileName: rec { + name = "mktemp"; + version = "0.4.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "975de676448231fcde04b9149d2543077e166b78fc29eae5aa219e7928410da2"; }; + dependencies = { + uuid = rustPackages."registry+https://github.com/rust-lang/crates.io-index".uuid."0.8.2" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".multer."2.0.2" = overridableMkRustCrate (profileName: rec { name = "multer"; version = "2.0.2"; @@ -2389,7 +2782,7 @@ in [ "default" ] ]; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; encoding_rs = rustPackages."registry+https://github.com/rust-lang/crates.io-index".encoding_rs."0.8.30" { inherit profileName; }; futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; @@ -2430,63 +2823,37 @@ in }; }); - "registry+https://github.com/rust-lang/crates.io-index".netapp."0.3.1" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".netapp."0.5.2" = overridableMkRustCrate (profileName: rec { name = "netapp"; - version = "0.3.1"; - registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "ac7dcd2c6c5a9d5ea88ffc17d3339d49d308e68c4d8190c494b55ddbf78d80ad"; }; - features = builtins.concatLists [ - [ "default" ] - ]; - dependencies = { - arc_swap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; - async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."0.6.0" { inherit profileName; }; - err_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.2.4" { profileName = "__noProfile"; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - kuska_handshake = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-handshake."0.2.0" { inherit profileName; }; - sodiumoxide = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; }; - log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; - rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.14.4" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - tokio_stream = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-stream."0.1.8" { inherit profileName; }; - tokio_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.6.9" { inherit profileName; }; - }; - }); - - "registry+https://github.com/rust-lang/crates.io-index".netapp."0.4.2" = overridableMkRustCrate (profileName: rec { - name = "netapp"; - version = "0.4.2"; + version = "0.5.2"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "d1a19af9ad24e6cdb166e2c5882a7ff9326cab8d45c9d82379fa638c0cbc84df"; }; + src = fetchCratesIo { inherit name version; sha256 = "4ffe47ac46d3b2ce2f736a70865492df082e042eb2bfdddfca3b8dd66bd9469d"; }; features = builtins.concatLists [ - [ "default" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "default") (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "opentelemetry") (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "opentelemetry-contrib") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "rand") (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "telemetry") ]; dependencies = { - arc_swap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; - async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."0.6.0" { inherit profileName; }; - cfg_if = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }; - err_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.2.4" { profileName = "__noProfile"; }; - futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; - hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; - kuska_handshake = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-handshake."0.2.0" { inherit profileName; }; - sodiumoxide = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; }; - log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "arc_swap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "async_trait" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "cfg_if" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "err_derive" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "hex" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "kuska_handshake" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-handshake."0.2.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "sodiumoxide" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "log" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "opentelemetry" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "opentelemetry_contrib" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-contrib."0.9.0" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "rand" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.5.6" { inherit profileName; }; - rmp_serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.14.4" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - tokio_stream = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-stream."0.1.8" { inherit profileName; }; - tokio_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.6.9" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "pin_project" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.10" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "rand" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "rmp_serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "serde" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tokio_stream" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-stream."0.1.8" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tokio_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.0" { inherit profileName; }; }; }); @@ -2530,10 +2897,10 @@ in (lib.optional (rootFeatures' ? "garage") "std") ]; dependencies = { - num_traits = rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "num_traits" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" { inherit profileName; }; }; buildDependencies = { - autocfg = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "autocfg" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }; }; }); @@ -2598,15 +2965,15 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"; }; features = builtins.concatLists [ - [ "vendored" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "vendored") ]; dependencies = { - bitflags = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" { inherit profileName; }; - cfg_if = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }; - foreign_types = rustPackages."registry+https://github.com/rust-lang/crates.io-index".foreign-types."0.3.2" { inherit profileName; }; - libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; - once_cell = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; - ffi = rustPackages."registry+https://github.com/rust-lang/crates.io-index".openssl-sys."0.9.72" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "bitflags" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "cfg_if" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "foreign_types" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".foreign-types."0.3.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "once_cell" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "ffi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".openssl-sys."0.9.72" { inherit profileName; }; }; }); @@ -2636,18 +3003,18 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"; }; features = builtins.concatLists [ - [ "openssl-src" ] - [ "vendored" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "openssl-src") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "vendored") ]; dependencies = { - libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; }; buildDependencies = { - autocfg = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }; - cc = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" { profileName = "__noProfile"; }; - openssl_src = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".openssl-src."111.18.0+1.1.1n" { profileName = "__noProfile"; }; - pkg_config = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".pkg-config."0.3.24" { profileName = "__noProfile"; }; - ${ if hostPlatform.parsed.abi.name == "msvc" then "vcpkg" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".vcpkg."0.2.15" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "autocfg" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "cc" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "openssl_src" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".openssl-src."111.18.0+1.1.1n" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client" then "pkg_config" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".pkg-config."0.3.24" { profileName = "__noProfile"; }; + ${ if (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") && hostPlatform.parsed.abi.name == "msvc" then "vcpkg" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".vcpkg."0.2.15" { profileName = "__noProfile"; }; }; }); @@ -2684,7 +3051,7 @@ in percent_encoding = rustPackages."registry+https://github.com/rust-lang/crates.io-index".percent-encoding."2.1.0" { inherit profileName; }; pin_project = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.10" { inherit profileName; }; rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; - thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; tokio_stream = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-stream."0.1.8" { inherit profileName; }; }; @@ -2723,7 +3090,7 @@ in http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; opentelemetry = rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }; prost = rustPackages."registry+https://github.com/rust-lang/crates.io-index".prost."0.9.0" { inherit profileName; }; - thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; tonic = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tonic."0.6.2" { inherit profileName; }; }; @@ -2758,6 +3125,27 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".os_str_bytes."6.0.1" = overridableMkRustCrate (profileName: rec { + name = "os_str_bytes"; + version = "6.0.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "029d8d0b2f198229de29dca79676f2738ff952edf3fde542eb8bf94d8c21b435"; }; + features = builtins.concatLists [ + [ "raw_os_str" ] + ]; + }); + + "registry+https://github.com/rust-lang/crates.io-index".page_size."0.4.2" = overridableMkRustCrate (profileName: rec { + name = "page_size"; + version = "0.4.2"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd"; }; + dependencies = { + ${ if hostPlatform.isUnix then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if hostPlatform.isWindows then "winapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.11.2" = overridableMkRustCrate (profileName: rec { name = "parking_lot"; version = "0.11.2"; @@ -2846,6 +3234,48 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".phf."0.7.24" = overridableMkRustCrate (profileName: rec { + name = "phf"; + version = "0.7.24"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"; }; + dependencies = { + phf_shared = rustPackages."registry+https://github.com/rust-lang/crates.io-index".phf_shared."0.7.24" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".phf_codegen."0.7.24" = overridableMkRustCrate (profileName: rec { + name = "phf_codegen"; + version = "0.7.24"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e"; }; + dependencies = { + phf_generator = rustPackages."registry+https://github.com/rust-lang/crates.io-index".phf_generator."0.7.24" { inherit profileName; }; + phf_shared = rustPackages."registry+https://github.com/rust-lang/crates.io-index".phf_shared."0.7.24" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".phf_generator."0.7.24" = overridableMkRustCrate (profileName: rec { + name = "phf_generator"; + version = "0.7.24"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"; }; + dependencies = { + phf_shared = rustPackages."registry+https://github.com/rust-lang/crates.io-index".phf_shared."0.7.24" { inherit profileName; }; + rand = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.6.5" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".phf_shared."0.7.24" = overridableMkRustCrate (profileName: rec { + name = "phf_shared"; + version = "0.7.24"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"; }; + dependencies = { + siphasher = rustPackages."registry+https://github.com/rust-lang/crates.io-index".siphasher."0.2.3" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".pin-project."0.4.29" = overridableMkRustCrate (profileName: rec { name = "pin-project"; version = "0.4.29"; @@ -2874,7 +3304,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -2886,7 +3316,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -2979,7 +3409,7 @@ in proc_macro_error_attr = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro-error-attr."1.0.4" { profileName = "__noProfile"; }; proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; buildDependencies = { version_check = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".version_check."0.9.4" { profileName = "__noProfile"; }; @@ -3037,7 +3467,7 @@ in memchr = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; parking_lot = rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.11.2" { inherit profileName; }; protobuf = rustPackages."registry+https://github.com/rust-lang/crates.io-index".protobuf."2.27.1" { inherit profileName; }; - thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; }; }); @@ -3052,7 +3482,7 @@ in [ "std" ] ]; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; prost_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".prost-derive."0.9.0" { profileName = "__noProfile"; }; }; }); @@ -3063,7 +3493,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5"; }; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; heck = rustPackages."registry+https://github.com/rust-lang/crates.io-index".heck."0.3.3" { inherit profileName; }; itertools = rustPackages."registry+https://github.com/rust-lang/crates.io-index".itertools."0.10.3" { inherit profileName; }; lazy_static = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }; @@ -3090,7 +3520,7 @@ in itertools = rustPackages."registry+https://github.com/rust-lang/crates.io-index".itertools."0.10.3" { inherit profileName; }; proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -3100,7 +3530,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a"; }; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; prost = rustPackages."registry+https://github.com/rust-lang/crates.io-index".prost."0.9.0" { inherit profileName; }; }; }); @@ -3131,7 +3561,7 @@ in ]; dependencies = { memchr = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; }; }); @@ -3149,27 +3579,32 @@ in }; }); - "registry+https://github.com/rust-lang/crates.io-index".rand."0.5.6" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".rand."0.6.5" = overridableMkRustCrate (profileName: rec { name = "rand"; - version = "0.5.6"; + version = "0.6.5"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"; }; + src = fetchCratesIo { inherit name version; sha256 = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"; }; features = builtins.concatLists [ [ "alloc" ] - [ "cloudabi" ] [ "default" ] - [ "fuchsia-cprng" ] - [ "libc" ] + [ "rand_os" ] [ "std" ] - [ "winapi" ] ]; dependencies = { - ${ if hostPlatform.parsed.kernel.name == "cloudabi" then "cloudabi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cloudabi."0.0.3" { inherit profileName; }; - ${ if hostPlatform.parsed.kernel.name == "fuchsia" then "fuchsia_cprng" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".fuchsia-cprng."0.1.1" { inherit profileName; }; ${ if hostPlatform.isUnix then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; - rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.3.1" { inherit profileName; }; + rand_chacha = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_chacha."0.1.1" { inherit profileName; }; + rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.4.2" { inherit profileName; }; + rand_hc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_hc."0.1.0" { inherit profileName; }; + rand_isaac = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_isaac."0.1.1" { inherit profileName; }; + rand_jitter = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_jitter."0.1.4" { inherit profileName; }; + rand_os = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_os."0.1.3" { inherit profileName; }; + rand_pcg = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_pcg."0.1.2" { inherit profileName; }; + rand_xorshift = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_xorshift."0.1.1" { inherit profileName; }; ${ if hostPlatform.isWindows then "winapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }; }; + buildDependencies = { + autocfg = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."0.1.8" { profileName = "__noProfile"; }; + }; }); "registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" = overridableMkRustCrate (profileName: rec { @@ -3178,19 +3613,32 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"; }; features = builtins.concatLists [ - [ "alloc" ] - [ "default" ] - [ "getrandom" ] - [ "libc" ] - [ "rand_chacha" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "small_rng") - [ "std" ] - [ "std_rng" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "alloc") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "getrandom") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "libc") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "rand_chacha") + (lib.optional (rootFeatures' ? "garage") "small_rng") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "std") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "std_rng") ]; dependencies = { - ${ if hostPlatform.isUnix then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; - rand_chacha = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_chacha."0.3.1" { inherit profileName; }; - rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.6.3" { inherit profileName; }; + ${ if (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") && hostPlatform.isUnix then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "rand_chacha" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_chacha."0.3.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "rand_core" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.6.3" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rand_chacha."0.1.1" = overridableMkRustCrate (profileName: rec { + name = "rand_chacha"; + version = "0.1.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"; }; + dependencies = { + rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.3.1" { inherit profileName; }; + }; + buildDependencies = { + autocfg = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."0.1.8" { profileName = "__noProfile"; }; }; }); @@ -3213,10 +3661,6 @@ in version = "0.3.1"; registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"; }; - features = builtins.concatLists [ - [ "alloc" ] - [ "std" ] - ]; dependencies = { rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.4.2" { inherit profileName; }; }; @@ -3248,6 +3692,93 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".rand_hc."0.1.0" = overridableMkRustCrate (profileName: rec { + name = "rand_hc"; + version = "0.1.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"; }; + dependencies = { + rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.3.1" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rand_isaac."0.1.1" = overridableMkRustCrate (profileName: rec { + name = "rand_isaac"; + version = "0.1.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"; }; + dependencies = { + rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.3.1" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rand_jitter."0.1.4" = overridableMkRustCrate (profileName: rec { + name = "rand_jitter"; + version = "0.1.4"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"; }; + features = builtins.concatLists [ + [ "std" ] + ]; + dependencies = { + ${ if hostPlatform.parsed.kernel.name == "darwin" || hostPlatform.parsed.kernel.name == "ios" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.4.2" { inherit profileName; }; + ${ if hostPlatform.parsed.kernel.name == "windows" then "winapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rand_os."0.1.3" = overridableMkRustCrate (profileName: rec { + name = "rand_os"; + version = "0.1.3"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"; }; + dependencies = { + ${ if hostPlatform.parsed.kernel.name == "cloudabi" then "cloudabi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cloudabi."0.0.3" { inherit profileName; }; + ${ if hostPlatform.parsed.kernel.name == "fuchsia" then "fuchsia_cprng" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".fuchsia-cprng."0.1.1" { inherit profileName; }; + ${ if hostPlatform.isUnix then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.4.2" { inherit profileName; }; + ${ if hostPlatform.parsed.abi.name == "sgx" then "rdrand" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rdrand."0.4.0" { inherit profileName; }; + ${ if hostPlatform.isWindows then "winapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rand_pcg."0.1.2" = overridableMkRustCrate (profileName: rec { + name = "rand_pcg"; + version = "0.1.2"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"; }; + dependencies = { + rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.4.2" { inherit profileName; }; + }; + buildDependencies = { + autocfg = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."0.1.8" { profileName = "__noProfile"; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rand_xorshift."0.1.1" = overridableMkRustCrate (profileName: rec { + name = "rand_xorshift"; + version = "0.1.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"; }; + dependencies = { + rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.3.1" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rdrand."0.4.0" = overridableMkRustCrate (profileName: rec { + name = "rdrand"; + version = "0.4.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"; }; + features = builtins.concatLists [ + [ "default" ] + [ "std" ] + ]; + dependencies = { + rand_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand_core."0.3.1" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".redox_syscall."0.2.11" = overridableMkRustCrate (profileName: rec { name = "redox_syscall"; version = "0.2.11"; @@ -3266,7 +3797,7 @@ in dependencies = { getrandom = rustPackages."registry+https://github.com/rust-lang/crates.io-index".getrandom."0.2.5" { inherit profileName; }; syscall = rustPackages."registry+https://github.com/rust-lang/crates.io-index".redox_syscall."0.2.11" { inherit profileName; }; - thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" { inherit profileName; }; + thiserror = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" { inherit profileName; }; }; }); @@ -3276,27 +3807,42 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"; }; features = builtins.concatLists [ - [ "aho-corasick" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "aho-corasick") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "memchr") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "perf") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "perf-cache") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "perf-dfa") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "perf-inline") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "perf-literal") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_rpc") "std") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "unicode") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "unicode-age") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "unicode-bool") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "unicode-case") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "unicode-gencat") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "unicode-perl") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "unicode-script") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "unicode-segment") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" then "aho_corasick" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aho-corasick."0.7.18" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" then "memchr" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_rpc" then "regex_syntax" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".regex-syntax."0.6.25" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".regex-automata."0.1.10" = overridableMkRustCrate (profileName: rec { + name = "regex-automata"; + version = "0.1.10"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"; }; + features = builtins.concatLists [ [ "default" ] - [ "memchr" ] - [ "perf" ] - [ "perf-cache" ] - [ "perf-dfa" ] - [ "perf-inline" ] - [ "perf-literal" ] + [ "regex-syntax" ] [ "std" ] - [ "unicode" ] - [ "unicode-age" ] - [ "unicode-bool" ] - [ "unicode-case" ] - [ "unicode-gencat" ] - [ "unicode-perl" ] - [ "unicode-script" ] - [ "unicode-segment" ] ]; dependencies = { - aho_corasick = rustPackages."registry+https://github.com/rust-lang/crates.io-index".aho-corasick."0.7.18" { inherit profileName; }; - memchr = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; regex_syntax = rustPackages."registry+https://github.com/rust-lang/crates.io-index".regex-syntax."0.6.25" { inherit profileName; }; }; }); @@ -3342,7 +3888,7 @@ in ]; dependencies = { ${ if hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "linux" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; - ${ if hostPlatform.parsed.kernel.name == "dragonfly" || hostPlatform.parsed.kernel.name == "freebsd" || hostPlatform.parsed.kernel.name == "illumos" || hostPlatform.parsed.kernel.name == "netbsd" || hostPlatform.parsed.kernel.name == "openbsd" || hostPlatform.parsed.kernel.name == "solaris" || hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "linux" then "once_cell" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; + ${ if hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "dragonfly" || hostPlatform.parsed.kernel.name == "freebsd" || hostPlatform.parsed.kernel.name == "illumos" || hostPlatform.parsed.kernel.name == "netbsd" || hostPlatform.parsed.kernel.name == "openbsd" || hostPlatform.parsed.kernel.name == "solaris" then "once_cell" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; ${ if hostPlatform.parsed.cpu.name == "i686" || hostPlatform.parsed.cpu.name == "x86_64" || (hostPlatform.parsed.cpu.name == "aarch64" || hostPlatform.parsed.cpu.name == "armv6l" || hostPlatform.parsed.cpu.name == "armv7l") && (hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "fuchsia" || hostPlatform.parsed.kernel.name == "linux") then "spin" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".spin."0.5.2" { inherit profileName; }; untrusted = rustPackages."registry+https://github.com/rust-lang/crates.io-index".untrusted."0.7.1" { inherit profileName; }; ${ if hostPlatform.parsed.cpu.name == "wasm32" && hostPlatform.parsed.vendor.name == "unknown" && hostPlatform.parsed.kernel.name == "unknown" && hostPlatform.parsed.abi.name == "" then "web_sys" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".web-sys."0.3.56" { inherit profileName; }; @@ -3364,18 +3910,6 @@ in }; }); - "registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.14.4" = overridableMkRustCrate (profileName: rec { - name = "rmp-serde"; - version = "0.14.4"; - registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8"; }; - dependencies = { - byteorder = rustPackages."registry+https://github.com/rust-lang/crates.io-index".byteorder."1.4.3" { inherit profileName; }; - rmp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp."0.8.10" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - }; - }); - "registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" = overridableMkRustCrate (profileName: rec { name = "rmp-serde"; version = "0.15.5"; @@ -3384,7 +3918,7 @@ in dependencies = { byteorder = rustPackages."registry+https://github.com/rust-lang/crates.io-index".byteorder."1.4.3" { inherit profileName; }; rmp = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp."0.8.10" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; }; }); @@ -3402,6 +3936,107 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".rusoto_core."0.48.0" = overridableMkRustCrate (profileName: rec { + name = "rusoto_core"; + version = "0.48.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2"; }; + features = builtins.concatLists [ + [ "default" ] + [ "hyper-tls" ] + [ "native-tls" ] + ]; + dependencies = { + async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; + base64 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + crc32fast = rustPackages."registry+https://github.com/rust-lang/crates.io-index".crc32fast."1.3.2" { inherit profileName; }; + futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; + http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; + hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; + hyper_tls = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper-tls."0.5.0" { inherit profileName; }; + lazy_static = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }; + log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; + rusoto_credential = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rusoto_credential."0.48.0" { inherit profileName; }; + rusoto_signature = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rusoto_signature."0.48.0" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + xml = rustPackages."registry+https://github.com/rust-lang/crates.io-index".xml-rs."0.8.4" { inherit profileName; }; + }; + buildDependencies = { + rustc_version = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".rustc_version."0.4.0" { profileName = "__noProfile"; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rusoto_credential."0.48.0" = overridableMkRustCrate (profileName: rec { + name = "rusoto_credential"; + version = "0.48.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05"; }; + dependencies = { + async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; + chrono = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; + dirs_next = rustPackages."registry+https://github.com/rust-lang/crates.io-index".dirs-next."2.0.0" { inherit profileName; }; + futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; + hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; + shlex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".shlex."1.1.0" { inherit profileName; }; + tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + zeroize = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zeroize."1.5.4" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rusoto_signature."0.48.0" = overridableMkRustCrate (profileName: rec { + name = "rusoto_signature"; + version = "0.48.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272"; }; + dependencies = { + base64 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + chrono = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; + digest = rustPackages."registry+https://github.com/rust-lang/crates.io-index".digest."0.9.0" { inherit profileName; }; + futures = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }; + hex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }; + hmac = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hmac."0.11.0" { inherit profileName; }; + http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; + hyper = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }; + log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; + md5 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".md-5."0.9.1" { inherit profileName; }; + percent_encoding = rustPackages."registry+https://github.com/rust-lang/crates.io-index".percent-encoding."2.1.0" { inherit profileName; }; + pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; + rusoto_credential = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rusoto_credential."0.48.0" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + sha2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.9.9" { inherit profileName; }; + tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + }; + buildDependencies = { + rustc_version = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".rustc_version."0.4.0" { profileName = "__noProfile"; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".rusqlite."0.27.0" = overridableMkRustCrate (profileName: rec { + name = "rusqlite"; + version = "0.27.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a"; }; + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "bundled") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db") "modern_sqlite") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "bitflags" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "fallible_iterator" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".fallible-iterator."0.2.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "fallible_streaming_iterator" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".fallible-streaming-iterator."0.1.9" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "hashlink" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".hashlink."0.7.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "libsqlite3_sys" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libsqlite3-sys."0.24.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "memchr" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model" then "smallvec" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".smallvec."1.8.0" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".rustc_version."0.4.0" = overridableMkRustCrate (profileName: rec { name = "rustc_version"; version = "0.4.0"; @@ -3496,8 +4131,8 @@ in dependencies = { dyn_clone = rustPackages."registry+https://github.com/rust-lang/crates.io-index".dyn-clone."1.0.5" { inherit profileName; }; schemars_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".schemars_derive."0.8.8" { profileName = "__noProfile"; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; }; }); @@ -3510,7 +4145,7 @@ in proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; serde_derive_internals = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_derive_internals."0.25.0" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -3556,12 +4191,12 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"; }; features = builtins.concatLists [ - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "OSX_10_9") - (lib.optional (rootFeatures' ? "garage_rpc") "default") + [ "OSX_10_9" ] + [ "default" ] ]; dependencies = { - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "core_foundation_sys" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".core-foundation-sys."0.8.3" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + core_foundation_sys = rustPackages."registry+https://github.com/rust-lang/crates.io-index".core-foundation-sys."0.8.3" { inherit profileName; }; + libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; }; }); @@ -3576,20 +4211,20 @@ in ]; }); - "registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" = overridableMkRustCrate (profileName: rec { name = "serde"; - version = "1.0.136"; + version = "1.0.137"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"; }; + src = fetchCratesIo { inherit name version; sha256 = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"; }; features = builtins.concatLists [ [ "default" ] - [ "derive" ] - [ "rc" ] - [ "serde_derive" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "derive") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "rc") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "serde_derive") [ "std" ] ]; dependencies = { - serde_derive = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_derive."1.0.136" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "serde_derive" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_derive."1.0.137" { profileName = "__noProfile"; }; }; }); @@ -3600,7 +4235,7 @@ in src = fetchCratesIo { inherit name version; sha256 = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"; }; dependencies = { ordered_float = rustPackages."registry+https://github.com/rust-lang/crates.io-index".ordered-float."2.10.0" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; }; }); @@ -3614,22 +4249,22 @@ in [ "std" ] ]; dependencies = { - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; }; }); - "registry+https://github.com/rust-lang/crates.io-index".serde_derive."1.0.136" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".serde_derive."1.0.137" = overridableMkRustCrate (profileName: rec { name = "serde_derive"; - version = "1.0.136"; + version = "1.0.137"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"; }; + src = fetchCratesIo { inherit name version; sha256 = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"; }; features = builtins.concatLists [ [ "default" ] ]; dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -3641,26 +4276,26 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); - "registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" = overridableMkRustCrate (profileName: rec { name = "serde_json"; - version = "1.0.79"; + version = "1.0.81"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"; }; + src = fetchCratesIo { inherit name version; sha256 = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"; }; features = builtins.concatLists [ [ "default" ] - (lib.optional (rootFeatures' ? "garage_rpc") "indexmap") - (lib.optional (rootFeatures' ? "garage_rpc") "preserve_order") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "indexmap") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "preserve_order") [ "std" ] ]; dependencies = { - ${ if rootFeatures' ? "garage_rpc" then "indexmap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".indexmap."1.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "indexmap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".indexmap."1.8.0" { inherit profileName; }; itoa = rustPackages."registry+https://github.com/rust-lang/crates.io-index".itoa."1.0.1" { inherit profileName; }; ryu = rustPackages."registry+https://github.com/rust-lang/crates.io-index".ryu."1.0.9" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; }; }); @@ -3672,7 +4307,7 @@ in dependencies = { indexmap = rustPackages."registry+https://github.com/rust-lang/crates.io-index".indexmap."1.8.0" { inherit profileName; }; ryu = rustPackages."registry+https://github.com/rust-lang/crates.io-index".ryu."1.0.9" { inherit profileName; }; - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; yaml_rust = rustPackages."registry+https://github.com/rust-lang/crates.io-index".yaml-rust."0.4.5" { inherit profileName; }; }; }); @@ -3695,6 +4330,43 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".sha2."0.10.2" = overridableMkRustCrate (profileName: rec { + name = "sha2"; + version = "0.10.2"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"; }; + features = builtins.concatLists [ + [ "default" ] + [ "std" ] + ]; + dependencies = { + cfg_if = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }; + ${ if hostPlatform.parsed.cpu.name == "aarch64" || hostPlatform.parsed.cpu.name == "x86_64" || hostPlatform.parsed.cpu.name == "i686" then "cpufeatures" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cpufeatures."0.2.2" { inherit profileName; }; + digest = rustPackages."registry+https://github.com/rust-lang/crates.io-index".digest."0.10.3" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".sharded-slab."0.1.4" = overridableMkRustCrate (profileName: rec { + name = "sharded-slab"; + version = "0.1.4"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"; }; + dependencies = { + lazy_static = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".shlex."1.1.0" = overridableMkRustCrate (profileName: rec { + name = "shlex"; + version = "1.1.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"; }; + features = builtins.concatLists [ + [ "default" ] + [ "std" ] + ]; + }); + "registry+https://github.com/rust-lang/crates.io-index".signal-hook-registry."1.4.0" = overridableMkRustCrate (profileName: rec { name = "signal-hook-registry"; version = "1.4.0"; @@ -3705,6 +4377,13 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".siphasher."0.2.3" = overridableMkRustCrate (profileName: rec { + name = "siphasher"; + version = "0.2.3"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"; }; + }); + "registry+https://github.com/rust-lang/crates.io-index".slab."0.4.5" = overridableMkRustCrate (profileName: rec { name = "slab"; version = "0.4.5"; @@ -3773,7 +4452,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -3819,7 +4498,7 @@ in ]; dependencies = { bitflags = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitflags."1.3.2" { inherit profileName; }; - ${ if hostPlatform.parsed.kernel.name == "android" || hostPlatform.parsed.kernel.name == "linux" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "android" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; ${ if !(hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "android") then "parking_lot" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.11.2" { inherit profileName; }; ${ if !(hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "android") then "parking_lot_core" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot_core."0.8.5" { inherit profileName; }; static_init_macro = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".static_init_macro."1.0.2" { profileName = "__noProfile"; }; @@ -3839,7 +4518,7 @@ in memchr = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; buildDependencies = { cfg_aliases = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg_aliases."0.1.1" { profileName = "__noProfile"; }; @@ -3875,7 +4554,7 @@ in proc_macro_error = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro-error."1.0.4" { inherit profileName; }; proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -3886,11 +4565,11 @@ in src = fetchCratesIo { inherit name version; sha256 = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"; }; }); - "registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" = overridableMkRustCrate (profileName: rec { name = "syn"; - version = "1.0.89"; + version = "1.0.94"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54"; }; + src = fetchCratesIo { inherit name version; sha256 = "a07e33e919ebcd69113d5be0e4d70c5707004ff45188910106854f38b960df4a"; }; features = builtins.concatLists [ [ "clone-impls" ] [ "default" ] @@ -3911,6 +4590,16 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".synchronoise."1.0.0" = overridableMkRustCrate (profileName: rec { + name = "synchronoise"; + version = "1.0.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "d717ed0efc9d39ab3b642a096bc369a3e02a38a51c41845d7fe31bdad1d6eaeb"; }; + dependencies = { + crossbeam_queue = rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-queue."0.1.2" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".synstructure."0.12.6" = overridableMkRustCrate (profileName: rec { name = "synstructure"; version = "0.12.6"; @@ -3923,7 +4612,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; unicode_xid = rustPackages."registry+https://github.com/rust-lang/crates.io-index".unicode-xid."0.2.2" { inherit profileName; }; }; }); @@ -3963,25 +4652,42 @@ in }; }); - "registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.30" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".textwrap."0.15.0" = overridableMkRustCrate (profileName: rec { + name = "textwrap"; + version = "0.15.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"; }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".thiserror."1.0.31" = overridableMkRustCrate (profileName: rec { name = "thiserror"; - version = "1.0.30"; + version = "1.0.31"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"; }; + src = fetchCratesIo { inherit name version; sha256 = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"; }; dependencies = { - thiserror_impl = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror-impl."1.0.30" { profileName = "__noProfile"; }; + thiserror_impl = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".thiserror-impl."1.0.31" { profileName = "__noProfile"; }; }; }); - "registry+https://github.com/rust-lang/crates.io-index".thiserror-impl."1.0.30" = overridableMkRustCrate (profileName: rec { + "registry+https://github.com/rust-lang/crates.io-index".thiserror-impl."1.0.31" = overridableMkRustCrate (profileName: rec { name = "thiserror-impl"; - version = "1.0.30"; + version = "1.0.31"; registry = "registry+https://github.com/rust-lang/crates.io-index"; - src = fetchCratesIo { inherit name version; sha256 = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"; }; + src = fetchCratesIo { inherit name version; sha256 = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"; }; dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".thread_local."1.1.4" = overridableMkRustCrate (profileName: rec { + name = "thread_local"; + version = "1.1.4"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"; }; + dependencies = { + once_cell = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; }; }); @@ -4014,6 +4720,23 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".timeago."0.3.1" = overridableMkRustCrate (profileName: rec { + name = "timeago"; + version = "0.3.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "6ec32dde57efb15c035ac074118d7f32820451395f28cb0524a01d4e94983b26"; }; + features = builtins.concatLists [ + [ "chrono" ] + [ "default" ] + [ "isolang" ] + [ "translations" ] + ]; + dependencies = { + chrono = rustPackages."registry+https://github.com/rust-lang/crates.io-index".chrono."0.4.19" { inherit profileName; }; + isolang = rustPackages."registry+https://github.com/rust-lang/crates.io-index".isolang."1.0.0" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".tinyvec."1.5.1" = overridableMkRustCrate (profileName: rec { name = "tinyvec"; version = "1.5.1"; @@ -4042,44 +4765,44 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee"; }; features = builtins.concatLists [ - [ "bytes" ] - [ "default" ] - [ "fs" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "full") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "io-std") - [ "io-util" ] - [ "libc" ] - [ "macros" ] - [ "memchr" ] - [ "mio" ] - [ "net" ] - [ "num_cpus" ] - [ "once_cell" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "parking_lot") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "process") - [ "rt" ] - [ "rt-multi-thread" ] - [ "signal" ] - [ "signal-hook-registry" ] - [ "socket2" ] - [ "sync" ] - [ "time" ] - [ "tokio-macros" ] - [ "winapi" ] - ]; - dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; - ${ if hostPlatform.isUnix then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; - memchr = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; - mio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".mio."0.8.2" { inherit profileName; }; - num_cpus = rustPackages."registry+https://github.com/rust-lang/crates.io-index".num_cpus."1.13.1" { inherit profileName; }; - once_cell = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "parking_lot" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.12.0" { inherit profileName; }; - pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; - ${ if hostPlatform.isUnix then "signal_hook_registry" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".signal-hook-registry."1.4.0" { inherit profileName; }; - socket2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".socket2."0.4.4" { inherit profileName; }; - tokio_macros = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-macros."1.7.0" { profileName = "__noProfile"; }; - ${ if hostPlatform.isWindows then "winapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }; + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "bytes") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "fs") + (lib.optional (rootFeatures' ? "garage") "full") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "k2v-client") "io-std") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "io-util") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "libc") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "macros") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "memchr") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "mio") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "net") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "num_cpus") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "once_cell") + (lib.optional (rootFeatures' ? "garage") "parking_lot") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "k2v-client") "process") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "rt") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "rt-multi-thread") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "signal") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "signal-hook-registry") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "socket2") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "sync") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "time") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "tokio-macros") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "winapi") + ]; + dependencies = { + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + ${ if (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") && hostPlatform.isUnix then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "memchr" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "mio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".mio."0.8.2" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "num_cpus" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".num_cpus."1.13.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "once_cell" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" then "parking_lot" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.12.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "pin_project_lite" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; + ${ if (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") && hostPlatform.isUnix then "signal_hook_registry" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".signal-hook-registry."1.4.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "socket2" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".socket2."0.4.4" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tokio_macros" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-macros."1.7.0" { profileName = "__noProfile"; }; + ${ if (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") && hostPlatform.isWindows then "winapi" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }; }; }); @@ -4102,7 +4825,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -4135,14 +4858,14 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"; }; features = builtins.concatLists [ - [ "default" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "default") (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "net") - [ "time" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "time") ]; dependencies = { - futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; - pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_core" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "pin_project_lite" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; }; }); @@ -4152,23 +4875,20 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"; }; features = builtins.concatLists [ - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "codec") - [ "compat" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "default") - [ "futures-io" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "io") - (lib.optional (rootFeatures' ? "garage_rpc") "slab") - (lib.optional (rootFeatures' ? "garage_rpc") "time") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "codec") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "default") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_web") "io") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "slab") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "time") ]; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; - futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; - futures_io = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-io."0.3.21" { inherit profileName; }; - futures_sink = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-sink."0.3.21" { inherit profileName; }; - log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; - pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; - ${ if rootFeatures' ? "garage_rpc" then "slab" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".slab."0.4.5" { inherit profileName; }; - tokio = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "bytes" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_core" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "futures_sink" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-sink."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "log" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "pin_project_lite" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "slab" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".slab."0.4.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; }; }); @@ -4177,9 +4897,15 @@ in version = "0.7.0"; registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1"; }; + features = builtins.concatLists [ + [ "compat" ] + [ "futures-io" ] + [ "io" ] + ]; dependencies = { - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; + futures_io = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-io."0.3.21" { inherit profileName; }; futures_sink = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-sink."0.3.21" { inherit profileName; }; log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; @@ -4196,7 +4922,7 @@ in [ "default" ] ]; dependencies = { - serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.136" { inherit profileName; }; + serde = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }; }; }); @@ -4224,7 +4950,7 @@ in async_stream = rustPackages."registry+https://github.com/rust-lang/crates.io-index".async-stream."0.3.3" { inherit profileName; }; async_trait = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }; base64 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; h2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".h2."0.3.12" { inherit profileName; }; @@ -4261,7 +4987,7 @@ in proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; prost_build = rustPackages."registry+https://github.com/rust-lang/crates.io-index".prost-build."0.9.0" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -4271,43 +4997,43 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e"; }; features = builtins.concatLists [ - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "__common") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "balance") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "buffer") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "default") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "discover") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "futures-core") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "futures-util") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "indexmap") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "limit") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "load") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "log") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "make") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "pin-project") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "pin-project-lite") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "rand") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "ready-cache") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "__common") + (lib.optional (rootFeatures' ? "garage") "balance") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "buffer") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "default") + (lib.optional (rootFeatures' ? "garage") "discover") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "futures-core") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "futures-util") + (lib.optional (rootFeatures' ? "garage") "indexmap") + (lib.optional (rootFeatures' ? "garage") "limit") + (lib.optional (rootFeatures' ? "garage") "load") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "log") + (lib.optional (rootFeatures' ? "garage") "make") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "pin-project") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "pin-project-lite") + (lib.optional (rootFeatures' ? "garage") "rand") + (lib.optional (rootFeatures' ? "garage") "ready-cache") (lib.optional (rootFeatures' ? "garage") "retry") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "slab") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "timeout") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "tokio") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "tokio-util") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "tracing") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "util") + (lib.optional (rootFeatures' ? "garage") "slab") + (lib.optional (rootFeatures' ? "garage") "timeout") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "tokio") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "tokio-util") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "tracing") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "util") ]; dependencies = { - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "futures_core" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "futures_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "indexmap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".indexmap."1.8.0" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "pin_project" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.10" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "pin_project_lite" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "rand" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "slab" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".slab."0.4.5" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "tokio_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.0" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "tower_layer" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-layer."0.3.1" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "tower_service" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-service."0.3.1" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "tracing" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "futures_core" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "futures_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; + ${ if rootFeatures' ? "garage" then "indexmap" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".indexmap."1.8.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "pin_project" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.0.10" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "pin_project_lite" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; + ${ if rootFeatures' ? "garage" then "rand" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" then "slab" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".slab."0.4.5" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "tokio" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "tokio_util" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.0" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "tower_layer" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-layer."0.3.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "tower_service" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-service."0.3.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "tracing" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; }; }); @@ -4326,7 +5052,7 @@ in ]; dependencies = { base64 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }; - bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.1.0" { inherit profileName; }; + bytes = rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }; futures_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }; futures_util = rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }; http = rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.6" { inherit profileName; }; @@ -4360,14 +5086,14 @@ in features = builtins.concatLists [ [ "attributes" ] [ "default" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "log") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "log") (lib.optional (rootFeatures' ? "garage") "log-always") [ "std" ] [ "tracing-attributes" ] ]; dependencies = { cfg_if = rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }; - ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web" then "log" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" then "log" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; pin_project_lite = rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.8" { inherit profileName; }; tracing_attributes = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing-attributes."0.1.20" { profileName = "__noProfile"; }; tracing_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing-core."0.1.23" { inherit profileName; }; @@ -4382,7 +5108,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; }; }); @@ -4392,11 +5118,14 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c"; }; features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage") "default") [ "lazy_static" ] [ "std" ] + (lib.optional (rootFeatures' ? "garage") "valuable") ]; dependencies = { lazy_static = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }; + ${ if false then "valuable" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".valuable."0.1.0" { inherit profileName; }; }; }); @@ -4417,6 +5146,59 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".tracing-log."0.1.3" = overridableMkRustCrate (profileName: rec { + name = "tracing-log"; + version = "0.1.3"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"; }; + features = builtins.concatLists [ + [ "log-tracer" ] + [ "std" ] + ]; + dependencies = { + lazy_static = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }; + log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; + tracing_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing-core."0.1.23" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".tracing-subscriber."0.3.11" = overridableMkRustCrate (profileName: rec { + name = "tracing-subscriber"; + version = "0.3.11"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596"; }; + features = builtins.concatLists [ + [ "alloc" ] + [ "ansi" ] + [ "ansi_term" ] + [ "default" ] + [ "env-filter" ] + [ "fmt" ] + [ "lazy_static" ] + [ "matchers" ] + [ "regex" ] + [ "registry" ] + [ "sharded-slab" ] + [ "smallvec" ] + [ "std" ] + [ "thread_local" ] + [ "tracing" ] + [ "tracing-log" ] + ]; + dependencies = { + ansi_term = rustPackages."registry+https://github.com/rust-lang/crates.io-index".ansi_term."0.12.1" { inherit profileName; }; + lazy_static = rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }; + matchers = rustPackages."registry+https://github.com/rust-lang/crates.io-index".matchers."0.1.0" { inherit profileName; }; + regex = rustPackages."registry+https://github.com/rust-lang/crates.io-index".regex."1.5.5" { inherit profileName; }; + sharded_slab = rustPackages."registry+https://github.com/rust-lang/crates.io-index".sharded-slab."0.1.4" { inherit profileName; }; + smallvec = rustPackages."registry+https://github.com/rust-lang/crates.io-index".smallvec."1.8.0" { inherit profileName; }; + thread_local = rustPackages."registry+https://github.com/rust-lang/crates.io-index".thread_local."1.1.4" { inherit profileName; }; + tracing = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }; + tracing_core = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing-core."0.1.23" { inherit profileName; }; + tracing_log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing-log."0.1.3" { inherit profileName; }; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".treediff."3.0.2" = overridableMkRustCrate (profileName: rec { name = "treediff"; version = "3.0.2"; @@ -4427,7 +5209,7 @@ in [ "with-serde-json" ] ]; dependencies = { - serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.79" { inherit profileName; }; + serde_json = rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }; }; }); @@ -4517,6 +5299,33 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".uuid."0.8.2" = overridableMkRustCrate (profileName: rec { + name = "uuid"; + version = "0.8.2"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"; }; + features = builtins.concatLists [ + [ "default" ] + [ "getrandom" ] + [ "std" ] + [ "v4" ] + ]; + dependencies = { + getrandom = rustPackages."registry+https://github.com/rust-lang/crates.io-index".getrandom."0.2.5" { inherit profileName; }; + }; + }); + + "registry+https://github.com/rust-lang/crates.io-index".valuable."0.1.0" = overridableMkRustCrate (profileName: rec { + name = "valuable"; + version = "0.1.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"; }; + features = builtins.concatLists [ + [ "alloc" ] + [ "std" ] + ]; + }); + "registry+https://github.com/rust-lang/crates.io-index".vcpkg."0.2.15" = overridableMkRustCrate (profileName: rec { name = "vcpkg"; version = "0.2.15"; @@ -4606,7 +5415,7 @@ in log = rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }; proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; wasm_bindgen_shared = rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen-shared."0.2.79" { inherit profileName; }; }; }); @@ -4636,7 +5445,7 @@ in dependencies = { proc_macro2 = rustPackages."registry+https://github.com/rust-lang/crates.io-index".proc-macro2."1.0.36" { inherit profileName; }; quote = rustPackages."registry+https://github.com/rust-lang/crates.io-index".quote."1.0.16" { inherit profileName; }; - syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.89" { inherit profileName; }; + syn = rustPackages."registry+https://github.com/rust-lang/crates.io-index".syn."1.0.94" { inherit profileName; }; wasm_bindgen_backend = rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen-backend."0.2.79" { inherit profileName; }; wasm_bindgen_shared = rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen-shared."0.2.79" { inherit profileName; }; }; @@ -4699,49 +5508,49 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"; }; features = builtins.concatLists [ - [ "cfg" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "cfg") [ "consoleapi" ] [ "errhandlingapi" ] - [ "evntrace" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "evntrace") [ "fileapi" ] [ "handleapi" ] - [ "in6addr" ] - [ "inaddr" ] - [ "ioapiset" ] - (lib.optional (rootFeatures' ? "garage_rpc") "knownfolders") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "lmcons") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "minschannel") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "in6addr") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "inaddr") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "ioapiset") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "knownfolders") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "lmcons") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "minschannel") [ "minwinbase" ] [ "minwindef" ] - [ "mswsock" ] - [ "namedpipeapi" ] - [ "ntdef" ] - [ "ntsecapi" ] - [ "ntstatus" ] - (lib.optional (rootFeatures' ? "garage_rpc") "objbase") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "mswsock") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "namedpipeapi") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "ntdef") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "ntsecapi") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "ntstatus") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "objbase") [ "processenv" ] - [ "processthreadsapi" ] - [ "profileapi" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "schannel") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "securitybaseapi") - (lib.optional (rootFeatures' ? "garage_rpc") "shlobj") - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "sspi") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_db" || rootFeatures' ? "garage_model") "processthreadsapi") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "profileapi") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "schannel") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "securitybaseapi") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "shlobj") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "sspi") [ "std" ] - [ "synchapi" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "synchapi") [ "sysinfoapi" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_admin" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_web") "threadpoollegacyapiset") - [ "timezoneapi" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "k2v-client") "threadpoollegacyapiset") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "timezoneapi") [ "winbase" ] [ "wincon" ] - (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc") "wincrypt") - [ "windef" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "k2v-client") "wincrypt") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "windef") [ "winerror" ] - [ "winioctl" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "winioctl") [ "winnt" ] - [ "winsock2" ] - [ "ws2def" ] - [ "ws2ipdef" ] - [ "ws2tcpip" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "winsock2") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "ws2def") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "ws2ipdef") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_rpc" || rootFeatures' ? "garage_table" || rootFeatures' ? "garage_util" || rootFeatures' ? "garage_web" || rootFeatures' ? "k2v-client") "ws2tcpip") (lib.optional (rootFeatures' ? "garage") "wtypesbase") ]; dependencies = { @@ -4789,11 +5598,11 @@ in [ "default" ] ]; dependencies = { - ${ if hostPlatform.config == "aarch64-uwp-windows-msvc" || hostPlatform.config == "aarch64-pc-windows-msvc" then "windows_aarch64_msvc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows_aarch64_msvc."0.32.0" { inherit profileName; }; - ${ if hostPlatform.config == "i686-uwp-windows-gnu" || hostPlatform.config == "i686-pc-windows-gnu" then "windows_i686_gnu" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows_i686_gnu."0.32.0" { inherit profileName; }; + ${ if hostPlatform.config == "aarch64-pc-windows-msvc" || hostPlatform.config == "aarch64-uwp-windows-msvc" then "windows_aarch64_msvc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows_aarch64_msvc."0.32.0" { inherit profileName; }; + ${ if hostPlatform.config == "i686-pc-windows-gnu" || hostPlatform.config == "i686-uwp-windows-gnu" then "windows_i686_gnu" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows_i686_gnu."0.32.0" { inherit profileName; }; ${ if hostPlatform.config == "i686-pc-windows-msvc" || hostPlatform.config == "i686-uwp-windows-msvc" then "windows_i686_msvc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows_i686_msvc."0.32.0" { inherit profileName; }; ${ if hostPlatform.config == "x86_64-uwp-windows-gnu" || hostPlatform.config == "x86_64-pc-windows-gnu" then "windows_x86_64_gnu" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows_x86_64_gnu."0.32.0" { inherit profileName; }; - ${ if hostPlatform.config == "x86_64-pc-windows-msvc" || hostPlatform.config == "x86_64-uwp-windows-msvc" then "windows_x86_64_msvc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows_x86_64_msvc."0.32.0" { inherit profileName; }; + ${ if hostPlatform.config == "x86_64-uwp-windows-msvc" || hostPlatform.config == "x86_64-pc-windows-msvc" then "windows_x86_64_msvc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".windows_x86_64_msvc."0.32.0" { inherit profileName; }; }; }); @@ -4832,6 +5641,13 @@ in src = fetchCratesIo { inherit name version; sha256 = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"; }; }); + "registry+https://github.com/rust-lang/crates.io-index".xml-rs."0.8.4" = overridableMkRustCrate (profileName: rec { + name = "xml-rs"; + version = "0.8.4"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"; }; + }); + "registry+https://github.com/rust-lang/crates.io-index".xmlparser."0.13.3" = overridableMkRustCrate (profileName: rec { name = "xmlparser"; version = "0.13.3"; @@ -4879,8 +5695,11 @@ in version = "0.9.2+zstd.1.5.1"; registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54"; }; + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_block") "pkg-config") + ]; dependencies = { - zstd_safe = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd-safe."4.1.3+zstd.1.5.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "zstd_safe" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd-safe."4.1.3+zstd.1.5.1" { inherit profileName; }; }; }); @@ -4890,11 +5709,12 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79"; }; features = builtins.concatLists [ - [ "std" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_block") "pkg-config") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web") "std") ]; dependencies = { - libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; - zstd_sys = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd-sys."1.6.2+zstd.1.5.1" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "zstd_sys" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".zstd-sys."1.6.2+zstd.1.5.1" { inherit profileName; }; }; }); @@ -4904,13 +5724,15 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f"; }; features = builtins.concatLists [ - [ "std" ] + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_block") "pkg-config") + (lib.optional (rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web") "std") ]; dependencies = { - libc = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "libc" else null } = rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }; }; buildDependencies = { - cc = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_api" || rootFeatures' ? "garage_block" || rootFeatures' ? "garage_model" || rootFeatures' ? "garage_web" then "cc" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" { profileName = "__noProfile"; }; + ${ if rootFeatures' ? "garage" || rootFeatures' ? "garage_block" then "pkg_config" else null } = buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".pkg-config."0.3.24" { profileName = "__noProfile"; }; }; }); @@ -1,16 +1,20 @@ [workspace] +resolver = "2" members = [ + "src/db", "src/util", "src/rpc", "src/table", "src/block", "src/model", - "src/admin", "src/api", "src/web", - "src/garage" + "src/garage", + "src/k2v-client", ] +default-members = ["src/garage"] + [profile.dev] lto = "off" @@ -1,13 +1,27 @@ -.PHONY: doc all release shell +.PHONY: doc all release shell run1 run2 run3 all: clear; cargo build -doc: - cd doc/book; mdbook build - release: nix-build --arg release true shell: nix-shell + +# ---- + +run1: + RUST_LOG=garage=debug ./target/debug/garage -c tmp/config1.toml server +run1rel: + RUST_LOG=garage=debug ./target/release/garage -c tmp/config1.toml server + +run2: + RUST_LOG=garage=debug ./target/debug/garage -c tmp/config2.toml server +run2rel: + RUST_LOG=garage=debug ./target/release/garage -c tmp/config2.toml server + +run3: + RUST_LOG=garage=debug ./target/debug/garage -c tmp/config3.toml server +run3rel: + RUST_LOG=garage=debug ./target/release/garage -c tmp/config3.toml server @@ -15,18 +15,24 @@ Garage [![Build Status](https://drone.deuxfleurs.fr/api/badges/Deuxfleurs/garage ] </p> -Garage is a lightweight S3-compatible distributed object store, with the following goals: +Garage is an S3-compatible distributed object storage service +designed for self-hosting at a small-to-medium scale. -- As self-contained as possible -- Easy to set up -- Highly resilient to network failures, network latency, disk failures, sysadmin failures -- Relatively simple -- Made for multi-datacenter deployments +Garage is designed for storage clusters composed of nodes running +at different physical locations, +in order to easily provide a storage service that replicates data at these different +locations and stays available even when some servers are unreachable. +Garage also focuses on being lightweight, easy to operate, and highly resilient to +machine failures. -Non-goals include: +Garage is built by [Deuxfleurs](https://deuxfleurs.fr), +an experimental small-scale self hosted service provider, +which has been using it in production since its first release in 2020. -- Extremely high performance -- Complete implementation of the S3 API -- Erasure coding (our replication model is simply to copy the data as is on several nodes, in different datacenters if possible) +Learn more on our dedicated documentation pages: -Our main use case is to provide a distributed storage layer for small-scale self hosted services such as [Deuxfleurs](https://deuxfleurs.fr). +- [Goals and use cases](https://garagehq.deuxfleurs.fr/documentation/design/goals/) +- [Features](https://garagehq.deuxfleurs.fr/documentation/reference-manual/features/) +- [Quick start](https://garagehq.deuxfleurs.fr/documentation/quick-start/) + +Garage is entirely free software released under the terms of the AGPLv3. diff --git a/default.nix b/default.nix index 21a413b2..7e44096c 100644 --- a/default.nix +++ b/default.nix @@ -1,147 +1,33 @@ { system ? builtins.currentSystem, - release ? false, - target ? "x86_64-unknown-linux-musl", - compileMode ? null, git_version ? null, }: with import ./nix/common.nix; -let - crossSystem = { config = target; }; -in let - log = v: builtins.trace v v; - - pkgs = import pkgsSrc { - inherit system crossSystem; - overlays = [ cargo2nixOverlay ]; +let + pkgs = import pkgsSrc { }; + compile = import ./nix/compile.nix; + build_debug_and_release = (target: { + debug = (compile { inherit target git_version; release = false; }).workspace.garage { compileMode = "build"; }; + release = (compile { inherit target git_version; release = true; }).workspace.garage { compileMode = "build"; }; + }); + test = (rustPkgs: pkgs.symlinkJoin { + name ="garage-tests"; + paths = builtins.map (key: rustPkgs.workspace.${key} { compileMode = "test"; }) (builtins.attrNames rustPkgs.workspace); + }); + +in { + pkgs = { + amd64 = build_debug_and_release "x86_64-unknown-linux-musl"; + i386 = build_debug_and_release "i686-unknown-linux-musl"; + arm64 = build_debug_and_release "aarch64-unknown-linux-musl"; + arm = build_debug_and_release "armv6l-unknown-linux-musleabihf"; }; - - - /* - Rust and Nix triples are not the same. Cargo2nix has a dedicated library - to convert Nix triples to Rust ones. We need this conversion as we want to - set later options linked to our (rust) target in a generic way. Not only - the triple terminology is different, but also the "roles" are named differently. - Nix uses a build/host/target terminology where Nix's "host" maps to Cargo's "target". - */ - rustTarget = log (pkgs.rustBuilder.rustLib.rustTriple pkgs.stdenv.hostPlatform); - - /* - Cargo2nix is built for rustOverlay which installs Rust from Mozilla releases. - We want our own Rust to avoid incompatibilities, like we had with musl 1.2.0. - rustc was built with musl < 1.2.0 and nix shipped musl >= 1.2.0 which lead to compilation breakage. - So we want a Rust release that is bound to our Nix repository to avoid these problems. - See here for more info: https://musl.libc.org/time64.html - Because Cargo2nix does not support the Rust environment shipped by NixOS, - we emulate the structure of the Rust object created by rustOverlay. - In practise, rustOverlay ships rustc+cargo in a single derivation while - NixOS ships them in separate ones. We reunite them with symlinkJoin. - */ - rustChannel = pkgs.symlinkJoin { - name ="rust-channel"; - paths = [ - pkgs.rustPlatform.rust.rustc - pkgs.rustPlatform.rust.cargo - ]; + test = { + amd64 = test (compile { inherit git_version; target = "x86_64-unknown-linux-musl"; }); }; - - /* - Cargo2nix provides many overrides by default, you can take inspiration from them: - https://github.com/cargo2nix/cargo2nix/blob/master/overlay/overrides.nix - - You can have a complete list of the available options by looking at the overriden object, mkcrate: - https://github.com/cargo2nix/cargo2nix/blob/master/overlay/mkcrate.nix - */ - overrides = pkgs.rustBuilder.overrides.all ++ [ - /* - [1] We need to alter Nix hardening to be able to statically compile: PIE, - Position Independent Executables seems to be supported only on amd64. Having - this flags set either make our executables crash or compile as dynamic on many platforms. - In the following section codegenOpts, we reactive it for the supported targets - (only amd64 curently) through the `-static-pie` flag. PIE is a feature used - by ASLR, which helps mitigate security issues. - Learn more about Nix Hardening: https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/cc-wrapper/add-hardening.sh - - [2] We want to inject the git version while keeping the build deterministic. - As we do not want to consider the .git folder as part of the input source, - we ask the user (the CI often) to pass the value to Nix. - */ - (pkgs.rustBuilder.rustLib.makeOverride { - name = "garage"; - overrideAttrs = drv: - /* [1] */ { hardeningDisable = [ "pie" ]; } - // - /* [2] */ (if git_version != null then { - preConfigure = '' - ${drv.preConfigure or ""} - export GIT_VERSION="${git_version}" - ''; - } else {}); - }) - - /* - We ship some parts of the code disabled by default by putting them behind a flag. - It speeds up the compilation (when the feature is not required) and released crates have less dependency by default (less attack surface, disk space, etc.). - But we want to ship these additional features when we release Garage. - In the end, we chose to exclude all features from debug builds while putting (all of) them in the release builds. - Currently, the only feature of Garage is kubernetes-discovery from the garage_rpc crate. - */ - (pkgs.rustBuilder.rustLib.makeOverride { - name = "garage_rpc"; - overrideArgs = old: - { - features = if release then [ "kubernetes-discovery" ] else []; - }; - }) - ]; - - packageFun = import ./Cargo.nix; - - /* - We compile fully static binaries with musl to simplify deployment on most systems. - When possible, we reactivate PIE hardening (see above). - - Also, if you set the RUSTFLAGS environment variable, the following parameters will - be ignored. - - For more information on static builds, please refer to Rust's RFC 1721. - https://rust-lang.github.io/rfcs/1721-crt-static.html#specifying-dynamicstatic-c-runtime-linkage - */ - - codegenOpts = { - "armv6l-unknown-linux-musleabihf" = [ "target-feature=+crt-static" "link-arg=-static" ]; /* compile as dynamic with static-pie */ - "aarch64-unknown-linux-musl" = [ "target-feature=+crt-static" "link-arg=-static" ]; /* segfault with static-pie */ - "i686-unknown-linux-musl" = [ "target-feature=+crt-static" "link-arg=-static" ]; /* segfault with static-pie */ - "x86_64-unknown-linux-musl" = [ "target-feature=+crt-static" "link-arg=-static-pie" ]; + clippy = { + amd64 = (compile { inherit git_version; compiler = "clippy"; }).workspace.garage { compileMode = "build"; } ; }; - - /* - The following definition is not elegant as we use a low level function of Cargo2nix - that enables us to pass our custom rustChannel object. We need this low level definition - to pass Nix's Rust toolchains instead of Mozilla's one. - - target is mandatory but must be kept to null to allow cargo2nix to set it to the appropriate value - for each crate. - */ - rustPkgs = pkgs.rustBuilder.makePackageSet { - inherit packageFun rustChannel release codegenOpts; - packageOverrides = overrides; - target = null; - - buildRustPackages = pkgs.buildPackages.rustBuilder.makePackageSet { - inherit rustChannel packageFun codegenOpts; - packageOverrides = overrides; - target = null; - }; - }; - - -in - if compileMode == "test" - then pkgs.symlinkJoin { - name ="garage-tests"; - paths = builtins.map (key: rustPkgs.workspace.${key} { inherit compileMode; }) (builtins.attrNames rustPkgs.workspace); - } - else rustPkgs.workspace.garage { inherit compileMode; } +} diff --git a/doc/book/connect/backup.md b/doc/book/connect/backup.md index 5110442c..48a2d7be 100644 --- a/doc/book/connect/backup.md +++ b/doc/book/connect/backup.md @@ -17,6 +17,61 @@ If you still want to use Borg, you can use it with `rclone mount`. ## Restic +Create your key and bucket: + +```bash +garage key new my-key +garage bucket create backup +garage bucket allow backup --read --write --key my-key +``` + +Then register your Key ID and Secret key in your environment: + +```bash +export AWS_ACCESS_KEY_ID=GKxxx +export AWS_SECRET_ACCESS_KEY=xxxx +``` + +Configure restic from environment too: + +```bash +export RESTIC_REPOSITORY="s3:http://localhost:3900/backups" + +echo "Generated password (save it safely): $(openssl rand -base64 32)" +export RESTIC_PASSWORD=xxx # copy paste your generated password here +``` + +Do not forget to save your password safely (in your password manager or print it). It will be needed to decrypt your backups. + +Now you can use restic: + +```bash +# Initialize the bucket, must be run once +restic init + +# Backup your PostgreSQL database +# (We suppose your PostgreSQL daemon is stopped for all commands) +restic backup /var/lib/postgresql + +# Show backup history +restic snapshots + +# Backup again your PostgreSQL database, it will be faster as only changes will be uploaded +restic backup /var/lib/postgresql + +# Show backup history (again) +restic snapshots + +# Restore a backup +# (79766175 is the ID of the snapshot you want to restore) +mv /var/lib/postgresql /var/lib/postgresql.broken +restic restore 79766175 --target /var/lib/postgresql +``` + +Restic has way more features than the ones presented here. +You can discover all of them by accessing its documentation from the link below. + + *External links:* [Restic Documentation > Amazon S3](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#amazon-s3) ## Duplicity diff --git a/doc/book/connect/websites.md b/doc/book/connect/websites.md index da3dac90..7b49fcad 100644 --- a/doc/book/connect/websites.md +++ b/doc/book/connect/websites.md @@ -3,7 +3,7 @@ title = "Websites (Hugo, Jekyll, Publii...)" weight = 10 +++ -Garage is also suitable to host static websites. +Garage is also suitable [to host static websites](@/documentation/cookbook/exposing-websites.md). While they can be deployed with traditional CLI tools, some static website generators have integrated options to ease your workflow. | Name | Status | Note | diff --git a/doc/book/cookbook/exposing-websites.md b/doc/book/cookbook/exposing-websites.md index be462dc9..5f6a5a28 100644 --- a/doc/book/cookbook/exposing-websites.md +++ b/doc/book/cookbook/exposing-websites.md @@ -5,12 +5,14 @@ weight = 25 ## Configuring a bucket for website access -There are two methods to expose buckets as website: +There are three methods to expose buckets as website: 1. using the PutBucketWebsite S3 API call, which is allowed for access keys that have the owner permission bit set 2. from the Garage CLI, by an adminstrator of the cluster +3. using the Garage administration API + The `PutBucketWebsite` API endpoint [is documented](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketWebsite.html) in the official AWS docs. This endpoint can also be called [using `aws s3api`](https://docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-website.html) on the command line. The website configuration supported by Garage is only a subset of the possibilities on Amazon S3: redirections are not supported, only the index document and error document can be specified. diff --git a/doc/book/cookbook/from-source.md b/doc/book/cookbook/from-source.md index 5973d411..bacf93ab 100644 --- a/doc/book/cookbook/from-source.md +++ b/doc/book/cookbook/from-source.md @@ -20,40 +20,76 @@ sudo apt-get update sudo apt-get install build-essential ``` -## Using source from `crates.io` +## Building from source from the Gitea repository -Garage's source code is published on `crates.io`, Rust's official package repository. -This means you can simply ask `cargo` to download and build this source code for you: +The primary location for Garage's source code is the +[Gitea repository](https://git.deuxfleurs.fr/Deuxfleurs/garage), +which contains all of the released versions as well as the code +for the developpement of the next version. + +Clone the repository and enter it as follows: ```bash -cargo install garage +git clone https://git.deuxfleurs.fr/Deuxfleurs/garage.git +cd garage ``` -That's all, `garage` should be in `$HOME/.cargo/bin`. - -You can add this folder to your `$PATH` or copy the binary somewhere else on your system. -For instance: +If you wish to build a specific version of Garage, check out the corresponding tag. For instance: ```bash -sudo cp $HOME/.cargo/bin/garage /usr/local/bin/garage +git tag # List available tags +git checkout v0.8.0 # Change v0.8.0 with the version you wish to build ``` +Otherwise you will be building a developpement build from the `main` branch +that includes all of the changes to be released in the next version. +Be careful that such a build might be unstable or contain bugs, +and could be incompatible with nodes that run stable versions of Garage. -## Using source from the Gitea repository +Finally, build Garage with the following command: -The primary location for Garage's source code is the -[Gitea repository](https://git.deuxfleurs.fr/Deuxfleurs/garage). +```bash +cargo build --release +``` -Clone the repository and build Garage with the following commands: +The binary built this way can now be found in `target/release/garage`. +You may simply copy this binary to somewhere in your `$PATH` in order to +have the `garage` command available in your shell, for instance: ```bash -git clone https://git.deuxfleurs.fr/Deuxfleurs/garage.git -cd garage -cargo build +sudo cp target/release/garage /usr/local/bin/garage ``` -Be careful, as this will make a debug build of Garage, which will be extremely slow! -To make a release build, invoke `cargo build --release` (this takes much longer). +If you are planning to develop Garage, +you might be interested in producing debug builds, which compile faster but run slower: +this can be done by removing the `--release` flag, and the resulting build can then +be found in `target/debug/garage`. + +## List of available Cargo feature flags -The binaries built this way are found in `target/{debug,release}/garage`. +Garage supports a number of compilation options in the form of Cargo feature flags, +which can be used to provide builds adapted to your system and your use case. +To produce a build with a given set of features, invoke the `cargo build` command +as follows: + +```bash +# This will build the default feature set plus feature1, feature2 and feature3 +cargo build --release --features feature1,feature2,feature3 +# This will build ONLY feature1, feature2 and feature3 +cargo build --release --no-default-features \ + --features feature1,feature2,feature3 +``` +The following feature flags are available in v0.8.0: + +| Feature flag | Enabled | Description | +| ------------ | ------- | ----------- | +| `bundled-libs` | *by default* | Use bundled version of sqlite3, zstd, lmdb and libsodium | +| `system-libs` | optional | Use system version of sqlite3, zstd, lmdb and libsodium<br>if available (exclusive with `bundled-libs`, build using<br>`cargo build --no-default-features --features system-libs`) | +| `k2v` | optional | Enable the experimental K2V API (if used, all nodes on your<br>Garage cluster must have it enabled as well) | +| `kubernetes-discovery` | optional | Enable automatic registration and discovery<br>of cluster nodes through the Kubernetes API | +| `metrics` | *by default* | Enable collection of metrics in Prometheus format on the admin API | +| `telemetry-otlp` | optional | Enable collection of execution traces using OpenTelemetry | +| `sled` | *by default* | Enable using Sled to store Garage's metadata | +| `lmdb` | optional | Enable using LMDB to store Garage's metadata | +| `sqlite` | optional | Enable using Sqlite3 to store Garage's metadata | diff --git a/doc/book/cookbook/kubernetes.md b/doc/book/cookbook/kubernetes.md new file mode 100644 index 00000000..9eafe3e1 --- /dev/null +++ b/doc/book/cookbook/kubernetes.md @@ -0,0 +1,87 @@ ++++ +title = "Deploying on Kubernetes" +weight = 32 ++++ + +Garage can also be deployed on a kubernetes cluster via helm chart. + +## Deploying + +Firstly clone the repository: + +```bash +git clone https://git.deuxfleurs.fr/Deuxfleurs/garage +cd garage/scripts/helm +``` + +Deploy with default options: + +```bash +helm install --create-namespace --namespace garage garage ./garage +``` + +Or deploy with custom values: + +```bash +helm install --create-namespace --namespace garage garage ./garage -f values.override.yaml +``` + +After deploying, cluster layout must be configured manually as described in [Creating a cluster layout](@/documentation/quick-start/_index.md#creating-a-cluster-layout). Use the following command to access garage CLI: + +```bash +kubectl exec --stdin --tty -n garage garage-0 -- ./garage status +``` + +## Overriding default values + +All possible configuration values can be found with: + +```bash +helm show values ./garage +``` + +This is an example `values.overrride.yaml` for deploying in a microk8s cluster with a https s3 api ingress route: + +```yaml +garage: + # Use only 2 replicas per object + replicationMode: "2" + +# Start 4 instances (StatefulSets) of garage +replicaCount: 4 + +# Override default storage class and size +persistence: + meta: + storageClass: "openebs-hostpath" + size: 100Mi + data: + storageClass: "openebs-hostpath" + size: 1Gi + +ingress: + s3: + api: + enabled: true + className: "public" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/proxy-body-size: 500m + hosts: + - host: s3-api.my-domain.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: garage-ingress-cert + hosts: + - s3-api.my-domain.com +``` + +## Removing + +```bash +helm delete --namespace garage garage +``` + +Note that this will leave behind custom CRD `garagenodes.deuxfleurs.fr`, which must be removed manually if desired. diff --git a/doc/book/cookbook/real-world.md b/doc/book/cookbook/real-world.md index e101a706..4fcb5cf7 100644 --- a/doc/book/cookbook/real-world.md +++ b/doc/book/cookbook/real-world.md @@ -51,15 +51,15 @@ to store 2 TB of data in total. ## Get a Docker image -Our docker image is currently named `dxflrs/amd64_garage` and is stored on the [Docker Hub](https://hub.docker.com/r/dxflrs/amd64_garage/tags?page=1&ordering=last_updated). -We encourage you to use a fixed tag (eg. `v0.4.0`) and not the `latest` tag. -For this example, we will use the latest published version at the time of the writing which is `v0.4.0` but it's up to you -to check [the most recent versions on the Docker Hub](https://hub.docker.com/r/dxflrs/amd64_garage/tags?page=1&ordering=last_updated). +Our docker image is currently named `dxflrs/garage` and is stored on the [Docker Hub](https://hub.docker.com/r/dxflrs/garage/tags?page=1&ordering=last_updated). +We encourage you to use a fixed tag (eg. `v0.8.0`) and not the `latest` tag. +For this example, we will use the latest published version at the time of the writing which is `v0.8.0` but it's up to you +to check [the most recent versions on the Docker Hub](https://hub.docker.com/r/dxflrs/garage/tags?page=1&ordering=last_updated). For example: ``` -sudo docker pull dxflrs/amd64_garage:v0.4.0 +sudo docker pull dxflrs/garage:v0.8.0 ``` ## Deploying and configuring Garage @@ -125,7 +125,7 @@ docker run \ -v /etc/garage.toml:/etc/garage.toml \ -v /var/lib/garage/meta:/var/lib/garage/meta \ -v /var/lib/garage/data:/var/lib/garage/data \ - lxpz/garage_amd64:v0.4.0 + dxflrs/garage:v0.8.0 ``` It should be restarted automatically at each reboot. diff --git a/doc/book/cookbook/reverse-proxy.md b/doc/book/cookbook/reverse-proxy.md index 61bc7933..fb918778 100644 --- a/doc/book/cookbook/reverse-proxy.md +++ b/doc/book/cookbook/reverse-proxy.md @@ -100,7 +100,7 @@ server { } ``` -## Exposing the web endpoint +### Exposing the web endpoint To better understand the logic involved, you can refer to the [Exposing buckets as websites](/cookbook/exposing_websites.html) section. Otherwise, the configuration is very similar to the S3 endpoint. @@ -140,6 +140,165 @@ server { @TODO -## Traefik +## Traefik v2 + +We will see in this part how to set up a reverse proxy with [Traefik](https://docs.traefik.io/). + +Here is [a basic configuration file](https://doc.traefik.io/traefik/https/acme/#configuration-examples): + +```toml +[entryPoints] + [entryPoints.web] + address = ":80" + + [entryPoints.websecure] + address = ":443" + +[certificatesResolvers.myresolver.acme] + email = "your-email@example.com" + storage = "acme.json" + [certificatesResolvers.myresolver.acme.httpChallenge] + # used during the challenge + entryPoint = "web" +``` + +### Add Garage service + +To add Garage on Traefik you should declare a new service using its IP address (or hostname) and port: + +```toml +[http.services] + [http.services.my_garage_service.loadBalancer] + [[http.services.my_garage_service.loadBalancer.servers]] + url = "http://xxx.xxx.xxx.xxx" + port = 3900 +``` + +It's possible to declare multiple Garage servers as back-ends: + +```toml +[http.services] + [[http.services.my_garage_service.loadBalancer.servers]] + url = "http://xxx.xxx.xxx.xxx" + port = 3900 + [[http.services.my_garage_service.loadBalancer.servers]] + url = "http://yyy.yyy.yyy.yyy" + port = 3900 + [[http.services.my_garage_service.loadBalancer.servers]] + url = "http://zzz.zzz.zzz.zzz" + port = 3900 +``` + +Traefik can remove unhealthy servers automatically with [a health check configuration](https://doc.traefik.io/traefik/routing/services/#health-check): + +``` +[http.services] + [http.services.my_garage_service.loadBalancer] + [http.services.my_garage_service.loadBalancer.healthCheck] + path = "/" + interval = "60s" + timeout = "5s" +``` + +### Adding a website + +To add a new website, add the following declaration to your Traefik configuration file: + +```toml +[http.routers] + [http.routers.my_website] + rule = "Host(`yoururl.example.org`)" + service = "my_garage_service" + entryPoints = ["web"] +``` + +Enable HTTPS access to your website with the following configuration section ([documentation](https://doc.traefik.io/traefik/https/overview/)): + +```toml +... + entryPoints = ["websecure"] + [http.routers.my_website.tls] + certResolver = "myresolver" +... +``` + +### Adding gzip compression + +Add the following configuration section [to compress response](https://doc.traefik.io/traefik/middlewares/http/compress/) using [gzip](https://developer.mozilla.org/en-US/docs/Glossary/GZip_compression) before sending them to the client: + +```toml +[http.routers] + [http.routers.my_website] + ... + middlewares = ["gzip_compress"] + ... +[http.middlewares] + [http.middlewares.gzip_compress.compress] +``` + +### Add caching response + +Traefik's caching middleware is only available on [entreprise version](https://doc.traefik.io/traefik-enterprise/middlewares/http-cache/), however the freely-available [Souin plugin](https://github.com/darkweak/souin#tr%C3%A6fik-container) can also do the job. (section to be completed) + +### Complete example + +```toml +[entryPoints] + [entryPoints.web] + address = ":80" + + [entryPoints.websecure] + address = ":443" + +[certificatesResolvers.myresolver.acme] + email = "your-email@example.com" + storage = "acme.json" + [certificatesResolvers.myresolver.acme.httpChallenge] + # used during the challenge + entryPoint = "web" + +[http.routers] + [http.routers.my_website] + rule = "Host(`yoururl.example.org`)" + service = "my_garage_service" + middlewares = ["gzip_compress"] + entryPoints = ["websecure"] + +[http.services] + [http.services.my_garage_service.loadBalancer] + [http.services.my_garage_service.loadBalancer.healthCheck] + path = "/" + interval = "60s" + timeout = "5s" + [[http.services.my_garage_service.loadBalancer.servers]] + url = "http://xxx.xxx.xxx.xxx" + [[http.services.my_garage_service.loadBalancer.servers]] + url = "http://yyy.yyy.yyy.yyy" + [[http.services.my_garage_service.loadBalancer.servers]] + url = "http://zzz.zzz.zzz.zzz" + +[http.middlewares] + [http.middlewares.gzip_compress.compress] +``` + +## Caddy + +Your Caddy configuration can be as simple as: + +```caddy +s3.garage.tld, *.s3.garage.tld { + reverse_proxy localhost:3900 192.168.1.2:3900 example.tld:3900 +} + +*.web.garage.tld { + reverse_proxy localhost:3902 192.168.1.2:3900 example.tld:3900 +} + +admin.garage.tld { + reverse_proxy localhost:3903 +} +``` + +But at the same time, the `reverse_proxy` is very flexible. +For a production deployment, you should [read its documentation](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy) as it supports features like DNS discovery of upstreams, load balancing with checks, streaming parameters, etc. -@TODO diff --git a/doc/book/design/benchmarks/index.md b/doc/book/design/benchmarks/index.md index c2215a4a..79cc5d62 100644 --- a/doc/book/design/benchmarks/index.md +++ b/doc/book/design/benchmarks/index.md @@ -1,6 +1,6 @@ +++ title = "Benchmarks" -weight = 10 +weight = 40 +++ With Garage, we wanted to build a software defined storage service that follow the [KISS principle](https://en.wikipedia.org/wiki/KISS_principle), diff --git a/doc/book/design/goals.md b/doc/book/design/goals.md index dea1d2c8..9c2d89f0 100644 --- a/doc/book/design/goals.md +++ b/doc/book/design/goals.md @@ -1,23 +1,23 @@ +++ title = "Goals and use cases" -weight = 5 +weight = 10 +++ ## Goals and non-goals Garage is a lightweight geo-distributed data store that implements the [Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) -object storage protocole. It enables applications to store large blobs such +object storage protocol. It enables applications to store large blobs such as pictures, video, images, documents, etc., in a redundant multi-node setting. S3 is versatile enough to also be used to publish a static website. Garage is an opinionated object storage solutoin, we focus on the following **desirable properties**: + - **Internet enabled**: made for multi-sites (eg. datacenters, offices, households, etc.) interconnected through regular Internet connections. - **Self-contained & lightweight**: works everywhere and integrates well in existing environments to target [hyperconverged infrastructures](https://en.wikipedia.org/wiki/Hyper-converged_infrastructure). - **Highly resilient**: highly resilient to network failures, network latency, disk failures, sysadmin failures. - **Simple**: simple to understand, simple to operate, simple to debug. - - **Internet enabled**: made for multi-sites (eg. datacenters, offices, households, etc.) interconnected through regular Internet connections. We also noted that the pursuit of some other goals are detrimental to our initial goals. The following has been identified as **non-goals** (if these points matter to you, you should not use Garage): diff --git a/doc/book/design/internals.md b/doc/book/design/internals.md index 05d852e2..777e017d 100644 --- a/doc/book/design/internals.md +++ b/doc/book/design/internals.md @@ -20,6 +20,49 @@ In the meantime, you can find some information at the following links: - [an old design draft](@/documentation/working-documents/design-draft.md) +## Request routing logic + +Data retrieval requests to Garage endpoints (S3 API and websites) are resolved +to an individual object in a bucket. Since objects are replicated to multiple nodes +Garage must ensure consistency before answering the request. + +### Using quorum to ensure consistency + +Garage ensures consistency by attempting to establish a quorum with the +data nodes responsible for the object. When a majority of the data nodes +have provided metadata on a object Garage can then answer the request. + +When a request arrives Garage will, assuming the recommended 3 replicas, perform the following actions: + +- Make a request to the two preferred nodes for object metadata +- Try the third node if one of the two initial requests fail +- Check that the metadata from at least 2 nodes match +- Check that the object hasn't been marked deleted +- Answer the request with inline data from metadata if object is small enough +- Or get data blocks from the preferred nodes and answer using the assembled object + +Garage dynamically determines which nodes to query based on health, preference, and +which nodes actually host a given data. Garage has no concept of "primary" so any +healthy node with the data can be used as long as a quorum is reached for the metadata. + +### Node health + +Garage keeps a TCP session open to each node in the cluster and periodically pings them. If a connection +cannot be established, or a node fails to answer a number of pings, the target node is marked as failed. +Failed nodes are not used for quorum or other internal requests. + +### Node preference + +Garage prioritizes which nodes to query according to a few criteria: + +- A node always prefers itself if it can answer the request +- Then the node prioritizes nodes in the same zone +- Finally the nodes with the lowest latency are prioritized + + +For further reading on the cluster structure look at the [gateway](@/documentation/cookbook/gateways.md) +and [cluster layout management](@/documentation/reference-manual/layout.md) pages. + ## Garbage collection A faulty garbage collection procedure has been the cause of diff --git a/doc/book/design/related-work.md b/doc/book/design/related-work.md index ade298ec..f96c6618 100644 --- a/doc/book/design/related-work.md +++ b/doc/book/design/related-work.md @@ -1,6 +1,6 @@ +++ title = "Related work" -weight = 15 +weight = 50 +++ ## Context diff --git a/doc/book/quick-start/_index.md b/doc/book/quick-start/_index.md index 025747bc..21331dcb 100644 --- a/doc/book/quick-start/_index.md +++ b/doc/book/quick-start/_index.md @@ -9,6 +9,15 @@ Let's start your Garage journey! In this chapter, we explain how to deploy Garage as a single-node server and how to interact with it. +## What is Garage? + +Before jumping in, you might be interested in reading the following pages: + +- [Goals and use cases](@/documentation/design/goals.md) +- [List of features](@/documentation/reference-manual/features.md) + +## Scope of this tutorial + Our goal is to introduce you to Garage's workflows. Following this guide is recommended before moving on to [configuring a multi-node cluster](@/documentation/cookbook/real-world.md). @@ -249,16 +258,6 @@ mc alias set \ --api S3v4 ``` -You must also add an environment variable to your configuration to -inform MinIO of our region (`garage` by default, corresponding to the `s3_region` parameter -in the configuration file). -The best way is to add the following snippet to your `$HOME/.bash_profile` -or `$HOME/.bashrc` file: - -```bash -export MC_REGION=garage -``` - ### Use `mc` You can not list buckets from `mc` currently. diff --git a/doc/book/reference-manual/admin-api.md b/doc/book/reference-manual/admin-api.md new file mode 100644 index 00000000..3a4a7aab --- /dev/null +++ b/doc/book/reference-manual/admin-api.md @@ -0,0 +1,644 @@ ++++ +title = "Administration API" +weight = 60 ++++ + +The Garage administration API is accessible through a dedicated server whose +listen address is specified in the `[admin]` section of the configuration +file (see [configuration file +reference](@/documentation/reference-manual/configuration.md)) + +**WARNING.** At this point, there is no comittement to stability of the APIs described in this document. +We will bump the version numbers prefixed to each API endpoint at each time the syntax +or semantics change, meaning that code that relies on these endpoint will break +when changes are introduced. + +The Garage administration API was introduced in version 0.7.2, this document +does not apply to older versions of Garage. + + +## Access control + +The admin API uses two different tokens for acces control, that are specified in the config file's `[admin]` section: + +- `metrics_token`: the token for accessing the Metrics endpoint (if this token + is not set in the config file, the Metrics endpoint can be accessed without + access control); + +- `admin_token`: the token for accessing all of the other administration + endpoints (if this token is not set in the config file, access to these + endpoints is disabled entirely). + +These tokens are used as simple HTTP bearer tokens. In other words, to +authenticate access to an admin API endpoint, add the following HTTP header +to your request: + +``` +Authorization: Bearer <token> +``` + +## Administration API endpoints + +### Metrics-related endpoints + +#### Metrics `GET /metrics` + +Returns internal Garage metrics in Prometheus format. + +### Cluster operations + +#### GetClusterStatus `GET /v0/status` + +Returns the cluster's current status in JSON, including: + +- ID of the node being queried and its version of the Garage daemon +- Live nodes +- Currently configured cluster layout +- Staged changes to the cluster layout + +Example response body: + +```json +{ + "node": "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f", + "garage_version": "git:v0.8.0", + "knownNodes": { + "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f": { + "addr": "10.0.0.11:3901", + "is_up": true, + "last_seen_secs_ago": 9, + "hostname": "node1" + }, + "4a6ae5a1d0d33bf895f5bb4f0a418b7dc94c47c0dd2eb108d1158f3c8f60b0ff": { + "addr": "10.0.0.12:3901", + "is_up": true, + "last_seen_secs_ago": 1, + "hostname": "node2" + }, + "23ffd0cdd375ebff573b20cc5cef38996b51c1a7d6dbcf2c6e619876e507cf27": { + "addr": "10.0.0.21:3901", + "is_up": true, + "last_seen_secs_ago": 7, + "hostname": "node3" + }, + "e2ee7984ee65b260682086ec70026165903c86e601a4a5a501c1900afe28d84b": { + "addr": "10.0.0.22:3901", + "is_up": true, + "last_seen_secs_ago": 1, + "hostname": "node4" + } + }, + "layout": { + "version": 12, + "roles": { + "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f": { + "zone": "dc1", + "capacity": 4, + "tags": [ + "node1" + ] + }, + "4a6ae5a1d0d33bf895f5bb4f0a418b7dc94c47c0dd2eb108d1158f3c8f60b0ff": { + "zone": "dc1", + "capacity": 6, + "tags": [ + "node2" + ] + }, + "23ffd0cdd375ebff573b20cc5cef38996b51c1a7d6dbcf2c6e619876e507cf27": { + "zone": "dc2", + "capacity": 10, + "tags": [ + "node3" + ] + } + }, + "stagedRoleChanges": { + "e2ee7984ee65b260682086ec70026165903c86e601a4a5a501c1900afe28d84b": { + "zone": "dc2", + "capacity": 5, + "tags": [ + "node4" + ] + } + } + } +} +``` + +#### ConnectClusterNodes `POST /v0/connect` + +Instructs this Garage node to connect to other Garage nodes at specified addresses. + +Example request body: + +```json +[ + "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f@10.0.0.11:3901", + "4a6ae5a1d0d33bf895f5bb4f0a418b7dc94c47c0dd2eb108d1158f3c8f60b0ff@10.0.0.12:3901" +] +``` + +The format of the string for a node to connect to is: `<node ID>@<ip address>:<port>`, same as in the `garage node connect` CLI call. + +Example response: + +```json +[ + { + "success": true, + "error": null + }, + { + "success": false, + "error": "Handshake error" + } +] +``` + +#### GetClusterLayout `GET /v0/layout` + +Returns the cluster's current layout in JSON, including: + +- Currently configured cluster layout +- Staged changes to the cluster layout + +(the info returned by this endpoint is a subset of the info returned by GetClusterStatus) + +Example response body: + +```json +{ + "version": 12, + "roles": { + "ec79480e0ce52ae26fd00c9da684e4fa56658d9c64cdcecb094e936de0bfe71f": { + "zone": "dc1", + "capacity": 4, + "tags": [ + "node1" + ] + }, + "4a6ae5a1d0d33bf895f5bb4f0a418b7dc94c47c0dd2eb108d1158f3c8f60b0ff": { + "zone": "dc1", + "capacity": 6, + "tags": [ + "node2" + ] + }, + "23ffd0cdd375ebff573b20cc5cef38996b51c1a7d6dbcf2c6e619876e507cf27": { + "zone": "dc2", + "capacity": 10, + "tags": [ + "node3" + ] + } + }, + "stagedRoleChanges": { + "e2ee7984ee65b260682086ec70026165903c86e601a4a5a501c1900afe28d84b": { + "zone": "dc2", + "capacity": 5, + "tags": [ + "node4" + ] + } + } +} +``` + +#### UpdateClusterLayout `POST /v0/layout` + +Send modifications to the cluster layout. These modifications will +be included in the staged role changes, visible in subsequent calls +of `GetClusterLayout`. Once the set of staged changes is satisfactory, +the user may call `ApplyClusterLayout` to apply the changed changes, +or `Revert ClusterLayout` to clear all of the staged changes in +the layout. + +Request body format: + +```json +{ + <node_id>: { + "capacity": <new_capacity>, + "zone": <new_zone>, + "tags": [ + <new_tag>, + ... + ] + }, + <node_id_to_remove>: null, + ... +} +``` + +Contrary to the CLI that may update only a subset of the fields +`capacity`, `zone` and `tags`, when calling this API all of these +values must be specified. + + +#### ApplyClusterLayout `POST /v0/layout/apply` + +Applies to the cluster the layout changes currently registered as +staged layout changes. + +Request body format: + +```json +{ + "version": 13 +} +``` + +Similarly to the CLI, the body must include the version of the new layout +that will be created, which MUST be 1 + the value of the currently +existing layout in the cluster. + +#### RevertClusterLayout `POST /v0/layout/revert` + +Clears all of the staged layout changes. + +Request body format: + +```json +{ + "version": 13 +} +``` + +Reverting the staged changes is done by incrementing the version number +and clearing the contents of the staged change list. +Similarly to the CLI, the body must include the incremented +version number, which MUST be 1 + the value of the currently +existing layout in the cluster. + + +### Access key operations + +#### ListKeys `GET /v0/key` + +Returns all API access keys in the cluster. + +Example response: + +```json +[ + { + "id": "GK31c2f218a2e44f485b94239e", + "name": "test" + }, + { + "id": "GKe10061ac9c2921f09e4c5540", + "name": "test2" + } +] +``` + +#### CreateKey `POST /v0/key` + +Creates a new API access key. + +Request body format: + +```json +{ + "name": "NameOfMyKey" +} +``` + +#### ImportKey `POST /v0/key/import` + +Imports an existing API key. + +Request body format: + +```json +{ + "accessKeyId": "GK31c2f218a2e44f485b94239e", + "secretAccessKey": "b892c0665f0ada8a4755dae98baa3b133590e11dae3bcc1f9d769d67f16c3835", + "name": "NameOfMyKey" +} +``` + +#### GetKeyInfo `GET /v0/key?id=<acces key id>` +#### GetKeyInfo `GET /v0/key?search=<pattern>` + +Returns information about the requested API access key. + +If `id` is set, the key is looked up using its exact identifier (faster). +If `search` is set, the key is looked up using its name or prefix +of identifier (slower, all keys are enumerated to do this). + +Example response: + +```json +{ + "name": "test", + "accessKeyId": "GK31c2f218a2e44f485b94239e", + "secretAccessKey": "b892c0665f0ada8a4755dae98baa3b133590e11dae3bcc1f9d769d67f16c3835", + "permissions": { + "createBucket": false + }, + "buckets": [ + { + "id": "70dc3bed7fe83a75e46b66e7ddef7d56e65f3c02f9f80b6749fb97eccb5e1033", + "globalAliases": [ + "test2" + ], + "localAliases": [], + "permissions": { + "read": true, + "write": true, + "owner": false + } + }, + { + "id": "d7452a935e663fc1914f3a5515163a6d3724010ce8dfd9e4743ca8be5974f995", + "globalAliases": [ + "test3" + ], + "localAliases": [], + "permissions": { + "read": true, + "write": true, + "owner": false + } + }, + { + "id": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b", + "globalAliases": [], + "localAliases": [ + "test" + ], + "permissions": { + "read": true, + "write": true, + "owner": true + } + }, + { + "id": "96470e0df00ec28807138daf01915cfda2bee8eccc91dea9558c0b4855b5bf95", + "globalAliases": [ + "alex" + ], + "localAliases": [], + "permissions": { + "read": true, + "write": true, + "owner": true + } + } + ] +} +``` + +#### DeleteKey `DELETE /v0/key?id=<acces key id>` + +Deletes an API access key. + +#### UpdateKey `POST /v0/key?id=<acces key id>` + +Updates information about the specified API access key. + +Request body format: + +```json +{ + "name": "NameOfMyKey", + "allow": { + "createBucket": true, + }, + "deny": {} +} +``` + +All fields (`name`, `allow` and `deny`) are optionnal. +If they are present, the corresponding modifications are applied to the key, otherwise nothing is changed. +The possible flags in `allow` and `deny` are: `createBucket`. + + +### Bucket operations + +#### ListBuckets `GET /v0/bucket` + +Returns all storage buckets in the cluster. + +Example response: + +```json +[ + { + "id": "70dc3bed7fe83a75e46b66e7ddef7d56e65f3c02f9f80b6749fb97eccb5e1033", + "globalAliases": [ + "test2" + ], + "localAliases": [] + }, + { + "id": "96470e0df00ec28807138daf01915cfda2bee8eccc91dea9558c0b4855b5bf95", + "globalAliases": [ + "alex" + ], + "localAliases": [] + }, + { + "id": "d7452a935e663fc1914f3a5515163a6d3724010ce8dfd9e4743ca8be5974f995", + "globalAliases": [ + "test3" + ], + "localAliases": [] + }, + { + "id": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b", + "globalAliases": [], + "localAliases": [ + { + "accessKeyId": "GK31c2f218a2e44f485b94239e", + "alias": "test" + } + ] + } +] +``` + +#### GetBucketInfo `GET /v0/bucket?id=<bucket id>` +#### GetBucketInfo `GET /v0/bucket?globalAlias=<alias>` + +Returns information about the requested storage bucket. + +If `id` is set, the bucket is looked up using its exact identifier. +If `globalAlias` is set, the bucket is looked up using its global alias. +(both are fast) + +Example response: + +```json +{ + "id": "afa8f0a22b40b1247ccd0affb869b0af5cff980924a20e4b5e0720a44deb8d39", + "globalAliases": [], + "websiteAccess": false, + "websiteConfig": null, + "keys": [ + { + "accessKeyId": "GK31c2f218a2e44f485b94239e", + "name": "Imported key", + "permissions": { + "read": true, + "write": true, + "owner": true + }, + "bucketLocalAliases": [ + "debug" + ] + } + ], + "objects": 14827, + "bytes": 13189855625, + "unfinshedUploads": 0, + "quotas": { + "maxSize": null, + "maxObjects": null + } +} +``` + +#### CreateBucket `POST /v0/bucket` + +Creates a new storage bucket. + +Request body format: + +```json +{ + "globalAlias": "NameOfMyBucket" +} +``` + +OR + +```json +{ + "localAlias": { + "accessKeyId": "GK31c2f218a2e44f485b94239e", + "alias": "NameOfMyBucket", + "allow": { + "read": true, + "write": true, + "owner": false + } + } +} +``` + +OR + +```json +{} +``` + +Creates a new bucket, either with a global alias, a local one, +or no alias at all. + +Technically, you can also specify both `globalAlias` and `localAlias` and that would create +two aliases, but I don't see why you would want to do that. + +#### DeleteBucket `DELETE /v0/bucket?id=<bucket id>` + +Deletes a storage bucket. A bucket cannot be deleted if it is not empty. + +Warning: this will delete all aliases associated with the bucket! + +#### UpdateBucket `PUT /v0/bucket?id=<bucket id>` + +Updates configuration of the given bucket. + +Request body format: + +```json +{ + "websiteAccess": { + "enabled": true, + "indexDocument": "index.html", + "errorDocument": "404.html" + }, + "quotas": { + "maxSize": 19029801, + "maxObjects": null, + } +} +``` + +All fields (`websiteAccess` and `quotas`) are optionnal. +If they are present, the corresponding modifications are applied to the bucket, otherwise nothing is changed. + +In `websiteAccess`: if `enabled` is `true`, `indexDocument` must be specified. +The field `errorDocument` is optional, if no error document is set a generic +error message is displayed when errors happen. Conversely, if `enabled` is +`false`, neither `indexDocument` nor `errorDocument` must be specified. + +In `quotas`: new values of `maxSize` and `maxObjects` must both be specified, or set to `null` +to remove the quotas. An absent value will be considered the same as a `null`. It is not possible +to change only one of the two quotas. + +### Operations on permissions for keys on buckets + +#### BucketAllowKey `POST /v0/bucket/allow` + +Allows a key to do read/write/owner operations on a bucket. + +Request body format: + +```json +{ + "bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b", + "accessKeyId": "GK31c2f218a2e44f485b94239e", + "permissions": { + "read": true, + "write": true, + "owner": true + }, +} +``` + +Flags in `permissions` which have the value `true` will be activated. +Other flags will remain unchanged. + +#### BucketDenyKey `POST /v0/bucket/deny` + +Denies a key from doing read/write/owner operations on a bucket. + +Request body format: + +```json +{ + "bucketId": "e6a14cd6a27f48684579ec6b381c078ab11697e6bc8513b72b2f5307e25fff9b", + "accessKeyId": "GK31c2f218a2e44f485b94239e", + "permissions": { + "read": false, + "write": false, + "owner": true + }, +} +``` + +Flags in `permissions` which have the value `true` will be deactivated. +Other flags will remain unchanged. + + +### Operations on bucket aliases + +#### GlobalAliasBucket `PUT /v0/bucket/alias/global?id=<bucket id>&alias=<global alias>` + +Empty body. Creates a global alias for a bucket. + +#### GlobalUnaliasBucket `DELETE /v0/bucket/alias/global?id=<bucket id>&alias=<global alias>` + +Removes a global alias for a bucket. + +#### LocalAliasBucket `PUT /v0/bucket/alias/local?id=<bucket id>&accessKeyId=<access key ID>&alias=<local alias>` + +Empty body. Creates a local alias for a bucket in the namespace of a specific access key. + +#### LocalUnaliasBucket `DELETE /v0/bucket/alias/local?id=<bucket id>&accessKeyId<access key ID>&alias=<local alias>` + +Removes a local alias for a bucket in the namespace of a specific access key. + diff --git a/doc/book/reference-manual/cli.md b/doc/book/reference-manual/cli.md index 43a0c823..82492c3e 100644 --- a/doc/book/reference-manual/cli.md +++ b/doc/book/reference-manual/cli.md @@ -1,6 +1,6 @@ +++ title = "Garage CLI" -weight = 15 +weight = 30 +++ The Garage CLI is mostly self-documented. Make use of the `help` subcommand diff --git a/doc/book/reference-manual/configuration.md b/doc/book/reference-manual/configuration.md index bb04650c..97da0e0e 100644 --- a/doc/book/reference-manual/configuration.md +++ b/doc/book/reference-manual/configuration.md @@ -1,6 +1,6 @@ +++ title = "Configuration file format" -weight = 5 +weight = 20 +++ Here is an example `garage.toml` configuration file that illustrates all of the possible options: @@ -9,6 +9,8 @@ Here is an example `garage.toml` configuration file that illustrates all of the metadata_dir = "/var/lib/garage/meta" data_dir = "/var/lib/garage/data" +db_engine = "lmdb" + block_size = 1048576 replication_mode = "3" @@ -47,6 +49,8 @@ root_domain = ".web.garage" [admin] api_bind_addr = "0.0.0.0:3903" +metrics_token = "cacce0b2de4bc2d9f5b5fdff551e01ac1496055aed248202d415398987e35f81" +admin_token = "ae8cb40ea7368bbdbb6430af11cca7da833d3458a5f52086f4e805a570fb5c2a" trace_sink = "http://localhost:4317" ``` @@ -69,6 +73,47 @@ This folder can be placed on an HDD. The space available for `data_dir` should be counted to determine a node's capacity when [adding it to the cluster layout](@/documentation/cookbook/real-world.md). +### `db_engine` (since `v0.8.0`) + +By default, Garage uses the Sled embedded database library +to store its metadata on-disk. Since `v0.8.0`, Garage can use alternative storage backends as follows: + +| DB engine | `db_engine` value | Database path | +| --------- | ----------------- | ------------- | +| [Sled](https://sled.rs) | `"sled"` | `<metadata_dir>/db/` | +| [LMDB](https://www.lmdb.tech) | `"lmdb"` | `<metadata_dir>/db.lmdb/` | +| [Sqlite](https://sqlite.org) | `"sqlite"` | `<metadata_dir>/db.sqlite` | + +Performance characteristics of the different DB engines are as follows: + +- Sled: the default database engine, which tends to produce + large data files and also has performance issues, especially when the metadata folder + is on a traditionnal HDD and not on SSD. +- LMDB: the recommended alternative on 64-bit systems, + much more space-efficiant and slightly faster. Note that the data format of LMDB is not portable + between architectures, so for instance the Garage database of an x86-64 + node cannot be moved to an ARM64 node. Also note that, while LMDB can technically be used on 32-bit systems, + this will limit your node to very small database sizes due to how LMDB works; it is therefore not recommended. +- Sqlite: Garage supports Sqlite as a storage backend for metadata, + however it may have issues and is also very slow in its current implementation, + so it is not recommended to be used for now. + +It is possible to convert Garage's metadata directory from one format to another with a small utility named `convert_db`, +which can be downloaded at the following locations: +[for amd64](https://garagehq.deuxfleurs.fr/_releases/convert_db/amd64/convert_db), +[for i386](https://garagehq.deuxfleurs.fr/_releases/convert_db/i386/convert_db), +[for arm64](https://garagehq.deuxfleurs.fr/_releases/convert_db/arm64/convert_db), +[for arm](https://garagehq.deuxfleurs.fr/_releases/convert_db/arm/convert_db). +The `convert_db` utility is used as folows: + +``` +convert-db -a <input db engine> -i <input db path> \ + -b <output db engine> -o <output db path> +``` + +Make sure to specify the full database path as presented in the table above, +and not just the path to the metadata directory. + ### `block_size` Garage splits stored objects in consecutive chunks of size `block_size` @@ -326,10 +371,24 @@ Garage has a few administration capabilities, in particular to allow remote moni ### `api_bind_addr` If specified, Garage will bind an HTTP server to this port and address, on -which it will listen to requests for administration features. Currently, -this endpoint only exposes Garage metrics in the Prometheus format at -`/metrics`. This endpoint is not authenticated. In the future, bucket and -access key management might be possible by REST calls to this endpoint. +which it will listen to requests for administration features. +See [administration API reference](@/documentation/reference-manual/admin-api.md) to learn more about these features. + +### `metrics_token` (since version 0.7.2) + +The token for accessing the Metrics endpoint. If this token is not set in +the config file, the Metrics endpoint can be accessed without access +control. + +You can use any random string for this value. We recommend generating a random token with `openssl rand -hex 32`. + +### `admin_token` (since version 0.7.2) + +The token for accessing all of the other administration endpoints. If this +token is not set in the config file, access to these endpoints is disabled +entirely. + +You can use any random string for this value. We recommend generating a random token with `openssl rand -hex 32`. ### `trace_sink` diff --git a/doc/book/reference-manual/features.md b/doc/book/reference-manual/features.md new file mode 100644 index 00000000..d2d28946 --- /dev/null +++ b/doc/book/reference-manual/features.md @@ -0,0 +1,125 @@ ++++ +title = "List of Garage features" +weight = 10 ++++ + + +### S3 API + +The main goal of Garage is to provide an object storage service that is compatible with the +[S3 API](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) from Amazon Web Services. +We try to adhere as strictly as possible to the semantics of the API as implemented by Amazon +and other vendors such as Minio or CEPH. + +Of course Garage does not implement the full span of API endpoints that AWS S3 does; +the exact list of S3 features implemented by Garage can be found [on our S3 compatibility page](@/documentation/reference-manual/s3-compatibility.md). + +### Geo-distribution + +Garage allows you to store copies of your data in multiple geographical locations in order to maximize resilience +to adverse events, such as network/power outages or hardware failures. +This allows Garage to run very well even at home, using consumer-grade Internet connectivity +(such as FTTH) and power, as long as cluster nodes can be spawned at several physical locations. +Garage exploits knowledge of the capacity and physical location of each storage node to design +a storage plan that best exploits the available storage capacity while satisfying the geo-distributed replication constraint. + +To learn more about geo-distributed Garage clusters, +read our documentation on [setting up a real-world deployment](@/documentation/cookbook/real-world.md). + +### Standalone/self-contained + +Garage is extremely simple to deploy, and does not depend on any external service to run. +This makes setting up and administering storage clusters, we hope, as easy as it could be. + +### Flexible topology + +A Garage cluster can very easily evolve over time, as storage nodes are added or removed. +Garage will automatically rebalance data between nodes as needed to ensure the desired number of copies. +Read about cluster layout management [here](@/documentation/reference-manual/layout.md). + +### No RAFT slowing you down + +It might seem strange to tout the absence of something as a desirable feature, +but this is in fact a very important point! Garage does not use RAFT or another +consensus algorithm internally to order incoming requests: this means that all requests +directed to a Garage cluster can be handled independently of one another instead +of going through a central bottleneck (the leader node). +As a consequence, requests can be handled much faster, even in cases where latency +between cluster nodes is important (see our [benchmarks](@/documentation/design/benchmarks/index.md) for data on this). +This is particularly usefull when nodes are far from one another and talk to one other through standard Internet connections. + +### Several replication modes + +Garage supports a variety of replication modes, with 1 copy, 2 copies or 3 copies of your data, +and with various levels of consistency, in order to adapt to a variety of usage scenarios. +Read our reference page on [supported replication modes](@/documentation/reference-manual/configuration.md#replication-mode) +to select the replication mode best suited to your use case (hint: in most cases, `replication_mode = "3"` is what you want). + +### Web server for static websites + +A storage bucket can easily be configured to be served directly by Garage as a static web site. +Domain names for multiple websites directly map to bucket names, making it easy to build +a platform for your users to autonomously build and host their websites over Garage. +Surprisingly, none of the other alternative S3 implementations we surveyed (such as Minio +or CEPH) support publishing static websites from S3 buckets, a feature that is however +directly inherited from S3 on AWS. +Read more on our [dedicated documentation page](@/documentation/cookbook/exposing-websites.md). + +### Bucket names as aliases + +In Garage, a bucket may have several names, known as aliases. +Aliases can easily be added and removed on demand: +this allows to easily rename buckets if needed +without having to copy all of their content, something that cannot be done on AWS. +For buckets served as static websites, having multiple aliases for a bucket can allow +exposing the same content under different domain names. + +Garage also supports bucket aliases which are local to a single user: +this allows different users to have different buckets with the same name, thus avoiding naming collisions. +This can be helpfull for instance if you want to write an application that creates per-user buckets with always the same name. + +This feature is totally invisible to S3 clients and does not break compatibility with AWS. + +### Cluster administration API + +Garage provides a fully-fledged REST API to administer your cluster programatically. +Functionnality included in the admin API include: setting up and monitoring +cluster nodes, managing access credentials, and managing storage buckets and bucket aliases. +A full reference of the administration API is available [here](@/documentation/reference-manual/admin-api.md). + +### Metrics and traces + +Garage makes some internal metrics available in the Prometheus data format, +which allows you to build interactive dashboards to visualize the load and internal state of your storage cluster. + +For developpers and performance-savvy administrators, +Garage also supports exporting traces of what it does internally in OpenTelemetry format. +This allows to monitor the time spent at various steps of the processing of requests, +in order to detect potential performance bottlenecks. + +### Kubernetes and Nomad integrations + +Garage can automatically discover other nodes in the cluster thanks to integration +with orchestrators such as Kubernetes and Nomad (when used with Consul). +This eases the configuration of your cluster as it removes one step where nodes need +to be manually connected to one another. + +### Support for changing IP addresses + +As long as all of your nodes don't thange their IP address at the same time, +Garage should be able to tolerate nodes with changing/dynamic IP addresses, +as nodes will regularly exchange the IP addresses of their peers and try to +reconnect using newer addresses when existing connections are broken. + +### K2V API (experimental) + +As part of an ongoing research project, Garage can expose an experimental key/value storage API called K2V. +K2V is made for the storage and retrieval of many small key/value pairs that need to be processed in bulk. +This completes the S3 API with an alternative that can be used to easily store and access metadata +related to objects stored in an S3 bucket. + +In the context of our research project, [Aérogramme](https://aerogramme.deuxfleurs.fr), +K2V is used to provide metadata and log storage for operations on encrypted e-mail storage. + +Learn more on the specification of K2V [here](https://git.deuxfleurs.fr/Deuxfleurs/garage/src/branch/k2v/doc/drafts/k2v-spec.md) +and on how to enable it in Garage [here](@/documentation/reference-manual/k2v.md). diff --git a/doc/book/reference-manual/k2v.md b/doc/book/reference-manual/k2v.md new file mode 100644 index 00000000..207d056a --- /dev/null +++ b/doc/book/reference-manual/k2v.md @@ -0,0 +1,58 @@ ++++ +title = "K2V" +weight = 70 ++++ + +Starting with version 0.7.2, Garage introduces an optionnal feature, K2V, +which is an alternative storage API designed to help efficiently store +many small values in buckets (in opposition to S3 which is more designed +to store large blobs). + +K2V is currently disabled at compile time in all builds, as the +specification is still subject to changes. To build a Garage version with +K2V, the Cargo feature flag `k2v` must be activated. Special builds with +the `k2v` feature flag enabled can be obtained from our download page under +"Extra builds": such builds can be identified easily as their tag name ends +with `-k2v` (example: `v0.7.2-k2v`). + +The specification of the K2V API can be found +[here](https://git.deuxfleurs.fr/Deuxfleurs/garage/src/branch/k2v/doc/drafts/k2v-spec.md). +This document also includes a high-level overview of K2V's design. + +The K2V API uses AWSv4 signatures for authentification, same as the S3 API. +The AWS region used for signature calculation is always the same as the one +defined for the S3 API in the config file. + +## Enabling and using K2V + +To enable K2V, download and run a build that has the `k2v` feature flag +enabled, or produce one yourself. Then, add the following section to your +configuration file: + +```toml +[k2v_api] +api_bind_addr = "<ip>:<port>" +``` + +Please select a port number that is not already in use by another API +endpoint (S3 api, admin API) or by the RPC server. + +We provide an early-stage K2V client library for Rust which can be imported by adding the following to your `Cargo.toml` file: + +```toml +k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git" } +``` + +There is also a simple CLI utility which can be built from source in the +following way: + +```sh +git clone https://git.deuxfleurs.fr/Deuxfleurs/garage.git +cd garage/src/k2v-client +cargo build --features cli --bin k2v-cli +``` + +The CLI utility is self-documented, run `k2v-cli --help` to learn how to use +it. There is also a short README.md in the `src/k2v-client` folder with some +instructions. + diff --git a/doc/book/reference-manual/layout.md b/doc/book/reference-manual/layout.md index 7debbf33..a7d6f51f 100644 --- a/doc/book/reference-manual/layout.md +++ b/doc/book/reference-manual/layout.md @@ -1,6 +1,6 @@ +++ title = "Cluster layout management" -weight = 10 +weight = 50 +++ The cluster layout in Garage is a table that assigns to each node a role in diff --git a/doc/book/reference-manual/routing.md b/doc/book/reference-manual/routing.md deleted file mode 100644 index aec637cc..00000000 --- a/doc/book/reference-manual/routing.md +++ /dev/null @@ -1,45 +0,0 @@ -+++ -title = "Request routing logic" -weight = 10 -+++ - -Data retrieval requests to Garage endpoints (S3 API and websites) are resolved -to an individual object in a bucket. Since objects are replicated to multiple nodes -Garage must ensure consistency before answering the request. - -## Using quorum to ensure consistency - -Garage ensures consistency by attempting to establish a quorum with the -data nodes responsible for the object. When a majority of the data nodes -have provided metadata on a object Garage can then answer the request. - -When a request arrives Garage will, assuming the recommended 3 replicas, perform the following actions: - -- Make a request to the two preferred nodes for object metadata -- Try the third node if one of the two initial requests fail -- Check that the metadata from at least 2 nodes match -- Check that the object hasn't been marked deleted -- Answer the request with inline data from metadata if object is small enough -- Or get data blocks from the preferred nodes and answer using the assembled object - -Garage dynamically determines which nodes to query based on health, preference, and -which nodes actually host a given data. Garage has no concept of "primary" so any -healthy node with the data can be used as long as a quorum is reached for the metadata. - -## Node health - -Garage keeps a TCP session open to each node in the cluster and periodically pings them. If a connection -cannot be established, or a node fails to answer a number of pings, the target node is marked as failed. -Failed nodes are not used for quorum or other internal requests. - -## Node preference - -Garage prioritizes which nodes to query according to a few criteria: - -- A node always prefers itself if it can answer the request -- Then the node prioritizes nodes in the same zone -- Finally the nodes with the lowest latency are prioritized - - -For further reading on the cluster structure look at the [gateway](@/documentation/cookbook/gateways.md) -and [cluster layout management](@/documentation/reference-manual/layout.md) pages.
\ No newline at end of file diff --git a/doc/book/reference-manual/s3-compatibility.md b/doc/book/reference-manual/s3-compatibility.md index 71b4c209..dd3492a0 100644 --- a/doc/book/reference-manual/s3-compatibility.md +++ b/doc/book/reference-manual/s3-compatibility.md @@ -1,53 +1,79 @@ +++ title = "S3 Compatibility status" -weight = 20 +weight = 40 +++ -## Endpoint implementation +## DISCLAIMER + +**The compatibility list for other platforms is given only for informational +purposes and based on available documentation.** They are sometimes completed, +in a best effort approach, with the source code and inputs from maintainers +when documentation is lacking. We are not proactively monitoring new versions +of each software: check the modification history to know when the page has been +updated for the last time. Some entries will be inexact or outdated. For any +serious decision, you must make your own tests. +**The official documentation of each project can be accessed by clicking on the +project name in the column header.** + +Feel free to open a PR to suggest fixes this table. Minio is missing because they do not provide a public S3 compatibility list. + +## Update history + +- 2022-02-07 - First version of this page +- 2022-05-25 - Many Ceph S3 endpoints are not documented but implemented. Following a notification from the Ceph community, we added them. -All APIs that are missing on Garage will return a 501 Not Implemented. -Some `x-amz-` headers are not implemented. -*The compatibility list for other platforms is given only for information purposes and based on available documentation. Some entries might be inexact. Feel free to open a PR to fix this table. Minio is missing because they do not provide a public S3 compatibility list.* -### Features +## High-level features | Feature | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| -| [signature v2](https://docs.aws.amazon.com/general/latest/gr/signature-version-2.html) (deprecated) | ❌ Missing | ✅ | ❌ | ✅ | ✅ | +| [signature v2](https://docs.aws.amazon.com/general/latest/gr/signature-version-2.html) (deprecated) | ❌ Missing | ✅ | ✅ | ✅ | ✅ | | [signature v4](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html) | ✅ Implemented | ✅ | ✅ | ❌ | ✅ | | [URL path-style](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#path-style-access) (eg. `host.tld/bucket/key`) | ✅ Implemented | ✅ | ✅ | ❓| ✅ | | [URL vhost-style](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access) URL (eg. `bucket.host.tld/key`) | ✅ Implemented | ❌| ✅| ✅ | ✅ | | [Presigned URLs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html) | ✅ Implemented | ❌| ✅ | ✅ | ✅(❓) | -*Note:* OpenIO does not says if it supports presigned URLs. Because it is part of signature v4 and they claim they support it without additional precisions, we suppose that OpenIO supports presigned URLs. +*Note:* OpenIO does not says if it supports presigned URLs. Because it is part +of signature v4 and they claim they support it without additional precisions, +we suppose that OpenIO supports presigned URLs. + + +## Endpoint implementation + +All endpoints that are missing on Garage will return a 501 Not Implemented. +Some `x-amz-` headers are not implemented. ### Core endoints -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| | [CreateBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | | [DeleteBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | | [GetBucketLocation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html) | ✅ Implemented | ✅ | ✅ | ❌ | ✅ | | [HeadBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | | [ListBuckets](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html) | ✅ Implemented | ❌| ✅ | ✅ | ✅ | -| [HeadObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | -| [CopyObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | +| [HeadObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | +| [CopyObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | | [DeleteObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | | [DeleteObjects](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | | [GetObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | | [ListObjects](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjects.html) | ✅ Implemented (see details below) | ✅ | ✅ | ✅ | ❌| -| [ListObjectsV2](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html) | ✅ Implemented | ❌| ❌| ❌| ✅ | -| [PostObject](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPOST.html) (compatibility API) | ❌ Missing | ❌| ✅ | ❌| ❌| +| [ListObjectsV2](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html) | ✅ Implemented | ❌| ✅ | ❌| ✅ | +| [PostObject](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPOST.html) | ✅ Implemented | ❌| ✅ | ❌| ❌| | [PutObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | -**ListObjects:** Implemented, but there isn't a very good specification of what `encoding-type=url` covers so there might be some encoding bugs. In our implementation the url-encoded fields are in the same in ListObjects as they are in ListObjectsV2. +**ListObjects:** Implemented, but there isn't a very good specification of what +`encoding-type=url` covers so there might be some encoding bugs. In our +implementation the url-encoded fields are in the same in ListObjects as they +are in ListObjectsV2. -*Note: Ceph API documentation is incomplete and miss at least HeadBucket and UploadPartCopy, but these endpoints are documented in [Red Hat Ceph Storage - Chapter 2. Ceph Object Gateway and the S3 API](https://access.redhat.com/documentation/en-us/red_hat_ceph_storage/4/html/developer_guide/ceph-object-gateway-and-the-s3-api)* +*Note: Ceph API documentation is incomplete and lacks at least HeadBucket and UploadPartCopy, +but these endpoints are documented in [Red Hat Ceph Storage - Chapter 2. Ceph Object Gateway and the S3 API](https://access.redhat.com/documentation/en-us/red_hat_ceph_storage/4/html/developer_guide/ceph-object-gateway-and-the-s3-api)* ### Multipart Upload endpoints -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| | [AbortMultipartUpload](https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html) | ✅ Implemented | ✅ | ✅ | ✅ | ✅ | | [CompleteMultipartUpload](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html) | ✅ Implemented (see details below) | ✅ | ✅ | ✅ | ✅ | @@ -62,18 +88,18 @@ For more information, please refer to our [issue tracker](https://git.deuxfleurs ### Website endpoints -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| | [DeleteBucketWebsite](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketWebsite.html) | ✅ Implemented | ❌| ❌| ❌| ❌| | [GetBucketWebsite](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketWebsite.html) | ✅ Implemented | ❌ | ❌| ❌| ❌| | [PutBucketWebsite](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketWebsite.html) | ⚠ Partially implemented (see below)| ❌| ❌| ❌| ❌| -| [DeleteBucketCors](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketCors.html) | ✅ Implemented | ❌| ❌| ❌| ✅ | -| [GetBucketCors](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketCors.html) | ✅ Implemented | ❌ | ❌| ❌| ✅ | -| [PutBucketCors](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketCors.html) | ✅ Implemented | ❌| ❌| ❌| ✅ | +| [DeleteBucketCors](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketCors.html) | ✅ Implemented | ❌| ✅ | ❌| ✅ | +| [GetBucketCors](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketCors.html) | ✅ Implemented | ❌ | ✅ | ❌| ✅ | +| [PutBucketCors](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketCors.html) | ✅ Implemented | ❌| ✅ | ❌| ✅ | **PutBucketWebsite:** Implemented, but only stores the index document suffix and the error document path. Redirects are not supported. -*Note: Ceph radosgw has some support for static websites but it is different from Amazon one plus it does not implement its configuration endpoints.* +*Note: Ceph radosgw has some support for static websites but it is different from the Amazon one. It also does not implement its configuration endpoints.* ### ACL, Policies endpoints @@ -81,29 +107,29 @@ Amazon has 2 access control mechanisms in S3: ACL (legacy) and policies (new one Garage implements none of them, and has its own system instead, built around a per-access-key-per-bucket logic. See Garage CLI reference manual to learn how to use Garage's permission system. -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| -| [DeleteBucketPolicy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketPolicy.html) | ❌ Missing | ❌| ❌| ✅ | ❌| -| [GetBucketPolicy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicy.html) | ❌ Missing | ❌| ❌| ⚠ | ❌| -| [GetBucketPolicyStatus](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicyStatus.html) | ❌ Missing | ❌| ❌| ❌| ❌| -| [PutBucketPolicy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketPolicy.html) | ❌ Missing | ❌| ❌| ⚠ | ❌| +| [DeleteBucketPolicy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketPolicy.html) | ❌ Missing | ❌| ✅ | ✅ | ❌| +| [GetBucketPolicy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicy.html) | ❌ Missing | ❌| ✅ | ⚠ | ❌| +| [GetBucketPolicyStatus](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicyStatus.html) | ❌ Missing | ❌| ✅ | ❌| ❌| +| [PutBucketPolicy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketPolicy.html) | ❌ Missing | ❌| ✅ | ⚠ | ❌| | [GetBucketAcl](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketAcl.html) | ❌ Missing | ✅ | ✅ | ✅ | ✅ | | [PutBucketAcl](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html) | ❌ Missing | ✅ | ✅ | ✅ | ✅ | | [GetObjectAcl](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAcl.html) | ❌ Missing | ✅ | ✅ | ✅ | ✅ | | [PutObjectAcl](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html) | ❌ Missing | ✅ | ✅ | ✅ | ✅ | -*Notes:* Ceph claims that it supports bucket policies but does not implement any Policy endpoints. They probably refer to their own permission system. Riak CS only supports a subset of the policy configuration. +*Notes:* Riak CS only supports a subset of the policy configuration. ### Versioning, Lifecycle endpoints -Garage does not support (yet) object versioning. +Garage does not (yet) support object versioning. If you need this feature, please [share your use case in our dedicated issue](https://git.deuxfleurs.fr/Deuxfleurs/garage/issues/166). -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| | [DeleteBucketLifecycle](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketLifecycle.html) | ❌ Missing | ❌| ✅| ❌| ✅| -| [GetBucketLifecycleConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html) | ❌ Missing | ❌| ⚠ | ❌| ✅| -| [PutBucketLifecycleConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html) | ❌ Missing | ❌| ⚠ | ❌| ✅| +| [GetBucketLifecycleConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html) | ❌ Missing | ❌| ✅ | ❌| ✅| +| [PutBucketLifecycleConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html) | ❌ Missing | ❌| ✅ | ❌| ✅| | [GetBucketVersioning](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketVersioning.html) | ❌ Stub (see below) | ✅| ✅ | ❌| ✅| | [ListObjectVersions](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html) | ❌ Missing | ❌| ✅ | ❌| ✅| | [PutBucketVersioning](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketVersioning.html) | ❌ Missing | ❌| ✅| ❌| ✅| @@ -111,64 +137,65 @@ If you need this feature, please [share your use case in our dedicated issue](ht **GetBucketVersioning:** Stub implementation (Garage does not yet support versionning so this always returns "versionning not enabled"). -*Note: Ceph only supports `Expiration`, `NoncurrentVersionExpiration` and `AbortIncompleteMultipartUpload` on its Lifecycle endpoints.* - ### Replication endpoints Please open an issue if you have a use case for replication. -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| | [DeleteBucketReplication](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html) | ❌ Missing | ❌| ✅ | ❌| ❌| | [GetBucketReplication](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html) | ❌ Missing | ❌| ✅ | ❌| ❌| | [PutBucketReplication](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html) | ❌ Missing | ❌| ⚠ | ❌| ❌| -*Note: Ceph documentation briefly says that Ceph supports [replication though the S3 API](https://docs.ceph.com/en/latest/radosgw/multisite-sync-policy/#s3-replication-api) but with some limitations. Additionaly, replication endpoints are not documented in the S3 compatibility page so I don't know what kind of support we can expect.* +*Note: Ceph documentation briefly says that Ceph supports +[replication through the S3 API](https://docs.ceph.com/en/latest/radosgw/multisite-sync-policy/#s3-replication-api) +but with some limitations. +Additionaly, replication endpoints are not documented in the S3 compatibility page so I don't know what kind of support we can expect.* ### Locking objects Amazon defines a concept of [object locking](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html) that can be achieved either through a Retention period or a Legal hold. -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| | [GetObjectLegalHold](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectLegalHold.html) | ❌ Missing | ❌| ✅ | ❌| ❌| | [PutObjectLegalHold](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLegalHold.html) | ❌ Missing | ❌| ✅ | ❌| ❌| | [GetObjectRetention](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectRetention.html) | ❌ Missing | ❌| ✅ | ❌| ❌| | [PutObjectRetention](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectRetention.html) | ❌ Missing | ❌| ✅ | ❌| ❌| -| [GetObjectLockConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectLockConfiguration.html) | ❌ Missing | ❌| ❌| ❌| ❌| -| [PutObjectLockConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLockConfiguration.html) | ❌ Missing | ❌| ❌| ❌| ❌| +| [GetObjectLockConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectLockConfiguration.html) | ❌ Missing | ❌| ✅ | ❌| ❌| +| [PutObjectLockConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLockConfiguration.html) | ❌ Missing | ❌| ✅ | ❌| ❌| ### (Server-side) encryption We think that you can either encrypt your server partition or do client-side encryption, so we did not implement server-side encryption for Garage. Please open an issue if you have a use case. -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| -| [DeleteBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketEncryption.html) | ❌ Missing | ❌| ❌| ❌| ❌| -| [GetBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketEncryption.html) | ❌ Missing | ❌| ❌| ❌| ❌| -| [PutBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketEncryption.html) | ❌ Missing | ❌| ❌| ❌| ❌| +| [DeleteBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketEncryption.html) | ❌ Missing | ❌| ✅ | ❌| ❌| +| [GetBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketEncryption.html) | ❌ Missing | ❌| ✅ | ❌| ❌| +| [PutBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketEncryption.html) | ❌ Missing | ❌| ✅ | ❌| ❌| ### Misc endpoints -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| | [GetBucketNotificationConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketNotificationConfiguration.html) | ❌ Missing | ❌| ✅ | ❌| ❌| | [PutBucketNotificationConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketNotificationConfiguration.html) | ❌ Missing | ❌| ✅ | ❌| ❌| -| [DeleteBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketTagging.html) | ❌ Missing | ❌| ❌| ❌| ✅ | -| [GetBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketTagging.html) | ❌ Missing | ❌| ❌| ❌| ✅ | -| [PutBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketTagging.html) | ❌ Missing | ❌| ❌| ❌| ✅ | -| [DeleteObjectTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjectTagging.html) | ❌ Missing | ❌| ❌| ❌| ✅ | -| [GetObjectTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectTagging.html) | ❌ Missing | ❌| ❌| ❌| ✅ | -| [PutObjectTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectTagging.html) | ❌ Missing | ❌| ❌| ❌| ✅ | -| [GetObjectTorrent](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectTorrent.html) | ❌ Missing | ❌| ❌| ❌| ❌| +| [DeleteBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketTagging.html) | ❌ Missing | ❌| ✅ | ❌| ✅ | +| [GetBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketTagging.html) | ❌ Missing | ❌| ✅ | ❌| ✅ | +| [PutBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketTagging.html) | ❌ Missing | ❌| ✅ | ❌| ✅ | +| [DeleteObjectTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjectTagging.html) | ❌ Missing | ❌| ✅ | ❌| ✅ | +| [GetObjectTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectTagging.html) | ❌ Missing | ❌| ✅ | ❌| ✅ | +| [PutObjectTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectTagging.html) | ❌ Missing | ❌| ✅ | ❌| ✅ | +| [GetObjectTorrent](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectTorrent.html) | ❌ Missing | ❌| ✅ | ❌| ❌| ### Vendor specific endpoints <details><summary>Display Amazon specifc endpoints</summary> -| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | +| Endpoint | Garage | [Openstack Swift](https://docs.openstack.org/swift/latest/s3_compat.html) | [Ceph Object Gateway](https://docs.ceph.com/en/latest/radosgw/s3/) | [Riak CS](https://docs.riak.com/riak/cs/2.1.1/references/apis/storage/s3/index.html) | [OpenIO](https://docs.openio.io/latest/source/arch-design/s3_compliancy.html) | |------------------------------|----------------------------------|-----------------|---------------|---------|-----| | [DeleteBucketAnalyticsConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketAnalyticsConfiguration.html) | ❌ Missing | ❌| ❌| ❌| ❌| | [DeleteBucketIntelligentTieringConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketIntelligentTieringConfiguration.html) | ❌ Missing | ❌| ❌| ❌| ❌| diff --git a/doc/book/working-documents/design-draft.md b/doc/book/working-documents/design-draft.md index 44849a41..3c8298b0 100644 --- a/doc/book/working-documents/design-draft.md +++ b/doc/book/working-documents/design-draft.md @@ -1,6 +1,6 @@ +++ -title = "Design draft" -weight = 25 +title = "Design draft (obsolete)" +weight = 50 +++ **WARNING: this documentation is a design draft which was written before Garage's actual implementation. diff --git a/doc/book/working-documents/load-balancing.md b/doc/book/working-documents/load-balancing.md index 87298ae6..bf6bdd95 100644 --- a/doc/book/working-documents/load-balancing.md +++ b/doc/book/working-documents/load-balancing.md @@ -1,6 +1,6 @@ +++ -title = "Load balancing data" -weight = 10 +title = "Load balancing data (obsolete)" +weight = 60 +++ **This is being yet improved in release 0.5. The working document has not been updated yet, it still only applies to Garage 0.2 through 0.4.** diff --git a/doc/book/working-documents/migration-07.md b/doc/book/working-documents/migration-07.md index 2d0444db..03cdfedc 100644 --- a/doc/book/working-documents/migration-07.md +++ b/doc/book/working-documents/migration-07.md @@ -16,7 +16,7 @@ The migration steps are as follows: 1. Do `garage repair --all-nodes --yes tables` and `garage repair --all-nodes --yes blocks`, check the logs and check that all data seems to be synced correctly between nodes. If you have time, do additional checks (`scrub`, `block_refs`, etc.) -2. Disable api and web access. Garage does not support disabling +2. Disable API and web access. Garage does not support disabling these endpoints but you can change the port number or stop your reverse proxy for instance. 3. Check once again that your cluster is healty. Run again `garage repair --all-nodes --yes tables` which is quick. diff --git a/doc/book/working-documents/migration-08.md b/doc/book/working-documents/migration-08.md new file mode 100644 index 00000000..5f97c45b --- /dev/null +++ b/doc/book/working-documents/migration-08.md @@ -0,0 +1,34 @@ ++++ +title = "Migrating from 0.7 to 0.8" +weight = 13 ++++ + +**This guide explains how to migrate to 0.8 if you have an existing 0.7 cluster. +We don't recommend trying to migrate to 0.8 directly from 0.6 or older.** + +**We make no guarantee that this migration will work perfectly: +back up all your data before attempting it!** + +Garage v0.8 introduces new data tables that allow the counting of objects in buckets in order to implement bucket quotas. +A manual migration step is required to first count objects in Garage buckets and populate these tables with accurate data. + +The migration steps are as follows: + +1. Disable API and web access. Garage v0.7 does not support disabling + these endpoints but you can change the port number or stop your reverse proxy for instance. +2. Do `garage repair --all-nodes --yes tables` and `garage repair --all-nodes --yes blocks`, + check the logs and check that all data seems to be synced correctly between + nodes. If you have time, do additional checks (`scrub`, `block_refs`, etc.) +3. Check that queues are empty: run `garage stats` to query them or inspect metrics in the Grafana dashboard. +4. Turn off Garage v0.7 +5. **Backup the metadata folder of all your nodes!** For instance, use the following command + if your metadata directory is `/var/lib/garage/meta`: `cd /var/lib/garage ; tar -acf meta-v0.7.tar.zst meta/` +6. Install Garage v0.8 +7. **Before starting Garage v0.8**, run the offline migration step: `garage offline-repair --yes object_counters`. + This can take a while to run, depending on the number of objects stored in your cluster. +8. Turn on Garage v0.8 +9. Do `garage repair --all-nodes --yes tables` and `garage repair --all-nodes --yes blocks`. + Wait for a full table sync to run. +10. Your upgraded cluster should be in a working state. Re-enable API and Web + access and check that everything went well. +11. Monitor your cluster in the next hours to see if it works well under your production load, report any issue. diff --git a/doc/drafts/k2v-spec.md b/doc/drafts/k2v-spec.md new file mode 100644 index 00000000..175bb02e --- /dev/null +++ b/doc/drafts/k2v-spec.md @@ -0,0 +1,717 @@ +# Specification of the Garage K2V API (K2V = Key/Key/Value) + +- We are storing triplets of the form `(partition key, sort key, value)` -> no + user-defined fields, the client is responsible of writing whatever he wants + in the value (typically an encrypted blob). Values are binary blobs, which + are always represented as their base64 encoding in the JSON API. Partition + keys and sort keys are utf8 strings. + +- Triplets are stored in buckets; each bucket stores a separate set of triplets + +- Bucket names and access keys are the same as for accessing the S3 API + +- K2V triplets exist separately from S3 objects. K2V triplets don't exist for + the S3 API, and S3 objects don't exist for the K2V API. + +- Values stored for triplets have associated causality information, that enables + Garage to detect concurrent writes. In case of concurrent writes, Garage + keeps the concurrent values until a further write supersedes the concurrent + values. This is the same method as Riak KV implements. The method used is + based on DVVS (dotted version vector sets), described in the paper "Scalable + and Accurate Causality Tracking for Eventually Consistent Data Stores", as + well as [here](https://github.com/ricardobcl/Dotted-Version-Vectors) + + +## Data format + +### Triple format + +Triples in K2V are constituted of three fields: + +- a partition key (`pk`), an utf8 string that defines in what partition the + triplet is stored; triplets in different partitions cannot be listed together + in a ReadBatch command, or deleted together in a DeleteBatch command: a + separate command must be included in the ReadBatch/DeleteBatch call for each + partition key in which the client wants to read/delete lists of items + +- a sort key (`sk`), an utf8 string that defines the index of the triplet inside its + partition; triplets are uniquely idendified by their partition key + sort key + +- a value (`v`), an opaque binary blob associated to the partition key + sort key; + they are transmitted as binary when possible but in most case in the JSON API + they will be represented as strings using base64 encoding; a value can also + be `null` to indicate a deleted triplet (a `null` value is called a tombstone) + +### Causality information + +K2V supports storing several concurrent values associated to a pk+sk, in the +case where insertion or deletion operations are detected to be concurrent (i.e. +there is not one that was aware of the other, they are not causally dependant +one on the other). In practice, it even looks more like the opposite: to +overwrite a previously existing value, the client must give a "causality token" +that "proves" (not in a cryptographic sense) that it had seen a previous value. +Otherwise, the value written will not overwrite an existing value, it will just +create a new concurrent value. + +The causality token is a binary/b64-encoded representation of a context, +specified below. + +A set of concurrent values looks like this: + +``` +(node1, tdiscard1, (v1, t1), (v2, t2)) ; tdiscard1 < t1 < t2 +(node2, tdiscard2, (v3, t3) ; tdiscard2 < t3 +``` + +`tdiscard` for a node `i` means that all values inserted by node `i` with times +`<= tdiscard` are obsoleted, i.e. have been read by a client that overwrote it +afterwards. + +The associated context would be the following: `[(node1, t2), (node2, t3)]`, +i.e. if a node reads this set of values and inserts a new values, we will now +have `tdiscard1 = t2` and `tdiscard2 = t3`, to indicate that values v1, v2 and v3 +are obsoleted by the new write. + +**Basic insertion.** To insert a new value `v4` with context `[(node1, t2), (node2, t3)]`, in a +simple case where there was no insertion in-between reading the value +mentionned above and writing `v4`, and supposing that node2 receives the +InsertItem query: + +- `node2` generates a timestamp `t4` such that `t4 > t3`. +- the new state is as follows: + +``` +(node1, tdiscard1', ()) ; tdiscard1' = t2 +(node2, tdiscard2', (v4, t4)) ; tdiscard2' = t3 +``` + +**A more complex insertion example.** In the general case, other intermediate values could have +been written before `v4` with context `[(node1, t2), (node2, t3)]` is sent to the system. +For instance, here is a possible sequence of events: + +1. First we have the set of values v1, v2 and v3 described above. + A node reads it, it obtains values v1, v2 and v3 with context `[(node1, t2), (node2, t3)]`. + +2. A node writes a value `v5` with context `[(node1, t1)]`, i.e. `v5` is only a + successor of v1 but not of v2 or v3. Suppose node1 receives the write, it + will generate a new timestamp `t5` larger than all of the timestamps it + knows of, i.e. `t5 > t2`. We will now have: + +``` +(node1, tdiscard1'', (v2, t2), (v5, t5)) ; tdiscard1'' = t1 < t2 < t5 +(node2, tdiscard2, (v3, t3) ; tdiscard2 < t3 +``` + +3. Now `v4` is written with context `[(node1, t2), (node2, t3)]`, and node2 + processes the query. It will generate `t4 > t3` and the state will become: + +``` +(node1, tdiscard1', (v5, t5)) ; tdiscard1' = t2 < t5 +(node2, tdiscard2', (v4, t4)) ; tdiscard2' = t3 +``` + +**Generic algorithm for handling insertions:** A certain node n handles the +InsertItem and is responsible for the correctness of this procedure. + +1. Lock the key (or the whole table?) at this node to prevent concurrent updates of the value that would mess things up +2. Read current set of values +3. Generate a new timestamp that is larger than the largest timestamp for node n +4. Add the inserted value in the list of values of node n +5. Update the discard times to be the times set in the context, and accordingly discard overwritten values +6. Release lock +7. Propagate updated value to other nodes +8. Return to user when propagation achieved the write quorum (propagation to other nodes continues asynchronously) + +**Encoding of contexts:** + +Contexts consist in a list of (node id, timestamp) pairs. +They are encoded in binary as follows: + +``` +checksum: u64, [ node: u64, timestamp: u64 ]* +``` + +The checksum is just the XOR of all of the node IDs and timestamps. + +Once encoded in binary, contexts are written and transmitted in base64. + + +### Indexing + +K2V keeps an index, a secondary data structure that is updated asynchronously, +that keeps tracks of the number of triplets stored for each partition key. +This allows easy listing of all of the partition keys for which triplets exist +in a bucket, as the partition key becomes the sort key in the index. + +How indexing works: + +- Each node keeps a local count of how many items it stores for each partition, + in a local Sled tree that is updated atomically when an item is modified. +- These local counters are asynchronously stored in the index table which is + a regular Garage table spread in the network. Counters are stored as LWW values, + so basically the final table will have the following structure: + +``` +- pk: bucket +- sk: partition key for which we are counting +- v: lwwmap (node id -> number of items) +``` + +The final number of items present in the partition can be estimated by taking +the maximum of the values (i.e. the value for the node that announces having +the most items for that partition). In most cases the values for different node +IDs should all be the same; more precisely, three node IDs should map to the +same non-zero value, and all other node IDs that are present are tombstones +that map to zeroes. Note that we need to filter out values from nodes that are +no longer part of the cluster layout, as when nodes are removed they won't +necessarily have had the time to set their counters to zero. + +## Important details + +**THIS SECTION CONTAINS A FEW WARNINGS ON THE K2V API WHICH ARE IMPORTANT +TO UNDERSTAND IN ORDER TO USE IT CORRECTLY.** + +- **Internal server errors on updates do not mean that the update isn't stored.** + K2V will return an internal server error when it cannot reach a quorum of nodes on + which to save an updated value. However the value may still be stored on just one + node, which will then propagate it to other nodes asynchronously via anti-entropy. + +- **Batch operations are not transactions.** When calling InsertBatch or DeleteBatch, + items may appear partially inserted/deleted while the operation is being processed. + More importantly, if InsertBatch or DeleteBatch returns an internal server error, + some of the items to be inserted/deleted might end up inserted/deleted on the server, + while others may still have their old value. + +- **Concurrent values are deduplicated.** When inserting a value for a key, + Garage might internally end up + storing the value several times if there are network errors. These values will end up as + concurrent values for a key, with the same byte string (or `null` for a deletion). + Garage fixes this by deduplicating concurrent values when they are returned to the + user on read operations. Importantly, *Garage does not differentiate between duplicate + concurrent values due to the user making the same call twice, or Garage having to + do an internal retry*. This means that all duplicate concurrent values are deduplicated + when an item is read: if the user inserts twice concurrently the same value, they will + only read it once. + +## API Endpoints + +**Remark.** Example queries and responses here are given in JSON5 format +for clarity. However the actual K2V API uses basic JSON so all examples +and responses need to be translated. + +### Operations on single items + +**ReadItem: `GET /<bucket>/<partition key>?sort_key=<sort key>`** + + +Query parameters: + +| name | default value | meaning | +| - | - | - | +| `sort_key` | **mandatory** | The sort key of the item to read | + +Returns the item with specified partition key and sort key. Values can be +returned in either of two ways: + +1. a JSON array of base64-encoded values, or `null`'s for tombstones, with + header `Content-Type: application/json` + +2. in the case where there are no concurrent values, the single present value + can be returned directly as the response body (or an HTTP 204 NO CONTENT for + a tombstone), with header `Content-Type: application/octet-stream` + +The choice between return formats 1 and 2 is directed by the `Accept` HTTP header: + +- if the `Accept` header is not present, format 1 is always used + +- if `Accept` contains `application/json` but not `application/octet-stream`, + format 1 is always used + +- if `Accept` contains `application/octet-stream` but not `application/json`, + format 2 is used when there is a single value, and an HTTP error 409 (HTTP + 409 CONFLICT) is returned in the case of multiple concurrent values + (including concurrent tombstones) + +- if `Accept` contains both, format 2 is used when there is a single value, and + format 1 is used as a fallback in case of concurrent values + +- if `Accept` contains none, HTTP 406 NOT ACCEPTABLE is raised + +Example query: + +``` +GET /my_bucket/mailboxes?sort_key=INBOX HTTP/1.1 +``` + +Example response: + +```json +HTTP/1.1 200 OK +X-Garage-Causality-Token: opaquetoken123 +Content-Type: application/json + +[ + "b64cryptoblob123", + "b64cryptoblob'123" +] +``` + +Example response in case the item is a tombstone: + +``` +HTTP/1.1 200 OK +X-Garage-Causality-Token: opaquetoken999 +Content-Type: application/json + +[ + null +] +``` + +Example query 2: + +``` +GET /my_bucket/mailboxes?sort_key=INBOX HTTP/1.1 +Accept: application/octet-stream +``` + +Example response if multiple concurrent versions exist: + +``` +HTTP/1.1 409 CONFLICT +X-Garage-Causality-Token: opaquetoken123 +Content-Type: application/octet-stream +``` + +Example response in case of single value: + +``` +HTTP/1.1 200 OK +X-Garage-Causality-Token: opaquetoken123 +Content-Type: application/octet-stream + +cryptoblob123 +``` + +Example response in case of a single value that is a tombstone: + +``` +HTTP/1.1 204 NO CONTENT +X-Garage-Causality-Token: opaquetoken123 +Content-Type: application/octet-stream +``` + + +**PollItem: `GET /<bucket>/<partition key>?sort_key=<sort key>&causality_token=<causality token>`** + +This endpoint will block until a new value is written to a key. + +The GET parameter `causality_token` should be set to the causality +token returned with the last read of the key, so that K2V knows +what values are concurrent or newer than the ones that the +client previously knew. + +This endpoint returns the new value in the same format as ReadItem. +If no new value is written and the timeout elapses, +an HTTP 304 NOT MODIFIED is returned. + +Query parameters: + +| name | default value | meaning | +| - | - | - | +| `sort_key` | **mandatory** | The sort key of the item to read | +| `causality_token` | **mandatory** | The causality token of the last known value or set of values | +| `timeout` | 300 | The timeout before 304 NOT MODIFIED is returned if the value isn't updated | + +The timeout can be set to any number of seconds, with a maximum of 600 seconds (10 minutes). + + +**InsertItem: `PUT /<bucket>/<partition key>?sort_key=<sort_key>`** + +Inserts a single item. This request does not use JSON, the body is sent directly as a binary blob. + +To supersede previous values, the HTTP header `X-Garage-Causality-Token` should +be set to the causality token returned by a previous read on this key. This +header can be ommitted for the first writes to the key. + +Example query: + +``` +PUT /my_bucket/mailboxes?sort_key=INBOX HTTP/1.1 +X-Garage-Causality-Token: opaquetoken123 + +myblobblahblahblah +``` + +Example response: + +``` +HTTP/1.1 200 OK +``` + +**DeleteItem: `DELETE /<bucket>/<partition key>?sort_key=<sort_key>`** + +Deletes a single item. The HTTP header `X-Garage-Causality-Token` must be set +to the causality token returned by a previous read on this key, to indicate +which versions of the value should be deleted. The request will not process if +`X-Garage-Causality-Token` is not set. + +Example query: + +``` +DELETE /my_bucket/mailboxes?sort_key=INBOX HTTP/1.1 +X-Garage-Causality-Token: opaquetoken123 +``` + +Example response: + +``` +HTTP/1.1 204 NO CONTENT +``` + +### Operations on index + +**ReadIndex: `GET /<bucket>?start=<start>&end=<end>&limit=<limit>`** + +Lists all partition keys in the bucket for which some triplets exist, and gives +for each the number of triplets, total number of values (which might be bigger +than the number of triplets in case of conflicts), total number of bytes of +these values, and number of triplets that are in a state of conflict. +The values returned are an approximation of the true counts in the bucket, +as these values are asynchronously updated, and thus eventually consistent. + +Query parameters: + +| name | default value | meaning | +| - | - | - | +| `prefix` | `null` | Restrict listing to partition keys that start with this prefix | +| `start` | `null` | First partition key to list, in lexicographical order | +| `end` | `null` | Last partition key to list (excluded) | +| `limit` | `null` | Maximum number of partition keys to list | +| `reverse` | `false` | Iterate in reverse lexicographical order | + +The response consists in a JSON object that repeats the parameters of the query and gives the result (see below). + +The listing starts at partition key `start`, or if not specified at the +smallest partition key that exists. It returns partition keys in increasing +order, or decreasing order if `reverse` is set to `true`, +and stops when either of the following conditions is met: + +1. if `end` is specfied, the partition key `end` is reached or surpassed (if it + is reached exactly, it is not included in the result) + +2. if `limit` is specified, `limit` partition keys have been listed + +3. no more partition keys are available to list + +In case 2, and if there are more partition keys to list before condition 1 +triggers, then in the result `more` is set to `true` and `nextStart` is set to +the first partition key that couldn't be listed due to the limit. In the first +case (if the listing stopped because of the `end` parameter), `more` is not set +and the `nextStart` key is not specified. + +Note that if `reverse` is set to `true`, `start` is the highest key +(in lexicographical order) for which values are returned. +This means that if an `end` is specified, it must be smaller than `start`, +otherwise no values will be returned. + +Example query: + +``` +GET /my_bucket HTTP/1.1 +``` + +Example response: + +```json +HTTP/1.1 200 OK + +{ + prefix: null, + start: null, + end: null, + limit: null, + reverse: false, + partitionKeys: [ + { + pk: "keys", + entries: 3043, + conflicts: 0, + values: 3043, + bytes: 121720, + }, + { + pk: "mailbox:INBOX", + entries: 42, + conflicts: 1, + values: 43, + bytes: 142029, + }, + { + pk: "mailbox:Junk", + entries: 2991 + conflicts: 0, + values: 2991, + bytes: 12019322, + }, + { + pk: "mailbox:Trash", + entries: 10, + conflicts: 0, + values: 10, + bytes: 32401, + }, + { + pk: "mailboxes", + entries: 3, + conflicts: 0, + values: 3, + bytes: 3019, + }, + ], + more: false, + nextStart: null, +} +``` + + +### Operations on batches of items + +**InsertBatch: `POST /<bucket>`** + +Simple insertion and deletion of triplets. The body is just a list of items to +insert in the following format: +`{ pk: "<partition key>", sk: "<sort key>", ct: "<causality token>"|null, v: "<value>"|null }`. + +The causality token should be the one returned in a previous read request (e.g. +by ReadItem or ReadBatch), to indicate that this write takes into account the +values that were returned from these reads, and supersedes them causally. If +the triplet is inserted for the first time, the causality token should be set to +`null`. + +The value is expected to be a base64-encoded binary blob. The value `null` can +also be used to delete the triplet while preserving causality information: this +allows to know if a delete has happenned concurrently with an insert, in which +case both are preserved and returned on reads (see below). + +Partition keys and sort keys are utf8 strings which are stored sorted by +lexicographical ordering of their binary representation. + +Example query: + +```json +POST /my_bucket HTTP/1.1 + +[ + { pk: "mailbox:INBOX", sk: "001892831", ct: "opaquetoken321", v: "b64cryptoblob321updated" }, + { pk: "mailbox:INBOX", sk: "001892912", ct: null, v: "b64cryptoblob444" }, + { pk: "mailbox:INBOX", sk: "001892932", ct: "opaquetoken654", v: null }, +] +``` + +Example response: + +``` +HTTP/1.1 200 OK +``` + + +**ReadBatch: `POST /<bucket>?search`**, or alternatively<br/> +**ReadBatch: `SEARCH /<bucket>`** + +Batch read of triplets in a bucket. + +The request body is a JSON list of searches, that each specify a range of +items to get (to get single items, set `singleItem` to `true`). A search is a +JSON struct with the following fields: + +| name | default value | meaning | +| - | - | - | +| `partitionKey` | **mandatory** | The partition key in which to search | +| `prefix` | `null` | Restrict items to list to those whose sort keys start with this prefix | +| `start` | `null` | The sort key of the first item to read | +| `end` | `null` | The sort key of the last item to read (excluded) | +| `limit` | `null` | The maximum number of items to return | +| `reverse` | `false` | Iterate in reverse lexicographical order on sort keys | +| `singleItem` | `false` | Whether to return only the item with sort key `start` | +| `conflictsOnly` | `false` | Whether to return only items that have several concurrent values | +| `tombstones` | `false` | Whether or not to return tombstone lines to indicate the presence of old deleted items | + + +For each of the searches, triplets are listed and returned separately. The +semantics of `prefix`, `start`, `end`, `limit` and `reverse` are the same as for ReadIndex. The +additionnal parameter `singleItem` allows to get a single item, whose sort key +is the one given in `start`. Parameters `conflictsOnly` and `tombstones` +control additional filters on the items that are returned. + +The result is a list of length the number of searches, that consists in for +each search a JSON object specified similarly to the result of ReadIndex, but +that lists triplets within a partition key. + +The format of returned tuples is as follows: `{ sk: "<sort key>", ct: "<causality +token>", v: ["<value1>", ...] }`, with the following fields: + +- `sk` (sort key): any unicode string used as a sort key + +- `ct` (causality token): an opaque token served by the server (generally + base64-encoded) to be used in subsequent writes to this key + +- `v` (list of values): each value is a binary blob, always base64-encoded; + contains multiple items when concurrent values exists + +- in case of concurrent update and deletion, a `null` is added to the list of concurrent values + +- if the `tombstones` query parameter is set to `true`, tombstones are returned + for items that have been deleted (this can be usefull for inserting after an + item that has been deleted, so that the insert is not considered + concurrent with the delete). Tombstones are returned as tuples in the + same format with only `null` values + +Example query: + +```json +POST /my_bucket?search HTTP/1.1 + +[ + { + partitionKey: "mailboxes", + }, + { + partitionKey: "mailbox:INBOX", + start: "001892831", + limit: 3, + }, + { + partitionKey: "keys", + start: "0", + singleItem: true, + }, +] +``` + +Example associated response body: + +```json +HTTP/1.1 200 OK + +[ + { + partitionKey: "mailboxes", + prefix: null, + start: null, + end: null, + limit: null, + reverse: false, + conflictsOnly: false, + tombstones: false, + singleItem: false, + items: [ + { sk: "INBOX", ct: "opaquetoken123", v: ["b64cryptoblob123", "b64cryptoblob'123"] }, + { sk: "Trash", ct: "opaquetoken456", v: ["b64cryptoblob456"] }, + { sk: "Junk", ct: "opaquetoken789", v: ["b64cryptoblob789"] }, + ], + more: false, + nextStart: null, + }, + { + partitionKey: "mailbox::INBOX", + prefix: null, + start: "001892831", + end: null, + limit: 3, + reverse: false, + conflictsOnly: false, + tombstones: false, + singleItem: false, + items: [ + { sk: "001892831", ct: "opaquetoken321", v: ["b64cryptoblob321"] }, + { sk: "001892832", ct: "opaquetoken654", v: ["b64cryptoblob654"] }, + { sk: "001892874", ct: "opaquetoken987", v: ["b64cryptoblob987"] }, + ], + more: true, + nextStart: "001892898", + }, + { + partitionKey: "keys", + prefix: null, + start: "0", + end: null, + conflictsOnly: false, + tombstones: false, + limit: null, + reverse: false, + singleItem: true, + items: [ + { sk: "0", ct: "opaquetoken999", v: ["b64binarystuff999"] }, + ], + more: false, + nextStart: null, + }, +] +``` + + + +**DeleteBatch: `POST /<bucket>?delete`** + +Batch deletion of triplets. The request format is the same for `POST +/<bucket>?search` to indicate items or range of items, except that here they +are deleted instead of returned, but only the fields `partitionKey`, `prefix`, `start`, +`end`, and `singleItem` are supported. Causality information is not given by +the user: this request will internally list all triplets and write deletion +markers that supersede all of the versions that have been read. + +This request returns for each series of items to be deleted, the number of +matching items that have been found and deleted. + +Example query: + +```json +POST /my_bucket?delete HTTP/1.1 + +[ + { + partitionKey: "mailbox:OldMailbox", + }, + { + partitionKey: "mailbox:INBOX", + start: "0018928321", + singleItem: true, + }, +] +``` + +Example response: + +``` +HTTP/1.1 200 OK + +[ + { + partitionKey: "mailbox:OldMailbox", + prefix: null, + start: null, + end: null, + singleItem: false, + deletedItems: 35, + }, + { + partitionKey: "mailbox:INBOX", + prefix: null, + start: "0018928321", + end: null, + singleItem: true, + deletedItems: 1, + }, +] +``` + + +## Internals: causality tokens + +The method used is based on DVVS (dotted version vector sets). See: + +- the paper "Scalable and Accurate Causality Tracking for Eventually Consistent Data Stores" +- <https://github.com/ricardobcl/Dotted-Version-Vectors> + +For DVVS to work, write operations (at each node) must take a lock on the data table. diff --git a/doc/talks/2022-06-23-stack/.gitignore b/doc/talks/2022-06-23-stack/.gitignore new file mode 100644 index 00000000..121caa92 --- /dev/null +++ b/doc/talks/2022-06-23-stack/.gitignore @@ -0,0 +1,14 @@ +* + +!assets + +!.gitignore +!*.svg +!*.png +!*.jpg +!*.tex +!Makefile +!.gitignore +!assets/*.drawio.pdf + +!talk.pdf diff --git a/doc/talks/2022-06-23-stack/Makefile b/doc/talks/2022-06-23-stack/Makefile new file mode 100644 index 00000000..3f0f126f --- /dev/null +++ b/doc/talks/2022-06-23-stack/Makefile @@ -0,0 +1,5 @@ +talk.pdf: talk.tex assets/consistent_hashing_1.pdf assets/consistent_hashing_2.pdf assets/consistent_hashing_3.pdf assets/consistent_hashing_4.pdf assets/garage_tables.pdf assets/deuxfleurs.pdf + pdflatex talk.tex + +assets/%.pdf: assets/%.svg + inkscape -D -z --file=$^ --export-pdf=$@ diff --git a/doc/talks/2022-06-23-stack/assets/AGPLv3_Logo.png b/doc/talks/2022-06-23-stack/assets/AGPLv3_Logo.png Binary files differnew file mode 100644 index 00000000..445284a3 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/AGPLv3_Logo.png diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme.png b/doc/talks/2022-06-23-stack/assets/aerogramme.png Binary files differnew file mode 100644 index 00000000..3aabe3ad --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme.png diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme.svg b/doc/talks/2022-06-23-stack/assets/aerogramme.svg new file mode 100644 index 00000000..0c1ee127 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme.svg @@ -0,0 +1,1241 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="1280" + height="720" + viewBox="0 0 338.66667 190.5" + version="1.1" + id="svg5" + inkscape:export-filename="aerogramme.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + sodipodi:docname="aerogramme.svg" + inkscape:version="1.2 (dc2aedaf03, 2022-05-15)" + xml:space="preserve" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview + id="namedview7" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:document-units="mm" + showgrid="false" + units="px" + inkscape:snap-global="false" + inkscape:zoom="0.54488787" + inkscape:cx="574.43011" + inkscape:cy="349.61321" + inkscape:window-width="1678" + inkscape:window-height="993" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:showpageshadow="2" + inkscape:deskcolor="#d1d1d1" /><defs + id="defs2"><marker + style="overflow:visible;" + id="Arrow1Mend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"><path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path12266" /></marker><marker + style="overflow:visible;" + id="Arrow1Lend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Lend" + inkscape:isstock="true"><path + transform="scale(0.8) rotate(180) translate(12.5,0)" + style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path12260" /></marker><marker + style="overflow:visible" + id="Arrow1Mend-3" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true" + viewBox="0 0 8.886927 5.078244" + markerWidth="8.8869267" + markerHeight="5.0782442" + preserveAspectRatio="xMidYMid"><path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path12266-5" /></marker><style + id="style2">.cls-1{fill:#3b2100;}.cls-2{fill:#ffd952;}.cls-3{fill:#45c8ff;}</style><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient5100" + id="linearGradient5094" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(266.85692,297.64826)" + x1="192.00021" + y1="140.29999" + x2="192" + y2="332.29999" /><linearGradient + inkscape:collect="always" + id="linearGradient5100"><stop + style="stop-color:#fbfcfc;stop-opacity:0.1" + offset="0" + id="stop5096" /><stop + style="stop-color:#fbfcfc;stop-opacity:0;" + offset="1" + id="stop5098" /></linearGradient><radialGradient + inkscape:collect="always" + xlink:href="#linearGradient5100" + id="radialGradient5715" + cx="2.5446666e-06" + cy="243.20909" + fx="2.5446666e-06" + fy="243.20909" + r="76.000107" + gradientTransform="matrix(2.526312,2.526312,-2.5263122,2.5263122,881.27923,-176.47384)" + gradientUnits="userSpaceOnUse" /><filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter13488" + x="-0.14" + width="1.28" + y="-0.105" + height="1.21"><feGaussianBlur + inkscape:collect="always" + stdDeviation="1.4" + id="feGaussianBlur13490" /></filter><filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter13338" + x="-0.056" + width="1.112" + y="-0.042" + height="1.084"><feGaussianBlur + inkscape:collect="always" + stdDeviation="0.56" + id="feGaussianBlur13340" /></filter><filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter13052" + x="-0.063652175" + width="1.1273044" + y="-0.038526317" + height="1.0770526"><feGaussianBlur + inkscape:collect="always" + stdDeviation="2.44" + id="feGaussianBlur13054" /></filter><filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter13165" + x="-0.063652175" + width="1.1273044" + y="-0.038526317" + height="1.0770526"><feGaussianBlur + inkscape:collect="always" + stdDeviation="2.44" + id="feGaussianBlur13167" /></filter><linearGradient + inkscape:collect="always" + xlink:href="#linearGradient14074" + id="linearGradient14078" + gradientUnits="userSpaceOnUse" + x1="80" + y1="252.29999" + x2="80" + y2="280.29999" + gradientTransform="translate(266.85714,297.64826)" /><linearGradient + inkscape:collect="always" + id="linearGradient14074"><stop + style="stop-color:#4d6570;stop-opacity:1" + offset="0" + id="stop14070" /><stop + style="stop-color:#4d6570;stop-opacity:0.06" + offset="1" + id="stop14072" /></linearGradient><filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter12606" + x="-0.033333331" + width="1.0666667" + y="-0.085714286" + height="1.1714286"><feGaussianBlur + inkscape:collect="always" + stdDeviation="2" + id="feGaussianBlur12608" /></filter><filter + inkscape:collect="always" + style="color-interpolation-filters:sRGB" + id="filter13315" + x="-0.054750001" + width="1.1095" + y="-0.042731701" + height="1.0854634"><feGaussianBlur + inkscape:collect="always" + stdDeviation="2.9199997" + id="feGaussianBlur13317" /></filter><filter + id="Adobe_OpacityMaskFilter" + filterUnits="userSpaceOnUse" + x="162.68" + y="18.355" + width="14.927" + height="6.629"> + <feColorMatrix + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" + id="feColorMatrix10045" /> + </filter><marker + style="overflow:visible" + id="Arrow1Mend-3-6" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true" + viewBox="0 0 8.886927 5.078244" + markerWidth="8.8869267" + markerHeight="5.0782442" + preserveAspectRatio="xMidYMid"><path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path12266-5-2" /></marker><marker + style="overflow:visible" + id="Arrow1Mend-3-1" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true" + viewBox="0 0 8.886927 5.078244" + markerWidth="8.8869267" + markerHeight="5.0782442" + preserveAspectRatio="xMidYMid"><path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path12266-5-27" /></marker><marker + style="overflow:visible" + id="Arrow1Mend-3-1-6" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true" + viewBox="0 0 8.886927 5.078244" + markerWidth="8.8869267" + markerHeight="5.0782442" + preserveAspectRatio="xMidYMid"><path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path12266-5-27-2" /></marker><marker + style="overflow:visible" + id="Arrow1Mend-3-1-6-9" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true" + viewBox="0 0 8.886927 5.078244" + markerWidth="8.8869267" + markerHeight="5.0782442" + preserveAspectRatio="xMidYMid"><path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path12266-5-27-2-2" /></marker></defs><g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"><text + xml:space="preserve" + style="font-size:12.5157px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="269.40045" + y="46.856514" + id="text4115-3-7"><tspan + sodipodi:role="line" + style="font-size:12.5157px;stroke-width:0.264583" + x="269.40045" + y="46.856514" + id="tspan4117-6-5">K2V API</tspan></text><text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="269.39954" + y="152.73567" + id="text4115-3-7-3"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="269.39954" + y="152.73567" + id="tspan559">S3 API</tspan></text><text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="147.04097" + y="116.71946" + id="text4115-3-7-3-3"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="147.04097" + y="116.71946" + id="tspan559-5">Aerogramme</tspan></text><g + id="g329" + transform="matrix(0.31121703,0,0,0.31121703,268.86086,51.929088)" + style="stroke-width:0.850155"><g + id="g1663" + transform="matrix(1.7099534,0,0,1.7099534,-88.607712,-87.994557)" + style="stroke-width:0.850155"><path + d="m 138.33068,100.19817 a 8.327649,8.327649 0 0 1 -2.77589,-0.288688 l -34.78736,-9.388036 a 8.4442361,8.4442361 0 0 1 -2.620433,-1.238044 z" + id="path6" + style="stroke-width:0.471988" /><path + class="cls-1" + d="m 85.377935,159.27452 5.163143,-0.0333 h 0.06662 q 2.864711,0 2.864711,2.69816 v 8.69407 a 24.849705,24.849705 0 0 1 -8.649651,1.43235 q -4.730105,0 -7.128468,-3.21447 -2.398363,-3.21447 -2.398363,-8.76068 0,-5.55177 2.981299,-8.62745 a 9.7600046,9.7600046 0 0 1 7.29502,-3.08123 13.368653,13.368653 0 0 1 7.811335,2.43167 3.9250986,3.9250986 0 0 1 -0.682867,1.76547 4.7634152,4.7634152 0 0 1 -1.282458,1.33242 9.798867,9.798867 0 0 0 -5.679457,-1.96533 5.3574542,5.3574542 0 0 0 -4.480275,2.04861 q -1.598909,2.03749 -1.598909,6.41229 0,8.22771 6.062529,8.22771 a 16.910679,16.910679 0 0 0 3.697476,-0.43303 v -3.16451 q 0,-1.49898 0.06662,-2.22071 h -2.442777 a 2.2873276,2.2873276 0 0 1 -1.515632,-0.41638 1.6655298,1.6655298 0 0 1 -0.483004,-1.33242 5.7072154,5.7072154 0 0 1 0.333106,-1.79322 z" + id="path8" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 111.07151,169.73404 a 4.3137222,4.3137222 0 0 1 -0.55518,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05902,-1.95422 6.7453957,6.7453957 0 0 1 -4.76342,2.13188 q -2.564913,0 -3.886233,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.299113,-3.4643 q 0,-2.77588 1.815427,-4.21379 a 7.3338829,7.3338829 0 0 1 4.669039,-1.3935 q 1.53228,0 2.89802,0.13325 v -0.99932 q 0,-2.63154 -2.53161,-2.63154 -1.79877,0 -5.096518,1.19918 a 4.674587,4.674587 0 0 1 -1.110353,-2.96464 18.581761,18.581761 0 0 1 7.217291,-1.49898 5.8682167,5.8682167 0 0 1 4.0639,1.39905 q 1.56559,1.39904 1.56559,4.23044 v 6.79537 q -0.0111,1.83208 0.9216,2.59822 z m -8.36096,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06525,0.68842 2.3928111,2.3928111 0 0 0 -0.69953,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.55518 z" + id="path10" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 113.76966,157.00939 a 3.986168,3.986168 0 0 1 0.55518,-1.21583 3.3310596,3.3310596 0 0 1 0.84942,-0.94935 4.1638245,4.1638245 0 0 1 3.51427,2.96464 q 1.33242,-2.96464 4.29707,-2.96464 a 10.215249,10.215249 0 0 1 1.93201,0.23317 7.4782288,7.4782288 0 0 1 -0.99932,3.88624 8.4497879,8.4497879 0 0 0 -1.49897,-0.19987 q -2.03195,0 -3.26444,2.16519 v 10.64829 a 11.575432,11.575432 0 0 1 -2.03195,0.16655 12.769062,12.769062 0 0 1 -2.09857,-0.16655 v -11.15905 q -0.0222,-2.40947 -1.2547,-3.40879 z" + id="path12" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 140.38483,169.73404 a 4.3137222,4.3137222 0 0 1 -0.58293,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05348,-1.95422 6.7453957,6.7453957 0 0 1 -4.76341,2.13188 q -2.56492,0 -3.88624,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.29911,-3.4643 q 0,-2.77588 1.81543,-4.21379 a 7.3338829,7.3338829 0 0 1 4.64682,-1.4157 q 1.53229,0 2.89803,0.13324 v -0.99932 q 0,-2.63153 -2.53161,-2.63153 -1.79877,0 -5.09652,1.19918 a 4.674587,4.674587 0 0 1 -1.11035,-2.96465 18.581761,18.581761 0 0 1 7.21729,-1.49897 5.8682167,5.8682167 0 0 1 4.0639,1.39904 q 1.56559,1.39905 1.56559,4.23045 v 6.81757 q 0.0333,1.83208 0.96601,2.59822 z m -8.37206,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06526,0.69952 2.3928111,2.3928111 0 0 0 -0.69952,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.54408 z" + id="path14" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 144.48203,169.6008 q -1.49897,-2.29843 -1.49897,-6.34567 0,-4.04724 1.8987,-6.34567 a 5.740526,5.740526 0 0 1 4.56355,-2.29843 6.4400486,6.4400486 0 0 1 4.49693,1.66553 3.7696491,3.7696491 0 0 1 2.63154,-1.43235 3.1200925,3.1200925 0 0 1 0.88273,0.93269 3.8862362,3.8862362 0 0 1 0.55518,1.16587 q -0.9327,0.79946 -0.9327,2.86472 v 9.438 q 0,5.29638 -1.73215,7.49488 -1.73215,2.1985 -5.69611,2.22071 a 16.100121,16.100121 0 0 1 -5.9626,-1.11036 4.4802752,4.4802752 0 0 1 1.03263,-3.03126 10.892565,10.892565 0 0 0 4.48028,1.03263 q 2.18184,0 3.0146,-1.11035 a 4.9965894,4.9965894 0 0 0 0.83277,-3.06458 v -1.33242 a 6.4011862,6.4011862 0 0 1 -4.16383,1.56559 4.9188647,4.9188647 0 0 1 -4.40255,-2.30953 z m 8.56083,-2.69816 v -7.72806 a 4.2915151,4.2915151 0 0 0 -2.86471,-1.36573 2.4039147,2.4039147 0 0 0 -2.18185,1.43235 8.6885138,8.6885138 0 0 0 -0.7828,4.09721 q 0,2.66485 0.71618,3.93065 a 2.1318781,2.1318781 0 0 0 1.88205,1.2658 4.2304457,4.2304457 0 0 0 3.23113,-1.63222 z" + id="path16" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 174.20619,164.67083 h -9.32697 a 5.6405943,5.6405943 0 0 0 0.88273,3.04792 q 0.7828,1.0826 2.74813,1.0826 a 10.120869,10.120869 0 0 0 4.36369,-1.16587 4.3803434,4.3803434 0 0 1 1.19918,2.5316 10.759323,10.759323 0 0 1 -6.41229,1.8987 q -3.74744,0 -5.37966,-2.43167 -1.63222,-2.43167 -1.63222,-6.2957 0,-3.88624 1.79877,-6.2957 a 6.0181143,6.0181143 0 0 1 5.14649,-2.43168 q 3.33106,0 5.14648,2.01529 a 7.3449864,7.3449864 0 0 1 1.79878,5.07987 13.04665,13.04665 0 0 1 -0.33311,2.96464 z m -6.42895,-7.06184 q -2.73146,0 -2.93133,4.13051 h 5.79605 v -0.39973 a 4.7245529,4.7245529 0 0 0 -0.69953,-2.69816 2.4316735,2.4316735 0 0 0 -2.14298,-1.03262 z" + id="path18" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + id="path24-3-6" + style="fill:#ffd952;fill-opacity:1;stroke-width:0.471988" + d="m 124.80273,70.162462 a 11.0036,11.0036 0 0 0 -4.33203,0.935547 L 76.261719,90.656602 a 1.5989086,1.5989086 0 0 0 -0.837891,2.138672 0.77169547,0.77169547 0 0 0 0.06641,0.177735 l 7.09375,14.021481 h 6.15625 l -0.875,-4.88867 c -0.07217,-0.39418 -0.711263,-3.187532 -1.316406,-5.197264 l 20.691398,6.462894 c 0.27198,1.28839 0.63292,2.49204 1.0625,3.62304 h 33.54883 c 0.36964,-1.13128 0.66138,-2.33705 0.85938,-3.62304 l 20.64648,-6.445316 c -0.60514,2.009734 -1.23639,4.785506 -1.30859,5.179686 l -0.875,4.88867 h 6.15429 l 7.02735,-13.894528 0.0664,-0.126953 0.0684,-0.171875 a 0.10548355,0.10548355 0 0 0 0,-0.04492 1.4878733,1.4878733 0 0 0 0.0664,-0.515625 1.5822533,1.5822533 0 0 0 -0.99414,-1.583985 L 129.35352,71.098009 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + id="path24-3" + style="fill:#49c8fa;fill-opacity:1;stroke-width:0.471988" + d="M 124.80273,79.416133 A 11.0036,11.0036 0 0 0 120.4707,80.35168 L 76.261719,99.910272 a 1.5989086,1.5989086 0 0 0 -0.837891,2.136718 0.77169547,0.77169547 0 0 0 0.06641,0.17773 l 3.847657,7.60352 h 8.175781 c -0.257897,-1.08856 -0.591943,-2.42953 -0.964844,-3.66797 l 11.744141,3.66797 h 53.371087 l 11.69336,-3.65039 c -0.37193,1.23522 -0.70076,2.56719 -0.95703,3.65039 h 8.17383 l 3.78125,-7.47656 0.0664,-0.12696 0.0684,-0.17187 a 0.10548355,0.10548355 0 0 0 0,-0.0449 1.4878733,1.4878733 0 0 0 0.0664,-0.51563 1.5822533,1.5822533 0 0 0 -0.99414,-1.582028 L 129.35352,80.35168 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + class="cls-2" + d="m 174.55595,110.92974 a 1.4878733,1.4878733 0 0 1 -0.0666,0.51631 0.10548355,0.10548355 0 0 1 0,0.0444 l -0.0666,0.17211 v 0 l -0.0666,0.12769 -10.69826,21.15223 c -1.48787,2.93688 -4.22489,2.84806 -3.76409,-0.12214 l 2.15408,-12.02512 c 0.0722,-0.39418 0.70508,-3.17006 1.31022,-5.1798 l -20.64702,6.4456 c -3.24223,21.05785 -30.95109,21.40761 -35.47023,0 l -20.691432,-6.46226 c 0.605143,2.00974 1.243596,4.80228 1.315769,5.19646 l 2.154085,12.02512 c 0.460796,2.9702 -2.276224,3.05902 -3.764098,0.12214 L 75.49024,111.66257 a 0.77169547,0.77169547 0 0 1 -0.06662,-0.17766 1.5989086,1.5989086 0 0 1 0.838317,-2.13743 L 120.47065,89.788613 a 11.0036,11.0036 0 0 1 8.88282,0 l 44.20871,19.558867 a 1.5822533,1.5822533 0 0 1 0.99377,1.58226 z" + id="path24" + style="stroke-width:0.471988" /><path + class="cls-3" + d="m 139.0413,114.61611 19.11473,-7.69475 a 0.81055784,0.81055784 0 0 0 0,-1.50453 c -2.2207,-0.92714 -4.96328,-1.99308 -7.65033,-3.10899 -0.49411,-0.20541 -5.17425,3.15341 -5.60173,3.49762 l -8.23882,6.58439 c -1.99309,1.67108 -0.26649,3.28665 2.37615,2.22626 z" + id="path26" + style="stroke-width:0.471988" /><circle + class="cls-3" + cx="125.18409" + cy="122.13319" + r="9.9654207" + id="circle28" + style="stroke-width:0.471988" /><path + d="m 138.33068,100.19817 a 8.327649,8.327649 0 0 1 -2.77589,-0.288688 l -34.78736,-9.388036 a 8.4442361,8.4442361 0 0 1 -2.620433,-1.238044 z" + id="path6-0" + style="stroke-width:0.471988" /><path + class="cls-1" + d="m 85.377935,159.27452 5.163143,-0.0333 h 0.06662 q 2.864711,0 2.864711,2.69816 v 8.69407 a 24.849705,24.849705 0 0 1 -8.649651,1.43235 q -4.730105,0 -7.128468,-3.21447 -2.398363,-3.21447 -2.398363,-8.76068 0,-5.55177 2.981299,-8.62745 a 9.7600046,9.7600046 0 0 1 7.29502,-3.08123 13.368653,13.368653 0 0 1 7.811335,2.43167 3.9250986,3.9250986 0 0 1 -0.682867,1.76547 4.7634152,4.7634152 0 0 1 -1.282458,1.33242 9.798867,9.798867 0 0 0 -5.679457,-1.96533 5.3574542,5.3574542 0 0 0 -4.480275,2.04861 q -1.598909,2.03749 -1.598909,6.41229 0,8.22771 6.062529,8.22771 a 16.910679,16.910679 0 0 0 3.697476,-0.43303 v -3.16451 q 0,-1.49898 0.06662,-2.22071 h -2.442777 a 2.2873276,2.2873276 0 0 1 -1.515632,-0.41638 1.6655298,1.6655298 0 0 1 -0.483004,-1.33242 5.7072154,5.7072154 0 0 1 0.333106,-1.79322 z" + id="path8-6" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 111.07151,169.73404 a 4.3137222,4.3137222 0 0 1 -0.55518,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05902,-1.95422 6.7453957,6.7453957 0 0 1 -4.76342,2.13188 q -2.564913,0 -3.886233,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.299113,-3.4643 q 0,-2.77588 1.815427,-4.21379 a 7.3338829,7.3338829 0 0 1 4.669039,-1.3935 q 1.53228,0 2.89802,0.13325 v -0.99932 q 0,-2.63154 -2.53161,-2.63154 -1.79877,0 -5.096518,1.19918 a 4.674587,4.674587 0 0 1 -1.110353,-2.96464 18.581761,18.581761 0 0 1 7.217291,-1.49898 5.8682167,5.8682167 0 0 1 4.0639,1.39905 q 1.56559,1.39904 1.56559,4.23044 v 6.79537 q -0.0111,1.83208 0.9216,2.59822 z m -8.36096,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06525,0.68842 2.3928111,2.3928111 0 0 0 -0.69953,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.55518 z" + id="path10-2" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 113.76966,157.00939 a 3.986168,3.986168 0 0 1 0.55518,-1.21583 3.3310596,3.3310596 0 0 1 0.84942,-0.94935 4.1638245,4.1638245 0 0 1 3.51427,2.96464 q 1.33242,-2.96464 4.29707,-2.96464 a 10.215249,10.215249 0 0 1 1.93201,0.23317 7.4782288,7.4782288 0 0 1 -0.99932,3.88624 8.4497879,8.4497879 0 0 0 -1.49897,-0.19987 q -2.03195,0 -3.26444,2.16519 v 10.64829 a 11.575432,11.575432 0 0 1 -2.03195,0.16655 12.769062,12.769062 0 0 1 -2.09857,-0.16655 v -11.15905 q -0.0222,-2.40947 -1.2547,-3.40879 z" + id="path12-6" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 140.38483,169.73404 a 4.3137222,4.3137222 0 0 1 -0.58293,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05348,-1.95422 6.7453957,6.7453957 0 0 1 -4.76341,2.13188 q -2.56492,0 -3.88624,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.29911,-3.4643 q 0,-2.77588 1.81543,-4.21379 a 7.3338829,7.3338829 0 0 1 4.64682,-1.4157 q 1.53229,0 2.89803,0.13324 v -0.99932 q 0,-2.63153 -2.53161,-2.63153 -1.79877,0 -5.09652,1.19918 a 4.674587,4.674587 0 0 1 -1.11035,-2.96465 18.581761,18.581761 0 0 1 7.21729,-1.49897 5.8682167,5.8682167 0 0 1 4.0639,1.39904 q 1.56559,1.39905 1.56559,4.23045 v 6.81757 q 0.0333,1.83208 0.96601,2.59822 z m -8.37206,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06526,0.69952 2.3928111,2.3928111 0 0 0 -0.69952,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.54408 z" + id="path14-1" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 144.48203,169.6008 q -1.49897,-2.29843 -1.49897,-6.34567 0,-4.04724 1.8987,-6.34567 a 5.740526,5.740526 0 0 1 4.56355,-2.29843 6.4400486,6.4400486 0 0 1 4.49693,1.66553 3.7696491,3.7696491 0 0 1 2.63154,-1.43235 3.1200925,3.1200925 0 0 1 0.88273,0.93269 3.8862362,3.8862362 0 0 1 0.55518,1.16587 q -0.9327,0.79946 -0.9327,2.86472 v 9.438 q 0,5.29638 -1.73215,7.49488 -1.73215,2.1985 -5.69611,2.22071 a 16.100121,16.100121 0 0 1 -5.9626,-1.11036 4.4802752,4.4802752 0 0 1 1.03263,-3.03126 10.892565,10.892565 0 0 0 4.48028,1.03263 q 2.18184,0 3.0146,-1.11035 a 4.9965894,4.9965894 0 0 0 0.83277,-3.06458 v -1.33242 a 6.4011862,6.4011862 0 0 1 -4.16383,1.56559 4.9188647,4.9188647 0 0 1 -4.40255,-2.30953 z m 8.56083,-2.69816 v -7.72806 a 4.2915151,4.2915151 0 0 0 -2.86471,-1.36573 2.4039147,2.4039147 0 0 0 -2.18185,1.43235 8.6885138,8.6885138 0 0 0 -0.7828,4.09721 q 0,2.66485 0.71618,3.93065 a 2.1318781,2.1318781 0 0 0 1.88205,1.2658 4.2304457,4.2304457 0 0 0 3.23113,-1.63222 z" + id="path16-8" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-1" + d="m 174.20619,164.67083 h -9.32697 a 5.6405943,5.6405943 0 0 0 0.88273,3.04792 q 0.7828,1.0826 2.74813,1.0826 a 10.120869,10.120869 0 0 0 4.36369,-1.16587 4.3803434,4.3803434 0 0 1 1.19918,2.5316 10.759323,10.759323 0 0 1 -6.41229,1.8987 q -3.74744,0 -5.37966,-2.43167 -1.63222,-2.43167 -1.63222,-6.2957 0,-3.88624 1.79877,-6.2957 a 6.0181143,6.0181143 0 0 1 5.14649,-2.43168 q 3.33106,0 5.14648,2.01529 a 7.3449864,7.3449864 0 0 1 1.79878,5.07987 13.04665,13.04665 0 0 1 -0.33311,2.96464 z m -6.42895,-7.06184 q -2.73146,0 -2.93133,4.13051 h 5.79605 v -0.39973 a 4.7245529,4.7245529 0 0 0 -0.69953,-2.69816 2.4316735,2.4316735 0 0 0 -2.14298,-1.03262 z" + id="path18-7" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><path + id="path24-3-6-9" + style="fill:#ff9329;fill-opacity:1;stroke-width:0.471988" + d="m 124.80273,70.162462 a 11.0036,11.0036 0 0 0 -4.33203,0.935547 L 76.261719,90.656602 a 1.5989086,1.5989086 0 0 0 -0.837891,2.138672 0.77169547,0.77169547 0 0 0 0.06641,0.177735 l 7.09375,14.021481 h 6.15625 l -0.875,-4.88867 c -0.07217,-0.39418 -0.711263,-3.187532 -1.316406,-5.197264 l 20.691398,6.462894 c 0.27198,1.28839 0.63292,2.49204 1.0625,3.62304 h 33.54883 c 0.36964,-1.13128 0.66138,-2.33705 0.85938,-3.62304 l 20.64648,-6.445316 c -0.60514,2.009734 -1.23639,4.785506 -1.30859,5.179686 l -0.875,4.88867 h 6.15429 l 7.02735,-13.894528 0.0664,-0.126953 0.0684,-0.171875 a 0.10548355,0.10548355 0 0 0 0,-0.04492 1.4878733,1.4878733 0 0 0 0.0664,-0.515625 1.5822533,1.5822533 0 0 0 -0.99414,-1.583985 L 129.35352,71.098009 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + id="path24-3-2" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" + d="M 124.80273,79.416133 A 11.0036,11.0036 0 0 0 120.4707,80.35168 L 76.261719,99.910272 a 1.5989086,1.5989086 0 0 0 -0.837891,2.136718 0.77169547,0.77169547 0 0 0 0.06641,0.17773 l 3.847657,7.60352 h 8.175781 c -0.257897,-1.08856 -0.591943,-2.42953 -0.964844,-3.66797 l 11.744141,3.66797 h 53.371087 l 11.69336,-3.65039 c -0.37193,1.23522 -0.70076,2.56719 -0.95703,3.65039 h 8.17383 l 3.78125,-7.47656 0.0664,-0.12696 0.0684,-0.17187 a 0.10548355,0.10548355 0 0 0 0,-0.0449 1.4878733,1.4878733 0 0 0 0.0664,-0.51563 1.5822533,1.5822533 0 0 0 -0.99414,-1.582028 L 129.35352,80.35168 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + class="cls-2" + d="m 174.55595,110.92974 a 1.4878733,1.4878733 0 0 1 -0.0666,0.51631 0.10548355,0.10548355 0 0 1 0,0.0444 l -0.0666,0.17211 v 0 l -0.0666,0.12769 -10.69826,21.15223 c -1.48787,2.93688 -4.22489,2.84806 -3.76409,-0.12214 l 2.15408,-12.02512 c 0.0722,-0.39418 0.70508,-3.17006 1.31022,-5.1798 l -20.64702,6.4456 c -3.24223,21.05785 -30.95109,21.40761 -35.47023,0 l -20.691432,-6.46226 c 0.605143,2.00974 1.243596,4.80228 1.315769,5.19646 l 2.154085,12.02512 c 0.460796,2.9702 -2.276224,3.05902 -3.764098,0.12214 L 75.49024,111.66257 a 0.77169547,0.77169547 0 0 1 -0.06662,-0.17766 1.5989086,1.5989086 0 0 1 0.838317,-2.13743 L 120.47065,89.788613 a 11.0036,11.0036 0 0 1 8.88282,0 l 44.20871,19.558867 a 1.5822533,1.5822533 0 0 1 0.99377,1.58226 z" + id="path24-0" + style="fill:#ff9329;fill-opacity:1;stroke-width:0.471988" /><path + class="cls-3" + d="m 139.0413,114.61611 19.11473,-7.69475 a 0.81055784,0.81055784 0 0 0 0,-1.50453 c -2.2207,-0.92714 -4.96328,-1.99308 -7.65033,-3.10899 -0.49411,-0.20541 -5.17425,3.15341 -5.60173,3.49762 l -8.23882,6.58439 c -1.99309,1.67108 -0.26649,3.28665 2.37615,2.22626 z" + id="path26-2" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /><circle + class="cls-3" + cx="125.18409" + cy="122.13319" + r="9.9654207" + id="circle28-3" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.471988" /></g></g><path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" + d="M 308.22048,53.699542 V 43.088993 h -11.45897" + id="path689" + sodipodi:nodetypes="ccc" /><path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" + d="m 308.22048,127.19381 v 21.9817 h -15.37964" + id="path689-7" + sodipodi:nodetypes="ccc" /><g + inkscape:label="Layer 1" + id="layer1-5" + transform="matrix(0.14374001,0,0,0.15293639,-29.475642,13.625808)" + style="stroke-width:1.7845"><path + inkscape:connector-curvature="0" + id="path13319" + d="m 316.85714,449.94826 c -3.324,0 -6,2.67599 -6,6 v 8 c 0,3.324 2.676,6 6,6 h 14 v 16 h 8 v -16 h 2 c 3.324,0 6,-2.676 6,-6 v -8 c 0,-3.32401 -2.676,-6 -6,-6 z m 68.00001,0 c -3.324,0 -6,2.67599 -6,6 v 8 c 0,3.324 2.676,6 6,6 h 2 v 16 h 8 v -16 h 14 c 3.324,0 6,-2.676 6,-6 v -8 c 0,-3.32401 -2.676,-6 -6,-6 z m -86.00001,108 v 12 l 25.60742,38 c 2.06983,3.58859 5.93583,6 10.39258,6 h 56.00002 c 4.45674,0 8.32274,-2.41141 10.39257,-6 h 0.0117 l 25.59569,-38 v -12 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.2;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter:url(#filter13315);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /><path + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.2;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter:url(#filter13315);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 316.85714,445.94826 c -3.324,0 -6,2.67599 -6,6 v 8 c 0,3.324 2.676,6 6,6 h 14 v 16 h 8 v -16 h 2 c 3.324,0 6,-2.676 6,-6 v -8 c 0,-3.32401 -2.676,-6 -6,-6 z m 68.00001,0 c -3.324,0 -6,2.67599 -6,6 v 8 c 0,3.324 2.676,6 6,6 h 2 v 16 h 8 v -16 h 14 c 3.324,0 6,-2.676 6,-6 v -8 c 0,-3.32401 -2.676,-6 -6,-6 z m -86.00001,108 v 12 l 25.60742,38 c 2.06983,3.58859 5.93583,6 10.39258,6 h 56.00002 c 4.45674,0 8.32274,-2.41141 10.39257,-6 h 0.0117 l 25.59569,-38 v -12 z" + id="path13272-9" + inkscape:connector-curvature="0" /><path + sodipodi:nodetypes="cccssccccc" + inkscape:connector-curvature="0" + id="path13149" + d="m 290.85714,553.94826 8,12 25.60743,38 c 2.06982,3.58859 5.93583,6 10.39257,6 h 56.00001 c 4.45675,0 8.32275,-2.41141 10.39258,-6 h 0.0117 l 25.59572,-38 8,-12 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.2;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter:url(#filter12606);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /><g + transform="translate(266.85714,297.64826)" + id="g13285" + style="stroke-width:1.7845"><path + inkscape:connector-curvature="0" + style="fill:#607d8b;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.670974px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 32,116 v 12 l 25.607422,38 c 2.069825,3.58859 5.935831,6 10.392578,6 h 56 c 4.45675,0 8.32275,-2.41141 10.39258,-6 h 0.0117 L 160,128 v -12 z" + transform="translate(0,140.3)" + id="path12541" /></g><rect + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#263238;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10.707;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect11626" + width="8" + height="28.000004" + x="330.85715" + y="453.94827" /><rect + y="453.94827" + x="386.85715" + height="28.000004" + width="8" + id="rect12099" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#263238;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10.707;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /><path + inkscape:connector-curvature="0" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#4d6570;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 298.85714,564.94826 v 1 l 25.60743,38 c 2.06982,3.58859 5.93583,6 10.39257,6 h 56.00001 c 4.45675,0 8.32275,-2.41141 10.39258,-6 h 0.0117 l 25.59572,-38 v -1 l -25.5957,38 h -0.0117 c -2.06983,3.58859 -5.93583,6 -10.39258,6 h -56.00003 c -4.45674,0 -8.32275,-2.41141 -10.39257,-6 z" + id="path3138" /><rect + ry="11.044247" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#607d8b;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect11624" + width="19.999992" + height="36" + x="445.94827" + y="-346.85715" + rx="10.380134" + transform="rotate(90)" /><rect + transform="rotate(90)" + rx="10.380134" + y="-414.85715" + x="445.94827" + height="36" + width="19.999992" + id="rect12097" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#607d8b;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + ry="11.044247" /><path + inkscape:connector-curvature="0" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#4d6570;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 310.85714,458.94826 v 1 c 0,3.324 2.676,6 6,6 h 24 c 3.324,0 6,-2.676 6,-6 v -1 c 0,3.324 -2.676,6 -6,6 h -24 c -3.324,0 -6,-2.676 -6,-6 z" + id="rect12496" /><path + inkscape:connector-curvature="0" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#4d6570;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 378.85715,458.94826 v 1 c 0,3.324 2.676,6 6,6 h 24 c 3.324,0 6,-2.676 6,-6 v -1 c 0,3.324 -2.676,6 -6,6 h -24 c -3.324,0 -6,-2.676 -6,-6 z" + id="path12514" /><path + inkscape:connector-curvature="0" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#8097a2;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 316.85714,445.94826 c -3.324,0 -6,2.676 -6,6 v 1 c 0,-3.324 2.676,-6 6,-6 h 24 c 3.324,0 6,2.676 6,6 v -1 c 0,-3.324 -2.676,-6 -6,-6 z" + id="path12516" /><path + inkscape:connector-curvature="0" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#8097a2;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 384.85715,445.94826 c -3.324,0 -6,2.676 -6,6 v 1 c 0,-3.324 2.676,-6 6,-6 h 24 c 3.324,0 6,2.676 6,6 v -1 c 0,-3.324 -2.676,-6 -6,-6 z" + id="path12518" /><path + inkscape:connector-curvature="0" + id="path14047" + d="m 298.85715,553.94826 v 12 l 25.60742,38 c 2.06983,3.58859 5.93583,6 10.39258,6 h 56 c 4.45675,0 8.32275,-2.41141 10.39258,-6 h 0.0117 l 25.59572,-38 v -12 z" + style="fill:url(#linearGradient14078);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.670974px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><g + transform="translate(266.85714,297.64826)" + id="g13270" + style="stroke-width:1.7845"><rect + ry="22.088493" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.2;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter:url(#filter13052);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect13038" + width="92" + height="152" + x="180.3" + y="-172" + rx="20.760267" + transform="rotate(90)" /><rect + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.2;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter:url(#filter13165);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect13151" + width="92" + height="152" + x="176.3" + y="-172" + rx="20.760267" + transform="rotate(90)" + ry="22.088493" /><rect + transform="rotate(90)" + rx="20.760267" + y="-172" + x="176.3" + height="152" + width="92" + id="rect11599" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#ff1744;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + ry="22.088493" /><path + style="fill:none;fill-rule:evenodd;stroke:#fbe9e7;stroke-width:10.707;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 36,192.3 60,32 60,-32" + id="path11029-3" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccc" /><path + inkscape:connector-curvature="0" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#ff4569;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 32,36 c -6.648001,0 -12,5.352 -12,12 v 1 c 0,-6.648 5.351999,-12 12,-12 h 128 c 6.648,0 12,5.352 12,12 v -1 c 0,-6.648 -5.352,-12 -12,-12 z" + transform="translate(0,140.3)" + id="rect12531" /><path + inkscape:connector-curvature="0" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#d81a3d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 20,115 v 1 c 0,6.648 5.351999,12 12,12 h 128 c 6.648,0 12,-5.352 12,-12 v -1 c 0,6.648 -5.352,12 -12,12 H 32 c -6.648001,0 -12,-5.352 -12,-12 z" + transform="translate(0,140.3)" + id="path12537" /></g><g + transform="translate(266.85714,297.64826)" + id="g13464" + style="stroke-width:1.7845"><path + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.2;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.0606;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter:url(#filter13488);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 90,296.3 c -3.324,0 -6,2.676 -6,6 v 12 c 0,0.27298 0.02557,0.53962 0.06055,0.80273 2.55e-4,0.002 -2.57e-4,0.004 0,0.006 A 11.999994,11.999994 0 0 0 84,316.3 a 11.999994,11.999994 0 0 0 12,12 11.999994,11.999994 0 0 0 12,-12 11.999994,11.999994 0 0 0 -0.0664,-1.15234 C 107.9726,314.8702 108,314.58855 108,314.3 v -12 c 0,-3.324 -2.676,-6 -6,-6 z" + id="path13466" + inkscape:connector-curvature="0" /><path + inkscape:connector-curvature="0" + id="circle13321" + transform="translate(0,140.3)" + d="m 90,152 c -3.324,0 -6,2.676 -6,6 v 12 c 0,0.27298 0.02557,0.53962 0.06055,0.80273 2.55e-4,0.002 -2.57e-4,0.004 0,0.006 A 11.999994,11.999994 0 0 0 84,172 a 11.999994,11.999994 0 0 0 12,12 11.999994,11.999994 0 0 0 12,-12 11.999994,11.999994 0 0 0 -0.0664,-1.15234 C 107.9726,170.5702 108,170.28855 108,170 v -12 c 0,-3.324 -2.676,-6 -6,-6 z" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.2;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.0606;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter:url(#filter13338);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /><rect + ry="11.044246" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#263238;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + id="rect11618" + width="24.000019" + height="23.999996" + x="292.29999" + y="-108" + rx="10.380132" + transform="rotate(90)" /><circle + r="11.999994" + cy="312.29999" + cx="96" + id="path11616" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#263238;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.0606;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /><path + inkscape:connector-curvature="0" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#37474f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.607021;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 90,152 c -3.324,0 -6,2.676 -6,6 v 1 c 0,-3.324 2.676,-6 6,-6 h 12 c 3.324,0 6,2.676 6,6 v -1 c 0,-3.324 -2.676,-6 -6,-6 z" + transform="translate(0,140.3)" + id="rect12574" /><path + inkscape:connector-curvature="0" + style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#1a252a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.0606;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="M 84.021484,171.42969 A 11.999994,11.999994 0 0 0 84,172 11.999994,11.999994 0 0 0 96,184 11.999994,11.999994 0 0 0 108,172 11.999994,11.999994 0 0 0 107.9805,171.58594 11.999994,11.999994 0 0 1 96,183 11.999994,11.999994 0 0 1 84.021484,171.42969 Z" + transform="translate(0,140.3)" + id="circle12577" /></g><path + style="fill:url(#radialGradient5715);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.670974px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 316.85692,445.94826 c -3.324,0 -6,2.676 -6,6 v 8 c 0,3.324 2.676,6 6,6 h 14 v 8 h -32 c -6.648,0 -12,5.352 -12,12 v 68 c 0,6.648 5.352,12 12,12 l 25.60743,38 c 2.06983,3.58859 5.93582,6 10.39257,6 h 16 a 11.999994,11.999994 0 0 0 12,12 11.999994,11.999994 0 0 0 11.99806,-12 h 16.00194 c 4.45675,0 8.32275,-2.41141 10.39258,-6 h 0.01 l 25.59765,-38 c 6.64709,-10e-4 12,-5.35265 12,-12 v -68 c 0,-6.648 -5.35395,-12 -12.00195,-12 h -31.99805 v -8 h 13.99805 c 3.324,0 6.00195,-2.676 6.00195,-6 v -8 c 0,-3.324 -2.67795,-6 -6.00195,-6 h -23.99804 c -3.324,0 -6,2.676 -6,6 v 8 c 0,3.324 2.676,6 6,6 h 2 v 8 h -48.00001 v -8 h 1.99805 c 3.324,0 6.00195,-2.676 6.00195,-6 v -8 c 0,-3.324 -2.67795,-6 -6.00195,-6 z" + id="path13540-7" + inkscape:connector-curvature="0" /><path + inkscape:connector-curvature="0" + id="path5092" + d="m 316.85692,445.94826 c -3.324,0 -6,2.676 -6,6 v 8 c 0,3.324 2.676,6 6,6 h 14 v 8 h -32 c -6.648,0 -12,5.352 -12,12 v 68 c 0,6.648 5.352,12 12,12 l 25.60743,38 c 2.06983,3.58859 5.93582,6 10.39257,6 h 16 a 11.999994,11.999994 0 0 0 12,12 11.999994,11.999994 0 0 0 11.99806,-12 h 16.00194 c 4.45675,0 8.32275,-2.41141 10.39258,-6 h 0.01 l 25.59765,-38 c 6.64709,-10e-4 12,-5.35265 12,-12 v -68 c 0,-6.648 -5.35395,-12 -12.00195,-12 h -31.99805 v -8 h 13.99805 c 3.324,0 6.00195,-2.676 6.00195,-6 v -8 c 0,-3.324 -2.67795,-6 -6.00195,-6 h -23.99804 c -3.324,0 -6,2.676 -6,6 v 8 c 0,3.324 2.676,6 6,6 h 2 v 8 h -48.00001 v -8 h 1.99805 c 3.324,0 6.00195,-2.676 6.00195,-6 v -8 c 0,-3.324 -2.67795,-6 -6.00195,-6 z" + style="fill:url(#linearGradient5094);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.670974px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g><image + width="30.360945" + height="28.817167" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 aGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4 OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6 Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRp b24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAv MS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5 cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1l bnRzLzEuMS8iCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4 bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0i aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpk b2NpZDpnaW1wOmNiZjJlZTZmLThlZjItNGFjYi1hNzlkLTBkNWY4MTVlN2M1YyIKICAgeG1wTU06 SW5zdGFuY2VJRD0ieG1wLmlpZDoxM2U3NzE1Mi04M2E5LTQxNDktOTMwOC03Y2VlNmU3ZjM4ZWQi CiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4OGQ4YWI2OS02MDBmLTRjYWQt YjczZS0yZGIwMjI1NDUzZWMiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICBHSU1QOkFQST0i Mi4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NTU5MDUw MTAzNjYxNTEiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zMCIKICAgdGlmZjpPcmllbnRhdGlvbj0i MSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiPgogICA8eG1wTU06SGlzdG9yeT4KICAg IDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAg c3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpkY2RiZTVm Ni1iY2E0LTQ2OWUtYWY5Ni03YzBmYzZjZjQ2MzYiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9 IkdpbXAgMi4xMCAoTGludXgpIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTA2LTIyVDE1OjM2OjUw KzAyOjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNj cmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg IAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Psh/3LgAAAAG YktHRAAAAAAAAPlDu38AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfmBhYNJDLHHIypAAAJ PklEQVR42u2dX08b2RnGH4+xsXFsxiYh7Xq1MYq6WWV3FUfK1q1EYCApaRUpQ7vWtr3pOp8g4RPY Ue+boPY69i0SUkHKtW2VK6gliDasmq0q3FW9TbxhASn8MVDcC2ZWhsVmxnNmfM7hPDdBBI898/P7 Pud9z5kzLnCoUqmkAIgDuAYgpv0sA4Db7UY4HIbH4znppUUA6wCeaz8vRaPRdRavgYsTkDEA4wCG tX9PlNfrRTgchiRJZg6/BGAWwEw0Gl0SYJ0BmgKgtoKpy+/3Q5Zlq29ZBjAJIEd7JLsYhCkDeAjg cy3NwiGox5UD8CgajZYFWOtQMwAe6H7ZQajUA3YxAlQBkDUaoQ5C1bUOYDIajWYEWONpN2vEQ4/L 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 " + id="image8180" + x="6.1239142" + y="133.71233" + style="stroke-width:1.3711" /><g + id="g10135" + transform="matrix(0.13518227,0,0,0.1370563,7.2998322,29.425797)" + style="stroke-width:1.94381"> + <g + id="g10109" + style="stroke-width:1.94381"> + <linearGradient + id="SVGID_1_" + gradientUnits="userSpaceOnUse" + x1="199.533" + y1="201.907" + x2="39.678699" + y2="42.0527"> + <stop + offset="0" + style="stop-color:#130036" + id="stop9943" /> + <stop + offset="0.2297" + style="stop-color:#18023B" + id="stop9945" /> + <stop + offset="0.5122" + style="stop-color:#26094A" + id="stop9947" /> + <stop + offset="0.8211" + style="stop-color:#3D1563" + id="stop9949" /> + <stop + offset="1" + style="stop-color:#4E1D75" + id="stop9951" /> + </linearGradient> + <path + class="st1" + d="M 188.887,35.492 C 174.386,26.02 155.841,22.954 145.606,21.883 134.652,20.738 125.138,20.94 116.77,22.168 c -0.709,-0.009 -1.414,-0.037 -2.127,-0.037 -0.544,0 -1.08,0.023 -1.627,0.029 0.225,-0.275 0.381,-0.445 0.381,-0.445 0,0 -0.44,0.069 -1.304,0.46 -2.717,0.044 -5.421,0.138 -8.085,0.32 3.711,-4.068 6.751,-6.2 6.751,-6.2 0,0 -3.608,0.528 -10.192,6.496 -3.132,0.288 -6.221,0.67 -9.258,1.145 6.918,-9.362 14.155,-13.304 14.155,-13.304 0,0 -8.772,-1.786 -22.507,12.016 -1.149,1.155 -2.215,2.356 -3.246,3.572 C 39.68,35.907 11.03,61.758 11.03,92.139 c 0,5.958 -1.829,12.511 0.143,19.054 -0.788,10.453 0.89,38.975 0.89,38.975 0,0 10.837,58.649 52.51,68.846 0.662,0.158 -8.707,-14.62 -12.851,-32.442 8.069,8.208 17.685,14.699 28.478,15.824 1.325,0.137 -6.165,-8.64 -12.725,-19.234 l 94.698,31.906 c 49.529,-21.274 43.763,-19.357 54.397,-30.875 23.428,-25.36 26.708,-39.75 20.806,-82.235 -3.993,-28.679 -25.16,-57.083 -48.489,-66.466 z" + id="path9954" + style="fill:url(#SVGID_1_);stroke-width:1.94381" /> + <polygon + class="st2" + points="201.939,95.591 22.687,62.276 8.546,142.68 16.387,166.974 172.911,219.015 " + id="polygon9956" + style="stroke-width:1.94381" /> + <g + id="g9960" + style="stroke-width:1.94381"> + <path + class="st3" + d="m 23.128,65.01 c 0.785,1.689 0.554,2.089 0.065,2.089 -0.22,0 -0.492,-0.081 -0.747,-0.161 -0.255,-0.081 -0.494,-0.161 -0.648,-0.161 -0.433,0 -0.189,0.639 2.263,3.72 3.468,4.406 54.399,81.293 57.182,81.293 0.015,0 0.028,-0.002 0.04,-0.006 23.71,-8.448 124.543,-43.058 124.543,-43.058 L 195.51,93.697 23.128,65.01" + id="path9958" + style="stroke-width:1.94381" /> + </g> + <path + class="st4" + d="m 24.607,63.121 c 0,0 0.76,3.932 4.281,8.284 3.5,4.392 50.51,75.571 52.869,74.944 30.795,-8.176 142.525,-51.674 142.525,-51.674 z" + id="path9962" + style="stroke-width:1.94381" /> + <polygon + class="st3" + points="19.094,171.178 168.976,221.012 167.321,217.161 18.568,167.703 " + id="polygon9964" + style="stroke-width:1.94381" /> + <linearGradient + id="SVGID_2_" + gradientUnits="userSpaceOnUse" + x1="10.2428" + y1="95.311302" + x2="57.703899" + y2="95.311302"> + <stop + offset="0" + style="stop-color:#3156A8" + id="stop9966" /> + <stop + offset="0.2474" + style="stop-color:#3351A4" + id="stop9968" /> + <stop + offset="0.5365" + style="stop-color:#3B4397" + id="stop9970" /> + <stop + offset="0.8453" + style="stop-color:#472C82" + id="stop9972" /> + <stop + offset="1" + style="stop-color:#4E1D75" + id="stop9974" /> + </linearGradient> + <path + class="st5" + d="m 57.704,59.795 c 0,0 -54.122,15.85 -29.288,71.033 0,0 -11.387,-10.426 -18.167,-23.757 C 9.944,106.474 21.55,65.242 21.55,65.242 Z" + id="path9977" + style="fill:url(#SVGID_2_);stroke-width:1.94381" /> + + <radialGradient + id="SVGID_3_" + cx="59.0732" + cy="113.9232" + r="85.246597" + gradientTransform="matrix(1,0,0,1.45,0,-51.2654)" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.1654" + style="stop-color:#14CDDA" + id="stop9979" /> + <stop + offset="0.5478" + style="stop-color:#2061BD" + id="stop9981" /> + <stop + offset="0.6546" + style="stop-color:#2658AC" + id="stop9983" /> + <stop + offset="0.864" + style="stop-color:#373F81" + id="stop9985" /> + <stop + offset="1" + style="stop-color:#432D62" + id="stop9987" /> + </radialGradient> + <path + class="st6" + d="m 28.416,130.828 c -4.707,-0.081 -12.269,-3.964 -14.722,-8.665 -3.066,51.098 17.782,80.939 50.88,96.853 C 53.759,217.776 0,189.407 0,121.14 0,63.093 46.06,11.208 118.142,8.831 118.584,12.085 82.406,20.245 81.249,24.082 79.453,30.054 75.325,36.555 71.843,41.766 67.125,48.827 78.4,54.865 70.114,56.767 57.646,59.628 41.016,58.044 28.753,73.986 10.319,97.951 22.62,125.365 28.416,130.828 Z" + id="path9990" + style="fill:url(#SVGID_3_);stroke-width:1.94381" /> + <g + id="g10003" + style="stroke-width:1.94381"> + <linearGradient + id="SVGID_4_" + gradientUnits="userSpaceOnUse" + x1="44.5387" + y1="57.897499" + x2="191.515" + y2="57.897499"> + <stop + offset="0" + style="stop-color:#2061BD" + id="stop9992" /> + <stop + offset="0.1846" + style="stop-color:#2B51AC" + id="stop9994" /> + <stop + offset="0.6826" + style="stop-color:#442C84" + id="stop9996" /> + <stop + offset="0.9409" + style="stop-color:#4E1D75" + id="stop9998" /> + </linearGradient> + <path + class="st7" + d="M 137.048,10.699 C 90.366,7.117 66.341,25.796 50.918,47.516 c -5.617,7.906 -5.002,15.166 -3.068,23.937 0.831,3.744 1.671,5.538 0.659,9.355 -0.558,2.106 -0.465,4.072 -1.158,5.314 -0.959,1.717 -2.18,3.594 -2.589,6.677 -1.022,7.699 1.703,10.151 3.406,12.741 2.426,-2.409 7.165,-8.079 15.71,-11.466 8.545,-3.387 14.167,-8.477 24.876,-13.941 14.632,-7.463 31.782,3.859 64.3,-6.796 C 163.06,70.058 186.056,35.993 191.515,34.685 176.312,17.873 150.448,11.726 137.048,10.699 Z" + id="path10001" + style="fill:url(#SVGID_4_);stroke-width:1.94381" /> + </g> + <path + class="st8" + d="m 106.207,0.032 c 0,0 -8.335,4.026 -11.965,11.301 8.381,-4.225 13.246,-6.533 15.546,-6.808 0,0 -2.203,1.067 -4.935,6.87 4.83,-1.547 6.493,-2.522 7.819,-2.537 0,0 -0.385,0.61 -0.737,6.402 -7.417,-2.404 -21.693,0.397 -29.287,5.153 C 80.072,6.678 106.207,0.032 106.207,0.032 Z" + id="path10005" + style="stroke-width:1.94381" /> + <linearGradient + id="SVGID_5_" + gradientUnits="userSpaceOnUse" + x1="66.173599" + y1="23.205601" + x2="167.26421" + y2="111.0823"> + <stop + offset="0.0202" + style="stop-color:#48A8E0" + id="stop10007" /> + <stop + offset="0.3883" + style="stop-color:#2061BD" + id="stop10009" /> + <stop + offset="0.4968" + style="stop-color:#2B51AC" + id="stop10011" /> + <stop + offset="0.7892" + style="stop-color:#442C84" + id="stop10013" /> + <stop + offset="0.9409" + style="stop-color:#4E1D75" + id="stop10015" /> + </linearGradient> + <path + class="st9" + d="M 139.379,8.63 C 125.07,6.776 113.109,7.141 102.94,9.1 c 3.198,-3.065 6.898,-4.575 6.898,-4.575 -4.795,0.424 -10.781,3.554 -15.917,6.868 -2.588,0.826 -5.054,1.758 -7.39,2.799 1.43,-1.885 3.094,-3.899 4.72,-5.445 6.039,-5.743 15.006,-8.715 15.006,-8.715 -7.654,-0.562 -33.268,5.995 -48.681,37.213 -2.526,3.077 -4.883,6.235 -7.129,9.398 -5.798,8.164 -5.162,15.658 -3.162,24.715 0.852,3.867 1.009,10.04 0.168,14.029 -0.168,0.799 -2.363,2.863 -2.926,8.428 -0.602,5.905 1.505,8.804 3.582,11.203 C 55.489,94.226 62.77,92.087 62.77,92.087 73.1,87.855 77.4,83.335 88.458,77.694 c 15.108,-7.709 67.939,17.45 101.512,6.448 10.332,-3.384 -3.977,-47.359 1.663,-48.709 C 175.934,18.073 153.139,10.413 139.379,8.63 Z" + id="path10018" + style="fill:url(#SVGID_5_);stroke-width:1.94381" /> + <linearGradient + id="SVGID_6_" + gradientUnits="userSpaceOnUse" + x1="19.676201" + y1="337.41391" + x2="217.8353" + y2="43.630901"> + <stop + offset="0.3787" + style="stop-color:#3156A8" + id="stop10020" /> + <stop + offset="1" + style="stop-color:#4E1D75" + id="stop10022" /> + </linearGradient> + <path + class="st10" + d="M 244.176,103.288 C 235.9,60.051 196.08,20.716 164.059,18.536 149.848,17.568 154.212,24.855 145.32,27.63 107,39.589 106.414,46.571 106.414,46.571 c 81.252,3.819 82.484,84.064 60.724,104.366 6.123,-1.428 12.762,-8.914 18.859,-20.666 -0.732,4.931 -1.36,10.98 -2.274,17.729 -2.788,20.595 0.823,63.614 -58.356,92.405 0,0 32.372,-2.646 48.425,-20.717 -6.528,15.012 -24.441,23.121 -24.441,23.121 13.182,-1.928 47.81,-12.146 69.868,-39.732 24.966,-31.225 32.474,-60.519 24.957,-99.789 z" + id="path10025" + style="fill:url(#SVGID_6_);stroke-width:1.94381" /> + <linearGradient + id="SVGID_7_" + gradientUnits="userSpaceOnUse" + x1="96.745201" + y1="278.64581" + x2="206.3175" + y2="32.542301"> + <stop + offset="0" + style="stop-color:#29ABE2" + id="stop10027" /> + <stop + offset="0.7733" + style="stop-color:#385AA6" + id="stop10029" /> + <stop + offset="0.8575" + style="stop-color:#414293" + id="stop10031" /> + <stop + offset="1" + style="stop-color:#4E1D75" + id="stop10033" /> + </linearGradient> + <path + class="st11" + d="m 164.993,182.221 c 0,0 26.396,-7.38 34.903,-21.633 -1.032,16.619 -16.107,33.662 -16.107,33.662 0,0 22.038,-4.46 31.436,-19.968 -1.224,13.994 -18.191,32.907 -18.191,32.907 17.346,-3.113 60.93,-30.645 47.143,-103.901 -8.142,-43.262 -48.096,-82.572 -80.117,-84.752 -14.211,-0.968 -9.847,6.319 -18.739,9.094 -38.32,11.959 -38.906,18.941 -38.906,18.941 81.252,3.819 102.844,46.344 60.724,103.231 6.123,-1.429 8.578,-4.794 13.178,-10.301 -0.002,0 2.084,21.78 -15.324,42.72 z" + id="path10036" + style="fill:url(#SVGID_7_);stroke-width:1.94381" /> + <linearGradient + id="SVGID_8_" + gradientUnits="userSpaceOnUse" + x1="48.2686" + y1="92.033699" + x2="54.241402" + y2="95.4683"> + <stop + offset="0" + style="stop-color:#B0DCD6" + id="stop10038" /> + <stop + offset="1" + style="stop-color:#53ACE0" + id="stop10040" /> + </linearGradient> + <path + class="st12" + d="m 60.609,85.567 c 0,0 -4.719,3.301 -8.136,2.46 -4.045,-0.993 -4.73,-4.913 -4.73,-4.913 -0.089,0.803 -0.192,1.578 -0.341,2.273 -0.168,0.799 -2.363,2.863 -2.926,8.428 -0.602,5.905 1.615,9.325 3.692,11.724 7.381,-10.792 14.552,-13.452 14.552,-13.452 -2.809,-0.982 -2.111,-6.52 -2.111,-6.52 z" + id="path10043" + style="fill:url(#SVGID_8_);stroke-width:1.94381" /> + <defs + id="defs10048"> + <filter + id="filter10202" + filterUnits="userSpaceOnUse" + x="162.67999" + y="18.355" + width="14.927" + height="6.6290002"> + <feColorMatrix + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" + id="feColorMatrix10200" /> + </filter> + </defs> + <mask + maskUnits="userSpaceOnUse" + x="162.68" + y="18.355" + width="14.927" + height="6.629" + id="SVGID_9_"> + <g + class="st13" + id="g10057"> + <radialGradient + id="SVGID_10_" + cx="154.384" + cy="67.998497" + r="51.967499" + gradientUnits="userSpaceOnUse"> + <stop + offset="0.868" + style="stop-color:#FFFFFF" + id="stop10050" /> + <stop + offset="1" + style="stop-color:#000000" + id="stop10052" /> + </radialGradient> + <rect + x="91.294998" + y="-7.2179999" + class="st14" + width="100.015" + height="93.338997" + id="rect10055" /> + </g> + </mask> + <g + class="st15" + mask="url(#SVGID_9_)" + id="g10075" + style="stroke-width:1.94381"> + <g + class="st16" + id="g10073" + style="stroke-width:1.94381"> + <g + class="st17" + id="g10071" + style="stroke-width:1.94381"> + <linearGradient + id="SVGID_11_" + gradientUnits="userSpaceOnUse" + x1="176.2139" + y1="23.0851" + x2="164.2403" + y2="20.555401"> + <stop + offset="0" + style="stop-color:#3092B9" + id="stop10060" /> + <stop + offset="0.2199" + style="stop-color:#258DB6" + id="stop10062" /> + <stop + offset="0.6564" + style="stop-color:#1685B1" + id="stop10064" /> + <stop + offset="1" + style="stop-color:#1082AF" + id="stop10066" /> + </linearGradient> + <path + class="st18" + d="m 163.513,18.434 c 1.106,-0.287 5.621,0.204 8.584,1.533 2.964,1.329 8.073,3.679 3.986,4.701 -4.088,1.022 -6.438,-0.613 -8.482,-2.351 -2.044,-1.738 -6.847,-3.168 -4.088,-3.883 z" + id="path10069" + style="fill:url(#SVGID_11_);stroke-width:1.94381" /> + </g> + </g> + </g> + <linearGradient + id="SVGID_12_" + gradientUnits="userSpaceOnUse" + x1="80.783798" + y1="38.0252" + x2="90.637001" + y2="77.544296"> + <stop + offset="0.0074" + style="stop-color:#1398D1;stop-opacity:0" + id="stop10077" /> + <stop + offset="0.2482" + style="stop-color:#1187C2;stop-opacity:0.6197" + id="stop10079" /> + <stop + offset="0.6422" + style="stop-color:#3F6499;stop-opacity:0.71" + id="stop10081" /> + <stop + offset="1" + style="stop-color:#2F4282;stop-opacity:0.5" + id="stop10083" /> + </linearGradient> + <path + class="st19" + d="m 61.051,84.921 c 0,0 25.888,-42.662 49.292,-54.175 2.173,-1.093 -32.405,9.191 -46.32,24.55 -8.271,9.129 -3.891,27.002 -2.972,29.625 z" + id="path10086" + style="fill:url(#SVGID_12_);stroke-width:1.94381" /> + <path + class="st20" + d="m 61.051,84.921 c 0.024,-0.015 0.05,-0.03 0.075,-0.045 C 67.82,80.74 79.292,78.358 82.99,70.861 97.227,41.992 110.344,30.746 110.344,30.746 86.939,42.259 61.051,84.921 61.051,84.921 Z" + id="path10088" + style="stroke-width:1.94381" /> + <path + class="st21" + d="m 63.512,77.118 c 0,0 -4.666,-7.159 2.053,-13.687 3.546,-3.44 8.919,-1.529 9.54,-0.91 2.644,2.622 0.982,8.156 -1.398,11.288 -1.345,1.764 -5.259,4.51 -10.195,3.309 z" + id="path10090" + style="stroke-width:1.94381" /> + + <linearGradient + id="SVGID_13_" + gradientUnits="userSpaceOnUse" + x1="48.7383" + y1="14.373" + x2="43.199299" + y2="11.3027" + gradientTransform="matrix(0.9994,0.0349,-0.0349,0.9994,24.5906,57.1202)"> + <stop + offset="0" + style="stop-color:#F9C21B" + id="stop10092" /> + <stop + offset="0.1479" + style="stop-color:#F3BA1B" + id="stop10094" /> + <stop + offset="0.3787" + style="stop-color:#E3A41B" + id="stop10096" /> + <stop + offset="0.6634" + style="stop-color:#C9801C" + id="stop10098" /> + <stop + offset="0.9884" + style="stop-color:#A44E1C" + id="stop10100" /> + <stop + offset="1" + style="stop-color:#A34C1C" + id="stop10102" /> + </linearGradient> + <path + class="st22" + d="m 66.187,76.674 c 0,0 -3.224,-4.949 1.422,-9.459 2.446,-2.376 6.161,-1.056 6.592,-0.63 1.826,1.812 0.676,5.637 -0.968,7.8 -0.929,1.223 -3.635,3.119 -7.046,2.289 z" + id="path10105" + style="fill:url(#SVGID_13_);stroke-width:1.94381" /> + <path + d="m 72.623,71.393 c -0.072,1.978 -1.746,3.522 -3.738,3.454 -1.996,-0.069 -3.558,-1.726 -3.487,-3.704 0.071,-1.977 1.744,-3.521 3.741,-3.453 1.992,0.069 3.553,1.727 3.484,3.703 z" + id="path10107" + style="stroke-width:1.94381" /> + </g> + <circle + class="st24" + cx="66.724998" + cy="70.287003" + r="1.136" + id="circle10111" + style="stroke-width:1.94381" /> + <linearGradient + id="SVGID_14_" + gradientUnits="userSpaceOnUse" + x1="206.2113" + y1="130.1391" + x2="169.42979" + y2="47.526402"> + <stop + offset="0" + style="stop-color:#409EC3" + id="stop10113" /> + <stop + offset="0.62" + style="stop-color:#2061BD" + id="stop10115" /> + </linearGradient> + <path + class="st23" + d="M 228.505,65.01 C 210.784,38.034 196.744,31.179 196.744,31.179 c 0,0 0.645,19.65 10.484,29.128 1.124,1.083 -11.774,-8.432 -11.774,-8.432 0,0 -2.758,9.076 3.562,19.51 -1.836,-2.439 -3.051,-3.363 -3.051,-3.363 0,0 -11.908,5.491 -15.423,13.109 -1.815,-3.426 -3.176,-5.444 -3.176,-5.444 0,0 -8.236,16.437 -7.201,35.495 1.684,30.988 -5.803,42.404 -5.803,42.404 0,0 20.057,-7.695 29.676,-32.468 3.974,10.533 -0.121,21.619 -0.121,21.619 0,0 16.206,-12.109 19.691,-34.18 3.863,6.23 2.727,18.664 2.727,18.664 0,0 10.344,-14.402 10.596,-30.086 4.794,3.343 5.235,16.399 5.235,16.399 0,0 15.336,-19.606 -3.661,-48.524 z" + id="path10118" + style="fill:url(#SVGID_14_);stroke-width:1.94381" /> + <linearGradient + id="SVGID_15_" + gradientUnits="userSpaceOnUse" + x1="176.7603" + y1="103.061" + x2="150.40691" + y2="21.9536"> + <stop + offset="0" + style="stop-color:#14B2DA" + id="stop10120" /> + <stop + offset="0.4028" + style="stop-color:#297CCC" + id="stop10122" /> + <stop + offset="0.5077" + style="stop-color:#256FC5" + id="stop10124" /> + <stop + offset="0.6492" + style="stop-color:#2164BF" + id="stop10126" /> + <stop + offset="0.8162" + style="stop-color:#2061BD" + id="stop10128" /> + <stop + offset="0.9835" + style="stop-color:#2061BD" + id="stop10130" /> + </linearGradient> + <path + class="st25" + d="m 211.282,46.158 c -4.631,-8.63 -12.803,-14.13 -19.986,-18.582 -17.909,-11.105 -29.427,-12.665 -29.427,-12.665 0,0 -16.528,3.074 -13.689,7.672 0.146,0.236 0.487,0.535 0.968,0.877 -14.498,-6.678 -22.722,8.677 -22.722,8.677 -8.284,-0.503 -18.371,3.575 -21.312,14.499 -0.305,1.134 3.711,0.496 5.777,0.945 12.079,2.626 23.45,8.32 28.675,11.134 12.154,6.548 19.365,17.451 23.528,25.629 5.037,9.894 7.094,27.304 7.094,27.304 0,0 13.253,-18.205 10.086,-27.552 5.249,3.26 6.285,14.891 6.285,14.891 0,0 8.835,-13.838 6.223,-25.891 6.815,4.153 7.042,12.601 7.042,12.601 0,0 6.939,-9.631 3.803,-25.427 6.858,5.453 8.293,12.517 8.293,12.517 0,0 5.606,-14.993 -0.638,-26.629 z" + id="path10133" + style="fill:url(#SVGID_15_);stroke-width:1.94381" /> +</g><image + width="45.539223" + height="26.901003" + preserveAspectRatio="none" + xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAYEBAUEBAYFBQUGBgYHCQ4JCQgICRINDQoOFRIWFhUS FBQXGiEcFxgfGRQUHScdHyIjJSUlFhwpLCgkKyEkJST/2wBDAQYGBgkICREJCREkGBQYJCQkJCQk JCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCT/wAARCAI9A8oDASIA AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3 ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3 uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDP8c+K Nbg8W6iIdQlijMgwgVf7o9qyX8Va/IuF1WUv9F4/SrnjxVHi/VFwA6SAA9f4RXN7DEzuPmJHOOa+ shCKifNzleRqnxdr0ahf7Sm/2vlXr+VM/wCEv13eVOpz59lX/CskyZAOMsRyO9KpLL8oI47jmtHC PQiM3sjXHizxAXCf2nKU/wB1f8K734ffFu80yaPTNYuWktZHCpOQPkJPfpxyfyry8LsjO/qvWu++ G3w5m8QSw6pqcUiaejhlR1KmXDcjt6EZFYV4UuW8jajKfN7p7smpSzIkkVwWjcBlYY5B5FTC8nJ/ 17/kKrLBHDFHDEmyONQqr/dAHHNOAA78147V9j07tbk/2ydGJMzYAz0Fc94usb3VLF7i01Bre4t1 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 " + id="image11884" + x="123.94752" + y="77.886711" + style="stroke-width:5.63571" /><path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-3)" + d="m 48.686201,95.267324 50.820238,0.392631" + id="path12300" /><path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-3-6)" + d="M 48.686201,140.25119 99.506439,113.14441" + id="path12300-9" + sodipodi:nodetypes="cc" /><path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-3-1)" + d="M 48.686201,52.965491 99.506439,76.696018" + id="path12300-0" + sodipodi:nodetypes="cc" /><path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-3-1-6)" + d="M 192.19253,78.231345 234.18084,48.465927" + id="path12300-0-6" + sodipodi:nodetypes="cc" /><text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:7.76111px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="59.602123" + y="92.486649" + id="text12479"><tspan + sodipodi:role="line" + id="tspan12477" + style="font-size:7.76111px;stroke-width:0.264583px" + x="59.602123" + y="92.486649">IMAP</tspan></text><text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:7.76111px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-8.8944416" + y="143.93439" + id="text12479-9" + transform="rotate(-29.79443)"><tspan + sodipodi:role="line" + id="tspan12477-3" + style="font-size:7.76111px;stroke-width:0.264583px" + x="-8.8944416" + y="143.93439">IMAP</tspan></text><text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:7.76111px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="85.542168" + y="17.383308" + id="text12479-6" + transform="rotate(30.05974)"><tspan + sodipodi:role="line" + id="tspan12477-0" + style="font-size:7.76111px;stroke-width:0.264583px" + x="85.542168" + y="17.383308">IMAP</tspan></text><path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-3-1-6-9)" + d="m 191.24295,103.43423 42.93789,35.147" + id="path12300-0-6-0" + sodipodi:nodetypes="cc" /><text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.46667px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="187.43295" + y="52.618786" + id="text15221"><tspan + sodipodi:role="line" + id="tspan15219" + style="font-size:8.46667px;stroke-width:0.264583px" + x="187.43295" + y="52.618786">Message</tspan><tspan + sodipodi:role="line" + style="font-size:8.46667px;stroke-width:0.264583px" + x="187.43295" + y="63.202122" + id="tspan15223">index</tspan></text><text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.46667px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="242.57494" + y="110.62234" + id="text15221-2"><tspan + sodipodi:role="line" + id="tspan15219-3" + style="font-size:8.46667px;text-align:end;text-anchor:end;stroke-width:0.264583px" + x="242.57494" + y="110.62234">Message</tspan><tspan + sodipodi:role="line" + style="font-size:8.46667px;text-align:end;text-anchor:end;stroke-width:0.264583px" + x="242.57494" + y="121.20567" + id="tspan15223-7">bodies</tspan></text></g><style + type="text/css" + id="style9941"> + .st0{fill:#363959;} + .st1{fill:url(#SVGID_1_);} + .st2{fill:#FBFBFB;} + .st3{fill:#999999;} + .st4{fill:#F8F8F8;} + .st5{fill:url(#SVGID_2_);} + .st6{fill:url(#SVGID_3_);} + .st7{fill:url(#SVGID_4_);} + .st8{fill:#3F6499;} + .st9{fill:url(#SVGID_5_);} + .st10{fill:url(#SVGID_6_);} + .st11{fill:url(#SVGID_7_);} + .st12{fill:url(#SVGID_8_);} + .st13{filter:url(#Adobe_OpacityMaskFilter);} + .st14{fill:url(#SVGID_10_);} + .st15{mask:url(#SVGID_9_);} + .st16{opacity:0.6;} + .st17{opacity:0.2;} + .st18{fill:url(#SVGID_11_);} + .st19{fill:url(#SVGID_12_);} + .st20{opacity:0.1;fill:#F2F2F2;} + .st21{fill:#2F4282;} + .st22{fill:url(#SVGID_13_);} + .st23{fill:url(#SVGID_14_);} + .st24{fill:#FFFFFF;} + .st25{fill:url(#SVGID_15_);} + .st26{fill:url(#SVGID_16_);} + .st27{fill:url(#SVGID_17_);} + .st28{fill:url(#SVGID_18_);} + .st29{fill:url(#SVGID_19_);} + .st30{fill:url(#SVGID_20_);} + .st31{fill:url(#SVGID_21_);} + .st32{fill:url(#SVGID_22_);} + .st33{fill:url(#SVGID_23_);} + .st34{fill:url(#SVGID_24_);} + .st35{filter:url(#Adobe_OpacityMaskFilter_1_);} + .st36{fill:url(#SVGID_26_);} + .st37{mask:url(#SVGID_25_);} + .st38{fill:url(#SVGID_27_);} + .st39{fill:url(#SVGID_28_);} + .st40{fill:url(#SVGID_29_);} + .st41{fill:url(#SVGID_30_);} + .st42{fill:url(#SVGID_31_);} + .st43{fill:url(#SVGID_32_);} + .st44{fill:url(#SVGID_33_);} + .st45{fill:url(#SVGID_34_);} + .st46{fill:url(#SVGID_35_);} + .st47{fill:url(#SVGID_36_);} + .st48{fill:url(#SVGID_37_);} + .st49{fill:url(#SVGID_38_);} + .st50{fill:url(#SVGID_39_);} + .st51{fill:url(#SVGID_40_);} + .st52{filter:url(#Adobe_OpacityMaskFilter_2_);} + .st53{fill:url(#SVGID_42_);} + .st54{mask:url(#SVGID_41_);} + .st55{fill:url(#SVGID_43_);} + .st56{fill:url(#SVGID_44_);} + .st57{fill:url(#SVGID_45_);} + .st58{fill:url(#SVGID_46_);} + .st59{fill:url(#SVGID_47_);} + .st60{fill:url(#SVGID_48_);} +</style></svg> diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme_components1.drawio.pdf b/doc/talks/2022-06-23-stack/assets/aerogramme_components1.drawio.pdf new file mode 100644 index 00000000..fd9e6a62 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme_components1.drawio.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9198d0cfc0e04a56f84a353dd660b294ecf0dc68fe429892c8e9e604de016748 +size 31966 diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme_components1.png b/doc/talks/2022-06-23-stack/assets/aerogramme_components1.png Binary files differnew file mode 100644 index 00000000..fb81b460 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme_components1.png diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme_components2.drawio.pdf b/doc/talks/2022-06-23-stack/assets/aerogramme_components2.drawio.pdf new file mode 100644 index 00000000..aac2f141 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme_components2.drawio.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14dbf4a45545889babb2fdc6580399811366f6cc786cb28e3467cbcedbfe9482 +size 31688 diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme_components2.png b/doc/talks/2022-06-23-stack/assets/aerogramme_components2.png Binary files differnew file mode 100644 index 00000000..f9e2df14 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme_components2.png diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme_datatype.drawio.pdf b/doc/talks/2022-06-23-stack/assets/aerogramme_datatype.drawio.pdf new file mode 100644 index 00000000..44146ddd --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme_datatype.drawio.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21a3bb004ad35c8b47f14195c720048f8db31f47bd6346c1d747000570149c67 +size 31073 diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme_datatype.png b/doc/talks/2022-06-23-stack/assets/aerogramme_datatype.png Binary files differnew file mode 100644 index 00000000..c3b015a1 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme_datatype.png diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme_keys.drawio.pdf b/doc/talks/2022-06-23-stack/assets/aerogramme_keys.drawio.pdf new file mode 100644 index 00000000..17d37855 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme_keys.drawio.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4e109078a784c3999ca2ccd1adda8ff5793d1b230a9e27b4ad2e419afa0d37d +size 25145 diff --git a/doc/talks/2022-06-23-stack/assets/aerogramme_keys.png b/doc/talks/2022-06-23-stack/assets/aerogramme_keys.png Binary files differnew file mode 100644 index 00000000..ed2077d9 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/aerogramme_keys.png diff --git a/doc/talks/2022-06-23-stack/assets/alex.jpg b/doc/talks/2022-06-23-stack/assets/alex.jpg Binary files differnew file mode 100644 index 00000000..eac0f0a9 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/alex.jpg diff --git a/doc/talks/2022-06-23-stack/assets/atuin.jpg b/doc/talks/2022-06-23-stack/assets/atuin.jpg Binary files differnew file mode 100644 index 00000000..f2fbd61d --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/atuin.jpg diff --git a/doc/talks/2022-06-23-stack/assets/compatibility.png b/doc/talks/2022-06-23-stack/assets/compatibility.png Binary files differnew file mode 100644 index 00000000..ce364a9b --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/compatibility.png diff --git a/doc/talks/2022-06-23-stack/assets/consistent_hashing_1.svg b/doc/talks/2022-06-23-stack/assets/consistent_hashing_1.svg new file mode 100644 index 00000000..f8d24fd8 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/consistent_hashing_1.svg @@ -0,0 +1,301 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="648" + height="480" + viewBox="0 0 171.45 127" + version="1.1" + id="svg2147" + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" + sodipodi:docname="consistent_hashing_1.svg"> + <defs + id="defs2141"> + <marker + style="overflow:visible;" + id="marker3465" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3463" /> + </marker> + <marker + style="overflow:visible;" + id="marker3455" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3453" /> + </marker> + <marker + style="overflow:visible;" + id="marker3445" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3443" /> + </marker> + <marker + style="overflow:visible;" + id="Arrow1Lend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Lend" + inkscape:isstock="true"> + <path + transform="scale(0.8) rotate(180) translate(12.5,0)" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3144" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.98994949" + inkscape:cx="297.97441" + inkscape:cy="245.37211" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + showgrid="false" + units="px" + inkscape:window-width="1404" + inkscape:window-height="1016" + inkscape:window-x="281" + inkscape:window-y="27" + inkscape:window-maximized="0" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2144"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g2851" + transform="matrix(0.84882735,0,0,0.84882735,-5.4514578,9.7344105)"> + <circle + style="fill:none;stroke:#000000;stroke-width:1" + id="path2710" + cx="89.153343" + cy="63.810429" + r="51.14566" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 118.92887,33.710015 131.54406,21.284558" + id="path2736" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 130.3168,71.515 17.36133,3.481276" + id="path2736-5" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 104.71157,102.41933 6.56219,16.44605" + id="path2736-6" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 51.073764,81.65523 35.108111,89.312443" + id="path2736-9" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 54.233873,39.571826 39.643162,29.539572" + id="path2736-3" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 69.962563,25.77499 61.999798,9.959512" + id="path2736-7" + sodipodi:nodetypes="cc" /> + </g> + <g + id="g2992" + transform="translate(-19.258685)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987"><tspan + sodipodi:role="line" + id="tspan2985" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">1</tspan></text> + </g> + <g + id="g2992-5" + transform="translate(-5.5636458,61.815287)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcQAAAHECAYAAACnX1ofAAAABmJLR0QA/wD/AP+gvaeTAAAULklE QVR4nO3de7B1d0Hf4U94X0LIBRJRiQyYGJAkiAItYtGAUBAaBLFNIXJxqjQ4VYQpWOnQC1Pb2qk3 phWRq1AVBBEj94sKxaLlVgoiSBIgRkIqF7kmGCAJ6R/rQF4hmPCetfdv772eZ2ZPhkyy1veQs8/n 3WtfzhGxy46pzqjuWN2hum31ddXx1QkDd8Gmu7r6ZHVZ9Ynqgur86h3V66u/HjeNVTli9ABmd5Pq wdUDq3tXR42dAzvnC01hfEn17OqDY+cwF0HcHbepHl39SFMUgdW7qnp19V+q/z14CyzeidXTqyua LvO4ubmNub2kul1srQOjB3DYDlSPr15U3bW6wdg5sHinVudUn63e1BRJtohLptvp1OrXq+8aPQS4 Vv+zelD1sdFDuP4Ecfs8tHpmdfToIcDf6bzqzOqi0UO4flxm2x4Hql+qnpcYwjY4remFNrcZPYTr xyPE7XBkUwj/6eghwNfswuq7qw+PHsLfzSPEzXdM9YrEELbVKdXv5D3BG08QN9uRTTG89+ghwL7c rXpBfuZuNG+72Gy/Wp01egQwi9Oa/pD72tFDuHaCuLl+onri6BHArO5Wvb965+ghfCUvqtlM31O9 rulPk8Bu+Wx1z6Y377NBBHHzfHP11uobRw8BVuZD1V2qi0cP4Rqe4N0sRzV9FJsYwm47semzT48Z PYRrCOJmeWr1naNHAGtxp+o3cqVuY3hRzeb46b0bsByn7/319UNXUPmTyaa4T/XKVvsHlPdWz6ne 0vSJGR9p+pVRwFc6qukN9d9f/WjTJc5Vubo6u+nN+7Bot2n6RPxV/Y62zzX9miiXx+Hw3LDpF29f 0urup5+p/v66viDYRMdV72p1d7IPVHdc21cDu+3Y6oWt7v56cfVNa/tqYIMcUZ3b6u5cF1Qnre2r gWW4QdMrwVd1v31TPvOUBXpiq7tTvaa6+fq+FFiUY5s+aWZV99/n5vUdLMgDq6tazZ3pT6obre9L gUU6ufpoq4viE9b3pcA4p1WfbDV3okuqW6zvS4FFu1fTK7VXcV++qukPzrCzTmh6bm8Vd6DL86Z+ WLfHtrpHiZdW37G+LwXW5wZNv9twVXeeh67vSwEO8axWd7++KB/luDaeuF2fn2t6P+AqXNn0Fgtg /Y6sbrnC4/9x0+XZz6/wHCSI63JW06dQ+P8bOBy/3vThAKyQzzJdvTtWL8/vNgQO3x2bnlN84+gh u8wjltW6WdPvNvyW0UOArfeFpleevnz0kF0liKtzfPX7eeUnMJ9PNf1Mee/oIbvIBz6vxo2a/hQn hsCcbtr0eoRjRw/ZRZ5DXI1n5E21wGqc2HR177Wjh+wal0zn96PVs0ePAHbaldUZ1ZtHD9klgjiv 45o+iWaVv0wUoOr9Ta8+vWz0kF3hOcR5/dvEEFiPW1f/YvSIXeIR4nxObPoT29GjhwCL8cHqlKYP GWefPEKcz6MSQ2C9bln90OgRu0IQ5+MtFsAIZ48esCtcMp3HkdXHq2NGDwEW59KmT8Vy2XSfPEKc x10SQ2CM45p+BrFPgjiP240eACzat48esAsEcR43Hz0AWDS/RHgGgjgPQQRGEsQZHBw9YEfcdKbj PKn6rZmOBWy+h1aPm+E4c/0MWjRB3CwfrN42egSwNncfPYBruGQKAAkiAFSCCACVIAJAJYgAUAki AFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgA UAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFR1cPQAtsrB 6g57t1Oqb6huXB01chTs+Wx1efXR6sLqHdU7qytHjmJ7CCLX5UD1A9VDqjOrY8fOga/JZdUrq+dX L6uuGjuHTeaSKV/Ngeqc6v3VudWDEkO2z7HVg6vfq95XPSI/9/gqfGNwbU6v3lI9szpp8BaYy8nV r1Vvrk4bvIUNJIh8uQdVb6v+3ughsCJ3bvoeP2v0EDaLIHKoc6oXNL1QBnbZ0dULm77noRJErvHA 6mn5nmA5btD0Pf+A0UPYDH74UXWr6jlNL6SBJTlQ/WaeKydBZPKc6oTRI2CQm1bPGD2C8QSRB1T3 Gj0CBrtPdb/RIxhLEPk3owfAhnjC6AGMJYjLdqfqH4weARvijKaPJWShBHHZzh49ADbMg0cPYBxB XLYzRw+ADeN5xAUTxOW6SXX70SNgw3x7ddzoEYwhiMt1ev77w5c7UJ06egRj+IG4XCePHgAb6ltG D2AMQVyu40cPgA3lvrFQfkHwcs31W+7fXv32TMeC/Ti76a1E+3X0DMdgCwnics11deDd1c/NdCzY j9s3TxBdOVso/+EBIEEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEA KkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACo BBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaCq g6MHMMzVMx3npOpBMx0L9uOkmY4z132DLSOIy3XpTMe5294NdsWnRw9gDJdMl+tTowfAhvrk6AGM IYjL9RejB8CGunD0AMYQxOV6V3XF6BGwYa6o3jN6BGMI4nJ9rvrT0SNgw7y96b7BAgnisr1k9ADY MC8ePYBxBHHZXpSXmMMXXV2dO3oE4wjisp1XvWb0CNgQr6rOHz2CcQSRp40eABvi6aMHMJYgco/R A2BDfO/oAYwliMt2fPXPR4+ADXFOddPRIxhHEJftH1fHjR4BG+Im1QNHj2AcQVy2Hxw9ADaM+8SC CeJyHZHnD+HL3bPpvsECCeJy3brpEhFwjeOrk0ePYAxBXK7TRg+ADXX66AGMIYjLdbPRA2BDuW8s lF8QvFxzXS79UNNvzoDRbl+dOMNxvPVioQRxueb6b/+H1Q/PdCzYj9+sHj7DcW44wzHYQi6ZAkCC CACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAki AFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgA UAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAVQdHD2DrHVmdMHoENH0v zuHqmY7DlhHE5fr8TMd58N4NdsVc9w22jEumy3XZ6AGwoS4dPYAxBHG5Pjh6AGyoi0cPYAxBXK4L Rg+ADfXe0QMYQxCX6+LqktEjYMNckvvFYgnisr1+9ADYMK8dPYBxBHHZfnf0ANgw544ewDiCuGwv z+Uh+KL/V71q9AjGEcRlu6J66ugRsCF+Oe9BXDRB5El5mTlcUj159AjGEkQurx4zegQM9hPV34we wViCSNWLc+mU5fqV6qWjRzCeIPJFj6leMXoErNkrqseOHsFmEES+6MrqrOoFo4fAmpxbPajpex8E kb/lc9XDqic0vQIVdtHnq3/dFMPLB29hgwgiX+4L1X+t7pxP7WD3/EHT9/bPN32vw5cIIl/NO6t7 V3evfqf67Ng5cNgur15YnVHdp/qzsXPYVH5BMNflDXu3Y6vva/qhcofqlOqE6vhx0+ArfLL6RHVh 9Y7qj6s/zO//5HoQRK6vy6rf27sB7ByXTAEgQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAq QQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgE EQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJE AKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShAB oBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAqg6OHgALdEp1RnV6dVJ1zN7f/0z1l9V7qjdUfzFk HSyUIMJ63KJ6ZPWw6luv579zQfXc6lnVX61oF7DHJVNYra+vfrW6sPoPXf8YVt22+o9NjxR/pfq6 2dcBXyKIsDoPrs6rfry60T6Oc6PqUdX51Vkz7AKuhSDC/G5Q/ffqt6ubzXjcr69eVP1S7rswO3cq mNfB6reqx6zwHI9rem7xwArPAYsjiDCfI6qnV2ev4VwPqZ66hvPAYggizOfR1SPWeL5HNj0/CcxA EGEet6t+fsB5n1SdOuC8sHMEEebxlPb3StLDdVT15AHnhZ0jiLB/967uMfD83zf4/LATBBH276dG D6h+evQA2HaCCPtzi6ZHaKPdpzpx9AjYZoII+/OANuP9gAer+48eAdtMEGF/Num5u3uOHgDbTBBh f+4wesAhvmP0ANhmggj7c8roAYe4zegBsM0EEQ7f0Y157+FXc9TeDTgMggiHb5Ni+EWCCIdJEOHw fWb0gGuxiZtgKwgiHL7PV58aPeIQn6iuGD0CtpUgwv5cMHrAIc4fPQC2mSDC/rx19IBDbNIW2DqC CPvz2tEDDrFJW2DrCCLsz6vajOcRP139/ugRsM0EEfbn8ur5o0dUz23aAhwmQYT9+8XqyoHnv6L6 hYHnh50giLB/76+eOvD8v1xdNPD8sBMEEebx76sPDDjvRdXPDDgv7BxBhHl8qvqh1vvG+M9XZ1eX rvGcsLMEEebzxuqfVV9Yw7murh5ZvWUN54JFEESY1/ObQnXVCs9xZfWI6jdWeA5YHEGE+T27+oHq 4ys49l9X96/+xwqODYsmiLAar6zutPfXubxs75ivmfGYwB5BhNX5QPX9TY/o9vNc35uq+zU96vzg DLuAa3Fw9ABYgFfs3e5SPay6b3Xqdfw751Wvrp5X/Z+VrgMqQYR1ekvXPFL8hup21a2q4/b+3qXV xdW7m54rBNZIEGGMj1Z/NHoEcA3PIQJAgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggA lSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBU dXD0AFiob6y+rbpVdcze3/tM9YHqz6uPDNoFiyWIsD7fVT28um/1rdfxz55fvaZ6XvWWFe8CcskU 1uGB1VurN1U/2XXHsOrU6jHVm/du91/ZOqASRFilk6pXVy+u7ryP49yleln1iuqbZ9gFXAtBhNW4 f/X2psujc7lf9X+rM2c8JrBHEGF+5zQ9KjxhBce+WfXS6hErODYsmiDCvB5WPb06sMJzHKyeVf3I Cs8BiyOIMJ8zque0nvvVEdUzqruu4VywCIII8zi+en51wzWe84Z757zJGs8JO0sQYR4/W91ywHlP qn5mwHlh5wgi7N+tqx8beP5HV7cZeH7YCYII+/f4xn7q04HqcQPPDztBEGF/jq4eMnpE06tbjx49 AraZIML+nFkdN3pE0wtr7jN6BGwzQYT9udfoAYfYpC2wdQQR9mc/n1E6t+8cPQC2mSDC/tx29IBD bNIW2DqCCIfvyOqmo0cc4oTW+8EAsFMEEQ7fMdf9j6zdJm6CrSCIcPg+N3rAtfjs6AGwrQQRDt/f tFlRvDxBhMMmiLA/7x894BDvGz0Atpkgwv786egBh3jn6AGwzQQR9uf1owcc4nWjB8A2E0TYn5dU V44e0bThlaNHwDYTRNifD1d/MHpE9erqQ6NHwDYTRNi/Xxw9oPqF0QNg2wki7N/rqtcOPP9rqv81 8PywEwQR5vGTjXkP4OXVowecF3aOIMI8zqv+1YDz/svqvQPOCztHEGE+T6meucbzPa16xhrPBztN EGFeP149fw3neV7TZVpgJoII87qqenj1pBUd/+qmV5T+8N65gJkIIszvC9VPVWdVH53xuB+u/kn1 +KYwAjMSRFidc6vTqie3v1egXl79t71jvXiGXcC1EERYrY9Xj6lOqZ5Ynf81/Lvvqf7d3r/72OqT s68DvuTg6AGwEH9V/ae928nVGdXp1UnVsXv/zKXVB5pC+IbqL9c/E5ZLEGH9Ltq7ARvEJVMASBAB oBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSA ShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAq QQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKjq4OgB/C03rk4YPQJYmxuP HsA1BHEeV8x0nJ/duwF8LT43esAucMl0HpeOHgAs2mWjB+wCQZzHJaMHAIt28egBu0AQ53HB6AHA or139IBdcMToATvi5tWHRo8AFuub8jNo3zxCnMeHq/NGjwAW6d2J4SwEcT6/O3oAsEjnjh6wK1wy nc/J1fuqA6OHAItxVXXb6sLRQ3aBR4jzuah66egRwKKcmxjOxiPEeZ1a/Vl1w9FDgJ13RfVteYXp bFzem9fHqiOru48eAuy8/5znD2flEeL8Dlavr75n9BBgZ72h+ofVlaOH7BJBXI2bVX/SdAkVYE7n VWc0XZFiRl5Usxofq763etvoIcBOeWd178RwJQRxdT5c3aP6tdFDgJ3wzOq789nJKyOIq3VZdU51 ZvXng7cA2+ld1T+qfqz6zOAtO81ziOtzoPrB6lFNr0L1Cl/gq7mq+qPqKdVL9v43KyaIY9y8um91 1+r21SnVTapjR44Chris+nTTG+zfVb2xenX1kZGjluj/A7eiP6AtHfmzAAAAAElFTkSuQmCC " + id="image2981-0" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-4"><tspan + sodipodi:role="line" + id="tspan2985-8" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">2</tspan></text> + </g> + <g + id="g2992-7" + transform="translate(-36.464671,99.052583)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-1" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-7"><tspan + sodipodi:role="line" + id="tspan2985-2" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">3</tspan></text> + </g> + <g + id="g2992-72" + transform="translate(-117.33947,73.967241)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-2" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-6"><tspan + sodipodi:role="line" + id="tspan2985-1" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">4</tspan></text> + </g> + <g + id="g2992-0" + transform="translate(-118.04937,17.044482)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-6" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-1"><tspan + sodipodi:role="line" + id="tspan2985-5" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">5</tspan></text> + </g> + <g + id="g2992-9" + transform="translate(-94.51307,-9.6130091)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-4" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-9"><tspan + sodipodi:role="line" + id="tspan2985-0" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">6</tspan></text> + </g> + <rect + style="fill:none;stroke:#000040;stroke-width:0.799999;stroke-dasharray:2.4, 0.79999900000000002;stroke-opacity:0.01249951" + id="rect894" + width="171.26611" + height="127.22334" + x="0.13010304" + y="0.24505959" /> + </g> +</svg> diff --git a/doc/talks/2022-06-23-stack/assets/consistent_hashing_2.svg b/doc/talks/2022-06-23-stack/assets/consistent_hashing_2.svg new file mode 100644 index 00000000..5ac8faf6 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/consistent_hashing_2.svg @@ -0,0 +1,334 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="648" + height="480" + viewBox="0 0 171.45 127" + version="1.1" + id="svg2147" + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" + sodipodi:docname="consistent_hashing_2.svg"> + <defs + id="defs2141"> + <marker + style="overflow:visible;" + id="marker3465" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3463" /> + </marker> + <marker + style="overflow:visible;" + id="marker3455" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3453" /> + </marker> + <marker + style="overflow:visible;" + id="marker3445" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3443" /> + </marker> + <marker + style="overflow:visible;" + id="Arrow1Lend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Lend" + inkscape:isstock="true"> + <path + transform="scale(0.8) rotate(180) translate(12.5,0)" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3144" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.7" + inkscape:cx="166.38273" + inkscape:cy="269.80211" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + showgrid="false" + units="px" + inkscape:window-width="1404" + inkscape:window-height="1016" + inkscape:window-x="281" + inkscape:window-y="27" + inkscape:window-maximized="0" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2144"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g2851" + transform="matrix(0.84882735,0,0,0.84882735,-5.4514578,9.7344105)"> + <circle + style="fill:none;stroke:#000000;stroke-width:1" + id="path2710" + cx="89.153343" + cy="63.810429" + r="51.14566" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 118.92887,33.710015 131.54406,21.284558" + id="path2736" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 130.3168,71.515 17.36133,3.481276" + id="path2736-5" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 104.71157,102.41933 6.56219,16.44605" + id="path2736-6" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 51.073764,81.65523 35.108111,89.312443" + id="path2736-9" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 54.233873,39.571826 39.643162,29.539572" + id="path2736-3" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 69.962563,25.77499 61.999798,9.959512" + id="path2736-7" + sodipodi:nodetypes="cc" /> + </g> + <g + id="g2992" + transform="translate(-19.258685)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987"><tspan + sodipodi:role="line" + id="tspan2985" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">1</tspan></text> + </g> + <g + id="g2992-5" + transform="translate(-5.5636458,61.815287)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-0" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-4"><tspan + sodipodi:role="line" + id="tspan2985-8" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">2</tspan></text> + </g> + <g + id="g2992-7" + transform="translate(-36.464671,99.052583)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-1" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-7"><tspan + sodipodi:role="line" + id="tspan2985-2" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">3</tspan></text> + </g> + <g + id="g2992-72" + transform="translate(-117.33947,73.967241)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-2" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-6"><tspan + sodipodi:role="line" + id="tspan2985-1" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">4</tspan></text> + </g> + <g + id="g2992-0" + transform="translate(-118.04937,17.044482)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-6" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-1"><tspan + sodipodi:role="line" + id="tspan2985-5" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">5</tspan></text> + </g> + <g + id="g2992-9" + transform="translate(-94.51307,-9.6130091)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-4" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-9"><tspan + sodipodi:role="line" + id="tspan2985-0" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">6</tspan></text> + </g> + <g + id="g3137" + transform="translate(6.1449507,10.235717)" + style="stroke:#0000ff;stroke-opacity:1"> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 161.12836,39.73877 V 54.647494 L 146.163,54.488794 V 33.300302 h 8.7615 l 5.86826,6.195557 h -5.87342 v -6.047936" + id="path3085" + sodipodi:nodetypes="cccccccc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,37.39937 h 4.21162" + id="path3087" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,40.644703 h 4.21162" + id="path3087-7" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,43.555576 h 9.81264" + id="path3087-1" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,46.646723 h 9.81264" + id="path3087-1-1" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,50.449131 h 9.81264" + id="path3087-1-5" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="fill:none;stroke:#000040;stroke-width:0.799999;stroke-dasharray:2.4, 0.799999;stroke-opacity:0.0124995" + id="rect894" + width="171.26611" + height="127.22335" + x="0.13010304" + y="0.24505216" /> + </g> +</svg> diff --git a/doc/talks/2022-06-23-stack/assets/consistent_hashing_3.svg b/doc/talks/2022-06-23-stack/assets/consistent_hashing_3.svg new file mode 100644 index 00000000..fdfd3efc --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/consistent_hashing_3.svg @@ -0,0 +1,358 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="648" + height="480" + viewBox="0 0 171.45 127" + version="1.1" + id="svg2147" + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" + sodipodi:docname="consistent_hashing_3.svg"> + <defs + id="defs2141"> + <marker + style="overflow:visible;" + id="marker3465" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3463" /> + </marker> + <marker + style="overflow:visible;" + id="marker3455" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3453" /> + </marker> + <marker + style="overflow:visible;" + id="marker3445" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3443" /> + </marker> + <marker + style="overflow:visible;" + id="Arrow1Mend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true" + inkscape:collect="always"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3150" /> + </marker> + <marker + style="overflow:visible;" + id="Arrow1Lend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Lend" + inkscape:isstock="true"> + <path + transform="scale(0.8) rotate(180) translate(12.5,0)" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3144" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.7" + inkscape:cx="166.38273" + inkscape:cy="269.80211" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + showgrid="false" + units="px" + inkscape:window-width="1404" + inkscape:window-height="1016" + inkscape:window-x="281" + inkscape:window-y="27" + inkscape:window-maximized="0" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2144"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g2851" + transform="matrix(0.84882735,0,0,0.84882735,-5.4514578,9.7344105)"> + <circle + style="fill:none;stroke:#000000;stroke-width:1" + id="path2710" + cx="89.153343" + cy="63.810429" + r="51.14566" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 118.92887,33.710015 131.54406,21.284558" + id="path2736" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 130.3168,71.515 17.36133,3.481276" + id="path2736-5" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 104.71157,102.41933 6.56219,16.44605" + id="path2736-6" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 51.073764,81.65523 35.108111,89.312443" + id="path2736-9" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 54.233873,39.571826 39.643162,29.539572" + id="path2736-3" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 69.962563,25.77499 61.999798,9.959512" + id="path2736-7" + sodipodi:nodetypes="cc" /> + </g> + <g + id="g2992" + transform="translate(-19.258685)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987"><tspan + sodipodi:role="line" + id="tspan2985" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">1</tspan></text> + </g> + <g + id="g2992-5" + transform="translate(-5.5636458,61.815287)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-0" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-4"><tspan + sodipodi:role="line" + id="tspan2985-8" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">2</tspan></text> + </g> + <g + id="g2992-7" + transform="translate(-36.464671,99.052583)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-1" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-7"><tspan + sodipodi:role="line" + id="tspan2985-2" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">3</tspan></text> + </g> + <g + id="g2992-72" + transform="translate(-117.33947,73.967241)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-2" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-6"><tspan + sodipodi:role="line" + id="tspan2985-1" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">4</tspan></text> + </g> + <g + id="g2992-0" + transform="translate(-118.04937,17.044482)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-6" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-1"><tspan + sodipodi:role="line" + id="tspan2985-5" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">5</tspan></text> + </g> + <g + id="g2992-9" + transform="translate(-94.51307,-9.6130091)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-4" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-9"><tspan + sodipodi:role="line" + id="tspan2985-0" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">6</tspan></text> + </g> + <path + style="fill:none;stroke:#0000ff;stroke-width:0.848828;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 104.64946,54.902986 119.22518,51.23513" + id="path2736-1" + sodipodi:nodetypes="cc" /> + <g + id="g3137" + transform="translate(6.1449507,10.235717)" + style="stroke:#0000ff;stroke-opacity:1"> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 161.12836,39.73877 V 54.647494 L 146.163,54.488794 V 33.300302 h 8.7615 l 5.86826,6.195557 h -5.87342 v -6.047936" + id="path3085" + sodipodi:nodetypes="cccccccc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,37.39937 h 4.21162" + id="path3087" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,40.644703 h 4.21162" + id="path3087-7" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,43.555576 h 9.81264" + id="path3087-1" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,46.646723 h 9.81264" + id="path3087-1-1" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,50.449131 h 9.81264" + id="path3087-1-5" + sodipodi:nodetypes="cc" /> + </g> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#Arrow1Mend)" + d="m 147.85661,51.721464 c -6.70172,-3.912196 -23.39473,-1.178632 -23.39473,-1.178632" + id="path3139" /> + <rect + style="fill:none;stroke:#000040;stroke-width:0.799999;stroke-dasharray:2.4, 0.799999;stroke-opacity:0.0124995" + id="rect894" + width="171.26611" + height="127.22335" + x="0.13010304" + y="0.24505216" /> + </g> +</svg> diff --git a/doc/talks/2022-06-23-stack/assets/consistent_hashing_4.svg b/doc/talks/2022-06-23-stack/assets/consistent_hashing_4.svg new file mode 100644 index 00000000..95ed0e02 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/consistent_hashing_4.svg @@ -0,0 +1,377 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="648" + height="480" + viewBox="0 0 171.45 127" + version="1.1" + id="svg2147" + inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)" + sodipodi:docname="consistent_hashing_4.svg"> + <defs + id="defs2141"> + <marker + style="overflow:visible;" + id="marker3465" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3463" /> + </marker> + <marker + style="overflow:visible;" + id="marker3455" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3453" /> + </marker> + <marker + style="overflow:visible;" + id="marker3445" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3443" /> + </marker> + <marker + style="overflow:visible;" + id="Arrow1Mend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true" + inkscape:collect="always"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3150" /> + </marker> + <marker + style="overflow:visible;" + id="Arrow1Lend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Lend" + inkscape:isstock="true"> + <path + transform="scale(0.8) rotate(180) translate(12.5,0)" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path3144" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.7" + inkscape:cx="166.38273" + inkscape:cy="269.80211" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + showgrid="false" + units="px" + inkscape:window-width="1404" + inkscape:window-height="1016" + inkscape:window-x="281" + inkscape:window-y="27" + inkscape:window-maximized="0" + showguides="true" + inkscape:guide-bbox="true" /> + <metadata + id="metadata2144"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g2851" + transform="matrix(0.84882735,0,0,0.84882735,-5.4514578,9.7344105)"> + <circle + style="fill:none;stroke:#000000;stroke-width:1" + id="path2710" + cx="89.153343" + cy="63.810429" + r="51.14566" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 118.92887,33.710015 131.54406,21.284558" + id="path2736" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 130.3168,71.515 17.36133,3.481276" + id="path2736-5" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 104.71157,102.41933 6.56219,16.44605" + id="path2736-6" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 51.073764,81.65523 35.108111,89.312443" + id="path2736-9" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 54.233873,39.571826 39.643162,29.539572" + id="path2736-3" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 69.962563,25.77499 61.999798,9.959512" + id="path2736-7" + sodipodi:nodetypes="cc" /> + </g> + <g + id="g2992" + transform="translate(-19.258685)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987"><tspan + sodipodi:role="line" + id="tspan2985" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">1</tspan></text> + </g> + <g + id="g2992-5" + transform="translate(-5.5636458,61.815287)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-0" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-4"><tspan + sodipodi:role="line" + id="tspan2985-8" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">2</tspan></text> + </g> + <g + id="g2992-7" + transform="translate(-36.464671,99.052583)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image2981-1" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-7"><tspan + sodipodi:role="line" + id="tspan2985-2" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">3</tspan></text> + </g> + <g + id="g2992-72" + transform="translate(-117.33947,73.967241)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcQAAAHECAYAAACnX1ofAAAABmJLR0QA/wD/AP+gvaeTAAAULklE QVR4nO3de7B1d0Hf4U94X0LIBRJRiQyYGJAkiAItYtGAUBAaBLFNIXJxqjQ4VYQpWOnQC1Pb2qk3 phWRq1AVBBEj94sKxaLlVgoiSBIgRkIqF7kmGCAJ6R/rQF4hmPCetfdv772eZ2ZPhkyy1veQs8/n 3WtfzhGxy46pzqjuWN2hum31ddXx1QkDd8Gmu7r6ZHVZ9Ynqgur86h3V66u/HjeNVTli9ABmd5Pq wdUDq3tXR42dAzvnC01hfEn17OqDY+cwF0HcHbepHl39SFMUgdW7qnp19V+q/z14CyzeidXTqyua LvO4ubmNub2kul1srQOjB3DYDlSPr15U3bW6wdg5sHinVudUn63e1BRJtohLptvp1OrXq+8aPQS4 Vv+zelD1sdFDuP4Ecfs8tHpmdfToIcDf6bzqzOqi0UO4flxm2x4Hql+qnpcYwjY4remFNrcZPYTr xyPE7XBkUwj/6eghwNfswuq7qw+PHsLfzSPEzXdM9YrEELbVKdXv5D3BG08QN9uRTTG89+ghwL7c rXpBfuZuNG+72Gy/Wp01egQwi9Oa/pD72tFDuHaCuLl+onri6BHArO5Wvb965+ghfCUvqtlM31O9 rulPk8Bu+Wx1z6Y377NBBHHzfHP11uobRw8BVuZD1V2qi0cP4Rqe4N0sRzV9FJsYwm47semzT48Z PYRrCOJmeWr1naNHAGtxp+o3cqVuY3hRzeb46b0bsByn7/319UNXUPmTyaa4T/XKVvsHlPdWz6ne 0vSJGR9p+pVRwFc6qukN9d9f/WjTJc5Vubo6u+nN+7Bot2n6RPxV/Y62zzX9miiXx+Hw3LDpF29f 0urup5+p/v66viDYRMdV72p1d7IPVHdc21cDu+3Y6oWt7v56cfVNa/tqYIMcUZ3b6u5cF1Qnre2r gWW4QdMrwVd1v31TPvOUBXpiq7tTvaa6+fq+FFiUY5s+aWZV99/n5vUdLMgDq6tazZ3pT6obre9L gUU6ufpoq4viE9b3pcA4p1WfbDV3okuqW6zvS4FFu1fTK7VXcV++qukPzrCzTmh6bm8Vd6DL86Z+ WLfHtrpHiZdW37G+LwXW5wZNv9twVXeeh67vSwEO8axWd7++KB/luDaeuF2fn2t6P+AqXNn0Fgtg /Y6sbrnC4/9x0+XZz6/wHCSI63JW06dQ+P8bOBy/3vThAKyQzzJdvTtWL8/vNgQO3x2bnlN84+gh u8wjltW6WdPvNvyW0UOArfeFpleevnz0kF0liKtzfPX7eeUnMJ9PNf1Mee/oIbvIBz6vxo2a/hQn hsCcbtr0eoRjRw/ZRZ5DXI1n5E21wGqc2HR177Wjh+wal0zn96PVs0ePAHbaldUZ1ZtHD9klgjiv 45o+iWaVv0wUoOr9Ta8+vWz0kF3hOcR5/dvEEFiPW1f/YvSIXeIR4nxObPoT29GjhwCL8cHqlKYP GWefPEKcz6MSQ2C9bln90OgRu0IQ5+MtFsAIZ48esCtcMp3HkdXHq2NGDwEW59KmT8Vy2XSfPEKc x10SQ2CM45p+BrFPgjiP240eACzat48esAsEcR43Hz0AWDS/RHgGgjgPQQRGEsQZHBw9YEfcdKbj PKn6rZmOBWy+h1aPm+E4c/0MWjRB3CwfrN42egSwNncfPYBruGQKAAkiAFSCCACVIAJAJYgAUAki AFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgA UAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFR1cPQAtsrB 6g57t1Oqb6huXB01chTs+Wx1efXR6sLqHdU7qytHjmJ7CCLX5UD1A9VDqjOrY8fOga/JZdUrq+dX L6uuGjuHTeaSKV/Ngeqc6v3VudWDEkO2z7HVg6vfq95XPSI/9/gqfGNwbU6v3lI9szpp8BaYy8nV r1Vvrk4bvIUNJIh8uQdVb6v+3ughsCJ3bvoeP2v0EDaLIHKoc6oXNL1QBnbZ0dULm77noRJErvHA 6mn5nmA5btD0Pf+A0UPYDH74UXWr6jlNL6SBJTlQ/WaeKydBZPKc6oTRI2CQm1bPGD2C8QSRB1T3 Gj0CBrtPdb/RIxhLEPk3owfAhnjC6AGMJYjLdqfqH4weARvijKaPJWShBHHZzh49ADbMg0cPYBxB XLYzRw+ADeN5xAUTxOW6SXX70SNgw3x7ddzoEYwhiMt1ev77w5c7UJ06egRj+IG4XCePHgAb6ltG D2AMQVyu40cPgA3lvrFQfkHwcs31W+7fXv32TMeC/Ti76a1E+3X0DMdgCwnics11deDd1c/NdCzY j9s3TxBdOVso/+EBIEEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEA KkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACo BBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaCq g6MHMMzVMx3npOpBMx0L9uOkmY4z132DLSOIy3XpTMe5294NdsWnRw9gDJdMl+tTowfAhvrk6AGM IYjL9RejB8CGunD0AMYQxOV6V3XF6BGwYa6o3jN6BGMI4nJ9rvrT0SNgw7y96b7BAgnisr1k9ADY MC8ePYBxBHHZXpSXmMMXXV2dO3oE4wjisp1XvWb0CNgQr6rOHz2CcQSRp40eABvi6aMHMJYgco/R A2BDfO/oAYwliMt2fPXPR4+ADXFOddPRIxhHEJftH1fHjR4BG+Im1QNHj2AcQVy2Hxw9ADaM+8SC CeJyHZHnD+HL3bPpvsECCeJy3brpEhFwjeOrk0ePYAxBXK7TRg+ADXX66AGMIYjLdbPRA2BDuW8s lF8QvFxzXS79UNNvzoDRbl+dOMNxvPVioQRxueb6b/+H1Q/PdCzYj9+sHj7DcW44wzHYQi6ZAkCC CACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAki AFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgA UAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAVQdHD2DrHVmdMHoENH0v zuHqmY7DlhHE5fr8TMd58N4NdsVc9w22jEumy3XZ6AGwoS4dPYAxBHG5Pjh6AGyoi0cPYAxBXK4L Rg+ADfXe0QMYQxCX6+LqktEjYMNckvvFYgnisr1+9ADYMK8dPYBxBHHZfnf0ANgw544ewDiCuGwv z+Uh+KL/V71q9AjGEcRlu6J66ugRsCF+Oe9BXDRB5El5mTlcUj159AjGEkQurx4zegQM9hPV34we wViCSNWLc+mU5fqV6qWjRzCeIPJFj6leMXoErNkrqseOHsFmEES+6MrqrOoFo4fAmpxbPajpex8E kb/lc9XDqic0vQIVdtHnq3/dFMPLB29hgwgiX+4L1X+t7pxP7WD3/EHT9/bPN32vw5cIIl/NO6t7 V3evfqf67Ng5cNgur15YnVHdp/qzsXPYVH5BMNflDXu3Y6vva/qhcofqlOqE6vhx0+ArfLL6RHVh 9Y7qj6s/zO//5HoQRK6vy6rf27sB7ByXTAEgQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAq QQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgE EQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJE AKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShAB oBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAqg6OHgALdEp1RnV6dVJ1zN7f/0z1l9V7qjdUfzFk HSyUIMJ63KJ6ZPWw6luv579zQfXc6lnVX61oF7DHJVNYra+vfrW6sPoPXf8YVt22+o9NjxR/pfq6 2dcBXyKIsDoPrs6rfry60T6Oc6PqUdX51Vkz7AKuhSDC/G5Q/ffqt6ubzXjcr69eVP1S7rswO3cq mNfB6reqx6zwHI9rem7xwArPAYsjiDCfI6qnV2ev4VwPqZ66hvPAYggizOfR1SPWeL5HNj0/CcxA EGEet6t+fsB5n1SdOuC8sHMEEebxlPb3StLDdVT15AHnhZ0jiLB/967uMfD83zf4/LATBBH276dG D6h+evQA2HaCCPtzi6ZHaKPdpzpx9AjYZoII+/OANuP9gAer+48eAdtMEGF/Num5u3uOHgDbTBBh f+4wesAhvmP0ANhmggj7c8roAYe4zegBsM0EEQ7f0Y157+FXc9TeDTgMggiHb5Ni+EWCCIdJEOHw fWb0gGuxiZtgKwgiHL7PV58aPeIQn6iuGD0CtpUgwv5cMHrAIc4fPQC2mSDC/rx19IBDbNIW2DqC CPvz2tEDDrFJW2DrCCLsz6vajOcRP139/ugRsM0EEfbn8ur5o0dUz23aAhwmQYT9+8XqyoHnv6L6 hYHnh50giLB/76+eOvD8v1xdNPD8sBMEEebx76sPDDjvRdXPDDgv7BxBhHl8qvqh1vvG+M9XZ1eX rvGcsLMEEebzxuqfVV9Yw7murh5ZvWUN54JFEESY1/ObQnXVCs9xZfWI6jdWeA5YHEGE+T27+oHq 4ys49l9X96/+xwqODYsmiLAar6zutPfXubxs75ivmfGYwB5BhNX5QPX9TY/o9vNc35uq+zU96vzg DLuAa3Fw9ABYgFfs3e5SPay6b3Xqdfw751Wvrp5X/Z+VrgMqQYR1ekvXPFL8hup21a2q4/b+3qXV xdW7m54rBNZIEGGMj1Z/NHoEcA3PIQJAgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggA lSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBU dXD0AFiob6y+rbpVdcze3/tM9YHqz6uPDNoFiyWIsD7fVT28um/1rdfxz55fvaZ6XvWWFe8CcskU 1uGB1VurN1U/2XXHsOrU6jHVm/du91/ZOqASRFilk6pXVy+u7ryP49yleln1iuqbZ9gFXAtBhNW4 f/X2psujc7lf9X+rM2c8JrBHEGF+5zQ9KjxhBce+WfXS6hErODYsmiDCvB5WPb06sMJzHKyeVf3I Cs8BiyOIMJ8zque0nvvVEdUzqruu4VywCIII8zi+en51wzWe84Z757zJGs8JO0sQYR4/W91ywHlP qn5mwHlh5wgi7N+tqx8beP5HV7cZeH7YCYII+/f4xn7q04HqcQPPDztBEGF/jq4eMnpE06tbjx49 AraZIML+nFkdN3pE0wtr7jN6BGwzQYT9udfoAYfYpC2wdQQR9mc/n1E6t+8cPQC2mSDC/tx29IBD bNIW2DqCCIfvyOqmo0cc4oTW+8EAsFMEEQ7fMdf9j6zdJm6CrSCIcPg+N3rAtfjs6AGwrQQRDt/f tFlRvDxBhMMmiLA/7x894BDvGz0Atpkgwv786egBh3jn6AGwzQQR9uf1owcc4nWjB8A2E0TYn5dU V44e0bThlaNHwDYTRNifD1d/MHpE9erqQ6NHwDYTRNi/Xxw9oPqF0QNg2wki7N/rqtcOPP9rqv81 8PywEwQR5vGTjXkP4OXVowecF3aOIMI8zqv+1YDz/svqvQPOCztHEGE+T6meucbzPa16xhrPBztN EGFeP149fw3neV7TZVpgJoII87qqenj1pBUd/+qmV5T+8N65gJkIIszvC9VPVWdVH53xuB+u/kn1 +KYwAjMSRFidc6vTqie3v1egXl79t71jvXiGXcC1EERYrY9Xj6lOqZ5Ynf81/Lvvqf7d3r/72OqT s68DvuTg6AGwEH9V/ae928nVGdXp1UnVsXv/zKXVB5pC+IbqL9c/E5ZLEGH9Ltq7ARvEJVMASBAB oBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSA ShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAq QQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKjq4OgB/C03rk4YPQJYmxuP HsA1BHEeV8x0nJ/duwF8LT43esAucMl0HpeOHgAs2mWjB+wCQZzHJaMHAIt28egBu0AQ53HB6AHA or139IBdcMToATvi5tWHRo8AFuub8jNo3zxCnMeHq/NGjwAW6d2J4SwEcT6/O3oAsEjnjh6wK1wy nc/J1fuqA6OHAItxVXXb6sLRQ3aBR4jzuah66egRwKKcmxjOxiPEeZ1a/Vl1w9FDgJ13RfVteYXp bFzem9fHqiOru48eAuy8/5znD2flEeL8Dlavr75n9BBgZ72h+ofVlaOH7BJBXI2bVX/SdAkVYE7n VWc0XZFiRl5Usxofq763etvoIcBOeWd178RwJQRxdT5c3aP6tdFDgJ3wzOq789nJKyOIq3VZdU51 ZvXng7cA2+ld1T+qfqz6zOAtO81ziOtzoPrB6lFNr0L1Cl/gq7mq+qPqKdVL9v43KyaIY9y8um91 1+r21SnVTapjR44Chris+nTTG+zfVb2xenX1kZGjluj/A7eiP6AtHfmzAAAAAElFTkSuQmCC " + id="image2981-2" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-6"><tspan + sodipodi:role="line" + id="tspan2985-1" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">4</tspan></text> + </g> + <g + id="g2992-0" + transform="translate(-118.04937,17.044482)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcQAAAHECAYAAACnX1ofAAAABmJLR0QA/wD/AP+gvaeTAAAULklE QVR4nO3de7B1d0Hf4U94X0LIBRJRiQyYGJAkiAItYtGAUBAaBLFNIXJxqjQ4VYQpWOnQC1Pb2qk3 phWRq1AVBBEj94sKxaLlVgoiSBIgRkIqF7kmGCAJ6R/rQF4hmPCetfdv772eZ2ZPhkyy1veQs8/n 3WtfzhGxy46pzqjuWN2hum31ddXx1QkDd8Gmu7r6ZHVZ9Ynqgur86h3V66u/HjeNVTli9ABmd5Pq wdUDq3tXR42dAzvnC01hfEn17OqDY+cwF0HcHbepHl39SFMUgdW7qnp19V+q/z14CyzeidXTqyua LvO4ubmNub2kul1srQOjB3DYDlSPr15U3bW6wdg5sHinVudUn63e1BRJtohLptvp1OrXq+8aPQS4 Vv+zelD1sdFDuP4Ecfs8tHpmdfToIcDf6bzqzOqi0UO4flxm2x4Hql+qnpcYwjY4remFNrcZPYTr xyPE7XBkUwj/6eghwNfswuq7qw+PHsLfzSPEzXdM9YrEELbVKdXv5D3BG08QN9uRTTG89+ghwL7c rXpBfuZuNG+72Gy/Wp01egQwi9Oa/pD72tFDuHaCuLl+onri6BHArO5Wvb965+ghfCUvqtlM31O9 rulPk8Bu+Wx1z6Y377NBBHHzfHP11uobRw8BVuZD1V2qi0cP4Rqe4N0sRzV9FJsYwm47semzT48Z PYRrCOJmeWr1naNHAGtxp+o3cqVuY3hRzeb46b0bsByn7/319UNXUPmTyaa4T/XKVvsHlPdWz6ne 0vSJGR9p+pVRwFc6qukN9d9f/WjTJc5Vubo6u+nN+7Bot2n6RPxV/Y62zzX9miiXx+Hw3LDpF29f 0urup5+p/v66viDYRMdV72p1d7IPVHdc21cDu+3Y6oWt7v56cfVNa/tqYIMcUZ3b6u5cF1Qnre2r gWW4QdMrwVd1v31TPvOUBXpiq7tTvaa6+fq+FFiUY5s+aWZV99/n5vUdLMgDq6tazZ3pT6obre9L gUU6ufpoq4viE9b3pcA4p1WfbDV3okuqW6zvS4FFu1fTK7VXcV++qukPzrCzTmh6bm8Vd6DL86Z+ WLfHtrpHiZdW37G+LwXW5wZNv9twVXeeh67vSwEO8axWd7++KB/luDaeuF2fn2t6P+AqXNn0Fgtg /Y6sbrnC4/9x0+XZz6/wHCSI63JW06dQ+P8bOBy/3vThAKyQzzJdvTtWL8/vNgQO3x2bnlN84+gh u8wjltW6WdPvNvyW0UOArfeFpleevnz0kF0liKtzfPX7eeUnMJ9PNf1Mee/oIbvIBz6vxo2a/hQn hsCcbtr0eoRjRw/ZRZ5DXI1n5E21wGqc2HR177Wjh+wal0zn96PVs0ePAHbaldUZ1ZtHD9klgjiv 45o+iWaVv0wUoOr9Ta8+vWz0kF3hOcR5/dvEEFiPW1f/YvSIXeIR4nxObPoT29GjhwCL8cHqlKYP GWefPEKcz6MSQ2C9bln90OgRu0IQ5+MtFsAIZ48esCtcMp3HkdXHq2NGDwEW59KmT8Vy2XSfPEKc x10SQ2CM45p+BrFPgjiP240eACzat48esAsEcR43Hz0AWDS/RHgGgjgPQQRGEsQZHBw9YEfcdKbj PKn6rZmOBWy+h1aPm+E4c/0MWjRB3CwfrN42egSwNncfPYBruGQKAAkiAFSCCACVIAJAJYgAUAki AFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgA UAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFR1cPQAtsrB 6g57t1Oqb6huXB01chTs+Wx1efXR6sLqHdU7qytHjmJ7CCLX5UD1A9VDqjOrY8fOga/JZdUrq+dX L6uuGjuHTeaSKV/Ngeqc6v3VudWDEkO2z7HVg6vfq95XPSI/9/gqfGNwbU6v3lI9szpp8BaYy8nV r1Vvrk4bvIUNJIh8uQdVb6v+3ughsCJ3bvoeP2v0EDaLIHKoc6oXNL1QBnbZ0dULm77noRJErvHA 6mn5nmA5btD0Pf+A0UPYDH74UXWr6jlNL6SBJTlQ/WaeKydBZPKc6oTRI2CQm1bPGD2C8QSRB1T3 Gj0CBrtPdb/RIxhLEPk3owfAhnjC6AGMJYjLdqfqH4weARvijKaPJWShBHHZzh49ADbMg0cPYBxB XLYzRw+ADeN5xAUTxOW6SXX70SNgw3x7ddzoEYwhiMt1ev77w5c7UJ06egRj+IG4XCePHgAb6ltG D2AMQVyu40cPgA3lvrFQfkHwcs31W+7fXv32TMeC/Ti76a1E+3X0DMdgCwnics11deDd1c/NdCzY j9s3TxBdOVso/+EBIEEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEA KkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACo BBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaCq g6MHMMzVMx3npOpBMx0L9uOkmY4z132DLSOIy3XpTMe5294NdsWnRw9gDJdMl+tTowfAhvrk6AGM IYjL9RejB8CGunD0AMYQxOV6V3XF6BGwYa6o3jN6BGMI4nJ9rvrT0SNgw7y96b7BAgnisr1k9ADY MC8ePYBxBHHZXpSXmMMXXV2dO3oE4wjisp1XvWb0CNgQr6rOHz2CcQSRp40eABvi6aMHMJYgco/R A2BDfO/oAYwliMt2fPXPR4+ADXFOddPRIxhHEJftH1fHjR4BG+Im1QNHj2AcQVy2Hxw9ADaM+8SC CeJyHZHnD+HL3bPpvsECCeJy3brpEhFwjeOrk0ePYAxBXK7TRg+ADXX66AGMIYjLdbPRA2BDuW8s lF8QvFxzXS79UNNvzoDRbl+dOMNxvPVioQRxueb6b/+H1Q/PdCzYj9+sHj7DcW44wzHYQi6ZAkCC CACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAki AFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgA UAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAVQdHD2DrHVmdMHoENH0v zuHqmY7DlhHE5fr8TMd58N4NdsVc9w22jEumy3XZ6AGwoS4dPYAxBHG5Pjh6AGyoi0cPYAxBXK4L Rg+ADfXe0QMYQxCX6+LqktEjYMNckvvFYgnisr1+9ADYMK8dPYBxBHHZfnf0ANgw544ewDiCuGwv z+Uh+KL/V71q9AjGEcRlu6J66ugRsCF+Oe9BXDRB5El5mTlcUj159AjGEkQurx4zegQM9hPV34we wViCSNWLc+mU5fqV6qWjRzCeIPJFj6leMXoErNkrqseOHsFmEES+6MrqrOoFo4fAmpxbPajpex8E kb/lc9XDqic0vQIVdtHnq3/dFMPLB29hgwgiX+4L1X+t7pxP7WD3/EHT9/bPN32vw5cIIl/NO6t7 V3evfqf67Ng5cNgur15YnVHdp/qzsXPYVH5BMNflDXu3Y6vva/qhcofqlOqE6vhx0+ArfLL6RHVh 9Y7qj6s/zO//5HoQRK6vy6rf27sB7ByXTAEgQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAq QQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgE EQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJE AKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShAB oBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAqg6OHgALdEp1RnV6dVJ1zN7f/0z1l9V7qjdUfzFk HSyUIMJ63KJ6ZPWw6luv579zQfXc6lnVX61oF7DHJVNYra+vfrW6sPoPXf8YVt22+o9NjxR/pfq6 2dcBXyKIsDoPrs6rfry60T6Oc6PqUdX51Vkz7AKuhSDC/G5Q/ffqt6ubzXjcr69eVP1S7rswO3cq mNfB6reqx6zwHI9rem7xwArPAYsjiDCfI6qnV2ev4VwPqZ66hvPAYggizOfR1SPWeL5HNj0/CcxA EGEet6t+fsB5n1SdOuC8sHMEEebxlPb3StLDdVT15AHnhZ0jiLB/967uMfD83zf4/LATBBH276dG D6h+evQA2HaCCPtzi6ZHaKPdpzpx9AjYZoII+/OANuP9gAer+48eAdtMEGF/Num5u3uOHgDbTBBh f+4wesAhvmP0ANhmggj7c8roAYe4zegBsM0EEQ7f0Y157+FXc9TeDTgMggiHb5Ni+EWCCIdJEOHw fWb0gGuxiZtgKwgiHL7PV58aPeIQn6iuGD0CtpUgwv5cMHrAIc4fPQC2mSDC/rx19IBDbNIW2DqC CPvz2tEDDrFJW2DrCCLsz6vajOcRP139/ugRsM0EEfbn8ur5o0dUz23aAhwmQYT9+8XqyoHnv6L6 hYHnh50giLB/76+eOvD8v1xdNPD8sBMEEebx76sPDDjvRdXPDDgv7BxBhHl8qvqh1vvG+M9XZ1eX rvGcsLMEEebzxuqfVV9Yw7murh5ZvWUN54JFEESY1/ObQnXVCs9xZfWI6jdWeA5YHEGE+T27+oHq 4ys49l9X96/+xwqODYsmiLAar6zutPfXubxs75ivmfGYwB5BhNX5QPX9TY/o9vNc35uq+zU96vzg DLuAa3Fw9ABYgFfs3e5SPay6b3Xqdfw751Wvrp5X/Z+VrgMqQYR1ekvXPFL8hup21a2q4/b+3qXV xdW7m54rBNZIEGGMj1Z/NHoEcA3PIQJAgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggA lSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBU dXD0AFiob6y+rbpVdcze3/tM9YHqz6uPDNoFiyWIsD7fVT28um/1rdfxz55fvaZ6XvWWFe8CcskU 1uGB1VurN1U/2XXHsOrU6jHVm/du91/ZOqASRFilk6pXVy+u7ryP49yleln1iuqbZ9gFXAtBhNW4 f/X2psujc7lf9X+rM2c8JrBHEGF+5zQ9KjxhBce+WfXS6hErODYsmiDCvB5WPb06sMJzHKyeVf3I Cs8BiyOIMJ8zque0nvvVEdUzqruu4VywCIII8zi+en51wzWe84Z757zJGs8JO0sQYR4/W91ywHlP qn5mwHlh5wgi7N+tqx8beP5HV7cZeH7YCYII+/f4xn7q04HqcQPPDztBEGF/jq4eMnpE06tbjx49 AraZIML+nFkdN3pE0wtr7jN6BGwzQYT9udfoAYfYpC2wdQQR9mc/n1E6t+8cPQC2mSDC/tx29IBD bNIW2DqCCIfvyOqmo0cc4oTW+8EAsFMEEQ7fMdf9j6zdJm6CrSCIcPg+N3rAtfjs6AGwrQQRDt/f tFlRvDxBhMMmiLA/7x894BDvGz0Atpkgwv786egBh3jn6AGwzQQR9uf1owcc4nWjB8A2E0TYn5dU V44e0bThlaNHwDYTRNifD1d/MHpE9erqQ6NHwDYTRNi/Xxw9oPqF0QNg2wki7N/rqtcOPP9rqv81 8PywEwQR5vGTjXkP4OXVowecF3aOIMI8zqv+1YDz/svqvQPOCztHEGE+T6meucbzPa16xhrPBztN EGFeP149fw3neV7TZVpgJoII87qqenj1pBUd/+qmV5T+8N65gJkIIszvC9VPVWdVH53xuB+u/kn1 +KYwAjMSRFidc6vTqie3v1egXl79t71jvXiGXcC1EERYrY9Xj6lOqZ5Ynf81/Lvvqf7d3r/72OqT s68DvuTg6AGwEH9V/ae928nVGdXp1UnVsXv/zKXVB5pC+IbqL9c/E5ZLEGH9Ltq7ARvEJVMASBAB oBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSA ShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAq QQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKjq4OgB/C03rk4YPQJYmxuP HsA1BHEeV8x0nJ/duwF8LT43esAucMl0HpeOHgAs2mWjB+wCQZzHJaMHAIt28egBu0AQ53HB6AHA or139IBdcMToATvi5tWHRo8AFuub8jNo3zxCnMeHq/NGjwAW6d2J4SwEcT6/O3oAsEjnjh6wK1wy nc/J1fuqA6OHAItxVXXb6sLRQ3aBR4jzuah66egRwKKcmxjOxiPEeZ1a/Vl1w9FDgJ13RfVteYXp bFzem9fHqiOru48eAuy8/5znD2flEeL8Dlavr75n9BBgZ72h+ofVlaOH7BJBXI2bVX/SdAkVYE7n VWc0XZFiRl5Usxofq763etvoIcBOeWd178RwJQRxdT5c3aP6tdFDgJ3wzOq789nJKyOIq3VZdU51 ZvXng7cA2+ld1T+qfqz6zOAtO81ziOtzoPrB6lFNr0L1Cl/gq7mq+qPqKdVL9v43KyaIY9y8um91 1+r21SnVTapjR44Chris+nTTG+zfVb2xenX1kZGjluj/A7eiP6AtHfmzAAAAAElFTkSuQmCC " + id="image2981-6" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-1"><tspan + sodipodi:role="line" + id="tspan2985-5" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">5</tspan></text> + </g> + <g + id="g2992-9" + transform="translate(-94.51307,-9.6130091)"> + <image + width="16.223312" + height="16.223312" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcQAAAHECAYAAACnX1ofAAAABmJLR0QA/wD/AP+gvaeTAAAULklE QVR4nO3de7B1d0Hf4U94X0LIBRJRiQyYGJAkiAItYtGAUBAaBLFNIXJxqjQ4VYQpWOnQC1Pb2qk3 phWRq1AVBBEj94sKxaLlVgoiSBIgRkIqF7kmGCAJ6R/rQF4hmPCetfdv772eZ2ZPhkyy1veQs8/n 3WtfzhGxy46pzqjuWN2hum31ddXx1QkDd8Gmu7r6ZHVZ9Ynqgur86h3V66u/HjeNVTli9ABmd5Pq wdUDq3tXR42dAzvnC01hfEn17OqDY+cwF0HcHbepHl39SFMUgdW7qnp19V+q/z14CyzeidXTqyua LvO4ubmNub2kul1srQOjB3DYDlSPr15U3bW6wdg5sHinVudUn63e1BRJtohLptvp1OrXq+8aPQS4 Vv+zelD1sdFDuP4Ecfs8tHpmdfToIcDf6bzqzOqi0UO4flxm2x4Hql+qnpcYwjY4remFNrcZPYTr xyPE7XBkUwj/6eghwNfswuq7qw+PHsLfzSPEzXdM9YrEELbVKdXv5D3BG08QN9uRTTG89+ghwL7c rXpBfuZuNG+72Gy/Wp01egQwi9Oa/pD72tFDuHaCuLl+onri6BHArO5Wvb965+ghfCUvqtlM31O9 rulPk8Bu+Wx1z6Y377NBBHHzfHP11uobRw8BVuZD1V2qi0cP4Rqe4N0sRzV9FJsYwm47semzT48Z PYRrCOJmeWr1naNHAGtxp+o3cqVuY3hRzeb46b0bsByn7/319UNXUPmTyaa4T/XKVvsHlPdWz6ne 0vSJGR9p+pVRwFc6qukN9d9f/WjTJc5Vubo6u+nN+7Bot2n6RPxV/Y62zzX9miiXx+Hw3LDpF29f 0urup5+p/v66viDYRMdV72p1d7IPVHdc21cDu+3Y6oWt7v56cfVNa/tqYIMcUZ3b6u5cF1Qnre2r gWW4QdMrwVd1v31TPvOUBXpiq7tTvaa6+fq+FFiUY5s+aWZV99/n5vUdLMgDq6tazZ3pT6obre9L gUU6ufpoq4viE9b3pcA4p1WfbDV3okuqW6zvS4FFu1fTK7VXcV++qukPzrCzTmh6bm8Vd6DL86Z+ WLfHtrpHiZdW37G+LwXW5wZNv9twVXeeh67vSwEO8axWd7++KB/luDaeuF2fn2t6P+AqXNn0Fgtg /Y6sbrnC4/9x0+XZz6/wHCSI63JW06dQ+P8bOBy/3vThAKyQzzJdvTtWL8/vNgQO3x2bnlN84+gh u8wjltW6WdPvNvyW0UOArfeFpleevnz0kF0liKtzfPX7eeUnMJ9PNf1Mee/oIbvIBz6vxo2a/hQn hsCcbtr0eoRjRw/ZRZ5DXI1n5E21wGqc2HR177Wjh+wal0zn96PVs0ePAHbaldUZ1ZtHD9klgjiv 45o+iWaVv0wUoOr9Ta8+vWz0kF3hOcR5/dvEEFiPW1f/YvSIXeIR4nxObPoT29GjhwCL8cHqlKYP GWefPEKcz6MSQ2C9bln90OgRu0IQ5+MtFsAIZ48esCtcMp3HkdXHq2NGDwEW59KmT8Vy2XSfPEKc x10SQ2CM45p+BrFPgjiP240eACzat48esAsEcR43Hz0AWDS/RHgGgjgPQQRGEsQZHBw9YEfcdKbj PKn6rZmOBWy+h1aPm+E4c/0MWjRB3CwfrN42egSwNncfPYBruGQKAAkiAFSCCACVIAJAJYgAUAki AFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgA UAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFR1cPQAtsrB 6g57t1Oqb6huXB01chTs+Wx1efXR6sLqHdU7qytHjmJ7CCLX5UD1A9VDqjOrY8fOga/JZdUrq+dX L6uuGjuHTeaSKV/Ngeqc6v3VudWDEkO2z7HVg6vfq95XPSI/9/gqfGNwbU6v3lI9szpp8BaYy8nV r1Vvrk4bvIUNJIh8uQdVb6v+3ughsCJ3bvoeP2v0EDaLIHKoc6oXNL1QBnbZ0dULm77noRJErvHA 6mn5nmA5btD0Pf+A0UPYDH74UXWr6jlNL6SBJTlQ/WaeKydBZPKc6oTRI2CQm1bPGD2C8QSRB1T3 Gj0CBrtPdb/RIxhLEPk3owfAhnjC6AGMJYjLdqfqH4weARvijKaPJWShBHHZzh49ADbMg0cPYBxB XLYzRw+ADeN5xAUTxOW6SXX70SNgw3x7ddzoEYwhiMt1ev77w5c7UJ06egRj+IG4XCePHgAb6ltG D2AMQVyu40cPgA3lvrFQfkHwcs31W+7fXv32TMeC/Ti76a1E+3X0DMdgCwnics11deDd1c/NdCzY j9s3TxBdOVso/+EBIEEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEA KkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACo BBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaASRACoBBEAKkEEgEoQAaCq g6MHMMzVMx3npOpBMx0L9uOkmY4z132DLSOIy3XpTMe5294NdsWnRw9gDJdMl+tTowfAhvrk6AGM IYjL9RejB8CGunD0AMYQxOV6V3XF6BGwYa6o3jN6BGMI4nJ9rvrT0SNgw7y96b7BAgnisr1k9ADY MC8ePYBxBHHZXpSXmMMXXV2dO3oE4wjisp1XvWb0CNgQr6rOHz2CcQSRp40eABvi6aMHMJYgco/R A2BDfO/oAYwliMt2fPXPR4+ADXFOddPRIxhHEJftH1fHjR4BG+Im1QNHj2AcQVy2Hxw9ADaM+8SC CeJyHZHnD+HL3bPpvsECCeJy3brpEhFwjeOrk0ePYAxBXK7TRg+ADXX66AGMIYjLdbPRA2BDuW8s lF8QvFxzXS79UNNvzoDRbl+dOMNxvPVioQRxueb6b/+H1Q/PdCzYj9+sHj7DcW44wzHYQi6ZAkCC CACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAki AFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgA UAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAJYgAUAkiAFSCCACVIAJAVQdHD2DrHVmdMHoENH0v zuHqmY7DlhHE5fr8TMd58N4NdsVc9w22jEumy3XZ6AGwoS4dPYAxBHG5Pjh6AGyoi0cPYAxBXK4L Rg+ADfXe0QMYQxCX6+LqktEjYMNckvvFYgnisr1+9ADYMK8dPYBxBHHZfnf0ANgw544ewDiCuGwv z+Uh+KL/V71q9AjGEcRlu6J66ugRsCF+Oe9BXDRB5El5mTlcUj159AjGEkQurx4zegQM9hPV34we wViCSNWLc+mU5fqV6qWjRzCeIPJFj6leMXoErNkrqseOHsFmEES+6MrqrOoFo4fAmpxbPajpex8E kb/lc9XDqic0vQIVdtHnq3/dFMPLB29hgwgiX+4L1X+t7pxP7WD3/EHT9/bPN32vw5cIIl/NO6t7 V3evfqf67Ng5cNgur15YnVHdp/qzsXPYVH5BMNflDXu3Y6vva/qhcofqlOqE6vhx0+ArfLL6RHVh 9Y7qj6s/zO//5HoQRK6vy6rf27sB7ByXTAEgQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAq QQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgE EQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJE AKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShAB oBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAqg6OHgALdEp1RnV6dVJ1zN7f/0z1l9V7qjdUfzFk HSyUIMJ63KJ6ZPWw6luv579zQfXc6lnVX61oF7DHJVNYra+vfrW6sPoPXf8YVt22+o9NjxR/pfq6 2dcBXyKIsDoPrs6rfry60T6Oc6PqUdX51Vkz7AKuhSDC/G5Q/ffqt6ubzXjcr69eVP1S7rswO3cq mNfB6reqx6zwHI9rem7xwArPAYsjiDCfI6qnV2ev4VwPqZ66hvPAYggizOfR1SPWeL5HNj0/CcxA EGEet6t+fsB5n1SdOuC8sHMEEebxlPb3StLDdVT15AHnhZ0jiLB/967uMfD83zf4/LATBBH276dG D6h+evQA2HaCCPtzi6ZHaKPdpzpx9AjYZoII+/OANuP9gAer+48eAdtMEGF/Num5u3uOHgDbTBBh f+4wesAhvmP0ANhmggj7c8roAYe4zegBsM0EEQ7f0Y157+FXc9TeDTgMggiHb5Ni+EWCCIdJEOHw fWb0gGuxiZtgKwgiHL7PV58aPeIQn6iuGD0CtpUgwv5cMHrAIc4fPQC2mSDC/rx19IBDbNIW2DqC CPvz2tEDDrFJW2DrCCLsz6vajOcRP139/ugRsM0EEfbn8ur5o0dUz23aAhwmQYT9+8XqyoHnv6L6 hYHnh50giLB/76+eOvD8v1xdNPD8sBMEEebx76sPDDjvRdXPDDgv7BxBhHl8qvqh1vvG+M9XZ1eX rvGcsLMEEebzxuqfVV9Yw7murh5ZvWUN54JFEESY1/ObQnXVCs9xZfWI6jdWeA5YHEGE+T27+oHq 4ys49l9X96/+xwqODYsmiLAar6zutPfXubxs75ivmfGYwB5BhNX5QPX9TY/o9vNc35uq+zU96vzg DLuAa3Fw9ABYgFfs3e5SPay6b3Xqdfw751Wvrp5X/Z+VrgMqQYR1ekvXPFL8hup21a2q4/b+3qXV xdW7m54rBNZIEGGMj1Z/NHoEcA3PIQJAgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggA lSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBUgggAlSACQCWIAFAJIgBU dXD0AFiob6y+rbpVdcze3/tM9YHqz6uPDNoFiyWIsD7fVT28um/1rdfxz55fvaZ6XvWWFe8CcskU 1uGB1VurN1U/2XXHsOrU6jHVm/du91/ZOqASRFilk6pXVy+u7ryP49yleln1iuqbZ9gFXAtBhNW4 f/X2psujc7lf9X+rM2c8JrBHEGF+5zQ9KjxhBce+WfXS6hErODYsmiDCvB5WPb06sMJzHKyeVf3I Cs8BiyOIMJ8zque0nvvVEdUzqruu4VywCIII8zi+en51wzWe84Z757zJGs8JO0sQYR4/W91ywHlP qn5mwHlh5wgi7N+tqx8beP5HV7cZeH7YCYII+/f4xn7q04HqcQPPDztBEGF/jq4eMnpE06tbjx49 AraZIML+nFkdN3pE0wtr7jN6BGwzQYT9udfoAYfYpC2wdQQR9mc/n1E6t+8cPQC2mSDC/tx29IBD bNIW2DqCCIfvyOqmo0cc4oTW+8EAsFMEEQ7fMdf9j6zdJm6CrSCIcPg+N3rAtfjs6AGwrQQRDt/f tFlRvDxBhMMmiLA/7x894BDvGz0Atpkgwv786egBh3jn6AGwzQQR9uf1owcc4nWjB8A2E0TYn5dU V44e0bThlaNHwDYTRNifD1d/MHpE9erqQ6NHwDYTRNi/Xxw9oPqF0QNg2wki7N/rqtcOPP9rqv81 8PywEwQR5vGTjXkP4OXVowecF3aOIMI8zqv+1YDz/svqvQPOCztHEGE+T6meucbzPa16xhrPBztN EGFeP149fw3neV7TZVpgJoII87qqenj1pBUd/+qmV5T+8N65gJkIIszvC9VPVWdVH53xuB+u/kn1 +KYwAjMSRFidc6vTqie3v1egXl79t71jvXiGXcC1EERYrY9Xj6lOqZ5Ynf81/Lvvqf7d3r/72OqT s68DvuTg6AGwEH9V/ae928nVGdXp1UnVsXv/zKXVB5pC+IbqL9c/E5ZLEGH9Ltq7ARvEJVMASBAB oBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSA ShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAq QQSAShABoBJEAKgEEQAqQQSAShABoBJEAKgEEQAqQQSAShABoBJEAKjq4OgB/C03rk4YPQJYmxuP HsA1BHEeV8x0nJ/duwF8LT43esAucMl0HpeOHgAs2mWjB+wCQZzHJaMHAIt28egBu0AQ53HB6AHA or139IBdcMToATvi5tWHRo8AFuub8jNo3zxCnMeHq/NGjwAW6d2J4SwEcT6/O3oAsEjnjh6wK1wy nc/J1fuqA6OHAItxVXXb6sLRQ3aBR4jzuah66egRwKKcmxjOxiPEeZ1a/Vl1w9FDgJ13RfVteYXp bFzem9fHqiOru48eAuy8/5znD2flEeL8Dlavr75n9BBgZ72h+ofVlaOH7BJBXI2bVX/SdAkVYE7n VWc0XZFiRl5Usxofq763etvoIcBOeWd178RwJQRxdT5c3aP6tdFDgJ3wzOq789nJKyOIq3VZdU51 ZvXng7cA2+ld1T+qfqz6zOAtO81ziOtzoPrB6lFNr0L1Cl/gq7mq+qPqKdVL9v43KyaIY9y8um91 1+r21SnVTapjR44Chris+nTTG+zfVb2xenX1kZGjluj/A7eiP6AtHfmzAAAAAElFTkSuQmCC " + id="image2981-4" + x="125.46542" + y="11.578013" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="140.23157" + y="26.731647" + id="text2987-9"><tspan + sodipodi:role="line" + id="tspan2985-0" + x="140.23157" + y="26.731647" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';stroke-width:0.264583">6</tspan></text> + </g> + <path + style="fill:none;stroke:#0000ff;stroke-width:0.848828;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 104.64946,54.902986 119.22518,51.23513" + id="path2736-1" + sodipodi:nodetypes="cc" /> + <g + id="g3137" + transform="translate(6.1449507,10.235717)" + style="stroke:#0000ff;stroke-opacity:1"> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 161.12836,39.73877 V 54.647494 L 146.163,54.488794 V 33.300302 h 8.7615 l 5.86826,6.195557 h -5.87342 v -6.047936" + id="path3085" + sodipodi:nodetypes="cccccccc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,37.39937 h 4.21162" + id="path3087" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,40.644703 h 4.21162" + id="path3087-7" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,43.555576 h 9.81264" + id="path3087-1" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,46.646723 h 9.81264" + id="path3087-1-1" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 148.46975,50.449131 h 9.81264" + id="path3087-1-5" + sodipodi:nodetypes="cc" /> + </g> + <path + style="fill:none;stroke:#0000ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#Arrow1Mend)" + d="m 147.85661,51.721464 c -6.70172,-3.912196 -23.39473,-1.178632 -23.39473,-1.178632" + id="path3139" /> + <g + id="g3602" + style="stroke:#0000ff;stroke-opacity:1"> + <path + style="fill:none;stroke:#0000ff;stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker3465);stroke-miterlimit:4;stroke-dasharray:2.4, 0.80000000000000004;stroke-dashoffset:0" + d="m 99.774673,58.026576 c -5.092676,4.046769 -2.589217,10.390276 3.295517,11.307367" + id="path3437" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker3455);stroke-miterlimit:4;stroke-dasharray:2.4, 0.80000000000000004;stroke-dashoffset:0" + d="M 98.854842,57.027565 C 87.49604,65.49508 80.412608,80.588074 82.926734,90.302514" + id="path3439" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker3445);stroke-miterlimit:4;stroke-dasharray:2.4, 0.80000000000000004;stroke-dashoffset:0" + d="M 98.611748,55.797585 C 83.469402,57.101491 46.01116,75.338227 43.693127,76.144807" + id="path3441" + sodipodi:nodetypes="cc" /> + </g> + <rect + style="fill:none;stroke:#000040;stroke-width:0.799999;stroke-dasharray:2.4, 0.799999;stroke-opacity:0.0124995" + id="rect894" + width="171.26611" + height="127.22335" + x="0.13010304" + y="0.24505216" /> + </g> +</svg> diff --git a/doc/talks/2022-06-23-stack/assets/deuxfleurs.svg b/doc/talks/2022-06-23-stack/assets/deuxfleurs.svg new file mode 100644 index 00000000..c298c22b --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/deuxfleurs.svg @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + viewBox="0 0 70.424515 70.300102" + version="1.1" + id="svg8" + sodipodi:docname="logo.svg" + inkscape:version="1.1 (c68e22c387, 2021-05-23)" + inkscape:export-filename="/home/quentin/Documents/dev/deuxfleurs/site/src/img/logo.png" + inkscape:export-xdpi="699.30194" + inkscape:export-ydpi="699.30194" + width="70.424515" + height="70.300102" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs12" /> + <sodipodi:namedview + id="namedview10" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + showgrid="false" + inkscape:zoom="12.125" + inkscape:cx="43.092783" + inkscape:cy="48.082474" + inkscape:window-width="3072" + inkscape:window-height="1659" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg8" /> + <g + id="g79969" + transform="translate(-0.827,34.992103)"> + <path + fill="#ffffff" + d="m 15.632,34.661 c -0.799,-0.597 -1.498,-1.484 -2.035,-2.592 l -0.228,-0.47 -0.46,0.249 c -0.975,0.528 -1.913,0.858 -2.744,0.969 L 9.963,29.061 6.327,30.029 C 6.17,29.175 6.202,28.142 6.423,27.007 L 6.526,26.482 5.994,26.416 C 4.752,26.262 3.688,25.891 2.89,25.336 L 4.411,22.419 1.423,20.896 C 1.742,19.952 2.371,19.014 3.257,18.161 L 3.634,17.798 3.255,17.438 C 2.452,16.674 1.847,15.884 1.485,15.127 L 4.995,13.774 2.95,10.615 C 3.69,10.213 4.643,9.929 5.739,9.783 L 6.258,9.715 6.167,9.201 C 5.952,7.99 5.995,6.863 6.291,5.913 l 3.308,0.523 0.524,-3.308 c 0.988,0.013 2.08,0.326 3.164,0.907 L 13.749,4.283 13.975,3.81 C 14.454,2.807 15.019,1.986 15.628,1.406 L 18,4.326 20.372,1.406 c 0.609,0.58 1.175,1.401 1.653,2.404 l 0.226,0.473 0.462,-0.247 C 23.798,3.455 24.891,3.142 25.877,3.13 L 26.4,6.438 29.71,5.913 c 0.296,0.951 0.34,2.078 0.124,3.288 l -0.092,0.515 0.518,0.069 c 1.095,0.145 2.048,0.43 2.788,0.832 l -2.046,3.156 3.511,1.355 c -0.361,0.757 -0.966,1.547 -1.77,2.311 l -0.379,0.36 0.377,0.363 c 0.888,0.854 1.516,1.793 1.835,2.736 l -2.984,1.52 1.521,2.984 c -0.812,0.574 -1.871,0.964 -3.094,1.134 l -0.518,0.072 0.096,0.514 c 0.201,1.089 0.226,2.083 0.073,2.909 l -3.634,-0.97 -0.204,3.757 c -0.83,-0.11 -1.768,-0.44 -2.742,-0.968 l -0.459,-0.249 -0.228,0.47 c -0.539,1.107 -1.237,1.994 -2.036,2.591 L 18,32.293 Z" + id="path2" /> + <path + d="M 7.092,10.678 C 6.562,9.189 6.394,7.708 6.66,6.478 l 2.368,0.375 0.987,0.156 0.157,-0.988 0.375,-2.368 C 11.808,3.78 13.16,4.396 14.409,5.359 14.527,5.022 14.653,4.696 14.791,4.392 13.24,3.257 11.568,2.629 10.061,2.629 9.938,2.629 9.816,2.633 9.695,2.642 L 9.184,5.865 5.96,5.354 C 5.36,6.841 5.395,8.769 6.045,10.747 6.38,10.71 6.729,10.686 7.092,10.678 Z M 21.593,5.359 c 1.248,-0.962 2.6,-1.578 3.86,-1.705 l 0.376,2.368 0.156,0.988 0.987,-0.157 2.369,-0.376 c 0.266,1.23 0.098,2.71 -0.432,4.2 0.361,0.009 0.711,0.032 1.046,0.07 C 30.606,8.769 30.64,6.841 30.04,5.353 L 26.815,5.865 26.304,2.641 c -0.12,-0.008 -0.242,-0.012 -0.365,-0.012 -1.507,0 -3.179,0.628 -4.73,1.762 0.14,0.306 0.266,0.631 0.384,0.968 z M 7.368,27 h 0.035 c 0.067,0 0.157,-0.604 0.26,-0.947 -0.098,0.004 -0.197,0.046 -0.294,0.046 -1.496,0 -2.826,-0.303 -3.83,-0.89 L 4.628,23.081 5.082,22.194 4.191,21.742 2.055,20.654 C 2.563,19.503 3.57,18.404 4.873,17.511 4.586,17.292 4.312,17.07 4.063,16.842 2.376,18.059 1.217,19.597 0.828,21.152 l 2.908,1.483 -1.482,2.843 C 3.475,26.501 5.303,27 7.368,27 Z m 27.806,-5.846 c -0.39,-1.555 -1.548,-3.093 -3.234,-4.311 -0.25,0.228 -0.523,0.451 -0.81,0.669 1.304,0.893 2.31,1.992 2.817,3.145 l -2.136,1.088 -0.891,0.453 0.454,0.892 1.089,2.137 c -1.004,0.587 -2.332,0.904 -3.828,0.904 -0.099,0 -0.199,-0.01 -0.299,-0.013 0.103,0.344 0.192,0.683 0.26,1.011 l 0.039,0.002 c 2.066,0 3.892,-0.563 5.112,-1.587 l -1.482,-2.908 z m -12.653,9.182 c -0.447,1.517 -1.181,2.812 -2.119,3.651 L 18.707,32.293 18,31.586 l -0.707,0.707 -1.695,1.694 c -0.938,-0.839 -1.673,-2.136 -2.12,-3.652 -0.296,0.206 -0.593,0.397 -0.886,0.563 0.636,1.98 1.741,3.559 3.1,4.409 L 18,33 l 2.308,2.308 c 1.358,-0.851 2.464,-2.428 3.101,-4.408 -0.295,-0.168 -0.591,-0.359 -0.888,-0.564 z" + fill="#ea596e" + id="path4" /> + <path + fill="#ea596e" + d="m 20.118,5.683 c 0.426,1.146 0.748,2.596 0.841,4.284 l 0.2,3.683 3.564,-0.946 c 1.32,-0.351 2.655,-0.536 3.86,-0.536 0.16,0 0.318,0.003 0.474,0.01 l -1.827,2.819 3.139,1.211 c -0.958,0.759 -2.237,1.514 -3.814,2.123 l -3.441,1.328 2.001,3.099 c 0.918,1.42 1.509,2.782 1.838,3.96 L 23.709,25.853 23.527,29.21 C 22.508,28.533 21.395,27.55 20.329,26.237 L 18,23.374 15.672,26.236 c -1.066,1.312 -2.179,2.295 -3.198,2.972 l -0.18,-3.354 -3.248,0.864 c 0.329,-1.178 0.921,-2.54 1.839,-3.961 L 12.889,19.658 9.447,18.33 C 7.87,17.721 6.591,16.967 5.633,16.208 L 8.768,15 6.941,12.177 c 0.155,-0.006 0.313,-0.01 0.473,-0.01 1.206,0 2.541,0.185 3.861,0.536 l 3.564,0.947 0.202,-3.683 c 0.092,-1.688 0.415,-3.138 0.84,-4.284 L 18,8.292 20.118,5.683 M 20.308,0.692 18,3.533 15.692,0.692 C 13.703,2.224 12.271,5.684 12.046,9.804 10.429,9.374 8.854,9.167 7.414,9.167 c -2.11,0 -3.929,0.445 -5.161,1.289 l 1.989,3.073 -3.415,1.316 c 0.842,2.366 3.69,4.797 7.54,6.283 -2.241,3.465 -3.116,7.106 -2.407,9.516 l 3.537,-0.941 0.196,3.654 c 2.512,-0.07 5.703,-2.027 8.307,-5.228 2.603,3.201 5.796,5.158 8.306,5.228 l 0.198,-3.655 3.535,0.943 c 0.71,-2.411 -0.165,-6.05 -2.404,-9.517 3.849,-1.485 6.696,-3.918 7.538,-6.283 l -3.415,-1.318 1.99,-3.07 c -1.233,-0.844 -3.053,-1.29 -5.164,-1.29 -1.438,0 -3.013,0.207 -4.63,0.636 C 23.729,5.684 22.297,2.224 20.308,0.692 Z" + id="path6" /> + </g> + <g + id="g79964" + transform="translate(-1.043816,35.993714)"> + <path + fill="#ffffff" + d="m 51.92633,-2.0247139 c -0.799,-0.597 -1.498,-1.484 -2.035,-2.592 l -0.228,-0.47 -0.46,0.249 c -0.975,0.528 -1.913,0.858 -2.744,0.969 l -0.202,-3.7560001 -3.636,0.968 c -0.157,-0.854 -0.125,-1.887 0.096,-3.022 l 0.103,-0.525 -0.532,-0.066 c -1.242,-0.154 -2.306,-0.525 -3.104,-1.08 l 1.521,-2.917 -2.988,-1.523 c 0.319,-0.944 0.948,-1.882 1.834,-2.735 l 0.377,-0.363 -0.379,-0.36 c -0.803,-0.764 -1.408,-1.554 -1.77,-2.311 l 3.51,-1.353 -2.045,-3.159 c 0.74,-0.402 1.693,-0.686 2.789,-0.832 l 0.519,-0.068 -0.091,-0.514 c -0.215,-1.211 -0.172,-2.338 0.124,-3.288 l 3.308,0.523 0.524,-3.308 c 0.988,0.013 2.08,0.326 3.164,0.907 l 0.462,0.248 0.226,-0.473 c 0.479,-1.003 1.044,-1.824 1.653,-2.404 l 2.372,2.92 2.372,-2.92 c 0.609,0.58 1.175,1.401 1.653,2.404 l 0.226,0.473 0.462,-0.247 c 1.085,-0.581 2.178,-0.894 3.164,-0.906 l 0.523,3.308 3.31,-0.525 c 0.296,0.951 0.34,2.078 0.124,3.288 l -0.092,0.515 0.518,0.069 c 1.095,0.145 2.048,0.43 2.788,0.832 l -2.046,3.156 3.511,1.355 c -0.361,0.757 -0.966,1.547 -1.77,2.311 l -0.379,0.36 0.377,0.363 c 0.888,0.854 1.516,1.793 1.835,2.736 l -2.984,1.52 1.521,2.984 c -0.812,0.574 -1.871,0.964 -3.094,1.134 l -0.518,0.072 0.096,0.514 c 0.201,1.089 0.226,2.083 0.073,2.909 l -3.634,-0.97 -0.204,3.7570001 c -0.83,-0.11 -1.768,-0.44 -2.742,-0.968 l -0.459,-0.249 -0.228,0.47 c -0.539,1.107 -1.237,1.994 -2.036,2.591 l -2.367,-2.369 z" + id="path2-9" /> + <path + d="m 43.38633,-26.007714 c -0.53,-1.489 -0.698,-2.97 -0.432,-4.2 l 2.368,0.375 0.987,0.156 0.157,-0.988 0.375,-2.368 c 1.261,0.127 2.613,0.743 3.862,1.706 0.118,-0.337 0.244,-0.663 0.382,-0.967 -1.551,-1.135 -3.223,-1.763 -4.73,-1.763 -0.123,0 -0.245,0.004 -0.366,0.013 l -0.511,3.223 -3.224,-0.511 c -0.6,1.487 -0.565,3.415 0.085,5.393 0.335,-0.037 0.684,-0.061 1.047,-0.069 z m 14.501,-5.319 c 1.248,-0.962 2.6,-1.578 3.86,-1.705 l 0.376,2.368 0.156,0.988 0.987,-0.157 2.369,-0.376 c 0.266,1.23 0.098,2.71 -0.432,4.2 0.361,0.009 0.711,0.032 1.046,0.07 0.651,-1.978 0.685,-3.906 0.085,-5.394 l -3.225,0.512 -0.511,-3.224 c -0.12,-0.008 -0.242,-0.012 -0.365,-0.012 -1.507,0 -3.179,0.628 -4.73,1.762 0.14,0.306 0.266,0.631 0.384,0.968 z m -14.225,21.641 h 0.035 c 0.067,0 0.157,-0.604 0.26,-0.947 -0.098,0.004 -0.197,0.046 -0.294,0.046 -1.496,0 -2.826,-0.303 -3.83,-0.89 l 1.089,-2.128 0.454,-0.887 -0.891,-0.452 -2.136,-1.088 c 0.508,-1.151 1.515,-2.25 2.818,-3.143 -0.287,-0.219 -0.561,-0.441 -0.81,-0.669 -1.687,1.217 -2.846,2.755 -3.235,4.31 l 2.908,1.483 -1.482,2.843 c 1.221,1.023 3.049,1.522 5.114,1.522 z m 27.806,-5.846 c -0.39,-1.555 -1.548,-3.093 -3.234,-4.311 -0.25,0.228 -0.523,0.451 -0.81,0.669 1.304,0.893 2.31,1.992 2.817,3.145 l -2.136,1.088 -0.891,0.453 0.454,0.892 1.089,2.137 c -1.004,0.587 -2.332,0.904 -3.828,0.904 -0.099,0 -0.199,-0.01 -0.299,-0.013 0.103,0.344 0.192,0.683 0.26,1.011 l 0.039,0.002 c 2.066,0 3.892,-0.563 5.112,-1.587 l -1.482,-2.908 z m -12.653,9.182 c -0.447,1.5170001 -1.181,2.8120001 -2.119,3.6510001 l -1.695,-1.694 -0.707,-0.707 -0.707,0.707 -1.695,1.694 c -0.938,-0.839 -1.673,-2.136 -2.12,-3.6520001 -0.296,0.2060001 -0.593,0.3970001 -0.886,0.5630001 0.636,1.98 1.741,3.559 3.1,4.409 l 2.308,-2.307 2.308,2.308 c 1.358,-0.851 2.464,-2.428 3.101,-4.408 -0.295,-0.168 -0.591,-0.359 -0.888,-0.5640001 z" + fill="#ea596e" + id="path4-3" /> + <path + fill="#ea596e" + d="m 56.41233,-31.002714 c 0.426,1.146 0.748,2.596 0.841,4.284 l 0.2,3.683 3.564,-0.946 c 1.32,-0.351 2.655,-0.536 3.86,-0.536 0.16,0 0.318,0.003 0.474,0.01 l -1.827,2.819 3.139,1.211 c -0.958,0.759 -2.237,1.514 -3.814,2.123 l -3.441,1.328 2.001,3.099 c 0.918,1.42 1.509,2.782 1.838,3.96 l -3.244,-0.865 -0.182,3.357 c -1.019,-0.677 -2.132,-1.66 -3.198,-2.973 l -2.329,-2.863 -2.328,2.862 c -1.066,1.312 -2.179,2.295 -3.198,2.972 l -0.18,-3.354 -3.248,0.864 c 0.329,-1.178 0.921,-2.54 1.839,-3.961 l 2.004,-3.099 -3.442,-1.328 c -1.577,-0.609 -2.856,-1.363 -3.814,-2.122 l 3.135,-1.208 -1.827,-2.823 c 0.155,-0.006 0.313,-0.01 0.473,-0.01 1.206,0 2.541,0.185 3.861,0.536 l 3.564,0.947 0.202,-3.683 c 0.092,-1.688 0.415,-3.138 0.84,-4.284 l 2.119,2.609 2.118,-2.609 m 0.19,-4.991 -2.308,2.841 -2.308,-2.841 c -1.989,1.532 -3.421,4.992 -3.646,9.112 -1.617,-0.43 -3.192,-0.637 -4.632,-0.637 -2.11,0 -3.929,0.445 -5.161,1.289 l 1.989,3.073 -3.415,1.316 c 0.842,2.366 3.69,4.797 7.54,6.283 -2.241,3.465 -3.116,7.106 -2.407,9.5160001 l 3.537,-0.9410001 0.196,3.6540001 c 2.512,-0.07 5.703,-2.027 8.307,-5.2280001 2.603,3.2010001 5.796,5.1580001 8.306,5.2280001 l 0.198,-3.6550001 3.535,0.9430001 c 0.71,-2.4110001 -0.165,-6.0500001 -2.404,-9.5170001 3.849,-1.485 6.696,-3.918 7.538,-6.283 l -3.415,-1.318 1.99,-3.07 c -1.233,-0.844 -3.053,-1.29 -5.164,-1.29 -1.438,0 -3.013,0.207 -4.63,0.636 -0.225,-4.119 -1.657,-7.579 -3.646,-9.111 z" + id="path6-6" /> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:42.6667px;line-height:1.25;font-family:sans-serif;fill:#ea596e;fill-opacity:1;stroke:none" + x="2.2188232" + y="31.430677" + id="text46212"><tspan + sodipodi:role="line" + id="tspan46210" + x="2.2188232" + y="31.430677" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.6667px;font-family:'TeX Gyre Termes';-inkscape-font-specification:'TeX Gyre Termes'">D</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:42.6667px;line-height:1.25;font-family:sans-serif;fill:#ea596e;fill-opacity:1;stroke:none" + x="41.347008" + y="67.114784" + id="text46212-1"><tspan + sodipodi:role="line" + id="tspan46210-5" + x="41.347008" + y="67.114784" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.6667px;font-family:'TeX Gyre Termes';-inkscape-font-specification:'TeX Gyre Termes'">F</tspan></text> +</svg> diff --git a/doc/talks/2022-06-23-stack/assets/endpoint-latency-dc.png b/doc/talks/2022-06-23-stack/assets/endpoint-latency-dc.png Binary files differnew file mode 100644 index 00000000..7c7411cd --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/endpoint-latency-dc.png diff --git a/doc/talks/2022-06-23-stack/assets/garage.drawio.pdf b/doc/talks/2022-06-23-stack/assets/garage.drawio.pdf new file mode 100644 index 00000000..2f7a5dcb --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/garage.drawio.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d9ca1a04f943664e14d0e1edbde596789e4798f8ad5e1cff68bc9dd0cc1334f +size 26098 diff --git a/doc/talks/2022-06-23-stack/assets/garage.drawio.png b/doc/talks/2022-06-23-stack/assets/garage.drawio.png Binary files differnew file mode 100644 index 00000000..386dd862 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/garage.drawio.png diff --git a/doc/talks/2022-06-23-stack/assets/garage2.drawio.png b/doc/talks/2022-06-23-stack/assets/garage2.drawio.png Binary files differnew file mode 100644 index 00000000..8562fbcf --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/garage2.drawio.png diff --git a/doc/talks/2022-06-23-stack/assets/garage2a.drawio.pdf b/doc/talks/2022-06-23-stack/assets/garage2a.drawio.pdf new file mode 100644 index 00000000..c4f5e0b7 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/garage2a.drawio.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bffa5d4a72ad25e0b18b43022258a2e4471ce94ff5397aab892fe43ec9d4d7d5 +size 33911 diff --git a/doc/talks/2022-06-23-stack/assets/garage2b.drawio.pdf b/doc/talks/2022-06-23-stack/assets/garage2b.drawio.pdf new file mode 100644 index 00000000..1a12a0d3 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/garage2b.drawio.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c4913b52e84b5af3f8effbcb7e2060845a8c6f43d5b38072b9ee33c0300d49f +size 31051 diff --git a/doc/talks/2022-06-23-stack/assets/garage_tables.svg b/doc/talks/2022-06-23-stack/assets/garage_tables.svg new file mode 100644 index 00000000..c7172713 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/garage_tables.svg @@ -0,0 +1,537 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="850" + height="480" + viewBox="0 0 224.89584 127" + version="1.1" + id="svg8" + inkscape:version="1.2 (dc2aedaf03, 2022-05-15)" + sodipodi:docname="garage_tables.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs2"> + <marker + style="overflow:visible" + id="marker1262" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path1260" /> + </marker> + <marker + style="overflow:visible" + id="Arrow1Mend" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true" + inkscape:collect="always"> + <path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path965" /> + </marker> + <marker + style="overflow:visible" + id="Arrow1Lend" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Lend" + inkscape:isstock="true"> + <path + transform="matrix(-0.8,0,0,-0.8,-10,0)" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path959" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.98994949" + inkscape:cx="429.31483" + inkscape:cy="289.40871" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + showgrid="false" + units="px" + inkscape:window-width="1678" + inkscape:window-height="993" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="39.570904" + y="38.452755" + id="text2025"><tspan + sodipodi:role="line" + id="tspan2023" + x="39.570904" + y="38.452755" + style="font-size:5.64444px;stroke-width:0.264583" /></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="101.95796" + y="92.835831" + id="text2139"><tspan + sodipodi:role="line" + id="tspan2137" + x="101.95796" + y="92.835831" + style="stroke-width:0.264583"> </tspan></text> + <g + id="g2316" + transform="translate(-11.455511,1.5722486)"> + <g + id="g2277"> + <rect + style="fill:none;stroke:#000000;stroke-width:0.8;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833" + width="47.419891" + height="95.353409" + x="18.534418" + y="24.42766" /> + <rect + style="fill:none;stroke:#000000;stroke-width:0.799999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833-3" + width="47.419891" + height="86.973076" + x="18.534418" + y="32.807987" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="32.250839" + y="29.894743" + id="text852"><tspan + sodipodi:role="line" + id="tspan850" + x="32.250839" + y="29.894743" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">Object</tspan></text> + </g> + <g + id="g2066" + transform="translate(-2.1807817,-3.0621439)"> + <g + id="g1969" + transform="matrix(0.12763631,0,0,0.12763631,0.7215051,24.717273)" + style="fill:#ff6600;fill-opacity:1;stroke:none;stroke-opacity:1"> + <path + style="fill:#ff6600;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-opacity:1" + d="m 203.71837,154.80038 c -1.11451,3.75057 -2.45288,5.84095 -5.11132,7.98327 -2.2735,1.83211 -4.66721,2.65982 -8.09339,2.79857 -2.59227,0.10498 -2.92868,0.0577 -5.02863,-0.70611 -3.99215,-1.45212 -7.1627,-4.65496 -8.48408,-8.57046 -1.28374,-3.80398 -0.61478,-8.68216 1.64793,-12.01698 0.87317,-1.28689 3.15089,-3.48326 4.18771,-4.03815 l 0.53332,-28.51234 5.78454,-5.09197 6.95158,6.16704 -3.21112,3.49026 3.17616,3.45499 -3.17616,3.40822 2.98973,3.28645 -3.24843,3.3829 4.49203,4.58395 0.0516,5.69106 c 1.06874,0.64848 3.81974,3.24046 4.69548,4.56257 0.452,0.68241 1.06834,2.0197 1.36962,2.97176 0.62932,1.98864 0.88051,5.785 0.47342,7.15497 z m -10.0406,2.32604 c -0.88184,-3.17515 -4.92402,-3.78864 -6.75297,-1.02492 -0.58328,0.8814 -0.6898,1.28852 -0.58362,2.23056 0.26492,2.35041 2.45434,3.95262 4.60856,3.37255 1.19644,-0.32217 2.39435,-1.44872 2.72875,-2.56621 0.30682,-1.02529 0.30686,-0.9045 -7.9e-4,-2.01198 z" + id="path1971" + sodipodi:nodetypes="ssscsscccccccccccssscsssscc" /> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="28.809687" + y="44.070885" + id="text852-9"><tspan + sodipodi:role="line" + id="tspan850-4" + x="28.809687" + y="44.070885" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">bucket </tspan></text> + </g> + <g + id="g2066-7" + transform="translate(-2.1807817,6.2627616)"> + <g + id="g1969-8" + transform="matrix(0.12763631,0,0,0.12763631,0.7215051,24.717273)" + style="fill:#ff6600;fill-opacity:1;stroke:none;stroke-opacity:1"> + <path + style="fill:#4040ff;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-opacity:1" + d="m 203.71837,154.80038 c -1.11451,3.75057 -2.45288,5.84095 -5.11132,7.98327 -2.2735,1.83211 -4.66721,2.65982 -8.09339,2.79857 -2.59227,0.10498 -2.92868,0.0577 -5.02863,-0.70611 -3.99215,-1.45212 -7.1627,-4.65496 -8.48408,-8.57046 -1.28374,-3.80398 -0.61478,-8.68216 1.64793,-12.01698 0.87317,-1.28689 3.15089,-3.48326 4.18771,-4.03815 l 0.53332,-28.51234 5.78454,-5.09197 6.95158,6.16704 -3.21112,3.49026 3.17616,3.45499 -3.17616,3.40822 2.98973,3.28645 -3.24843,3.3829 4.49203,4.58395 0.0516,5.69106 c 1.06874,0.64848 3.81974,3.24046 4.69548,4.56257 0.452,0.68241 1.06834,2.0197 1.36962,2.97176 0.62932,1.98864 0.88051,5.785 0.47342,7.15497 z m -10.0406,2.32604 c -0.88184,-3.17515 -4.92402,-3.78864 -6.75297,-1.02492 -0.58328,0.8814 -0.6898,1.28852 -0.58362,2.23056 0.26492,2.35041 2.45434,3.95262 4.60856,3.37255 1.19644,-0.32217 2.39435,-1.44872 2.72875,-2.56621 0.30682,-1.02529 0.30686,-0.9045 -7.9e-4,-2.01198 z" + id="path1971-4" + sodipodi:nodetypes="ssscsscccccccccccssscsssscc" /> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="28.809687" + y="44.070885" + id="text852-9-5"><tspan + sodipodi:role="line" + id="tspan850-4-0" + x="28.809687" + y="44.070885" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">file path </tspan></text> + <path + style="fill:#ff6600;fill-opacity:1;stroke:none;stroke-width:0.0337704;stroke-opacity:1" + d="m 174.20027,104.45585 c -0.14225,0.47871 -0.31308,0.74552 -0.65239,1.01896 -0.29018,0.23384 -0.5957,0.33949 -1.03301,0.3572 -0.33087,0.0134 -0.37381,0.007 -0.64184,-0.0901 -0.50954,-0.18534 -0.91422,-0.59414 -1.08287,-1.0939 -0.16385,-0.48552 -0.0785,-1.10816 0.21033,-1.5338 0.11145,-0.16426 0.40217,-0.44459 0.53451,-0.51542 l 0.0681,-3.639207 0.73832,-0.64992 0.88727,0.787138 -0.40986,0.445484 0.4054,0.440982 -0.4054,0.435013 0.3816,0.41947 -0.41461,0.43178 0.57334,0.58508 0.007,0.72639 c 0.13641,0.0828 0.48753,0.4136 0.59931,0.58235 0.0577,0.0871 0.13636,0.25778 0.17481,0.3793 0.0803,0.25382 0.11239,0.73838 0.0604,0.91323 z m -1.28154,0.29689 c -0.11256,-0.40526 -0.62849,-0.48357 -0.86193,-0.13082 -0.0745,0.1125 -0.088,0.16447 -0.0745,0.2847 0.0338,0.3 0.31326,0.5045 0.58822,0.43046 0.15271,-0.0411 0.30561,-0.1849 0.34829,-0.32754 0.0392,-0.13086 0.0392,-0.11544 -1e-4,-0.2568 z" + id="path1971-3" + sodipodi:nodetypes="ssscsscccccccccccssscsssscc" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="177.8474" + y="104.05132" + id="text852-9-6"><tspan + sodipodi:role="line" + id="tspan850-4-7" + x="177.8474" + y="104.05132" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">= partition key </tspan></text> + <path + style="fill:#4040ff;fill-opacity:1;stroke:none;stroke-width:0.0337704;stroke-opacity:1" + d="m 174.20027,113.78076 c -0.14225,0.47871 -0.31308,0.74552 -0.65239,1.01895 -0.29018,0.23385 -0.5957,0.33949 -1.03301,0.3572 -0.33087,0.0134 -0.37381,0.007 -0.64184,-0.0901 -0.50954,-0.18534 -0.91422,-0.59414 -1.08287,-1.0939 -0.16385,-0.48553 -0.0785,-1.10816 0.21033,-1.53381 0.11145,-0.16425 0.40217,-0.44459 0.53451,-0.51541 l 0.0681,-3.63921 0.73832,-0.64992 0.88727,0.78714 -0.40986,0.44548 0.4054,0.44098 -0.4054,0.43502 0.3816,0.41947 -0.41461,0.43178 0.57334,0.58508 0.007,0.72638 c 0.13641,0.0828 0.48753,0.4136 0.59931,0.58235 0.0577,0.0871 0.13636,0.25779 0.17481,0.37931 0.0803,0.25382 0.11239,0.73837 0.0604,0.91323 z m -1.28154,0.29689 c -0.11256,-0.40527 -0.62849,-0.48357 -0.86193,-0.13082 -0.0745,0.1125 -0.088,0.16446 -0.0745,0.2847 0.0338,0.3 0.31326,0.5045 0.58822,0.43046 0.15271,-0.0411 0.30561,-0.18491 0.34829,-0.32754 0.0392,-0.13087 0.0392,-0.11545 -1e-4,-0.2568 z" + id="path1971-4-5" + sodipodi:nodetypes="ssscsscccccccccccssscsssscc" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="177.8474" + y="113.37622" + id="text852-9-5-3"><tspan + sodipodi:role="line" + id="tspan850-4-0-5" + x="177.8474" + y="113.37622" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">= sort key </tspan></text> + </g> + <g + id="g2161" + transform="translate(-62.264403,-59.333115)"> + <g + id="g2271" + transform="translate(0,67.042823)"> + <rect + style="fill:none;stroke:#000000;stroke-width:0.799999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833-6" + width="39.008453" + height="16.775949" + x="84.896881" + y="90.266838" /> + <rect + style="fill:none;stroke:#000000;stroke-width:0.799999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833-3-1" + width="39.008453" + height="8.673645" + x="84.896881" + y="98.369141" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="89.826942" + y="96.212921" + id="text852-0"><tspan + sodipodi:role="line" + id="tspan850-6" + x="89.826942" + y="96.212921" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">Version 1</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="89.826942" + y="104.71013" + id="text852-0-3"><tspan + sodipodi:role="line" + id="tspan850-6-2" + x="89.826942" + y="104.71013" + style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';fill:#4d4d4d;stroke-width:0.264583">deleted</tspan></text> + </g> + </g> + <g + id="g2263" + transform="translate(0,-22.791204)"> + <g + id="g2161-1" + transform="translate(-62.264403,-10.910843)"> + <rect + style="fill:none;stroke:#000000;stroke-width:0.799999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833-6-5" + width="39.008453" + height="36.749603" + x="84.896881" + y="90.266838" /> + <rect + style="fill:none;stroke:#000000;stroke-width:0.799999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833-3-1-5" + width="39.008453" + height="28.647301" + x="84.896881" + y="98.369141" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="89.826942" + y="96.212921" + id="text852-0-4"><tspan + sodipodi:role="line" + id="tspan850-6-7" + x="89.826942" + y="96.212921" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">Version 2</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="89.826942" + y="104.71013" + id="text852-0-3-6"><tspan + sodipodi:role="line" + id="tspan850-6-2-5" + x="89.826942" + y="104.71013" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';fill:#000000;stroke-width:0.264583">id</tspan></text> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="27.56254" + y="100.34132" + id="text852-0-3-6-6"><tspan + sodipodi:role="line" + id="tspan850-6-2-5-9" + x="27.56254" + y="100.34132" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';fill:#000000;stroke-width:0.264583">size</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="27.56254" + y="106.90263" + id="text852-0-3-6-6-3"><tspan + sodipodi:role="line" + id="tspan850-6-2-5-9-7" + x="27.56254" + y="106.90263" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';fill:#000000;stroke-width:0.264583">MIME type</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="27.56254" + y="111.92816" + id="text852-0-3-6-6-3-4"><tspan + sodipodi:role="line" + id="tspan850-6-2-5-9-7-5" + x="27.56254" + y="111.92816" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';fill:#000000;stroke-width:0.264583">...</tspan></text> + </g> + </g> + <g + id="g898" + transform="translate(-6.2484318,29.95006)"> + <rect + style="fill:none;stroke:#000000;stroke-width:0.799999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833-7" + width="47.419891" + height="44.007515" + x="95.443573" + y="24.42766" /> + <rect + style="fill:none;stroke:#000000;stroke-width:0.799999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833-3-4" + width="47.419891" + height="35.627186" + x="95.443573" + y="32.807987" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="107.46638" + y="29.894743" + id="text852-4"><tspan + sodipodi:role="line" + id="tspan850-3" + x="107.46638" + y="29.894743" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">Version</tspan></text> + <path + style="fill:#ff6600;fill-opacity:1;stroke:none;stroke-width:0.0337704;stroke-opacity:1" + d="m 102.90563,41.413279 c -0.14226,0.478709 -0.31308,0.745518 -0.65239,1.018956 -0.29019,0.233843 -0.59571,0.339489 -1.03301,0.357199 -0.33087,0.0134 -0.37381,0.0074 -0.64184,-0.09013 -0.50954,-0.185343 -0.914221,-0.594142 -1.082877,-1.093901 -0.163852,-0.485526 -0.07847,-1.108159 0.210335,-1.533803 0.111448,-0.164254 0.402172,-0.444591 0.534502,-0.515415 l 0.0681,-3.63921 0.73832,-0.64992 0.88727,0.787138 -0.40985,0.445484 0.40539,0.440982 -0.40539,0.435013 0.3816,0.41947 -0.41462,0.431781 0.57335,0.585078 0.007,0.726386 c 0.13641,0.08277 0.48753,0.413601 0.59931,0.58235 0.0577,0.0871 0.13636,0.257787 0.17481,0.379304 0.0803,0.253823 0.11239,0.738377 0.0604,0.913234 z m -1.28155,0.296888 c -0.11255,-0.405265 -0.62848,-0.483569 -0.86192,-0.130817 -0.0744,0.112498 -0.088,0.164461 -0.0745,0.2847 0.0338,0.299998 0.31326,0.504498 0.58822,0.43046 0.15271,-0.04112 0.3056,-0.184909 0.34828,-0.327542 0.0392,-0.130864 0.0392,-0.115447 -1e-4,-0.256801 z" + id="path1971-0" + sodipodi:nodetypes="ssscsscccccccccccssscsssscc" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="104.99195" + y="41.008743" + id="text852-9-7"><tspan + sodipodi:role="line" + id="tspan850-4-8" + x="104.99195" + y="41.008743" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">id </tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="104.99195" + y="49.168018" + id="text852-9-7-6"><tspan + sodipodi:role="line" + id="tspan850-4-8-8" + x="104.99195" + y="49.168018" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">h(block 1)</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="104.99195" + y="56.583336" + id="text852-9-7-6-8"><tspan + sodipodi:role="line" + id="tspan850-4-8-8-4" + x="104.99195" + y="56.583336" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">h(block 2)</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="104.99195" + y="64.265732" + id="text852-9-7-6-3"><tspan + sodipodi:role="line" + id="tspan850-4-8-8-1" + x="104.99195" + y="64.265732" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">...</tspan></text> + </g> + <g + id="g898-3" + transform="translate(75.777779,38.888663)"> + <rect + style="fill:none;stroke:#000000;stroke-width:0.799999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833-7-6" + width="47.419891" + height="29.989157" + x="95.443573" + y="24.42766" /> + <rect + style="fill:none;stroke:#000000;stroke-width:0.799999;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect833-3-4-7" + width="47.419891" + height="21.608831" + x="95.443573" + y="32.807987" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="102.11134" + y="29.894743" + id="text852-4-5"><tspan + sodipodi:role="line" + id="tspan850-3-3" + x="102.11134" + y="29.894743" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">Data block</tspan></text> + <path + style="fill:#ff6600;fill-opacity:1;stroke:none;stroke-width:0.0337704;stroke-opacity:1" + d="m 102.90563,41.413279 c -0.14226,0.478709 -0.31308,0.745518 -0.65239,1.018956 -0.29019,0.233843 -0.59571,0.339489 -1.03301,0.357199 -0.33087,0.0134 -0.37381,0.0074 -0.64184,-0.09013 -0.50954,-0.185343 -0.914221,-0.594142 -1.082877,-1.093901 -0.163852,-0.485526 -0.07847,-1.108159 0.210335,-1.533803 0.111448,-0.164254 0.402172,-0.444591 0.534502,-0.515415 l 0.0681,-3.63921 0.73832,-0.64992 0.88727,0.787138 -0.40985,0.445484 0.40539,0.440982 -0.40539,0.435013 0.3816,0.41947 -0.41462,0.431781 0.57335,0.585078 0.007,0.726386 c 0.13641,0.08277 0.48753,0.413601 0.59931,0.58235 0.0577,0.0871 0.13636,0.257787 0.17481,0.379304 0.0803,0.253823 0.11239,0.738377 0.0604,0.913234 z m -1.28155,0.296888 c -0.11255,-0.405265 -0.62848,-0.483569 -0.86192,-0.130817 -0.0744,0.112498 -0.088,0.164461 -0.0745,0.2847 0.0338,0.299998 0.31326,0.504498 0.58822,0.43046 0.15271,-0.04112 0.3056,-0.184909 0.34828,-0.327542 0.0392,-0.130864 0.0392,-0.115447 -1e-4,-0.256801 z" + id="path1971-0-5" + sodipodi:nodetypes="ssscsscccccccccccssscsssscc" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="104.99195" + y="41.008743" + id="text852-9-7-62"><tspan + sodipodi:role="line" + id="tspan850-4-8-9" + x="104.99195" + y="41.008743" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">hash </tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="104.99195" + y="49.168018" + id="text852-9-7-6-1"><tspan + sodipodi:role="line" + id="tspan850-4-8-8-2" + x="104.99195" + y="49.168018" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">data</tspan></text> + </g> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 42.105292,69.455903 89.563703,69.317144" + id="path954" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker1262)" + d="m 134.32612,77.363197 38.12618,0.260865" + id="path1258" + sodipodi:nodetypes="cc" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="8.6727352" + y="16.687063" + id="text852-3"><tspan + sodipodi:role="line" + id="tspan850-67" + x="8.6727352" + y="16.687063" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">Objects table </tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="89.190445" + y="16.687063" + id="text852-3-5"><tspan + sodipodi:role="line" + id="tspan850-67-3" + x="89.190445" + y="16.687063" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">Versions table </tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" + x="174.55702" + y="16.687063" + id="text852-3-56"><tspan + sodipodi:role="line" + id="tspan850-67-2" + x="174.55702" + y="16.687063" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:5.64444px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';stroke-width:0.264583">Blocks table</tspan></text> + </g> +</svg> diff --git a/doc/talks/2022-06-23-stack/assets/garageuses.png b/doc/talks/2022-06-23-stack/assets/garageuses.png Binary files differnew file mode 100644 index 00000000..b66d7f30 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/garageuses.png diff --git a/doc/talks/2022-06-23-stack/assets/inframap.jpg b/doc/talks/2022-06-23-stack/assets/inframap.jpg Binary files differnew file mode 100644 index 00000000..19905a99 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/inframap.jpg diff --git a/doc/talks/2022-06-23-stack/assets/location-aware.png b/doc/talks/2022-06-23-stack/assets/location-aware.png Binary files differnew file mode 100644 index 00000000..f5966865 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/location-aware.png diff --git a/doc/talks/2022-06-23-stack/assets/logo_chatons.png b/doc/talks/2022-06-23-stack/assets/logo_chatons.png Binary files differnew file mode 100644 index 00000000..890cf17e --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/logo_chatons.png diff --git a/doc/talks/2022-06-23-stack/assets/map.png b/doc/talks/2022-06-23-stack/assets/map.png Binary files differnew file mode 100644 index 00000000..1dff3ab6 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/map.png diff --git a/doc/talks/2022-06-23-stack/assets/minio.png b/doc/talks/2022-06-23-stack/assets/minio.png Binary files differnew file mode 100644 index 00000000..a71e9ccc --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/minio.png diff --git a/doc/talks/2022-06-23-stack/assets/neptune.jpg b/doc/talks/2022-06-23-stack/assets/neptune.jpg Binary files differnew file mode 100644 index 00000000..e59f0bfa --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/neptune.jpg diff --git a/doc/talks/2022-06-23-stack/assets/quentin.jpg b/doc/talks/2022-06-23-stack/assets/quentin.jpg Binary files differnew file mode 100644 index 00000000..d9a7b1e7 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/quentin.jpg diff --git a/doc/talks/2022-06-23-stack/assets/rust_logo.png b/doc/talks/2022-06-23-stack/assets/rust_logo.png Binary files differnew file mode 100644 index 00000000..0e4809ec --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/rust_logo.png diff --git a/doc/talks/2022-06-23-stack/assets/slide1.png b/doc/talks/2022-06-23-stack/assets/slide1.png Binary files differnew file mode 100644 index 00000000..eb2e67a0 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/slide1.png diff --git a/doc/talks/2022-06-23-stack/assets/slide2.png b/doc/talks/2022-06-23-stack/assets/slide2.png Binary files differnew file mode 100644 index 00000000..126a39b8 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/slide2.png diff --git a/doc/talks/2022-06-23-stack/assets/slide3.png b/doc/talks/2022-06-23-stack/assets/slide3.png Binary files differnew file mode 100644 index 00000000..a39f96bf --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/slide3.png diff --git a/doc/talks/2022-06-23-stack/assets/slideB1.png b/doc/talks/2022-06-23-stack/assets/slideB1.png Binary files differnew file mode 100644 index 00000000..b14b6070 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/slideB1.png diff --git a/doc/talks/2022-06-23-stack/assets/slideB2.png b/doc/talks/2022-06-23-stack/assets/slideB2.png Binary files differnew file mode 100644 index 00000000..a881a796 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/slideB2.png diff --git a/doc/talks/2022-06-23-stack/assets/slideB3.png b/doc/talks/2022-06-23-stack/assets/slideB3.png Binary files differnew file mode 100644 index 00000000..830709d2 --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/slideB3.png diff --git a/doc/talks/2022-06-23-stack/assets/slides.svg b/doc/talks/2022-06-23-stack/assets/slides.svg new file mode 100644 index 00000000..9946c6fb --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/slides.svg @@ -0,0 +1,4326 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="1280" + height="720" + viewBox="0 0 338.66667 190.5" + version="1.1" + id="svg5" + inkscape:export-filename="/home/lx/Deuxfleurs/garage/doc/talks/2022-02-06-fosdem/assets/slide1.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + sodipodi:docname="slides.svg" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview7" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:document-units="mm" + showgrid="false" + units="px" + inkscape:snap-global="false" + inkscape:zoom="0.77058782" + inkscape:cx="609.27514" + inkscape:cy="365.95439" + inkscape:window-width="1918" + inkscape:window-height="1033" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" /> + <defs + id="defs2"> + <marker + style="overflow:visible;" + id="Arrow1Mend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path12266" /> + </marker> + <marker + style="overflow:visible;" + id="Arrow1Lend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Lend" + inkscape:isstock="true"> + <path + transform="scale(0.8) rotate(180) translate(12.5,0)" + style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path12260" /> + </marker> + <marker + style="overflow:visible" + id="Arrow1Mend-3" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path12266-5" /> + </marker> + </defs> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <image + width="26.416821" + height="26.416821" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 +AAAAAElFTkSuQmCC +" + id="image1263" + x="134.51895" + y="20.857262" + style="stroke-width:2.00314" /> + <image + width="23.48313" + height="25.232662" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 +gg== +" + id="image1275" + x="220.0013" + y="21.449341" + style="stroke-width:10.7374" /> + <image + width="30.300915" + height="29.702868" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 +NXb+cQHjSzf2DwNl/pKGHoE0T2E7UXwO7KpBFexX8dF/KEhI/zT0KtpIW/TITz+A5UHeVOSJ9ROb +6uncafKBMBwlW++6qYWi4fQlRmSSbkpMhJjpWQIt9yPCWa09JrOhrOn8L8mwl1d43y+bf+UvB7eB +YnH6PwwE8Za0Qn3QcVm4cKB1qmpJ6iqemvooqXlVym3K5jbnv/67n/ntgQj5rNTS/wEy4kxsAPkh +XQAAAYVpQ0NQSUNDIHByb2ZpbGUAAHicfZE9SMNAHMVfW8UPWhzsUMQhQ3WyICpFcNEqFKFCqBVa +dTC59AuaNCQpLo6Ca8HBj8Wqg4uzrg6ugiD4AeLm5qToIiX+Lym0iPHguB/v7j3u3gH+RoWpZtc4 +oGqWkU4mhGxuVeh5RR8iCGEGcYmZ+pwopuA5vu7h4+tdjGd5n/tzhJS8yQCfQDzLdMMi3iCOb1o6 +533iMCtJCvE58ZhBFyR+5Lrs8hvnosN+nhk2Mul54jCxUOxguYNZyVCJp4ijiqpRvj/rssJ5i7Na +qbHWPfkLg3ltZZnrNIeRxCKWIEKAjBrKqMBCjFaNFBNp2k94+Iccv0gumVxlMHIsoAoVkuMH/4Pf +3ZqFyQk3KZgAul9s+2ME6NkFmnXb/j627eYJEHgGrrS2v9oApj9Jr7e16BEwsA1cXLc1eQ+43AEi +T7pkSI4UoOkvFID3M/qmHDB4C/Svub219nH6AGSoq9QNcHAIjBYpe93j3b2dvf17ptXfD/nLct0c +jlvtAAANGmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlk +PSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpu +czptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6 +cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRm +OkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9i +ZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94 +YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9y +Zy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94 +bXAvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4 +bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50 +SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDozYmZlYzk3MS01ODI1LTRlMTEtODk3YS00ODBjOGUzY2ZlODci +CiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Y2E4N2VhMzAtNTY2NC00ZDU4LThmZGItODZi +NWI3YmJiMWI4IgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6YWYxNmU4Mjct +MDJjYS00YjQ3LTkxYzEtYmJiOGY4NmMyYTI4IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAg +R0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iTGludXgiCiAgIEdJTVA6VGltZVN0YW1w +PSIxNjMyNDkyMDU0NzQwNTU2IgogICBHSU1QOlZlcnNpb249IjIuMTAuMjQiCiAgIHRpZmY6T3Jp +ZW50YXRpb249IjEiCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIj4KICAgPHhtcE1NOkhp +c3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2 +ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5p +aWQ6Y2MzMmRmN2ItYjQ2YS00NTM0LTlhYjgtMmRhOTRjOTYwY2NmIgogICAgICBzdEV2dDpzb2Z0 +d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMS0wOS0y +NFQxNjowMDo1NCswMjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAg +PC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg +ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9Inci +Pz55r0WaAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5QkY +DgA218SFAwAAIABJREFUeNrtndl3Glfert8qqjQgNFvzBAjZElAMshPHAo1MstP/57k5w+rO3N9a +5+ac7tjxEOc76+vEkkAzAqHBEZoioOpcFGC7MycequB9bnKRrJWlAh5efnvvdwuapmkghBATIvIR +EEIoMEIIocAIIYQCI4RQYIQQQoERQggFRgghFBghhAIjhBAKjBBCKDBCCAVGCCEUGCGEUGCEEEKB +EUIoMEIIocDeJMVika80IRSY+SiVSvjuu++Qy+VQKpX4ihNSQ0i1/gcWCgX8x3/8HW2tbbgzMwO7 +3Q6r1cpXnhAKzPioqor19Q3867/+hWfPniEWjyMQCKCrqwsWi4XvAEIoMOP/jNza3MT+fhZbW1tY +XFzE7NwcHA4Hmpub+S4ghAIzLgIATdOQP8nj8aPH2N3dRSqVQiweh8/nQ3d3N9MYIRSYwUUmCCgW +i9jd2cXnn32Ozc0tLC0tIjw7yzRGCAVmDokBwMnJCZ48fozM3h6SqRQikQiCwSBnY4RQYOYQWaFQ +wPb2No6Pj7GeWkckGsHc3BzGxsbQ3NxclR0hhAIzpMQ0TUM+n8d/fvMN9vezSK6tIRaLwx/wczZG +CAVmDq6urrC9tY2jwyNsbm4iFothJhSC3W5nGiOEAjM+mqbh9PQUTx4/we7uLtaSSUSjUfj9fnR2 +dkKS+LgIocAMTqlUwl56D59+/Am2NrewsLiAcDjMNEYIBWauNPbwq6+ws7ONVCqFaDQKn8+Hrq4u +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 +" + id="image1287" + x="175.31808" + y="19.214237" + style="stroke-width:3.53931" /> + <image + width="29.202602" + height="29.202602" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 +CkvGGFWwV9YDEF1SwgLIYD2Q2SEME02Fgc5Qhu4WKndlxyBtslYOunEW0+Yk/9ACqQf/3LR7saUl +ZKNI1xbp8kBcEWqJbjm+yoStoaw1zM8eoz15E/rohzi8/X3o8XlQinGsFCJO1NdIGtfv0xcdILdg +CD42SgZM7RmuoWZPFZ1umEHyeFRt5KYa6V5SuQABhfCX41r628IejAY/rlc9aH+O3EROA+oArbmw +5Ia3w/oRus4tKWTGD/L+mn/LJIL/lmGFiJtqi/tNE2dt1dFlTZfROswHMKcwCoVkQJl2iE45LXjv +eRMmAKadx6wOV8jWXdnM14Av//LfxP/8F/4dfAagCy+TfxQUlstftl+F4c/hne//unzwkz+JMk99 +5zIENbJbQ7Rs0tLwgGK4hLmzxK4ewLtmUBw5wWdmUEhPk6iHNozasLPHn3E5kFW2RRuAUnwyTTMp +atTjO8E7Fen3Vp1Pa7PDiywkaXRV4zuV9B8QB99OG0eft2uvQzseIDdPII/egH3wOuzxO5DFudiC +eM9WcAkg8QLZVm2I9ZjTAElwjdGSMiLDRYdYSigsQi9cX7CVVpkYjuGGMMQCs7iYn+9jrbkZGa2D +rjwckNoJ74iEl6CjRj++DfdlYOYyNpk4YOC0XANoRXcJx2lg/bdSt5qiKyzeijEhEvGuhlFBDlad +jbnp4bPhJtdjd3MFbsHzkRmTFxIbx8S5vDQvaIdBrC22ffgTvPrV/xG//p/9Bv5t+b07J9pnTP7R +UVhdLvHf268C+Gcg+Ofk8Y9/xW6eP0S93gHwrjfk6XZczwxjA9NVWQiAQMdbJW4HgNriXNbi/eUy +5cwd2VHF4RYNqPKolKfbgHzHeFwFpkLero13z4F6nKca23dJWgBSJm+IUcoqXp0Koy5+P7HDLxWC +A0wmyHTp93u4hjz6MfD6t2BPXoftryBVEIyoCQ+wQZmvCHBLty7G8+fnSiosv2xCFaL+M42i7hKm +Xgl3hy5g6ikoDM3zAGHFxRijjw+A4drDreEVqj/77O9XG2TqCsuM546MXwT4QzEhXo87jutUlsb4 +3EmDaMTqRcVF0DsrDftQuJm189iSYYA7REG1FFdWtoxEpa7sysSg+YIVcJRKN65HrLqy2lw6hff+ +cAPRZ/bq176LX/31P5z+5D/7jeXPye8DuMJZznKWs5zlLGc5y1nOcpaznOUsZznLWc5ylrOc5Sxn +OctZznKWs5zlLGc5y1nOcpaznOUsZznLWc5ylrOc5SxnOctZznKWs5zlLGc5y1nOcpaznOUsZznL +Wc5ylrOc5SxnOctZznKWs5zlLGc5y1nOcpaznOUsZznLWc5ylrOc5SxnOctZznKWs5zlLGc5y1nO +cpaznOUsZznLWc5ylrOc5SxnOctZznKWs5zlLGc5y1nOcpaznOUsZznLWc5ylrOc5SxnOcvnV/5/ +fl07WlwHHbMAAAAASUVORK5CYII= +" + id="image1359" + x="90.934036" + y="19.464371" + style="stroke-width:2.71808" /> + <text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="169.47803" + y="66.103424" + id="text4115"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="169.47803" + y="66.103424" + id="tspan4117">User-facing application</tspan></text> + <text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="73.229004" + y="183.68733" + id="text4115-3"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="73.229004" + y="183.68733" + id="tspan4117-6">Database</tspan></text> + <text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="266.38498" + y="182.65727" + id="text4115-3-7"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="266.38498" + y="182.65727" + id="tspan4117-6-5">Filesystem</tspan></text> + <image + width="65.039139" + height="65.039139" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABmJLR0QA/wD/AP+gvaeTAAAgAElE +QVR4nO3debxddX3v/9fnG4aQAYgQhqiAcQASLWIcqqBYxKEK1TpUH3qx2sd1eLS92tbWsbe1va11 +6PWn1qpwb2uLQyveqjU4IbaCwWolCMUEEA2DSoCACZBAmL6f3x9rHziJCRnY53z32ev1fDzWI2ft +nCw++3D2+r7XWt8BJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS +JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS +JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS +JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS +JEmSJEmSJEmSJI2iaF2ApKHbG5gzaX8PYP6k/QD23+rfbABy0v6twN2T9m8D7hhijZIaMwBIbc0H +DgEOBPYb7O8HzAXmllL2Hbw2NzPn0jXc8yb+HlhA19jvPc1130EXCtYDmwbbRmBDREzs31prvXnw +fZuAm+mCxc3AjcB1g31JDRgApOGaCzyYrkFfOPjzkFLKgcCBmbkQOHjS3013wz1q7qALA+uA6yLi +RuDGWus64PrB6zcOtp/RBQlJQ2AAkHbNAmAxsAg4tJSyGFicmROvHYKfq6m0GbgWWBMRa2qta4C1 +E68BVwG1XXnSzOGJStrSHODIwfaoUsoRmXkYMLH1/Yp91N0BXANcExHX1FqvAn4IXD7YbmtYmzRS +DADqq4kr+aWllCV0V/FLgaOA0rQyTaW1wKrB3YPVwCq6OwdXsmUnSGnsGQA07g4AlgHHllKOzMyj +6a7uF7QtSyNmPXB5RKyutV4OXASsBG5qW5Y0dQwAGif7AY8BlpVSlmXmMuBo/D3X7lsLrMzMlXSB +4D/pOidKM54nRs1U+wOPxsZe089QoLHgyVIzxcOAp5VSnp6ZT6N7fi+NijURcV6t9ZvAuXSjEaSR +ZgDQqFoMHF9KOS4zn0kXAKSZYm1ErKi1ngOcD6zGToYaMQYAjYqjgRNKKU/LzBPoxtRL4+LaiPhm +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 +P2vmuIUtH0lsb+TDgklfz6JrvDVzXJSZH6Obvc/JsbRDBgDtjmWllNdm5ivwKk5qaXNELK+1no4d ++7SLDAB6IBYAL42Il9OtbubvkzT1KrAiMz8NfIb+9iHRA+QJW8PyELqFh04FlrUuRhpDqzPzs8An +gB+3LkYznwFAU+GXSikvz8yX0g3ZkrR7roqIz9RaPwVc0roYjRcDgKbaUuDkiDgFeAr+zkk7snrw +XP8s4Hy6KXulofNkrOm0mG5Fwucys9dKl4bpDuBbmfll4AvAlY3rUU8YANTKHOAppZRTMvP5dLO5 +SX1xXUR8vda6HPga3VBNaVoZADQqjgSeVko5ITOfTjftqzQufhoR36y1ngecC/ywdUGSAUCjahFw +XCnlpMw8nm5BF2mmWBsRK2qt59A9x1/VuiBpawYAzRSHAycM7hA8DXhE64KkSa6IiPNqrefSXeFf +07ogaUcMAJqp9gMeQzcr4bLMXAYcjb/TmnprgZWZuRJYCXwXuKFtSdKu82SpcbIv8EsYCjQ8Wzf2 +36FbuEma8TwxatwtAB4HHFtKOTIzjwaOoluRTppwE3BZRFxaa70MuIiuwXeaXY0tA4D6agHdvARL +SylLgMWZuZRuNMKsppVpKq0FVkXEmlrrarrOeWsGm9QrBgBpS/vQhYAjgUeVUg7PzMOAw+g6Is5u +WZx2aDNwNXBNRFxTa72absjd5YPt9pbFSaPEACDtmgV0QxQPBRaXUhbT3T2YeO0IoLQrb+ytp7uK +v3ZwFT9x9b4WuBa4im61PEk7YACQhmsfuiBwEHAgsBA4qJRyEHBgZi4cvHbw4O/3aVXoiLgduBG4 +HlgXEeuAG2ut19N1tls36e+vwyt4aWgMAFJb87gvEMwH9h+8NheYW0pZMPE1MC8z96ebRnku3aiH +fYE96YZFTqebgbvoprC9BdgE3BYRG4CNg/1Ntdb1E18PXt8A3ErXoN8weF1SAwYAafwEXZCYbMEO +9tfvYH8DrkonSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk +SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk +SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk +SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk +SZIkSZIkSdKU+/8B8UD79J00AfcAAAAASUVORK5CYII= +" + id="image7444" + x="42.921436" + y="103.45746" + style="stroke-width:2.08285" /> + <image + width="48.37225" + height="48.37225" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 +" + id="image7640" + x="242.05431" + y="112.98055" + style="stroke-width:2.62548" /> + <path + style="fill:none;stroke:#000000;stroke-width:1.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#Arrow1Mend)" + d="M 147.15073,80.962811 V 93.477746 H 74.989012 v 12.859624" + id="path12236" /> + <path + style="fill:none;stroke:#000000;stroke-width:1.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-3)" + d="m 190.64085,80.962811 v 12.514935 h 72.16171 v 12.859624" + id="path12236-6" /> + <image + width="34.06498" + height="34.06498" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 +" + id="image13044" + x="74.692276" + y="130.02034" + style="stroke-width:3.97671" /> + <rect + style="fill:#ffffff;stroke:none;stroke-width:1.265;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:0.73301107" + id="rect13086" + width="130.82137" + height="113.0019" + x="32.462914" + y="73.501602" /> + <image + width="57.52919" + height="57.52919" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ +bWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp +bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6 +eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0 +MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJo +dHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlw +dGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAv +IiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RS +ZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpD +cmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIFdpbmRvd3MiIHhtcE1NOkluc3RhbmNl +SUQ9InhtcC5paWQ6RDJGNjUxMzYzRkI5MTFFQTgxMTFCMzM0MTU3RjA5QjciIHhtcE1NOkRvY3Vt +ZW50SUQ9InhtcC5kaWQ6RDJGNjUxMzczRkI5MTFFQTgxMTFCMzM0MTU3RjA5QjciPiA8eG1wTU06 +RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEMkY2NTEzNDNGQjkxMUVBODEx +MUIzMzQxNTdGMDlCNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEMkY2NTEzNTNGQjkxMUVB +ODExMUIzMzQxNTdGMDlCNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1w +bWV0YT4gPD94cGFja2V0IGVuZD0iciI/PhMrP1wAAAGAUExURWOmTOnp6pCQj1GYO2hLIm1tbfl6 +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 +MNJGE1brA6mS/l65sLZFnYpfQPusDlhpYAWGh5M16oXJPzeXD+sfKdUi3Jnsr5u1Xi+t3A0r2xOW +hevKPOaka8Kq0LomVIRmJNkyWudZWKpF0e97rpU+qJW0IXqj7bPcSjHXea79RbBaSkKtlHN0M9HU +7IsQwKq3d/D0xbDkfPhasKDXNjUhuF8x4uzUhectS3LadbIlttXsupl0eDQ211V51fZZXWlSOU+1 +9+aSyrliXVY6uqM9UpFuDItim5Fhh4M/Awtv1vVgNcod3hXPdm9J1x6WFS5eOE+jONqw0vVsl7hp +Soe0IrcbVxKsHaM6STRZM532JVsqNxalat3dnD8Kn0fYhHVNy4JbauW61Ljcqos/a1lqIxxJXw1W +t7hp6Sw672y3nLV7VAcPF6ZqWYfY/AwlT9083InozLlcR+pOzdXjpZLcCiiuDashZbpiPbe9XlZ7 +WtalsOT8FWAVuzKlPSYkValStgjNGLj3mSBXg9VUuvauZAYYb87qFNM3hoW7uCppd2eoZyGutYdl +FdM9Zx4URSbDw3dgcZnuTGnvlGFzgCbf48awLHqPK3SuslQx46ZS9M1hoXPNiJy9PXanI2R+87zP +qmiJD3eqfmbmIeMkfeq7sKgrZWH0MSfdW2tdDZZa1C/H7ujcLxVvUThiv7llgWulLM6IKNtbs+pE +4/QYDTd0WJFsrdzdSLbnB8HK2W8NS21kdefiPnfy8q1g6Uon64zE080kEE4GX6yz0rRF6lU+dFNY +Zx7NpfUE121gVZojN+bhunOzxYth6TU1BbYFa/M8LC2BbqnrXzOCUUrbslrDVjjuvkxe3wiWuhmh +c1RX+rygp3csN++GoIlKrYoKUiDT+sbhS2DVWL1vNTdbU3Nnu6GeLpb0WVWARXVZVq01j6T5xtOu +OoR28v8msFS8mVxEU/zkt54VO+XytdvAUlupnwLXNSRaNWXZE1alrn92c2eVsG4/TVgWfdBRcTs8 +/Z5SHZaV7th2Wefsjnfss2QV6GxXpvRasMKYk3Wn4y1RpX97HMfKt4HVkRp32+N03kompDccgpy+ +WGc1x4U0S7Y/ppys/bRTlDpKspDT+5kjpZcZEFg5penvw5YM5mDAEwjNK9HLzaicwKXTrOZdbgBL +ddj1rB8byedyuQwtF5oxcDas3gYWVjK1g5OUjLGZ3BZJPWE1rM3cG+uw5pyC0i5TAV9E+pqbo3PA +PZtpxXZ4VVIzdE/HBdYO1tYgA567OSeeyVlzmTr5dwFo3Ww0lNiO6+G4dnBYYDfKN44N9Vaj7ReX +HPUMpEHkFVpZtQ41pWUdLLrLBvBxPUfs1u+pGu4KpRQnZjQkR8u2C/aUvfnOaZKtuwGsSt19waUU +exvWdWBJtYjivohVSsj1gCVZlTPPK7hbluVsX3uTo1106pK9K7qBIRBNq9zrbmEZg9qasLhEwZ/v +hpa6vWf+hr5oWubqsODDwpl4z/xQwS4LTgumSnVrAQ+puSKp2FWCWpC1rlSoo2WJZ5KUbjubb97T +pq/V1VeGjFhU5OzHu9N4aWp7jk3shtWsKVUyvdaElCNc+lzJpxzJXbT3+XVWWEBPsBRL9kJHjRyZ +Mk6VhEy2HNZqSgtYSon6otGkVWjNLtvjGdqOU1ukG+J37XgveB/R2U6wWOWO17FODUElD+OJu2uG +wbFJBtacTObPelgWaT0dPFkspnS84WnzDRu3hwWyqEJZi+1gzk2KocVIJmfRD7kp0wrO6aXqel5d +xd3siDm40fwyWauAT1AiOOckwXgmayzdhTTHRpyW9nx3xVrXX0fuRViTZZQ1Etfpu9N2TozkN/W7 +JKbs+MZnplQsIu4YbldYZ09RCmLYScf1YgH4hvAd4A0vnA677npDMlFVFESyaAALksiBQFSz8ESy +5MmpAc72dw5vOgR4NivSGauFgm9XpOmi7hUqlpxDEONxeCeh6Nwsdy5jqVgcdRZ/Ql7XFJ+VMr4C +H2frtGMjW9PnACvZDH5w8Yy7qcCXLUYixfwFLhuGEvgKNH6Q9h2ytUuW0lx7caaq1TBlrdA2s5Za +GZNDnaFLmAS37Y/EPKRlE56LZyxJ+hN0Q1TRVsl7ZS3l1txnO99rseqvUztSHa1X1Lpeon/wWbOQ +wpevJoKvV6FqWe1qzn6H28LSCtgqlYo2u1s5s4URWQMjdT2ID1XIU/VEQ+fPyQ8rPd5I/xztdWrj +/NuFz9yk5gerje98nx4FU/r1VL6zsfNNt1e5NI12wbMvCPYvzcf1/ol6yw0aLrqaH1JFYzQDlgHL +gGXAMmAZsIxmwDJgGbAMWAYsA5bRDFgGLAOWAcuAZcAymgHLgGXAMmAZsAxYRjNgGbAMWAYsA5YB +y2gGLAOWAcuAZcAyYBnNgGXAMmAZsAxYBiyjGbAMWAYsA5YBy4BlNAOWAcuAZcAyYBmwjGbAum77 +/wIMAMjwwmKcHMt1AAAAAElFTkSuQmCC +" + id="image13664" + x="181.2438" + y="110.06655" + style="stroke-width:1.37973" /> + <image + width="30.972841" + height="42.781239" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 +AElFTkSuQmCC +" + id="image13722" + x="297.28098" + y="115.33748" + style="stroke-width:1.45791" /> + </g> +</svg> diff --git a/doc/talks/2022-06-23-stack/assets/slidesB.svg b/doc/talks/2022-06-23-stack/assets/slidesB.svg new file mode 100644 index 00000000..c0a6e97c --- /dev/null +++ b/doc/talks/2022-06-23-stack/assets/slidesB.svg @@ -0,0 +1,444 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="1280" + height="720" + viewBox="0 0 338.66667 190.5" + version="1.1" + id="svg5" + inkscape:export-filename="slideB1.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + sodipodi:docname="slidesB.svg" + inkscape:version="1.2 (dc2aedaf03, 2022-05-15)" + xml:space="preserve" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview + id="namedview7" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:document-units="mm" + showgrid="false" + units="px" + inkscape:snap-global="false" + inkscape:zoom="0.77058782" + inkscape:cx="408.77885" + inkscape:cy="376.98494" + inkscape:window-width="1678" + inkscape:window-height="993" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer2" + inkscape:showpageshadow="2" + inkscape:deskcolor="#d1d1d1" /><defs + id="defs2"><marker + style="overflow:visible;" + id="Arrow1Mend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"><path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path12266" /></marker><marker + style="overflow:visible;" + id="Arrow1Lend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Lend" + inkscape:isstock="true"><path + transform="scale(0.8) rotate(180) translate(12.5,0)" + style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path12260" /></marker><marker + style="overflow:visible" + id="Arrow1Mend-3" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"><path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path12266-5" /></marker><style + id="style2">.cls-1{fill:#3b2100;}.cls-2{fill:#ffd952;}.cls-3{fill:#45c8ff;}</style></defs><g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + sodipodi:insensitive="true"><image + width="26.416821" + height="26.416821" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAIABJREFUeJztnXd4lFXa/z/nmZkk k8ykRzoJASmGZgGUpiFUlXVfWXF17fXddXVFwLa7btz3/bEi2HVd31V0Lbsr1hVFkUCUJiKK0kRa IIChpE9mUmbmOb8/QKWkzMzzzDzTPtfFdYXMPOd8CfnOafe5b0GcoGAvmZOlKAl5XtQ8oSp5CNkZ RDbILODYH5EIMu3YIwlAyrGvnUDL0S9FHchmoApkJYgqoApJhVTkXpOkTLrNe+on31Ud2n9hbCCM FhDppJQ83gncgxTEEAQDJXKwgD5Aaoil1AM7QGxCyk0qbBReNjZMmn04xDqiirhB/GHhQpMte3d/ gXkUUo5GMBpJL6NltYukAiG+BLkKVax21DSsY3pxi9GyIoW4QTrAXvJIX4GcIhU5GckYfpoGRSpO YIVAfoiXj+on3r3DaEHhTNwgJ7NwocmWWTZWUUzTpFSngMg3WlKQ2SXhQ1TealjtXEFxsWq0oHAi bhCA4mLFPso2Ugp5mYDLEHQxWpJBVAGLVcnLzlXO5XGzxLhBrEse6WEyqzcANwjoabSeMGOvlGKB 1+Rd0Fh4z36jxRhF7Blk4UKTPbN8KsibEEwGTEZLCnO8SD5C4e+Oytz3mT7da7SgUBIzBskpfcbW JBuvRMqZQF+j9UQmYg/Iv5kU83O1hTNqjVYTCqLeIMkfz+1qMil3ALcC6UbriRJqQDzntZiedI2d UWG0mGAStQaxl87PRpWzgDsAq9F6opQWpHzJm2ApjlajRJ1B7CVzsoSw3C5hBqE/zY5VnFLygsQy xzn+zkNGi9GT6DHI4icT7QlNv0OI3xM3hlHUgfxfR5XryWg5rY8Kg9iXzZsKPAb0NlpLHAB2SuT9 DUV3v2G0EK1EtEHsy+f2E1I8IxFFRmuJcyoSuVTxclskh7NEpkFKi82pXtttUsj/R+THRkU7TQLm 1lc550TitCviDJJS+vBQRRXPA2cbrSWOX2wUqDfXF92zzmgh/hA5BiktNqeqyX+SiPuIn35HKl4h mVNvcv6ZwmKP0WJ8ISIMkl76lzyvan4VGGW0ljh6INYJr3pVJKxNFKMFdIR9+cM3eFXzJuLmiCLk cGkSX9pL5l9rtJKOCN8RpLQ4yS5TnkZyo9FS4gSVVxxW062MvKvRaCGtEZYGsS55pIfZLN8EOdxo LXGCj4QNJg/T6ibNLjNay8mE3RQrrWT+BLNZ3RA3R+wg4EzVzLqU5Y+E3XlWWBnEXjLvRlXIDzia FidObJGtSHWJbdnDvzZayPGEx3aplCJ1rK0YwaOEi6Y4RqAIxEUJ10zMbMkf9TGffCKNFmT8GmTx k4m2xKYXBeIKo6XECSMkbzmSTVcbvXg31CCdlsxLabTwrpSMN1JHnLDl08REdWrl6HscRgkwzCDp pY+le1XPB8BIozTEiQi+EG7TZKNSqxpikNSPHs2UFvXD+E5VHF+QsAGLe1LD2PuPhLrvkBvk2FXY UmBgqPuOE9FsRhGFjsJZlaHsNKTbvBlLH0pDlR8RN0cc/xmIKkvSVv4lI5SdhswgXRcVJ3sU0yLi YepxAmeI2mxanFP6jC1UHYbGIKXFSY5k2/vAmJD0Fyd6EeLcZtX5LoufTAxFd8E3iJTCJpOfB1kY 9L7ixAQSUZSa2PwyUgZ9DR30U2v7+fa5QhJW4QNxooKCpLI15uaXly4PZidBdaB92fybQP49mH3E iW0k8jcNRXc/G6z2g2aQtJL5E1QhFwPmYPURJw7gljC5oWh2UEaSoBgkY+lDPT2K6UsgOxjtx4lz EtWKh3OCcZ9E/0V6aXGSR5jeIm6OOKEj02vmLdY8qnsOZt2nP3av7RmEPEfvdiONkendmZSVT8+k VJJMZpweN07VTa27CZfXzeEWJzsba9jhrOZgi9NouRGPgDPtLu/fHKDrPXddp1j25Q/fgBQv6Nlm pNE/JYun+k9iRFpXn59xeFrY2VjNhvpDrKzdx8qacg63uIKoMnoRiGvqi2a9ol97OpG27OHeKmID YNerzUhjRFpX3h7yC+zmBE3tSGCbs5IVNeW8c3g7n9Xux/CbQ5FDg/DKs/RKKaSPQUqLzXZv8kqE OFeX9iKQnIRk1g6/jpyEZN3b3ttYx78ObuX1Q1vZ5arRvf0o5AtHWuoozrnVrbUhXRbpqWryn2LZ HAB3550bFHMA5FrTuLfXeWw490YWn3U5RZl5Qeknihhmq6u/X4+GNI8gqSXzh0khPyOG75KbhcLO 0b8m0xK6QlabGo7wdPl6Fh7ailfGJ2Ct4FGQI+qK7v5KSyPaRpDSYrMq5HPEsDkA+iZnhtQcAINs OTx3xhRWD7+WsRnxCtatYFYRC1j/nEVLI5oMYlNT7hFwppY2ooHsIE2tfOGMlGzeP3M6LxRcROeE eCWIkxhir6+/U0sDARvEXvJIXwF/0NJ5tFDraTJaApd1GsCX597If3c/C0UYn6wmbJA8mFr6aJ9A Hw/YIEJ4/wokBfp8NLHDVUOjV/OGiWbs5gQe7juOt4ZMC9qGQQRildL7VKAPB2QQ+7J5/xUve/YT jV43H1TuMlrGjxRl5rFq2DWMyehhtJTwQDI5dfm8iwJ51H+DLCxOAOYG0lk0M6dsNU1q+NSE6ZJo 472h05mVN8JoKWGBlDweyC1Evw1iz0qZAZzu73PRzk5XDbd9uwQ1jLZcTULwQP4Ynug/EVN8XdLH ntB8m78P+fVTO5ayZxfxOuRtMikrn6cHTKJTmO0oLTqygxu3fBBWo5wB1Aq3qbc/Sej8Or9IvHpC MYJx/uuKHXY11rDgwDccbHEiEHiRmIRAQWASxiXT75eSxXnp3XjvyA5aVK9hOgwmCbPqbfmH79d0 fR5BbCvm5Ai3ZTcQspQr0YZFKKSZk8i1pnF6cgZ9kzMZYu/EyPTupJg0nWf5zMqafUz75q1YHkmc 0kN+w6TZh315s+/3Qdzm3xM3hybcUqXS7aLS7eLL+oofv28WCmendmZcZh7TOw2gd3LwcqONyejB CwUXcc3m92I1RCVFmJkNzPblzT6NINZlj3Yz491J/NwjJAxP68oVnQu4svMZWIM0srz0/UZ+t+3j mAyjl+CS0pLvHH/noY7e69MaxHrdhD8A52tWFscnDjQ7WFK1m39UbMIrVQbaTiNR0Tfcbai9E27p ZU3tAV3bjQQEWBShNvuSMqjDESR71Vx7c7NSDqTroi6O32RYkvhj/mhu6DpE1zASr5RM3bCQVbX7 dGszgqhJUpJ7Him8raG9N3W4rdLSrNxI3ByGUuNu4q7vSihc/xobHAd1a9ckBC8UXBSrYSkZzarr uo7e1L5BFi40SeTteimKo40NjoMUrf8nfylbo9uBZJdEGwsKLo7Jg0QpuIuFC9udu7b7ov3WEZcg 4mlDfcVuTqBroo1cazq9kzPol5LFEHsnelnTsQgTlW7tiRhUJKtq9/Gl4yDjs3qRrMMiPs+aRpW7 6YSdtRghIzG5bn3Ly0u3t/WG9rd5hbxZd0lRQJJiZlhaF4baO9EnOZM+1gxOT8ns8D7GnsY6Ht37 Oa9UbNK8xbq0qozC9a/y7tDLyLdqnwE/kD+a945sp6K53Sl5NHIzsKitF9scV62lc7ubVWUPMX5b EEARgmGpXTg/oydjMnoyIq0rSUrgKcU+rtrNtZsX4dQhRL5TQgpvDpnGEPtpmttaePBbbtr6geZ2 IgyPB1NeY9FdrW7ntbkGsajKTcS4OQakZPPn3mPZOvJWlp59JX/IH835GT01mQNgYlY+zxdcpEtK mUMtTi7e8DqbG7SX77us84BYvL5rNuG9rq0XW/8/Ki5WbGNSygTE3E/Lqpi5qstAruk6iCH2TkHt 64Yt7/PmoW26tNU5IYWlZ19JrjVNUzubG44wat0/YusAUVDmKJzVGyFO+We3OoLYxthGx5o5bKYE ZuQOZ9PIW3ik3/igmwPg9p76ZWg92OLk59+8SbW7UVM7A205TMnurZOqCEHSy75sfqtpq1o1iCKY HlxF4UOyycJ9vUayddQtPNh7LKeF8ExgqL0z2Rb9+tvlquGWrR9q/vSfnRd7Kc5kG7/zpxqkuFiR qrw06IrCgJ/lnM76ETdwX6+RpJtDH2YmgB5J+l6t+bhqN0+Vf6GpjbNTu1CYmauToshASC6nuPgU P5zyDduYlAsQdAmNLGPoZU3nzSGX8uqgS+ieZGwqYa9UdW/zwV0r2djgUzR3m8zMjbGruoIu9tHJ o07+9qmOESKqR48buw3h8xHXMTEr32gpeKWkvKle93bdUuWObR9rOm0fk9GTPI0L/khDCqad/L1T DCKlnBwaOaHFbk7gpYFTeazfBM3btHqxtu5A0HJqfVV/kFcqNgf8vAB+2fkM/QRFAAJxyu/+CQax lzzSF4i6LYyh9k6sHHYNl57Wz2gpJ/DI3s+D2v6Du1bi0nAY+cvOBcGt8hp+9Etb9vAJv/8nGEQg p4RWT/CZnN2bJWf9UpdwDD15rWIzJVW6l9Q7gUq3ixe/3xjw8/nWdIb5UQgoGvAKZdLxfz/BIFKJ runVVV0G8s9BlwTtVl6gvPT9Ru7Y9nFI+nqy/AuaNSRpuCSnr45qwh8hOWGQ+MkgpcVmJKNDrihI zMgdzjMDJmM2MJPI8XilZHXtfi795i3u2PYx7iDsXrVGRXMDb2k4rY+90BN5AaXFPy5Sf/wi1Ws7 UwoZFUkZ7u11Hvf3OmXHLmiox3ajdrqq2e6qZndjLS6vG6fXTa2niWp3E+VNddS4jUly/a+DW7iy S0FAzw6y5ZBlsVKl8YQ+grAle+yDXLABjg93FzJ0v1FB5KZuQ0Niju2uapZX72FFTTmravaHRYb3 tlhRU055Ux09k/zftlWEYFR6d947okvJv4jArKijONkgEiLeIBfn9GFe3+Dl1K5xN/Huke38++AW PougZAcSeOfwdn7Xc1hAz4/N6BlTBuGoF56GEy9MnWeMFn0Ym9GTFwumBuXq6E5XDY/tXce/D24J 2dpBb0qr9wZskME63DWJKMRPg4UCkFL6cGegm2GCNNIl0caLBRfrnhqnvKmOG7a8zzmfL+CVik0R aw6Az+oOBLybdXpyps5qwhsp6WFbMu80ODaCmFUGR+p/vSIEz51xoa6ZOdxS5YUDX/PgrpW63PoL Bxq9btbXVzAqvbvfz2ZZrGRarH6F0gvgrNQuDLLlkGZO5IjbxdraA+xurPW7f0MwMxBYbgZQpTKI U++KRAT39xrFBTpuRW5uOML1W97nO2eVbm2GC1sajgRkEIA+yRmsq/PNIL/o1J8H8se0Gsv1We0B 7t/5SdgniFBgMLD86CGBIgcZKycwxmT0YJaOUacvfr+RovWvRaU54OjOW6D4Ms0SwPy+RSwouLjN QMfz0rvx8dlX8KsuAwPWEgqkFIPg2BRLSgZFWsxNgmLisX4TdMk06DkW/fqqhuC+SGCHBoNk+VDm +u6887ile8dFjy1C4en+k/i+2UFp9d6ANQUVcXTQUAAEBFwF1Ch+13MYfXVYPDarXq7bvCjqzQGw t7Eu4Gft5oR2X++ZlMYsP24imoTgiX4TSdB5Y0VH+gAo9pI5WURYxahca5ouUyun183Pvl4YM3v8 Dd6WgJ+1mdo3yA3dBvu9i5hnTWNcZl7AmoJMRnrpY+mKoiTkGa3EX/7Sp1BzAGKL6uXKTe9G1IGf Vhye4BnkgozArugWBvhcKHDjzlO8Kr2MFuIPg+2ncVGOthmhKiW3fvth+M5/g0Sj6sET4FmOrYMP pK4BXl3uZvCV5/ZQVNFLERC+Fm6FWbnnar7E8/CetZoiXCOZygCDDh0dTM8yDEh6EWyEEHkKQnY2 Woiv9E3O5Gc52ipQr6rdx9w9a3RSFHnscAa2k7XTVdPmaxahBBzF0KBh2hdspJSdFBDZRgvxlbty R2ja1q1yN3Ljlg9itTYfcLQctL+oUvJB5c42X7d1sMPVHg5vc8DPBh1BtgIyy2gdvpBuTmJap/6a 2nhg54pYzF5+Ai9XbPL7Z/DmoW3samcE0TK9agjnUB4psxQgIkaQX3TqrykYcW3dAV6t2KSjosjE 5XVz3ZZFPtdK39tYxz072i/lp6Uqr5adtaAjRLYCRESoppYUNKqUzPyuJLYSMrfDZ7UHuGzj2x0G H35Vf5DJX/27w9uEWg5s9zfrnxdMNyRZZiKg9rnW7BrvV+5kkw7lAaKJ0uq9nLV2Abf3PIdpp/X/ MXZKlZIv6it4pWITr1Vs9mm9piUcvr3Fv9FISDEDga+wQsT0zgM0be3O37NWNy3RRLW7kQd3reTB XSuxmixkmpM43OL0+96Llg8vLfFhwUYgEyPCIIGe0sLRT8qvHR3Wi495Gr1uDgSwYM60WClICWwZ e7DFGd5rEESCQpgbxGqycHZq4Lm0X44vzIPK2IweAW+9bw/zawUSEsPeICPTugW8e+XwtLD4SNv7 93G0M0lDEvB1dd/rqER/xDGDhDVaEpe9ffg7GlWPjmriHI/VZOGS0wLPvLiiplxHNcFBAcJ5Eshw DQvAj6t266gkzslMzenTYZRvWzSrXj4P8xFEQnPYGyTQPXb1WKrPOMHj6i6B39T+vO5A2I/uItwN kmZODDhbycaGw34XtDQLha6JNlLNiQH1GUucc6xufKB8UhMJVw1ki5kwNoiWA6gvfMyaIYBpnfpz XdfBjEzv/mOy64rmBhYd2cHj5evY3+QIWEe0oqXQpwTdyl8HE4loNgNhG72nxSC+hHVnWJL4x8Cf tZo2qEuijVu6n8nVXQfx22+X8MahbwPWEm2cae/MZA2loj+r3c8eDffjQ4UApxkI283obkmBR8F0 dEJrVcy8PWRah2csVsXM8wUXAcRNwtFEffP7FWmKbPj3wa266QkylQrISqNVtEWgOyQAuxrbj/H5 ff4onw8gBfBk/4l0TkgJWE+0cH3XwQzTcHDbpHp45/B3OioKKpUKiLAdQewaFsvt1eJINyf5lL/p eFJMFn7b85yA9UQD3ZPsPJA/RlMbbxz6ljpPGF+SOgFZpSAJ4xEk8Mwl7eXUHZ+VF1Cl24s1XveN ZMxC4YUzLibDEvjlKK+UPLZ3nY6qgo1SqYAI20i+QKdYHWXv6Jsc2CXKvKQ0LGFS0i3UPJA/mvPS tRUAeOvQtrAObz8ZITisSEXdY7QQvantoNRZSoAjkyKETyk4o43pnQfwu9zhmtqQwKNBLnutN1Kl TDGh7DFaSFt83xzY+cOBDp7TkmEwRUOCgkhkYlY+zw6YojnV0tuHtrHVGbaz+VZRFVGmyBYluMW6 NbA8wMRuy6r3tPt6MFNwRhOj0rvz8sCpmqeVTq+bP+z8VCdVoSPB696j1E++qxoIy1ObkqqydrNp tEaT6uHl79u/A6IlF1NqjIwgF2X34e2hvyBZhxrzc8pWdziqhyE1NRPurfvhoyEsL024pcpd20v8 ymM1p2wN+5raTwSgJdVMj6SIyvMdEDd2G8Krgy7BGsBO38lsdVbyt31f6aAq1IgdcKz8AYiwvXZX Wr2X325b4lOammf3fcUTPmwjVrpdAeuJ5np9ySYLfxswhcf6TdClGKpXSu7ctjRCazuqG+GHKrdS btK8Cgsir1Vs5ltnJXP6XMDIVkqI7XLVULxrJf85st2n9rRsNUarQQps2bxYMJX+KfrlEXyobA1r 6yIze74QRweNozUKYWO47+7/kKOplzWdc9O6cVpCMjWeJjY5jvC146BfOa8ONDto9LoDKqEw2BZd JZGtJgt39hzGzNwRuhazWVFTzvy9kZtNRlXVnwwiEtzf4Na+GAsFZY21lGmslKpKya7GWgbacvx+ Ns+aRo+k1A7XOeGOWShc1mkAD/QeTbdEfUsQHGxxcsOW9yM7B3KCdzMcW4M0jL3/CBBT1++05GPS ck/eaBIUE9d3HcxX597Ic2dM0d0cTaqH6zYv4nBL4Os8o5FQfswT/DizEhBTNQE2Og4H/Gw4V0Vq iwEp2TzYeyxbR97KE/0ntlmFVgteKbll62LWRP5V51U/fPHTPp5gNZLphsgxAC0ZNS7O6UOKydJu QKTRZFmsjMnowZiMnhRl5pFvTQ96n3d9t5R3D/u2URLWCLH6hy9/NIhArJYxlN55g+MQDk9Lh9Vb WyPZZOHinNN53aCLP5kWK7lJaWRYkkgzJ2I3J5BispBvTef05Ez6JGfQIyktpBuT/7t7FS9+vzGE PQYPSSsGqRMN39hlSgMRkMxaDzxSZXXtvoCvjl7ZuSDkBpmS3ZsZucMZntpVl/rweiCBP+78lCfL vzBail7UOyt7/FgT/Kfd3cJiD7DSCEVG8amGadYFmbkMCmAXLBASFBPPDpjM64P/i3PTuoWNOdxS 5dati6PJHID8hOnTfzyVPuH4Qwg+Cr0g43jvyA7UALciBTBTQ2YPf3ii3wR+1WVgSPryFZfXzS83 vhNJ98t9QnKiB048H/TID0OqxmD2NdWzpi7wHZef5/TVVDzGFyZn9w47c+xwVTP+y3+ytCpsA8ED xiQtS47/+wkGqZ949w5gV0gVGYyWT0BFCOb2HaejmlOZ0VPbRSW9ef3gVsZ+8Qqbo7Mg0ba68TNO yFd7SoSJhJgaRd45/B2NGrZrizLzNJembosMSxIjNOQm1pNGr5vfblvCzVsXh/X2thZOnl5BKwZB 5a2QqAkTHJ4W/hNAaeTjeej0cUG5SJWblBYWC/KPKncxfN1LHd6ziXSEkG+e/L1TDNKw2rkCCO+0 2zrz6N7PA16sw9F0OE8PmKSjoqMIg0Os9zbWcfnGd5i+8R32RkAmRI3sd6xwfXbyN08dQYqLVYmI qVFkm7OKxZXall6XntaP67sO1knRUfY31xtydFvlbuTPu1cy/PMX+VDjzyViECykuPiUiyutRrkL lYXBVxRezNOh0OfcvuM4R0PWwZM50uLia8dB3drriIMtTu7f+QkFa/6P+Xs+D/vyBHoiaP13vlWD OFY3rAEiIT+9bmxwHKRE47ZlkmLmjSGX6rr1+1T5et3aaosv6yu487ulDF7zd54uX48rShfhbSN3 1xfOavUqauv3pIqLVSnFgqBqCkN+v/NTzddDsyxW3h36C7on6RNG/tahbXxQqX/KgPKmOh7es5az 1y6gcP1rLDjwDU0xNGKcgOQFhGh1NtvmKtBaOre7WVX2APpdM4sA/tx7LHdqTJIGRw8hf/71m7rU AU8xWXix4GJNJQcavW4+qzvApzXlfFpTzoZ6/25hRjEer1fNdU28p9WNqXa3SezL5n0AXBgUWWGK 1WThixHX0TNJ+32JGncTl218W5dqrooQXNVlIDNzR9Crg9D1A80Odrpq2OmqYYermq8dh1hfX+FT 4ovYQ77nKLr7krZebd8gy+ddguRd/UWFN1NzTue1QW3+zPzC5XVz53dLdY1Z6peSRb41nSTFjMPb gtPbgtPjpt7bzOEWVwyuITTxM0fR7EVtvdj+RvvChSZ71t7vgMDH9gjlrwMmc5WOMVD/PriVO79b Gv/lDScEZY7K3NOPj949mfaTmUyf7hWCJ3UXFgHM3L6Mb3XMJfvLzmewatg1FGZG3nXdaEUi5rdn DujIIIDVzQuEcZm2YNHodXPt5kW6fuL3Sc7gP0Mv46WBU3VPlhDHb6pT3PIfHb2pwx0q56tL3YnX TsoARusiK4KodDeyv9nBVJ2DEQekZHNz9zPpkmjjW2dVBFVcih6EkPOqJ9z9cUfv82kL13zTlK2K qv4GiIzkWTqyueEIqpS6p/oxC4WzUjtzc/cz6ZeSicPbQnmTMaElMYhTesRVLa8udXb0Rp8M4n5x SUPitROyQJynXVvksbp2P1kWq89FP/3BJAQFthyu6FzANV0H0zkxBVVKDrU4262SFUcDQjzeMGH2 f3x6q69t2kvnZ6PK3UBMTp4VIXjhjIuY1ql/SPprVr2sq/uerxwH2emqZoerhl2uGuo9zTEVIxUE GqSH3g2TZvuUGM2veGr7snkPAfcEJCsKSFBMPH/GRfz8tL6G6vBKicPbTLW7iX1N9ZRUlfFqxWaq 3I2G6ooEhOR/6sfPfsDn9/vTuL1kThbCsgvQPy1fhGASgnl9i7ip21CjpZxAvaeZe3eU8mrF5o7f HLvUmBRzfm3hDJ+TO/sVZ9Xy8rLGxGsmuRFM9F9bdCCBJVW7kUjGhFGO3kTFzEU5fXB53XyuQ2hL NCIE99aNm+lXLTi/qx44qhueAqIgv6Q2Hir7jN98+1HYrQce7D1Wc7nmKGVbfWrqs/4+5H9ZkOnF LcAsv5+LQl6t2EzR+td0idjVC0UIivPHGi0j7BCIuzjnVr9PfQOqm+Momr1IIpcG8my0sbnhCGO/ eMWwPL2tcW56N93uo0QJi+uLZgWUrSfgwlKKl9uA+LYJR8sc37x1MTdu+YBDLR2ePQUdQfRVwgoU CS4FeUegzwdskPqJd+9A8D+BPh+NvHHoW85eu4Dn9n9leHWldEuSof2HC0KKB+qK7g4484Sm0oQO 4ZwnIRJr/AaNek8zs7cv5/z1r/BZrXEFLCO5wpOOfONIt2uKRteceCmtdO45qqqsJcau5vrKeend mNFzuKbrsv7ikSr5K/9KracpZH2GIR6vqgx3TZi5QUsjmn+pm/9R8n3SdRNMIM7X2lY0sr/JwRuH tlFSVUZOQgr51oygZ0tcVLmDfx3cEtQ+wh0pKHaOn/W61nb0+Z8qLTbb1ZQVQEwGM/pD10Qbv+g0 gCs6F1Bgy9a9fafXzegvXmaXhlrwUcBqR1Xu+R1dhvIF3T7K0koey1eFZwOQqleb0c4gWw6XdurP BRk9GWrvjEnjyNKkerh286LYyYbYOnUmxTO0tvC+PXo0putYby+Zfy1CvqRnm7FCqjmRUendOT+j J2enduH05AwyLVafn9/YcJjfbVvKl/UVQVQZ/gjBr+rHzf6nbu3p1dAP2Jc9/HcQN+ndbiySabHS JzmDvsmZdEpIIfVYwU6b6WjRTq+UR6N5q8uMp1MEAAAFLklEQVT4tKZcUwLuaEDAs/VFs3+jZ5vm jt/iH47mpN/aE5uHAMP0bjvWqHY3sq6uUZe8WjHA5/XNiTP0bjQo2ynWJY/0MJvVL4HQVLmME+sc 9ijq2Y2F9wReT68NNB0UtkXjpJn7VKFcAYRXqGucaMQtVS4PhjkgSAYBcI6buQzJfwer/ThxAKTk 9oYJsz8JVvtBMwiAY/zsF5D8JZh9xIldhJB/bhg/+7mg9hHMxgGQUtiXz38ZuCrofcWJGYQQr9cX zryirbIFehHUEQQAIaRDcd4skMuC3lecmEAil9Y3JVwbbHNAKAwCUFjcZHO5fgasCEl/caKZz6xK yqVceEdI0lGGtIxq5uInU90JzcsQnBPKfuNEB0LytUj0jKsbc1/IAs1CXmf4WAK6UkC/2gJxYgC5 CekpdIy/P6SJ1EMzxToOR+GsSiXBMxb4PNR9x4lMJHyFoowLtTnAAIMA1I25r8aseicBq43oP04E IVhlUb3jHIWz9CvW4geGGASgZsK9dXaXc6KEDlPQx4lVRGmSSJ5SM+HeOqMUGGYQgO+nFrsaqpxT gVeN1BEn/BCINxxKw4VHCm9rMFZHOCClSF0+/08S/mS0lDjGIyVPNhTNujMU5xwdER4GOYZ92bzr geeIwUI9cQDwSMlvgx0+4g9hZRAA27J54wT8m3iofKxxWKpcHszAw0AIO4MAWJc92s0sPW8ixLlG a4kTEr40IabVFs3aa7SQkzF0kd4WjUV3HXC0JF0gZWyWoI4ppPw/R5VzZDiaA8J0BDme1GXzr5bI p4lnS4k26oTgN3omWAgGYW8QgPRl83O9yFeAMUZriaMDUq5VBFdpyZkbKsJyinUytUWz9joU5zgJ xcSv8UYyHil4wFGdNzoSzAERMoIcT0rJvMGK4HniWVMijW8URb2prvCe9UYL8YeIGEGOxzl+9kaH 4hwppLgTMPSUNY5PNAL3OhTnOZFmDojAEeR40pY93FsV4mkkk43WEqdVFivSfHvd+Bm7jRYSKBFt kB9IKZk3XhHicZAFRmuJA8B2pJzpGH/3+0YL0UpUGASA9c9Z7LX1tyP4I5ButJwYpUZI8WB9uv2v gRTMDEeixyDHyF41197crPwGuA9IM1pPjNAAPKMkeOaG8jpsKIg6g/yAvWROFsIyW8LtApKN1hON SHAheV5imeMcf+cho/UEg6g1yA+klDzeySTct0v4NZBptJ4ooUogn1U94qmGSbMPGy0mmES9QX5k 8ZOJqUlNl0sp7gf6GS0nIhGUCVU8YfXK5w9Nmm18vesQEDsG+YGFC032rL0XAjcDFxIvPtoRHpCL QTzvqMpdrEdZs0gi9gxyHNZlj3YzoV4vhLwBSS+j9YQXcrdELFC96ouuiffEbIGSmDbI8diWP1Sg SNNlEnk1iHyj9RjEASl5S0jxhmP8zNXhcOXVaOIGOZniYsU+OnmUFEwTiClAX6MlBZnvQC5GVd52 rG5YQ3GxarSgcCJukA5IK3ks36t4JwvJFJDnA3ajNWmkHvhUIj80ecRHdZNmlxktKJyJG8QfFi40 peTsGWRSxWgVOVIRYrSU9DBaVnsIwT5VylUKYo1XKCudlT02x9pCWwtxg2jEXjo/W5ViiEmqg6Rg ICpDEPQGMkIspQbYiWCjUMUmVchNQhEbjcpIGC3EDRIk0ksfS3fjzlNU0UtArhSiCyrZILNQyEKS BTIRRBpHrx1YANuxxxsAN6CCrAPRjKAKlSoQVShUCikrJOxVFVmW4FHLjMw+GM38f4EawqbWMRX9 AAAAAElFTkSuQmCC " + id="image1263" + x="134.51895" + y="20.857262" + style="stroke-width:2.00314" /><image + width="23.48313" + height="25.232662" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 gg== " + id="image1275" + x="220.0013" + y="21.449341" + style="stroke-width:10.7374" /><image + width="30.300915" + height="29.702868" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 NXb+cQHjSzf2DwNl/pKGHoE0T2E7UXwO7KpBFexX8dF/KEhI/zT0KtpIW/TITz+A5UHeVOSJ9ROb 6uncafKBMBwlW++6qYWi4fQlRmSSbkpMhJjpWQIt9yPCWa09JrOhrOn8L8mwl1d43y+bf+UvB7eB YnH6PwwE8Za0Qn3QcVm4cKB1qmpJ6iqemvooqXlVym3K5jbnv/67n/ntgQj5rNTS/wEy4kxsAPkh XQAAAYVpQ0NQSUNDIHByb2ZpbGUAAHicfZE9SMNAHMVfW8UPWhzsUMQhQ3WyICpFcNEqFKFCqBVa dTC59AuaNCQpLo6Ca8HBj8Wqg4uzrg6ugiD4AeLm5qToIiX+Lym0iPHguB/v7j3u3gH+RoWpZtc4 oGqWkU4mhGxuVeh5RR8iCGEGcYmZ+pwopuA5vu7h4+tdjGd5n/tzhJS8yQCfQDzLdMMi3iCOb1o6 533iMCtJCvE58ZhBFyR+5Lrs8hvnosN+nhk2Mul54jCxUOxguYNZyVCJp4ijiqpRvj/rssJ5i7Na qbHWPfkLg3ltZZnrNIeRxCKWIEKAjBrKqMBCjFaNFBNp2k94+Iccv0gumVxlMHIsoAoVkuMH/4Pf 3ZqFyQk3KZgAul9s+2ME6NkFmnXb/j627eYJEHgGrrS2v9oApj9Jr7e16BEwsA1cXLc1eQ+43AEi T7pkSI4UoOkvFID3M/qmHDB4C/Svub219nH6AGSoq9QNcHAIjBYpe93j3b2dvf17ptXfD/nLct0c jlvtAAANGmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlk PSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpu czptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6 cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRm OkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9i ZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94 YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9y Zy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94 bXAvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4 bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50 SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDozYmZlYzk3MS01ODI1LTRlMTEtODk3YS00ODBjOGUzY2ZlODci CiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Y2E4N2VhMzAtNTY2NC00ZDU4LThmZGItODZi NWI3YmJiMWI4IgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6YWYxNmU4Mjct MDJjYS00YjQ3LTkxYzEtYmJiOGY4NmMyYTI4IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAg R0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iTGludXgiCiAgIEdJTVA6VGltZVN0YW1w PSIxNjMyNDkyMDU0NzQwNTU2IgogICBHSU1QOlZlcnNpb249IjIuMTAuMjQiCiAgIHRpZmY6T3Jp ZW50YXRpb249IjEiCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIj4KICAgPHhtcE1NOkhp c3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2 ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5p aWQ6Y2MzMmRmN2ItYjQ2YS00NTM0LTlhYjgtMmRhOTRjOTYwY2NmIgogICAgICBzdEV2dDpzb2Z0 d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMS0wOS0y NFQxNjowMDo1NCswMjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAg PC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9Inci Pz55r0WaAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5QkY DgA218SFAwAAIABJREFUeNrtndl3Glfert8qqjQgNFvzBAjZElAMshPHAo1MstP/57k5w+rO3N9a 5+ac7tjxEOc76+vEkkAzAqHBEZoioOpcFGC7MycequB9bnKRrJWlAh5efnvvdwuapmkghBATIvIR EEIoMEIIocAIIYQCI4RQYIQQQoERQggFRgghFBghhAIjhBAKjBBCKDBCCAVGCCEUGCGEUGCEEEKB EUIoMEIIocDeJMVika80IRSY+SiVSvjuu++Qy+VQKpX4ihNSQ0i1/gcWCgX8x3/8HW2tbbgzMwO7 3Q6r1cpXnhAKzPioqor19Q3867/+hWfPniEWjyMQCKCrqwsWi4XvAEIoMOP/jNza3MT+fhZbW1tY XFzE7NwcHA4Hmpub+S4ghAIzLgIATdOQP8nj8aPH2N3dRSqVQiweh8/nQ3d3N9MYIRSYwUUmCCgW i9jd2cXnn32Ozc0tLC0tIjw7yzRGCAVmDokBwMnJCZ48fozM3h6SqRQikQiCwSBnY4RQYOYQWaFQ wPb2No6Pj7GeWkckGsHc3BzGxsbQ3NxclR0hhAIzpMQ0TUM+n8d/fvMN9vezSK6tIRaLwx/wczZG CAVmDq6urrC9tY2jwyNsbm4iFothJhSC3W5nGiOEAjM+mqbh9PQUTx4/we7uLtaSSUSjUfj9fnR2 dkKS+LgIocAMTqlUwl56D59+/Am2NrewsLiAcDjMNEYIBWauNPbwq6+ws7ONVCqFaDQKn8+Hrq4u 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 " + id="image1287" + x="175.31808" + y="19.214237" + style="stroke-width:3.53931" /><image + width="29.202602" + height="29.202602" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 CkvGGFWwV9YDEF1SwgLIYD2Q2SEME02Fgc5Qhu4WKndlxyBtslYOunEW0+Yk/9ACqQf/3LR7saUl ZKNI1xbp8kBcEWqJbjm+yoStoaw1zM8eoz15E/rohzi8/X3o8XlQinGsFCJO1NdIGtfv0xcdILdg CD42SgZM7RmuoWZPFZ1umEHyeFRt5KYa6V5SuQABhfCX41r628IejAY/rlc9aH+O3EROA+oArbmw 5Ia3w/oRus4tKWTGD/L+mn/LJIL/lmGFiJtqi/tNE2dt1dFlTZfROswHMKcwCoVkQJl2iE45LXjv eRMmAKadx6wOV8jWXdnM14Av//LfxP/8F/4dfAagCy+TfxQUlstftl+F4c/hne//unzwkz+JMk99 5zIENbJbQ7Rs0tLwgGK4hLmzxK4ewLtmUBw5wWdmUEhPk6iHNozasLPHn3E5kFW2RRuAUnwyTTMp atTjO8E7Fen3Vp1Pa7PDiywkaXRV4zuV9B8QB99OG0eft2uvQzseIDdPII/egH3wOuzxO5DFudiC eM9WcAkg8QLZVm2I9ZjTAElwjdGSMiLDRYdYSigsQi9cX7CVVpkYjuGGMMQCs7iYn+9jrbkZGa2D rjwckNoJ74iEl6CjRj++DfdlYOYyNpk4YOC0XANoRXcJx2lg/bdSt5qiKyzeijEhEvGuhlFBDlad jbnp4bPhJtdjd3MFbsHzkRmTFxIbx8S5vDQvaIdBrC22ffgTvPrV/xG//p/9Bv5t+b07J9pnTP7R UVhdLvHf268C+Gcg+Ofk8Y9/xW6eP0S93gHwrjfk6XZczwxjA9NVWQiAQMdbJW4HgNriXNbi/eUy 5cwd2VHF4RYNqPKolKfbgHzHeFwFpkLero13z4F6nKca23dJWgBSJm+IUcoqXp0Koy5+P7HDLxWC A0wmyHTp93u4hjz6MfD6t2BPXoftryBVEIyoCQ+wQZmvCHBLty7G8+fnSiosv2xCFaL+M42i7hKm Xgl3hy5g6ikoDM3zAGHFxRijjw+A4drDreEVqj/77O9XG2TqCsuM546MXwT4QzEhXo87jutUlsb4 3EmDaMTqRcVF0DsrDftQuJm189iSYYA7REG1FFdWtoxEpa7sysSg+YIVcJRKN65HrLqy2lw6hff+ cAPRZ/bq176LX/31P5z+5D/7jeXPye8DuMJZznKWs5zlLGc5y1nOcpaznOUsZznLWc5ylrOc5Sxn OctZznKWs5zlLGc5y1nOcpaznOUsZznLWc5ylrOc5SxnOctZznKWs5zlLGc5y1nOcpaznOUsZznL Wc5ylrOc5SxnOctZznKWs5zlLGc5y1nOcpaznOUsZznLWc5ylrOc5SxnOctZznKWs5zlLGc5y1nO cpaznOUsZznLWc5ylrOc5SxnOctZznKWs5zlLGc5y1nOcpaznOUsZznLWc5ylrOc5SxnOcvnV/5/ fl07WlwHHbMAAAAASUVORK5CYII= " + id="image1359" + x="90.934036" + y="19.464371" + style="stroke-width:2.71808" /><text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="169.47803" + y="66.103424" + id="text4115"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="169.47803" + y="66.103424" + id="tspan4117">User-facing application</tspan></text><text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="73.229004" + y="183.68733" + id="text4115-3"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="73.229004" + y="183.68733" + id="tspan4117-6">Database*</tspan></text><text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="111.25725" + y="162.21248" + id="text4115-3-6"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="111.25725" + y="162.21248" + id="tspan4117-6-8">K2V</tspan></text><text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="266.38498" + y="182.65727" + id="text4115-3-7"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="266.38498" + y="182.65727" + id="tspan4117-6-5">Object storage</tspan></text><path + style="fill:none;stroke:#000000;stroke-width:1.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend)" + d="M 147.15073,80.962811 V 93.477746 H 74.989012 v 12.859624" + id="path12236" /><path + style="fill:none;stroke:#000000;stroke-width:1.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-3)" + d="m 190.64085,80.962811 v 12.514935 h 75.18482 v 12.859624" + id="path12236-6" + sodipodi:nodetypes="cccc" /><g + id="g329" + transform="matrix(0.25773706,0,0,0.25773706,234.20703,108.42714)" + style="stroke-width:1.02656"><g + id="g1663" + transform="matrix(1.7099534,0,0,1.7099534,-88.607712,-87.994557)" + style="stroke-width:1.02656"><path + d="m 138.33068,100.19817 a 8.327649,8.327649 0 0 1 -2.77589,-0.288688 l -34.78736,-9.388036 a 8.4442361,8.4442361 0 0 1 -2.620433,-1.238044 z" + id="path6" + style="stroke-width:0.569924" /><path + class="cls-1" + d="m 85.377935,159.27452 5.163143,-0.0333 h 0.06662 q 2.864711,0 2.864711,2.69816 v 8.69407 a 24.849705,24.849705 0 0 1 -8.649651,1.43235 q -4.730105,0 -7.128468,-3.21447 -2.398363,-3.21447 -2.398363,-8.76068 0,-5.55177 2.981299,-8.62745 a 9.7600046,9.7600046 0 0 1 7.29502,-3.08123 13.368653,13.368653 0 0 1 7.811335,2.43167 3.9250986,3.9250986 0 0 1 -0.682867,1.76547 4.7634152,4.7634152 0 0 1 -1.282458,1.33242 9.798867,9.798867 0 0 0 -5.679457,-1.96533 5.3574542,5.3574542 0 0 0 -4.480275,2.04861 q -1.598909,2.03749 -1.598909,6.41229 0,8.22771 6.062529,8.22771 a 16.910679,16.910679 0 0 0 3.697476,-0.43303 v -3.16451 q 0,-1.49898 0.06662,-2.22071 h -2.442777 a 2.2873276,2.2873276 0 0 1 -1.515632,-0.41638 1.6655298,1.6655298 0 0 1 -0.483004,-1.33242 5.7072154,5.7072154 0 0 1 0.333106,-1.79322 z" + id="path8" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 111.07151,169.73404 a 4.3137222,4.3137222 0 0 1 -0.55518,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05902,-1.95422 6.7453957,6.7453957 0 0 1 -4.76342,2.13188 q -2.564913,0 -3.886233,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.299113,-3.4643 q 0,-2.77588 1.815427,-4.21379 a 7.3338829,7.3338829 0 0 1 4.669039,-1.3935 q 1.53228,0 2.89802,0.13325 v -0.99932 q 0,-2.63154 -2.53161,-2.63154 -1.79877,0 -5.096518,1.19918 a 4.674587,4.674587 0 0 1 -1.110353,-2.96464 18.581761,18.581761 0 0 1 7.217291,-1.49898 5.8682167,5.8682167 0 0 1 4.0639,1.39905 q 1.56559,1.39904 1.56559,4.23044 v 6.79537 q -0.0111,1.83208 0.9216,2.59822 z m -8.36096,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06525,0.68842 2.3928111,2.3928111 0 0 0 -0.69953,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.55518 z" + id="path10" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 113.76966,157.00939 a 3.986168,3.986168 0 0 1 0.55518,-1.21583 3.3310596,3.3310596 0 0 1 0.84942,-0.94935 4.1638245,4.1638245 0 0 1 3.51427,2.96464 q 1.33242,-2.96464 4.29707,-2.96464 a 10.215249,10.215249 0 0 1 1.93201,0.23317 7.4782288,7.4782288 0 0 1 -0.99932,3.88624 8.4497879,8.4497879 0 0 0 -1.49897,-0.19987 q -2.03195,0 -3.26444,2.16519 v 10.64829 a 11.575432,11.575432 0 0 1 -2.03195,0.16655 12.769062,12.769062 0 0 1 -2.09857,-0.16655 v -11.15905 q -0.0222,-2.40947 -1.2547,-3.40879 z" + id="path12" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 140.38483,169.73404 a 4.3137222,4.3137222 0 0 1 -0.58293,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05348,-1.95422 6.7453957,6.7453957 0 0 1 -4.76341,2.13188 q -2.56492,0 -3.88624,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.29911,-3.4643 q 0,-2.77588 1.81543,-4.21379 a 7.3338829,7.3338829 0 0 1 4.64682,-1.4157 q 1.53229,0 2.89803,0.13324 v -0.99932 q 0,-2.63153 -2.53161,-2.63153 -1.79877,0 -5.09652,1.19918 a 4.674587,4.674587 0 0 1 -1.11035,-2.96465 18.581761,18.581761 0 0 1 7.21729,-1.49897 5.8682167,5.8682167 0 0 1 4.0639,1.39904 q 1.56559,1.39905 1.56559,4.23045 v 6.81757 q 0.0333,1.83208 0.96601,2.59822 z m -8.37206,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06526,0.69952 2.3928111,2.3928111 0 0 0 -0.69952,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.54408 z" + id="path14" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 144.48203,169.6008 q -1.49897,-2.29843 -1.49897,-6.34567 0,-4.04724 1.8987,-6.34567 a 5.740526,5.740526 0 0 1 4.56355,-2.29843 6.4400486,6.4400486 0 0 1 4.49693,1.66553 3.7696491,3.7696491 0 0 1 2.63154,-1.43235 3.1200925,3.1200925 0 0 1 0.88273,0.93269 3.8862362,3.8862362 0 0 1 0.55518,1.16587 q -0.9327,0.79946 -0.9327,2.86472 v 9.438 q 0,5.29638 -1.73215,7.49488 -1.73215,2.1985 -5.69611,2.22071 a 16.100121,16.100121 0 0 1 -5.9626,-1.11036 4.4802752,4.4802752 0 0 1 1.03263,-3.03126 10.892565,10.892565 0 0 0 4.48028,1.03263 q 2.18184,0 3.0146,-1.11035 a 4.9965894,4.9965894 0 0 0 0.83277,-3.06458 v -1.33242 a 6.4011862,6.4011862 0 0 1 -4.16383,1.56559 4.9188647,4.9188647 0 0 1 -4.40255,-2.30953 z m 8.56083,-2.69816 v -7.72806 a 4.2915151,4.2915151 0 0 0 -2.86471,-1.36573 2.4039147,2.4039147 0 0 0 -2.18185,1.43235 8.6885138,8.6885138 0 0 0 -0.7828,4.09721 q 0,2.66485 0.71618,3.93065 a 2.1318781,2.1318781 0 0 0 1.88205,1.2658 4.2304457,4.2304457 0 0 0 3.23113,-1.63222 z" + id="path16" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 174.20619,164.67083 h -9.32697 a 5.6405943,5.6405943 0 0 0 0.88273,3.04792 q 0.7828,1.0826 2.74813,1.0826 a 10.120869,10.120869 0 0 0 4.36369,-1.16587 4.3803434,4.3803434 0 0 1 1.19918,2.5316 10.759323,10.759323 0 0 1 -6.41229,1.8987 q -3.74744,0 -5.37966,-2.43167 -1.63222,-2.43167 -1.63222,-6.2957 0,-3.88624 1.79877,-6.2957 a 6.0181143,6.0181143 0 0 1 5.14649,-2.43168 q 3.33106,0 5.14648,2.01529 a 7.3449864,7.3449864 0 0 1 1.79878,5.07987 13.04665,13.04665 0 0 1 -0.33311,2.96464 z m -6.42895,-7.06184 q -2.73146,0 -2.93133,4.13051 h 5.79605 v -0.39973 a 4.7245529,4.7245529 0 0 0 -0.69953,-2.69816 2.4316735,2.4316735 0 0 0 -2.14298,-1.03262 z" + id="path18" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + id="path24-3-6" + style="fill:#ffd952;fill-opacity:1;stroke-width:0.569924" + d="m 124.80273,70.162462 a 11.0036,11.0036 0 0 0 -4.33203,0.935547 L 76.261719,90.656602 a 1.5989086,1.5989086 0 0 0 -0.837891,2.138672 0.77169547,0.77169547 0 0 0 0.06641,0.177735 l 7.09375,14.021481 h 6.15625 l -0.875,-4.88867 c -0.07217,-0.39418 -0.711263,-3.187532 -1.316406,-5.197264 l 20.691398,6.462894 c 0.27198,1.28839 0.63292,2.49204 1.0625,3.62304 h 33.54883 c 0.36964,-1.13128 0.66138,-2.33705 0.85938,-3.62304 l 20.64648,-6.445316 c -0.60514,2.009734 -1.23639,4.785506 -1.30859,5.179686 l -0.875,4.88867 h 6.15429 l 7.02735,-13.894528 0.0664,-0.126953 0.0684,-0.171875 a 0.10548355,0.10548355 0 0 0 0,-0.04492 1.4878733,1.4878733 0 0 0 0.0664,-0.515625 1.5822533,1.5822533 0 0 0 -0.99414,-1.583985 L 129.35352,71.098009 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + id="path24-3" + style="fill:#49c8fa;fill-opacity:1;stroke-width:0.569924" + d="M 124.80273,79.416133 A 11.0036,11.0036 0 0 0 120.4707,80.35168 L 76.261719,99.910272 a 1.5989086,1.5989086 0 0 0 -0.837891,2.136718 0.77169547,0.77169547 0 0 0 0.06641,0.17773 l 3.847657,7.60352 h 8.175781 c -0.257897,-1.08856 -0.591943,-2.42953 -0.964844,-3.66797 l 11.744141,3.66797 h 53.371087 l 11.69336,-3.65039 c -0.37193,1.23522 -0.70076,2.56719 -0.95703,3.65039 h 8.17383 l 3.78125,-7.47656 0.0664,-0.12696 0.0684,-0.17187 a 0.10548355,0.10548355 0 0 0 0,-0.0449 1.4878733,1.4878733 0 0 0 0.0664,-0.51563 1.5822533,1.5822533 0 0 0 -0.99414,-1.582028 L 129.35352,80.35168 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + class="cls-2" + d="m 174.55595,110.92974 a 1.4878733,1.4878733 0 0 1 -0.0666,0.51631 0.10548355,0.10548355 0 0 1 0,0.0444 l -0.0666,0.17211 v 0 l -0.0666,0.12769 -10.69826,21.15223 c -1.48787,2.93688 -4.22489,2.84806 -3.76409,-0.12214 l 2.15408,-12.02512 c 0.0722,-0.39418 0.70508,-3.17006 1.31022,-5.1798 l -20.64702,6.4456 c -3.24223,21.05785 -30.95109,21.40761 -35.47023,0 l -20.691432,-6.46226 c 0.605143,2.00974 1.243596,4.80228 1.315769,5.19646 l 2.154085,12.02512 c 0.460796,2.9702 -2.276224,3.05902 -3.764098,0.12214 L 75.49024,111.66257 a 0.77169547,0.77169547 0 0 1 -0.06662,-0.17766 1.5989086,1.5989086 0 0 1 0.838317,-2.13743 L 120.47065,89.788613 a 11.0036,11.0036 0 0 1 8.88282,0 l 44.20871,19.558867 a 1.5822533,1.5822533 0 0 1 0.99377,1.58226 z" + id="path24" + style="stroke-width:0.569924" /><path + class="cls-3" + d="m 139.0413,114.61611 19.11473,-7.69475 a 0.81055784,0.81055784 0 0 0 0,-1.50453 c -2.2207,-0.92714 -4.96328,-1.99308 -7.65033,-3.10899 -0.49411,-0.20541 -5.17425,3.15341 -5.60173,3.49762 l -8.23882,6.58439 c -1.99309,1.67108 -0.26649,3.28665 2.37615,2.22626 z" + id="path26" + style="stroke-width:0.569924" /><circle + class="cls-3" + cx="125.18409" + cy="122.13319" + r="9.9654207" + id="circle28" + style="stroke-width:0.569924" /><path + d="m 138.33068,100.19817 a 8.327649,8.327649 0 0 1 -2.77589,-0.288688 l -34.78736,-9.388036 a 8.4442361,8.4442361 0 0 1 -2.620433,-1.238044 z" + id="path6-0" + style="stroke-width:0.569924" /><path + class="cls-1" + d="m 85.377935,159.27452 5.163143,-0.0333 h 0.06662 q 2.864711,0 2.864711,2.69816 v 8.69407 a 24.849705,24.849705 0 0 1 -8.649651,1.43235 q -4.730105,0 -7.128468,-3.21447 -2.398363,-3.21447 -2.398363,-8.76068 0,-5.55177 2.981299,-8.62745 a 9.7600046,9.7600046 0 0 1 7.29502,-3.08123 13.368653,13.368653 0 0 1 7.811335,2.43167 3.9250986,3.9250986 0 0 1 -0.682867,1.76547 4.7634152,4.7634152 0 0 1 -1.282458,1.33242 9.798867,9.798867 0 0 0 -5.679457,-1.96533 5.3574542,5.3574542 0 0 0 -4.480275,2.04861 q -1.598909,2.03749 -1.598909,6.41229 0,8.22771 6.062529,8.22771 a 16.910679,16.910679 0 0 0 3.697476,-0.43303 v -3.16451 q 0,-1.49898 0.06662,-2.22071 h -2.442777 a 2.2873276,2.2873276 0 0 1 -1.515632,-0.41638 1.6655298,1.6655298 0 0 1 -0.483004,-1.33242 5.7072154,5.7072154 0 0 1 0.333106,-1.79322 z" + id="path8-6" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 111.07151,169.73404 a 4.3137222,4.3137222 0 0 1 -0.55518,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05902,-1.95422 6.7453957,6.7453957 0 0 1 -4.76342,2.13188 q -2.564913,0 -3.886233,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.299113,-3.4643 q 0,-2.77588 1.815427,-4.21379 a 7.3338829,7.3338829 0 0 1 4.669039,-1.3935 q 1.53228,0 2.89802,0.13325 v -0.99932 q 0,-2.63154 -2.53161,-2.63154 -1.79877,0 -5.096518,1.19918 a 4.674587,4.674587 0 0 1 -1.110353,-2.96464 18.581761,18.581761 0 0 1 7.217291,-1.49898 5.8682167,5.8682167 0 0 1 4.0639,1.39905 q 1.56559,1.39904 1.56559,4.23044 v 6.79537 q -0.0111,1.83208 0.9216,2.59822 z m -8.36096,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06525,0.68842 2.3928111,2.3928111 0 0 0 -0.69953,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.55518 z" + id="path10-2" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 113.76966,157.00939 a 3.986168,3.986168 0 0 1 0.55518,-1.21583 3.3310596,3.3310596 0 0 1 0.84942,-0.94935 4.1638245,4.1638245 0 0 1 3.51427,2.96464 q 1.33242,-2.96464 4.29707,-2.96464 a 10.215249,10.215249 0 0 1 1.93201,0.23317 7.4782288,7.4782288 0 0 1 -0.99932,3.88624 8.4497879,8.4497879 0 0 0 -1.49897,-0.19987 q -2.03195,0 -3.26444,2.16519 v 10.64829 a 11.575432,11.575432 0 0 1 -2.03195,0.16655 12.769062,12.769062 0 0 1 -2.09857,-0.16655 v -11.15905 q -0.0222,-2.40947 -1.2547,-3.40879 z" + id="path12-6" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 140.38483,169.73404 a 4.3137222,4.3137222 0 0 1 -0.58293,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05348,-1.95422 6.7453957,6.7453957 0 0 1 -4.76341,2.13188 q -2.56492,0 -3.88624,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.29911,-3.4643 q 0,-2.77588 1.81543,-4.21379 a 7.3338829,7.3338829 0 0 1 4.64682,-1.4157 q 1.53229,0 2.89803,0.13324 v -0.99932 q 0,-2.63153 -2.53161,-2.63153 -1.79877,0 -5.09652,1.19918 a 4.674587,4.674587 0 0 1 -1.11035,-2.96465 18.581761,18.581761 0 0 1 7.21729,-1.49897 5.8682167,5.8682167 0 0 1 4.0639,1.39904 q 1.56559,1.39905 1.56559,4.23045 v 6.81757 q 0.0333,1.83208 0.96601,2.59822 z m -8.37206,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06526,0.69952 2.3928111,2.3928111 0 0 0 -0.69952,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.54408 z" + id="path14-1" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 144.48203,169.6008 q -1.49897,-2.29843 -1.49897,-6.34567 0,-4.04724 1.8987,-6.34567 a 5.740526,5.740526 0 0 1 4.56355,-2.29843 6.4400486,6.4400486 0 0 1 4.49693,1.66553 3.7696491,3.7696491 0 0 1 2.63154,-1.43235 3.1200925,3.1200925 0 0 1 0.88273,0.93269 3.8862362,3.8862362 0 0 1 0.55518,1.16587 q -0.9327,0.79946 -0.9327,2.86472 v 9.438 q 0,5.29638 -1.73215,7.49488 -1.73215,2.1985 -5.69611,2.22071 a 16.100121,16.100121 0 0 1 -5.9626,-1.11036 4.4802752,4.4802752 0 0 1 1.03263,-3.03126 10.892565,10.892565 0 0 0 4.48028,1.03263 q 2.18184,0 3.0146,-1.11035 a 4.9965894,4.9965894 0 0 0 0.83277,-3.06458 v -1.33242 a 6.4011862,6.4011862 0 0 1 -4.16383,1.56559 4.9188647,4.9188647 0 0 1 -4.40255,-2.30953 z m 8.56083,-2.69816 v -7.72806 a 4.2915151,4.2915151 0 0 0 -2.86471,-1.36573 2.4039147,2.4039147 0 0 0 -2.18185,1.43235 8.6885138,8.6885138 0 0 0 -0.7828,4.09721 q 0,2.66485 0.71618,3.93065 a 2.1318781,2.1318781 0 0 0 1.88205,1.2658 4.2304457,4.2304457 0 0 0 3.23113,-1.63222 z" + id="path16-8" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 174.20619,164.67083 h -9.32697 a 5.6405943,5.6405943 0 0 0 0.88273,3.04792 q 0.7828,1.0826 2.74813,1.0826 a 10.120869,10.120869 0 0 0 4.36369,-1.16587 4.3803434,4.3803434 0 0 1 1.19918,2.5316 10.759323,10.759323 0 0 1 -6.41229,1.8987 q -3.74744,0 -5.37966,-2.43167 -1.63222,-2.43167 -1.63222,-6.2957 0,-3.88624 1.79877,-6.2957 a 6.0181143,6.0181143 0 0 1 5.14649,-2.43168 q 3.33106,0 5.14648,2.01529 a 7.3449864,7.3449864 0 0 1 1.79878,5.07987 13.04665,13.04665 0 0 1 -0.33311,2.96464 z m -6.42895,-7.06184 q -2.73146,0 -2.93133,4.13051 h 5.79605 v -0.39973 a 4.7245529,4.7245529 0 0 0 -0.69953,-2.69816 2.4316735,2.4316735 0 0 0 -2.14298,-1.03262 z" + id="path18-7" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + id="path24-3-6-9" + style="fill:#ff9329;fill-opacity:1;stroke-width:0.569924" + d="m 124.80273,70.162462 a 11.0036,11.0036 0 0 0 -4.33203,0.935547 L 76.261719,90.656602 a 1.5989086,1.5989086 0 0 0 -0.837891,2.138672 0.77169547,0.77169547 0 0 0 0.06641,0.177735 l 7.09375,14.021481 h 6.15625 l -0.875,-4.88867 c -0.07217,-0.39418 -0.711263,-3.187532 -1.316406,-5.197264 l 20.691398,6.462894 c 0.27198,1.28839 0.63292,2.49204 1.0625,3.62304 h 33.54883 c 0.36964,-1.13128 0.66138,-2.33705 0.85938,-3.62304 l 20.64648,-6.445316 c -0.60514,2.009734 -1.23639,4.785506 -1.30859,5.179686 l -0.875,4.88867 h 6.15429 l 7.02735,-13.894528 0.0664,-0.126953 0.0684,-0.171875 a 0.10548355,0.10548355 0 0 0 0,-0.04492 1.4878733,1.4878733 0 0 0 0.0664,-0.515625 1.5822533,1.5822533 0 0 0 -0.99414,-1.583985 L 129.35352,71.098009 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + id="path24-3-2" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" + d="M 124.80273,79.416133 A 11.0036,11.0036 0 0 0 120.4707,80.35168 L 76.261719,99.910272 a 1.5989086,1.5989086 0 0 0 -0.837891,2.136718 0.77169547,0.77169547 0 0 0 0.06641,0.17773 l 3.847657,7.60352 h 8.175781 c -0.257897,-1.08856 -0.591943,-2.42953 -0.964844,-3.66797 l 11.744141,3.66797 h 53.371087 l 11.69336,-3.65039 c -0.37193,1.23522 -0.70076,2.56719 -0.95703,3.65039 h 8.17383 l 3.78125,-7.47656 0.0664,-0.12696 0.0684,-0.17187 a 0.10548355,0.10548355 0 0 0 0,-0.0449 1.4878733,1.4878733 0 0 0 0.0664,-0.51563 1.5822533,1.5822533 0 0 0 -0.99414,-1.582028 L 129.35352,80.35168 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + class="cls-2" + d="m 174.55595,110.92974 a 1.4878733,1.4878733 0 0 1 -0.0666,0.51631 0.10548355,0.10548355 0 0 1 0,0.0444 l -0.0666,0.17211 v 0 l -0.0666,0.12769 -10.69826,21.15223 c -1.48787,2.93688 -4.22489,2.84806 -3.76409,-0.12214 l 2.15408,-12.02512 c 0.0722,-0.39418 0.70508,-3.17006 1.31022,-5.1798 l -20.64702,6.4456 c -3.24223,21.05785 -30.95109,21.40761 -35.47023,0 l -20.691432,-6.46226 c 0.605143,2.00974 1.243596,4.80228 1.315769,5.19646 l 2.154085,12.02512 c 0.460796,2.9702 -2.276224,3.05902 -3.764098,0.12214 L 75.49024,111.66257 a 0.77169547,0.77169547 0 0 1 -0.06662,-0.17766 1.5989086,1.5989086 0 0 1 0.838317,-2.13743 L 120.47065,89.788613 a 11.0036,11.0036 0 0 1 8.88282,0 l 44.20871,19.558867 a 1.5822533,1.5822533 0 0 1 0.99377,1.58226 z" + id="path24-0" + style="fill:#ff9329;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-3" + d="m 139.0413,114.61611 19.11473,-7.69475 a 0.81055784,0.81055784 0 0 0 0,-1.50453 c -2.2207,-0.92714 -4.96328,-1.99308 -7.65033,-3.10899 -0.49411,-0.20541 -5.17425,3.15341 -5.60173,3.49762 l -8.23882,6.58439 c -1.99309,1.67108 -0.26649,3.28665 2.37615,2.22626 z" + id="path26-2" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><circle + class="cls-3" + cx="125.18409" + cy="122.13319" + r="9.9654207" + id="circle28-3" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /></g></g><g + id="g329-5" + transform="matrix(0.25773706,0,0,0.25773706,41.303976,108.42714)" + style="stroke-width:1.02656"><g + id="g1663-6" + transform="matrix(1.7099534,0,0,1.7099534,-88.607712,-87.994557)" + style="stroke-width:1.02656"><path + d="m 138.33068,100.19817 a 8.327649,8.327649 0 0 1 -2.77589,-0.288688 l -34.78736,-9.388036 a 8.4442361,8.4442361 0 0 1 -2.620433,-1.238044 z" + id="path6-9" + style="stroke-width:0.569924" /><path + class="cls-1" + d="m 85.377935,159.27452 5.163143,-0.0333 h 0.06662 q 2.864711,0 2.864711,2.69816 v 8.69407 a 24.849705,24.849705 0 0 1 -8.649651,1.43235 q -4.730105,0 -7.128468,-3.21447 -2.398363,-3.21447 -2.398363,-8.76068 0,-5.55177 2.981299,-8.62745 a 9.7600046,9.7600046 0 0 1 7.29502,-3.08123 13.368653,13.368653 0 0 1 7.811335,2.43167 3.9250986,3.9250986 0 0 1 -0.682867,1.76547 4.7634152,4.7634152 0 0 1 -1.282458,1.33242 9.798867,9.798867 0 0 0 -5.679457,-1.96533 5.3574542,5.3574542 0 0 0 -4.480275,2.04861 q -1.598909,2.03749 -1.598909,6.41229 0,8.22771 6.062529,8.22771 a 16.910679,16.910679 0 0 0 3.697476,-0.43303 v -3.16451 q 0,-1.49898 0.06662,-2.22071 h -2.442777 a 2.2873276,2.2873276 0 0 1 -1.515632,-0.41638 1.6655298,1.6655298 0 0 1 -0.483004,-1.33242 5.7072154,5.7072154 0 0 1 0.333106,-1.79322 z" + id="path8-3" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 111.07151,169.73404 a 4.3137222,4.3137222 0 0 1 -0.55518,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05902,-1.95422 6.7453957,6.7453957 0 0 1 -4.76342,2.13188 q -2.564913,0 -3.886233,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.299113,-3.4643 q 0,-2.77588 1.815427,-4.21379 a 7.3338829,7.3338829 0 0 1 4.669039,-1.3935 q 1.53228,0 2.89802,0.13325 v -0.99932 q 0,-2.63154 -2.53161,-2.63154 -1.79877,0 -5.096518,1.19918 a 4.674587,4.674587 0 0 1 -1.110353,-2.96464 18.581761,18.581761 0 0 1 7.217291,-1.49898 5.8682167,5.8682167 0 0 1 4.0639,1.39905 q 1.56559,1.39904 1.56559,4.23044 v 6.79537 q -0.0111,1.83208 0.9216,2.59822 z m -8.36096,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06525,0.68842 2.3928111,2.3928111 0 0 0 -0.69953,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.55518 z" + id="path10-7" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 113.76966,157.00939 a 3.986168,3.986168 0 0 1 0.55518,-1.21583 3.3310596,3.3310596 0 0 1 0.84942,-0.94935 4.1638245,4.1638245 0 0 1 3.51427,2.96464 q 1.33242,-2.96464 4.29707,-2.96464 a 10.215249,10.215249 0 0 1 1.93201,0.23317 7.4782288,7.4782288 0 0 1 -0.99932,3.88624 8.4497879,8.4497879 0 0 0 -1.49897,-0.19987 q -2.03195,0 -3.26444,2.16519 v 10.64829 a 11.575432,11.575432 0 0 1 -2.03195,0.16655 12.769062,12.769062 0 0 1 -2.09857,-0.16655 v -11.15905 q -0.0222,-2.40947 -1.2547,-3.40879 z" + id="path12-4" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 140.38483,169.73404 a 4.3137222,4.3137222 0 0 1 -0.58293,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05348,-1.95422 6.7453957,6.7453957 0 0 1 -4.76341,2.13188 q -2.56492,0 -3.88624,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.29911,-3.4643 q 0,-2.77588 1.81543,-4.21379 a 7.3338829,7.3338829 0 0 1 4.64682,-1.4157 q 1.53229,0 2.89803,0.13324 v -0.99932 q 0,-2.63153 -2.53161,-2.63153 -1.79877,0 -5.09652,1.19918 a 4.674587,4.674587 0 0 1 -1.11035,-2.96465 18.581761,18.581761 0 0 1 7.21729,-1.49897 5.8682167,5.8682167 0 0 1 4.0639,1.39904 q 1.56559,1.39905 1.56559,4.23045 v 6.81757 q 0.0333,1.83208 0.96601,2.59822 z m -8.37206,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06526,0.69952 2.3928111,2.3928111 0 0 0 -0.69952,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.54408 z" + id="path14-5" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 144.48203,169.6008 q -1.49897,-2.29843 -1.49897,-6.34567 0,-4.04724 1.8987,-6.34567 a 5.740526,5.740526 0 0 1 4.56355,-2.29843 6.4400486,6.4400486 0 0 1 4.49693,1.66553 3.7696491,3.7696491 0 0 1 2.63154,-1.43235 3.1200925,3.1200925 0 0 1 0.88273,0.93269 3.8862362,3.8862362 0 0 1 0.55518,1.16587 q -0.9327,0.79946 -0.9327,2.86472 v 9.438 q 0,5.29638 -1.73215,7.49488 -1.73215,2.1985 -5.69611,2.22071 a 16.100121,16.100121 0 0 1 -5.9626,-1.11036 4.4802752,4.4802752 0 0 1 1.03263,-3.03126 10.892565,10.892565 0 0 0 4.48028,1.03263 q 2.18184,0 3.0146,-1.11035 a 4.9965894,4.9965894 0 0 0 0.83277,-3.06458 v -1.33242 a 6.4011862,6.4011862 0 0 1 -4.16383,1.56559 4.9188647,4.9188647 0 0 1 -4.40255,-2.30953 z m 8.56083,-2.69816 v -7.72806 a 4.2915151,4.2915151 0 0 0 -2.86471,-1.36573 2.4039147,2.4039147 0 0 0 -2.18185,1.43235 8.6885138,8.6885138 0 0 0 -0.7828,4.09721 q 0,2.66485 0.71618,3.93065 a 2.1318781,2.1318781 0 0 0 1.88205,1.2658 4.2304457,4.2304457 0 0 0 3.23113,-1.63222 z" + id="path16-2" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 174.20619,164.67083 h -9.32697 a 5.6405943,5.6405943 0 0 0 0.88273,3.04792 q 0.7828,1.0826 2.74813,1.0826 a 10.120869,10.120869 0 0 0 4.36369,-1.16587 4.3803434,4.3803434 0 0 1 1.19918,2.5316 10.759323,10.759323 0 0 1 -6.41229,1.8987 q -3.74744,0 -5.37966,-2.43167 -1.63222,-2.43167 -1.63222,-6.2957 0,-3.88624 1.79877,-6.2957 a 6.0181143,6.0181143 0 0 1 5.14649,-2.43168 q 3.33106,0 5.14648,2.01529 a 7.3449864,7.3449864 0 0 1 1.79878,5.07987 13.04665,13.04665 0 0 1 -0.33311,2.96464 z m -6.42895,-7.06184 q -2.73146,0 -2.93133,4.13051 h 5.79605 v -0.39973 a 4.7245529,4.7245529 0 0 0 -0.69953,-2.69816 2.4316735,2.4316735 0 0 0 -2.14298,-1.03262 z" + id="path18-5" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + id="path24-3-6-4" + style="fill:#ffd952;fill-opacity:1;stroke-width:0.569924" + d="m 124.80273,70.162462 a 11.0036,11.0036 0 0 0 -4.33203,0.935547 L 76.261719,90.656602 a 1.5989086,1.5989086 0 0 0 -0.837891,2.138672 0.77169547,0.77169547 0 0 0 0.06641,0.177735 l 7.09375,14.021481 h 6.15625 l -0.875,-4.88867 c -0.07217,-0.39418 -0.711263,-3.187532 -1.316406,-5.197264 l 20.691398,6.462894 c 0.27198,1.28839 0.63292,2.49204 1.0625,3.62304 h 33.54883 c 0.36964,-1.13128 0.66138,-2.33705 0.85938,-3.62304 l 20.64648,-6.445316 c -0.60514,2.009734 -1.23639,4.785506 -1.30859,5.179686 l -0.875,4.88867 h 6.15429 l 7.02735,-13.894528 0.0664,-0.126953 0.0684,-0.171875 a 0.10548355,0.10548355 0 0 0 0,-0.04492 1.4878733,1.4878733 0 0 0 0.0664,-0.515625 1.5822533,1.5822533 0 0 0 -0.99414,-1.583985 L 129.35352,71.098009 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + id="path24-3-7" + style="fill:#49c8fa;fill-opacity:1;stroke-width:0.569924" + d="M 124.80273,79.416133 A 11.0036,11.0036 0 0 0 120.4707,80.35168 L 76.261719,99.910272 a 1.5989086,1.5989086 0 0 0 -0.837891,2.136718 0.77169547,0.77169547 0 0 0 0.06641,0.17773 l 3.847657,7.60352 h 8.175781 c -0.257897,-1.08856 -0.591943,-2.42953 -0.964844,-3.66797 l 11.744141,3.66797 h 53.371087 l 11.69336,-3.65039 c -0.37193,1.23522 -0.70076,2.56719 -0.95703,3.65039 h 8.17383 l 3.78125,-7.47656 0.0664,-0.12696 0.0684,-0.17187 a 0.10548355,0.10548355 0 0 0 0,-0.0449 1.4878733,1.4878733 0 0 0 0.0664,-0.51563 1.5822533,1.5822533 0 0 0 -0.99414,-1.582028 L 129.35352,80.35168 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + class="cls-2" + d="m 174.55595,110.92974 a 1.4878733,1.4878733 0 0 1 -0.0666,0.51631 0.10548355,0.10548355 0 0 1 0,0.0444 l -0.0666,0.17211 v 0 l -0.0666,0.12769 -10.69826,21.15223 c -1.48787,2.93688 -4.22489,2.84806 -3.76409,-0.12214 l 2.15408,-12.02512 c 0.0722,-0.39418 0.70508,-3.17006 1.31022,-5.1798 l -20.64702,6.4456 c -3.24223,21.05785 -30.95109,21.40761 -35.47023,0 l -20.691432,-6.46226 c 0.605143,2.00974 1.243596,4.80228 1.315769,5.19646 l 2.154085,12.02512 c 0.460796,2.9702 -2.276224,3.05902 -3.764098,0.12214 L 75.49024,111.66257 a 0.77169547,0.77169547 0 0 1 -0.06662,-0.17766 1.5989086,1.5989086 0 0 1 0.838317,-2.13743 L 120.47065,89.788613 a 11.0036,11.0036 0 0 1 8.88282,0 l 44.20871,19.558867 a 1.5822533,1.5822533 0 0 1 0.99377,1.58226 z" + id="path24-4" + style="fill:#ffd952;stroke-width:0.569924" /><path + class="cls-3" + d="m 139.0413,114.61611 19.11473,-7.69475 a 0.81055784,0.81055784 0 0 0 0,-1.50453 c -2.2207,-0.92714 -4.96328,-1.99308 -7.65033,-3.10899 -0.49411,-0.20541 -5.17425,3.15341 -5.60173,3.49762 l -8.23882,6.58439 c -1.99309,1.67108 -0.26649,3.28665 2.37615,2.22626 z" + id="path26-4" + style="fill:#45c8ff;stroke-width:0.569924" /><circle + class="cls-3" + cx="125.18409" + cy="122.13319" + r="9.9654207" + id="circle28-30" + style="fill:#45c8ff;stroke-width:0.569924" /><path + d="m 138.33068,100.19817 a 8.327649,8.327649 0 0 1 -2.77589,-0.288688 l -34.78736,-9.388036 a 8.4442361,8.4442361 0 0 1 -2.620433,-1.238044 z" + id="path6-0-7" + style="stroke-width:0.569924" /><path + class="cls-1" + d="m 85.377935,159.27452 5.163143,-0.0333 h 0.06662 q 2.864711,0 2.864711,2.69816 v 8.69407 a 24.849705,24.849705 0 0 1 -8.649651,1.43235 q -4.730105,0 -7.128468,-3.21447 -2.398363,-3.21447 -2.398363,-8.76068 0,-5.55177 2.981299,-8.62745 a 9.7600046,9.7600046 0 0 1 7.29502,-3.08123 13.368653,13.368653 0 0 1 7.811335,2.43167 3.9250986,3.9250986 0 0 1 -0.682867,1.76547 4.7634152,4.7634152 0 0 1 -1.282458,1.33242 9.798867,9.798867 0 0 0 -5.679457,-1.96533 5.3574542,5.3574542 0 0 0 -4.480275,2.04861 q -1.598909,2.03749 -1.598909,6.41229 0,8.22771 6.062529,8.22771 a 16.910679,16.910679 0 0 0 3.697476,-0.43303 v -3.16451 q 0,-1.49898 0.06662,-2.22071 h -2.442777 a 2.2873276,2.2873276 0 0 1 -1.515632,-0.41638 1.6655298,1.6655298 0 0 1 -0.483004,-1.33242 5.7072154,5.7072154 0 0 1 0.333106,-1.79322 z" + id="path8-6-8" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 111.07151,169.73404 a 4.3137222,4.3137222 0 0 1 -0.55518,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05902,-1.95422 6.7453957,6.7453957 0 0 1 -4.76342,2.13188 q -2.564913,0 -3.886233,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.299113,-3.4643 q 0,-2.77588 1.815427,-4.21379 a 7.3338829,7.3338829 0 0 1 4.669039,-1.3935 q 1.53228,0 2.89802,0.13325 v -0.99932 q 0,-2.63154 -2.53161,-2.63154 -1.79877,0 -5.096518,1.19918 a 4.674587,4.674587 0 0 1 -1.110353,-2.96464 18.581761,18.581761 0 0 1 7.217291,-1.49898 5.8682167,5.8682167 0 0 1 4.0639,1.39905 q 1.56559,1.39904 1.56559,4.23044 v 6.79537 q -0.0111,1.83208 0.9216,2.59822 z m -8.36096,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06525,0.68842 2.3928111,2.3928111 0 0 0 -0.69953,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.55518 z" + id="path10-2-6" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 113.76966,157.00939 a 3.986168,3.986168 0 0 1 0.55518,-1.21583 3.3310596,3.3310596 0 0 1 0.84942,-0.94935 4.1638245,4.1638245 0 0 1 3.51427,2.96464 q 1.33242,-2.96464 4.29707,-2.96464 a 10.215249,10.215249 0 0 1 1.93201,0.23317 7.4782288,7.4782288 0 0 1 -0.99932,3.88624 8.4497879,8.4497879 0 0 0 -1.49897,-0.19987 q -2.03195,0 -3.26444,2.16519 v 10.64829 a 11.575432,11.575432 0 0 1 -2.03195,0.16655 12.769062,12.769062 0 0 1 -2.09857,-0.16655 v -11.15905 q -0.0222,-2.40947 -1.2547,-3.40879 z" + id="path12-6-8" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 140.38483,169.73404 a 4.3137222,4.3137222 0 0 1 -0.58293,1.18253 4.0305821,4.0305821 0 0 1 -0.84942,0.94935 3.7640973,3.7640973 0 0 1 -3.05348,-1.95422 6.7453957,6.7453957 0 0 1 -4.76341,2.13188 q -2.56492,0 -3.88624,-1.49898 a 5.1298318,5.1298318 0 0 1 -1.29911,-3.4643 q 0,-2.77588 1.81543,-4.21379 a 7.3338829,7.3338829 0 0 1 4.64682,-1.4157 q 1.53229,0 2.89803,0.13324 v -0.99932 q 0,-2.63153 -2.53161,-2.63153 -1.79877,0 -5.09652,1.19918 a 4.674587,4.674587 0 0 1 -1.11035,-2.96465 18.581761,18.581761 0 0 1 7.21729,-1.49897 5.8682167,5.8682167 0 0 1 4.0639,1.39904 q 1.56559,1.39905 1.56559,4.23045 v 6.81757 q 0.0333,1.83208 0.96601,2.59822 z m -8.37206,-0.83276 a 4.7134493,4.7134493 0 0 0 3.33106,-1.59891 v -2.94244 a 22.368065,22.368065 0 0 0 -2.53161,-0.13324 2.775883,2.775883 0 0 0 -2.06526,0.69952 2.3928111,2.3928111 0 0 0 -0.69952,1.76546 2.3539488,2.3539488 0 0 0 0.55518,1.66553 1.8431863,1.8431863 0 0 0 1.41015,0.54408 z" + id="path14-1-8" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 144.48203,169.6008 q -1.49897,-2.29843 -1.49897,-6.34567 0,-4.04724 1.8987,-6.34567 a 5.740526,5.740526 0 0 1 4.56355,-2.29843 6.4400486,6.4400486 0 0 1 4.49693,1.66553 3.7696491,3.7696491 0 0 1 2.63154,-1.43235 3.1200925,3.1200925 0 0 1 0.88273,0.93269 3.8862362,3.8862362 0 0 1 0.55518,1.16587 q -0.9327,0.79946 -0.9327,2.86472 v 9.438 q 0,5.29638 -1.73215,7.49488 -1.73215,2.1985 -5.69611,2.22071 a 16.100121,16.100121 0 0 1 -5.9626,-1.11036 4.4802752,4.4802752 0 0 1 1.03263,-3.03126 10.892565,10.892565 0 0 0 4.48028,1.03263 q 2.18184,0 3.0146,-1.11035 a 4.9965894,4.9965894 0 0 0 0.83277,-3.06458 v -1.33242 a 6.4011862,6.4011862 0 0 1 -4.16383,1.56559 4.9188647,4.9188647 0 0 1 -4.40255,-2.30953 z m 8.56083,-2.69816 v -7.72806 a 4.2915151,4.2915151 0 0 0 -2.86471,-1.36573 2.4039147,2.4039147 0 0 0 -2.18185,1.43235 8.6885138,8.6885138 0 0 0 -0.7828,4.09721 q 0,2.66485 0.71618,3.93065 a 2.1318781,2.1318781 0 0 0 1.88205,1.2658 4.2304457,4.2304457 0 0 0 3.23113,-1.63222 z" + id="path16-8-4" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-1" + d="m 174.20619,164.67083 h -9.32697 a 5.6405943,5.6405943 0 0 0 0.88273,3.04792 q 0.7828,1.0826 2.74813,1.0826 a 10.120869,10.120869 0 0 0 4.36369,-1.16587 4.3803434,4.3803434 0 0 1 1.19918,2.5316 10.759323,10.759323 0 0 1 -6.41229,1.8987 q -3.74744,0 -5.37966,-2.43167 -1.63222,-2.43167 -1.63222,-6.2957 0,-3.88624 1.79877,-6.2957 a 6.0181143,6.0181143 0 0 1 5.14649,-2.43168 q 3.33106,0 5.14648,2.01529 a 7.3449864,7.3449864 0 0 1 1.79878,5.07987 13.04665,13.04665 0 0 1 -0.33311,2.96464 z m -6.42895,-7.06184 q -2.73146,0 -2.93133,4.13051 h 5.79605 v -0.39973 a 4.7245529,4.7245529 0 0 0 -0.69953,-2.69816 2.4316735,2.4316735 0 0 0 -2.14298,-1.03262 z" + id="path18-7-3" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><path + id="path24-3-6-9-1" + style="fill:#ff9329;fill-opacity:1;stroke-width:0.569924" + d="m 124.80273,70.162462 a 11.0036,11.0036 0 0 0 -4.33203,0.935547 L 76.261719,90.656602 a 1.5989086,1.5989086 0 0 0 -0.837891,2.138672 0.77169547,0.77169547 0 0 0 0.06641,0.177735 l 7.09375,14.021481 h 6.15625 l -0.875,-4.88867 c -0.07217,-0.39418 -0.711263,-3.187532 -1.316406,-5.197264 l 20.691398,6.462894 c 0.27198,1.28839 0.63292,2.49204 1.0625,3.62304 h 33.54883 c 0.36964,-1.13128 0.66138,-2.33705 0.85938,-3.62304 l 20.64648,-6.445316 c -0.60514,2.009734 -1.23639,4.785506 -1.30859,5.179686 l -0.875,4.88867 h 6.15429 l 7.02735,-13.894528 0.0664,-0.126953 0.0684,-0.171875 a 0.10548355,0.10548355 0 0 0 0,-0.04492 1.4878733,1.4878733 0 0 0 0.0664,-0.515625 1.5822533,1.5822533 0 0 0 -0.99414,-1.583985 L 129.35352,71.098009 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + id="path24-3-2-4" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" + d="M 124.80273,79.416133 A 11.0036,11.0036 0 0 0 120.4707,80.35168 L 76.261719,99.910272 a 1.5989086,1.5989086 0 0 0 -0.837891,2.136718 0.77169547,0.77169547 0 0 0 0.06641,0.17773 l 3.847657,7.60352 h 8.175781 c -0.257897,-1.08856 -0.591943,-2.42953 -0.964844,-3.66797 l 11.744141,3.66797 h 53.371087 l 11.69336,-3.65039 c -0.37193,1.23522 -0.70076,2.56719 -0.95703,3.65039 h 8.17383 l 3.78125,-7.47656 0.0664,-0.12696 0.0684,-0.17187 a 0.10548355,0.10548355 0 0 0 0,-0.0449 1.4878733,1.4878733 0 0 0 0.0664,-0.51563 1.5822533,1.5822533 0 0 0 -0.99414,-1.582028 L 129.35352,80.35168 a 11.0036,11.0036 0 0 0 -4.55079,-0.935547 z" /><path + class="cls-2" + d="m 174.55595,110.92974 a 1.4878733,1.4878733 0 0 1 -0.0666,0.51631 0.10548355,0.10548355 0 0 1 0,0.0444 l -0.0666,0.17211 v 0 l -0.0666,0.12769 -10.69826,21.15223 c -1.48787,2.93688 -4.22489,2.84806 -3.76409,-0.12214 l 2.15408,-12.02512 c 0.0722,-0.39418 0.70508,-3.17006 1.31022,-5.1798 l -20.64702,6.4456 c -3.24223,21.05785 -30.95109,21.40761 -35.47023,0 l -20.691432,-6.46226 c 0.605143,2.00974 1.243596,4.80228 1.315769,5.19646 l 2.154085,12.02512 c 0.460796,2.9702 -2.276224,3.05902 -3.764098,0.12214 L 75.49024,111.66257 a 0.77169547,0.77169547 0 0 1 -0.06662,-0.17766 1.5989086,1.5989086 0 0 1 0.838317,-2.13743 L 120.47065,89.788613 a 11.0036,11.0036 0 0 1 8.88282,0 l 44.20871,19.558867 a 1.5822533,1.5822533 0 0 1 0.99377,1.58226 z" + id="path24-0-9" + style="fill:#ff9329;fill-opacity:1;stroke-width:0.569924" /><path + class="cls-3" + d="m 139.0413,114.61611 19.11473,-7.69475 a 0.81055784,0.81055784 0 0 0 0,-1.50453 c -2.2207,-0.92714 -4.96328,-1.99308 -7.65033,-3.10899 -0.49411,-0.20541 -5.17425,3.15341 -5.60173,3.49762 l -8.23882,6.58439 c -1.99309,1.67108 -0.26649,3.28665 2.37615,2.22626 z" + id="path26-2-2" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /><circle + class="cls-3" + cx="125.18409" + cy="122.13319" + r="9.9654207" + id="circle28-3-0" + style="fill:#4e4e4e;fill-opacity:1;stroke-width:0.569924" /></g></g><text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:5.64444px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="118.24205" + y="182.54555" + id="text1882"><tspan + sodipodi:role="line" + id="tspan1880" + x="118.24205" + y="182.54555" + style="font-size:5.64444px;stroke-width:0.264583px">*(not really a database)</tspan></text></g><g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Layer 2" + style="display:inline"><rect + style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.265;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13086-2" + width="154.11569" + height="77.61647" + x="33.112736" + y="109.40408" /><image + width="65.039139" + height="65.039139" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABmJLR0QA/wD/AP+gvaeTAAAgAElE QVR4nO3debxddX3v/9fnG4aQAYgQhqiAcQASLWIcqqBYxKEK1TpUH3qx2sd1eLS92tbWsbe1va11 6PWn1qpwb2uLQyveqjU4IbaCwWolCMUEEA2DSoCACZBAmL6f3x9rHziJCRnY53z32ev1fDzWI2ft nCw++3D2+r7XWt8BJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS JEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmSJEmS JEmSJEmSJEmSJI2iaF2ApKHbG5gzaX8PYP6k/QD23+rfbABy0v6twN2T9m8D7hhijZIaMwBIbc0H DgEOBPYb7O8HzAXmllL2Hbw2NzPn0jXc8yb+HlhA19jvPc1130EXCtYDmwbbRmBDREzs31prvXnw fZuAm+mCxc3AjcB1g31JDRgApOGaCzyYrkFfOPjzkFLKgcCBmbkQOHjS3013wz1q7qALA+uA6yLi RuDGWus64PrB6zcOtp/RBQlJQ2AAkHbNAmAxsAg4tJSyGFicmROvHYKfq6m0GbgWWBMRa2qta4C1 E68BVwG1XXnSzOGJStrSHODIwfaoUsoRmXkYMLH1/Yp91N0BXANcExHX1FqvAn4IXD7YbmtYmzRS DADqq4kr+aWllCV0V/FLgaOA0rQyTaW1wKrB3YPVwCq6OwdXsmUnSGnsGQA07g4AlgHHllKOzMyj 6a7uF7QtSyNmPXB5RKyutV4OXASsBG5qW5Y0dQwAGif7AY8BlpVSlmXmMuBo/D3X7lsLrMzMlXSB 4D/pOidKM54nRs1U+wOPxsZe089QoLHgyVIzxcOAp5VSnp6ZT6N7fi+NijURcV6t9ZvAuXSjEaSR ZgDQqFoMHF9KOS4zn0kXAKSZYm1ErKi1ngOcD6zGToYaMQYAjYqjgRNKKU/LzBPoxtRL4+LaiPhm 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 P2vmuIUtH0lsb+TDgklfz6JrvDVzXJSZH6Obvc/JsbRDBgDtjmWllNdm5ivwKk5qaXNELK+1no4d +7SLDAB6IBYAL42Il9OtbubvkzT1KrAiMz8NfIb+9iHRA+QJW8PyELqFh04FlrUuRhpDqzPzs8An gB+3LkYznwFAU+GXSikvz8yX0g3ZkrR7roqIz9RaPwVc0roYjRcDgKbaUuDkiDgFeAr+zkk7snrw XP8s4Hy6KXulofNkrOm0mG5Fwucys9dKl4bpDuBbmfll4AvAlY3rUU8YANTKHOAppZRTMvP5dLO5 SX1xXUR8vda6HPga3VBNaVoZADQqjgSeVko5ITOfTjftqzQufhoR36y1ngecC/ywdUGSAUCjahFw XCnlpMw8nm5BF2mmWBsRK2qt59A9x1/VuiBpawYAzRSHAycM7hA8DXhE64KkSa6IiPNqrefSXeFf 07ogaUcMAJqp9gMeQzcr4bLMXAYcjb/TmnprgZWZuRJYCXwXuKFtSdKu82SpcbIv8EsYCjQ8Wzf2 36FbuEma8TwxatwtAB4HHFtKOTIzjwaOoluRTppwE3BZRFxaa70MuIiuwXeaXY0tA4D6agHdvARL SylLgMWZuZRuNMKsppVpKq0FVkXEmlrrarrOeWsGm9QrBgBpS/vQhYAjgUeVUg7PzMOAw+g6Is5u WZx2aDNwNXBNRFxTa72absjd5YPt9pbFSaPEACDtmgV0QxQPBRaXUhbT3T2YeO0IoLQrb+ytp7uK v3ZwFT9x9b4WuBa4im61PEk7YACQhmsfuiBwEHAgsBA4qJRyEHBgZi4cvHbw4O/3aVXoiLgduBG4 HlgXEeuAG2ut19N1tls36e+vwyt4aWgMAFJb87gvEMwH9h+8NheYW0pZMPE1MC8z96ebRnku3aiH fYE96YZFTqebgbvoprC9BdgE3BYRG4CNg/1Ntdb1E18PXt8A3ErXoN8weF1SAwYAafwEXZCYbMEO 9tfvYH8DrkonSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk SZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIkSZIk SZIkSZIkSdKU+/8B8UD79J00AfcAAAAASUVORK5CYII= " + id="image7444" + x="42.921436" + y="103.45746" + style="stroke-width:2.08285" /><image + width="34.06498" + height="34.06498" + preserveAspectRatio="none" + xlink:href="data:image/png;base64,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 " + id="image13044" + x="74.692276" + y="130.02034" + style="stroke-width:3.97671" /><text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;stroke-width:0.264583" + x="73.229004" + y="183.68733" + id="text4115-3-5"><tspan + sodipodi:role="line" + style="font-size:12.7px;stroke-width:0.264583" + x="73.229004" + y="183.68733" + id="tspan4117-6-6">Database</tspan></text><rect + style="fill:#ffffff;fill-opacity:0.733011;stroke:none;stroke-width:1.265;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect13086" + width="130.82137" + height="113.0019" + x="37.281914" + y="74.605423" /></g><g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="Layer 3" + style="display:none;fill:#ffffff" + sodipodi:insensitive="true"><rect + style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:1.2745" + id="rect2004" + width="172.00893" + height="19.827272" + x="80.964157" + y="52.231174" /><rect + style="fill:#ffffff;fill-opacity:0.703087;stroke-width:1.2745" + id="rect2006" + width="185.24678" + height="32.308674" + x="77.66877" + y="16.311932" /><text + xml:space="preserve" + style="font-size:12.7px;line-height:1.25;font-family:sans-serif;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.264583" + x="169.47803" + y="66.103424" + id="text4115-0"><tspan + sodipodi:role="line" + style="font-size:12.7px;fill:#000000;fill-opacity:1;stroke-width:0.264583" + x="169.47803" + y="66.103424" + id="tspan4117-4"><tspan + style="font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1" + id="tspan2744">Custom</tspan> user-facing application</tspan></text><path + style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.05833;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" + d="M 81.62001,34.797981 H 251.8384" + id="path2876" /></g></svg> diff --git a/doc/talks/2022-06-23-stack/talk.pdf b/doc/talks/2022-06-23-stack/talk.pdf new file mode 100644 index 00000000..880f83d6 --- /dev/null +++ b/doc/talks/2022-06-23-stack/talk.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f245bb017eb85a227d96e576ae821f165ef478d46de02535c196638aa5fc84b0 +size 2575953 diff --git a/doc/talks/2022-06-23-stack/talk.tex b/doc/talks/2022-06-23-stack/talk.tex new file mode 100644 index 00000000..206af43a --- /dev/null +++ b/doc/talks/2022-06-23-stack/talk.tex @@ -0,0 +1,480 @@ +%\nonstopmode +\documentclass[aspectratio=169]{beamer} +\usepackage[utf8]{inputenc} +% \usepackage[frenchb]{babel} +\usepackage{amsmath} +\usepackage{mathtools} +\usepackage{breqn} +\usepackage{multirow} +\usetheme{boxes} +\usepackage{graphicx} +\usepackage{adjustbox} +%\useoutertheme[footline=authortitle,subsection=false]{miniframes} +%\useoutertheme[footline=authorinstitute,subsection=false]{miniframes} +\useoutertheme{infolines} +\setbeamertemplate{headline}{} + +\beamertemplatenavigationsymbolsempty + +\definecolor{TitleOrange}{RGB}{255,137,0} +\setbeamercolor{title}{fg=TitleOrange} +\setbeamercolor{frametitle}{fg=TitleOrange} + +\definecolor{ListOrange}{RGB}{255,145,5} +\setbeamertemplate{itemize item}{\color{ListOrange}$\blacktriangleright$} + +\definecolor{verygrey}{RGB}{70,70,70} +\setbeamercolor{normal text}{fg=verygrey} + + +\usepackage{tabu} +\usepackage{multicol} +\usepackage{vwcol} +\usepackage{stmaryrd} +\usepackage{graphicx} + +\usepackage[normalem]{ulem} + +\title{Introducing Garage} +\subtitle{a new storage platform for self-hosted geo-distributed clusters} +\author{Deuxfleurs Association} +\date{IMT Atlantique, 2022-06-23} + +\begin{document} + +\begin{frame} + \centering + \includegraphics[width=.3\linewidth]{../../sticker/Garage.pdf} + \vspace{1em} + + {\large\bf Deuxfleurs Association} + \vspace{1em} + + \url{https://garagehq.deuxfleurs.fr/} + + Matrix channel: \texttt{\#garage:deuxfleurs.fr} +\end{frame} + +\begin{frame} + \frametitle{Who we are} + \begin{columns}[t] + \begin{column}{.2\textwidth} + \centering + \adjincludegraphics[width=.4\linewidth, valign=t]{assets/alex.jpg} + \end{column} + \begin{column}{.6\textwidth} + \textbf{Alex Auvolat}\\ + PhD at Inria, team WIDE; co-founder of Deuxfleurs + \end{column} + \begin{column}{.2\textwidth} + ~ + \end{column} + \end{columns} + \vspace{1em} + + \begin{columns}[t] + \begin{column}{.2\textwidth} + ~ + \end{column} + \begin{column}{.6\textwidth} + \textbf{Quentin Dufour}\\ + PhD at Inria, team WIDE; co-founder of Deuxfleurs + \end{column} + \begin{column}{.2\textwidth} + \centering + \adjincludegraphics[width=.5\linewidth, valign=t]{assets/quentin.jpg} + \end{column} + \end{columns} + \vspace{2em} + + \begin{columns}[t] + \begin{column}{.2\textwidth} + \centering + \adjincludegraphics[width=.5\linewidth, valign=t]{assets/deuxfleurs.pdf} + \end{column} + \begin{column}{.6\textwidth} + \textbf{Deuxfleurs}\\ + A non-profit self-hosting collective,\\ + member of the CHATONS network + \end{column} + \begin{column}{.2\textwidth} + \centering + \adjincludegraphics[width=.7\linewidth, valign=t]{assets/logo_chatons.png} + \end{column} + \end{columns} + +\end{frame} + +\begin{frame} + \frametitle{Our objective at Deuxfleurs} + + \begin{center} + \textbf{Promote self-hosting and small-scale hosting\\ + as an alternative to large cloud providers} + \end{center} + \vspace{2em} + \visible<2->{ + Why is it hard? + } + \visible<3->{ + \vspace{2em} + \begin{center} + \textbf{\underline{Resilience}}\\ + {\footnotesize (we want good uptime/availability with low supervision)} + \end{center} + } +\end{frame} + +\begin{frame} + \frametitle{How to make a \underline{stable} system} + + Enterprise-grade systems typically employ: + \vspace{1em} + \begin{itemize} + \item RAID + \item Redundant power grid + UPS + \item Redundant Internet connections + \item Low-latency links + \item ... + \end{itemize} + \vspace{1em} + $\to$ it's costly and only worth it at DC scale +\end{frame} + +\begin{frame} + \frametitle{How to make a \underline{resilient} system} + + \only<1,4-5>{ + Instead, we use: + \vspace{1em} + \begin{itemize} + \item \textcolor<2->{gray}{Commodity hardware (e.g. old desktop PCs)} + \vspace{.5em} + \item<4-> \textcolor<5->{gray}{Commodity Internet (e.g. FTTB, FTTH) and power grid} + \vspace{.5em} + \item<5-> \textcolor<6->{gray}{\textbf{Geographical redundancy} (multi-site replication)} + \end{itemize} + } + \only<2>{ + \begin{center} + \includegraphics[width=.8\linewidth]{assets/atuin.jpg} + \end{center} + } + \only<3>{ + \begin{center} + \includegraphics[width=.8\linewidth]{assets/neptune.jpg} + \end{center} + } + \only<6>{ + \begin{center} + \includegraphics[width=.5\linewidth]{assets/inframap.jpg} + \end{center} + } +\end{frame} + +\begin{frame} + \frametitle{How to make this happen} + \begin{center} + \only<1>{\includegraphics[width=.8\linewidth]{assets/slide1.png}}% + \only<2>{\includegraphics[width=.8\linewidth]{assets/slide2.png}}% + \only<3>{\includegraphics[width=.8\linewidth]{assets/slide3.png}}% + \end{center} +\end{frame} + +\begin{frame} + \frametitle{Distributed file systems are slow} + File systems are complex, for example: + \vspace{1em} + \begin{itemize} + \item Concurrent modification by several processes + \vspace{1em} + \item Folder hierarchies + \vspace{1em} + \item Other requirements of the POSIX spec + \end{itemize} + \vspace{1em} + Coordination in a distributed system is costly + + \vspace{1em} + Costs explode with commodity hardware / Internet connections\\ + {\small (we experienced this!)} +\end{frame} + +\begin{frame} + \frametitle{A simpler solution: object storage} + Only two operations: + \vspace{1em} + \begin{itemize} + \item Put an object at a key + \vspace{1em} + \item Retrieve an object from its key + \end{itemize} + \vspace{1em} + {\footnotesize (and a few others)} + + \vspace{1em} + Sufficient for many applications! +\end{frame} + +\begin{frame} + \frametitle{A simpler solution: object storage} + \begin{center} + \includegraphics[width=.2\linewidth]{../2020-12-02_wide-team/img/Amazon-S3.jpg} + \hspace{5em} + \includegraphics[width=.2\linewidth]{assets/minio.png} + \end{center} + \vspace{1em} + S3: a de-facto standard, many compatible applications + + \vspace{1em} + + MinIO is self-hostable but not suited for geo-distributed deployments +\end{frame} + + +\begin{frame} + \frametitle{But what is Garage, exactly?} + \textbf{Garage is a self-hosted drop-in replacement for the Amazon S3 object store}\\ + \vspace{.5em} + that implements resilience through geographical redundancy on commodity hardware + \begin{center} + \includegraphics[width=.8\linewidth]{assets/garageuses.png} + \end{center} +\end{frame} + +\begin{frame} + \frametitle{Overview} + \begin{center} + \only<1>{\includegraphics[width=.45\linewidth]{assets/garage2a.drawio.pdf}}% + \only<2>{\includegraphics[width=.45\linewidth]{assets/garage2b.drawio.pdf}}% + \end{center} +\end{frame} + +\begin{frame} + \frametitle{Garage is \emph{location-aware}} + \begin{center} + \includegraphics[width=\linewidth]{assets/location-aware.png} + \end{center} + \vspace{2em} + Garage replicates data on different zones when possible +\end{frame} + +\begin{frame} + \frametitle{Garage is \emph{location-aware}} + \begin{center} + \includegraphics[width=.8\linewidth]{assets/map.png} + \end{center} +\end{frame} + +\begin{frame} + \frametitle{How to spread files over different cluster nodes?} + \textbf{Consistent hashing (DynamoDB):} + \vspace{1em} + + \begin{center} + \only<1>{\includegraphics[width=.45\columnwidth]{assets/consistent_hashing_1.pdf}}% + \only<2>{\includegraphics[width=.45\columnwidth]{assets/consistent_hashing_2.pdf}}% + \only<3>{\includegraphics[width=.45\columnwidth]{assets/consistent_hashing_3.pdf}}% + \only<4>{\includegraphics[width=.45\columnwidth]{assets/consistent_hashing_4.pdf}}% + \end{center} +\end{frame} + +\begin{frame} + \frametitle{How to spread files over different cluster nodes?} + \textbf{Issues with consistent hashing:} + \vspace{1em} + \begin{itemize} + \item Doesn't dispatch data based on geographical location of nodes + \vspace{1em} + \item<2-> Geographically aware adaptation, try 1:\\ + data quantities not well balanced between nodes + \vspace{1em} + \item<3-> Geographically aware adaptation, try 2:\\ + too many reshuffles when adding/removing nodes + \end{itemize} +\end{frame} + +\begin{frame} + \frametitle{How to spread files over different cluster nodes?} + \textbf{Garage's method: build an index table} + \vspace{1em} + + Realization: we can actually precompute an optimal solution + \vspace{1em} + + \visible<2->{ + \begin{center} + \begin{tabular}{|l|l|l|l|} + \hline + \textbf{Partition} & \textbf{Node 1} & \textbf{Node 2} & \textbf{Node 3} \\ + \hline + \hline + Partition 0 & Io (jupiter) & Drosera (atuin) & Courgette (neptune) \\ + \hline + Partition 1 & Datura (atuin) & Courgette (neptune) & Io (jupiter) \\ + \hline + Partition 2 & Io(jupiter) & Celeri (neptune) & Drosera (atuin) \\ + \hline + \hspace{1em}$\vdots$ & \hspace{1em}$\vdots$ & \hspace{1em}$\vdots$ & \hspace{1em}$\vdots$ \\ + \hline + Partition 255 & Concombre (neptune) & Io (jupiter) & Drosera (atuin) \\ + \hline + \end{tabular} + \end{center} + } + \vspace{1em} + \visible<3->{ + The index table is built centrally using an optimal* algorithm,\\ + then propagated to all nodes\\ + \hfill\footnotesize *not yet optimal but will be soon + } +\end{frame} + +\begin{frame} + \frametitle{Garage's internal data structures} + \centering + \includegraphics[width=.75\columnwidth]{assets/garage_tables.pdf} +\end{frame} + +%\begin{frame} +% \frametitle{Garage's architecture} +% \begin{center} +% \includegraphics[width=.35\linewidth]{assets/garage.drawio.pdf} +% \end{center} +%\end{frame} + +\begin{frame} + \frametitle{Garage is \emph{coordination-free}:} + \begin{itemize} + \item No Raft or Paxos + \vspace{1em} + \item Internal data types are CRDTs + \vspace{1em} + \item All nodes are equivalent (no master/leader/index node) + \end{itemize} + \vspace{2em} + $\to$ less sensitive to higher latencies between nodes +\end{frame} + +\begin{frame} + \frametitle{Consistency model} + \begin{itemize} + \item Not ACID (not required by S3 spec) / not linearizable + \vspace{1em} + \item \textbf{Read-after-write consistency}\\ + {\footnotesize (stronger than eventual consistency)} + \end{itemize} +\end{frame} + +\begin{frame} + \frametitle{Impact on performances} + \begin{center} + \includegraphics[width=.8\linewidth]{assets/endpoint-latency-dc.png} + \end{center} +\end{frame} + + +\begin{frame} + \frametitle{An ever-increasing compatibility list} + \begin{center} + \includegraphics[width=.7\linewidth]{assets/compatibility.png} + \end{center} +\end{frame} + +\begin{frame} + \frametitle{Further plans for Garage} + \begin{center} + \only<1>{\includegraphics[width=.8\linewidth]{assets/slideB1.png}}% + \only<2>{\includegraphics[width=.8\linewidth]{assets/slideB2.png}}% + \only<3>{\includegraphics[width=.8\linewidth]{assets/slideB3.png}}% + \end{center} +\end{frame} + +\begin{frame} + \frametitle{K2V Design} + \begin{itemize} + \item A new, custom, minimal API + \vspace{1em} + \item<2-> Exposes the partitoning mechanism of Garage\\ + K2V = partition key / sort key / value (like Dynamo) + \vspace{1em} + \item<3-> Coordination-free, CRDT-friendly (inspired by Riak)\\ + \vspace{1em} + \item<4-> Cryptography-friendly: values are binary blobs + \end{itemize} +\end{frame} + +\begin{frame} + \frametitle{Application: an e-mail storage server} + \begin{center} + \only<1>{\includegraphics[width=.9\linewidth]{assets/aerogramme.png}}% + \end{center} +\end{frame} + +\begin{frame} + \frametitle{Aerogramme data model} + \begin{center} + \only<1>{\includegraphics[width=.4\linewidth]{assets/aerogramme_datatype.drawio.pdf}}% + \only<2->{\includegraphics[width=.9\linewidth]{assets/aerogramme_keys.drawio.pdf}\vspace{1em}}% + \end{center} + \visible<3->{Aerogramme encrypts all stored values for privacy\\ + (Garage server administrators can't read your mail)} +\end{frame} + +\begin{frame} + \frametitle{Different deployment scenarios} + \begin{center} + \only<1>{\includegraphics[width=.9\linewidth]{assets/aerogramme_components1.drawio.pdf}}% + \only<2>{\includegraphics[width=.9\linewidth]{assets/aerogramme_components2.drawio.pdf}}% + \end{center} +\end{frame} + +\begin{frame} + \frametitle{A new model for building resilient software} + \begin{itemize} + \item Design a data model suited to K2V\\ + {\footnotesize (see Cassandra docs on porting SQL data models to Cassandra)} + \vspace{1em} + \begin{itemize} + \item Use CRDTs or other eventually consistent data types (see e.g. Bayou) + \vspace{1em} + \item Store opaque binary blobs to provide End-to-End Encryption\\ + \end{itemize} + \vspace{1em} + \item Store big blobs (files) in S3 + \vspace{1em} + \item Let Garage manage sharding, replication, failover, etc. + \end{itemize} +\end{frame} + +\begin{frame} + \frametitle{Research perspectives} + \begin{itemize} + \item Write about Garage's global architecture \emph{(paper in progress)} + \vspace{1em} + \item Measure and improve Garage's performances + \vspace{1em} + \item Discuss the optimal layout algorithm, provide proofs + \vspace{1em} + \item Write about our proposed architecture for (E2EE) apps over K2V+S3 + \end{itemize} +\end{frame} + +\begin{frame} + \frametitle{Where to find us} + \begin{center} + \includegraphics[width=.25\linewidth]{../../logo/garage_hires.png}\\ + \vspace{-1em} + \url{https://garagehq.deuxfleurs.fr/}\\ + \url{mailto:garagehq@deuxfleurs.fr}\\ + \texttt{\#garage:deuxfleurs.fr} on Matrix + + \vspace{1.5em} + \includegraphics[width=.06\linewidth]{assets/rust_logo.png} + \includegraphics[width=.13\linewidth]{assets/AGPLv3_Logo.png} + \end{center} +\end{frame} + +\end{document} + +%% vim: set ts=4 sw=4 tw=0 noet spelllang=en : diff --git a/k2v_test.py b/k2v_test.py new file mode 100755 index 00000000..3219056e --- /dev/null +++ b/k2v_test.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python + +import os +import requests +from datetime import datetime + +# let's talk to our AWS Elasticsearch cluster +#from requests_aws4auth import AWS4Auth +#auth = AWS4Auth('GK31c2f218a2e44f485b94239e', +# 'b892c0665f0ada8a4755dae98baa3b133590e11dae3bcc1f9d769d67f16c3835', +# 'us-east-1', +# 's3') + +from aws_requests_auth.aws_auth import AWSRequestsAuth +auth = AWSRequestsAuth(aws_access_key='GK31c2f218a2e44f485b94239e', + aws_secret_access_key='b892c0665f0ada8a4755dae98baa3b133590e11dae3bcc1f9d769d67f16c3835', + aws_host='localhost:3812', + aws_region='us-east-1', + aws_service='k2v') + + +print("-- ReadIndex") +response = requests.get('http://localhost:3812/alex', + auth=auth) +print(response.headers) +print(response.text) + + +sort_keys = ["a", "b", "c", "d"] + +for sk in sort_keys: + print("-- (%s) Put initial (no CT)"%sk) + response = requests.put('http://localhost:3812/alex/root?sort_key=%s'%sk, + auth=auth, + data='{}: Hello, world!'.format(datetime.timestamp(datetime.now()))) + print(response.headers) + print(response.text) + + print("-- Get") + response = requests.get('http://localhost:3812/alex/root?sort_key=%s'%sk, + auth=auth) + print(response.headers) + print(response.text) + ct = response.headers["x-garage-causality-token"] + + print("-- ReadIndex") + response = requests.get('http://localhost:3812/alex', + auth=auth) + print(response.headers) + print(response.text) + + print("-- Put with CT") + response = requests.put('http://localhost:3812/alex/root?sort_key=%s'%sk, + auth=auth, + headers={'x-garage-causality-token': ct}, + data='{}: Good bye, world!'.format(datetime.timestamp(datetime.now()))) + print(response.headers) + print(response.text) + + print("-- Get") + response = requests.get('http://localhost:3812/alex/root?sort_key=%s'%sk, + auth=auth) + print(response.headers) + print(response.text) + + print("-- Put again with same CT (concurrent)") + response = requests.put('http://localhost:3812/alex/root?sort_key=%s'%sk, + auth=auth, + headers={'x-garage-causality-token': ct}, + data='{}: Concurrent value, oops'.format(datetime.timestamp(datetime.now()))) + print(response.headers) + print(response.text) + +for sk in sort_keys: + print("-- (%s) Get"%sk) + response = requests.get('http://localhost:3812/alex/root?sort_key=%s'%sk, + auth=auth) + print(response.headers) + print(response.text) + ct = response.headers["x-garage-causality-token"] + + print("-- Delete") + response = requests.delete('http://localhost:3812/alex/root?sort_key=%s'%sk, + headers={'x-garage-causality-token': ct}, + auth=auth) + print(response.headers) + print(response.text) + +print("-- ReadIndex") +response = requests.get('http://localhost:3812/alex', + auth=auth) +print(response.headers) +print(response.text) + +print("-- InsertBatch") +response = requests.post('http://localhost:3812/alex', + auth=auth, + data=''' +[ + {"pk": "root", "sk": "a", "ct": null, "v": "aW5pdGlhbCB0ZXN0Cg=="}, + {"pk": "root", "sk": "b", "ct": null, "v": "aW5pdGlhbCB0ZXN1Cg=="}, + {"pk": "root", "sk": "c", "ct": null, "v": "aW5pdGlhbCB0ZXN2Cg=="} +] +''') +print(response.headers) +print(response.text) + +print("-- ReadIndex") +response = requests.get('http://localhost:3812/alex', + auth=auth) +print(response.headers) +print(response.text) + +for sk in sort_keys: + print("-- (%s) Get"%sk) + response = requests.get('http://localhost:3812/alex/root?sort_key=%s'%sk, + auth=auth) + print(response.headers) + print(response.text) + ct = response.headers["x-garage-causality-token"] + +print("-- ReadBatch") +response = requests.post('http://localhost:3812/alex?search', + auth=auth, + data=''' +[ + {"partitionKey": "root"}, + {"partitionKey": "root", "tombstones": true}, + {"partitionKey": "root", "tombstones": true, "limit": 2}, + {"partitionKey": "root", "start": "c", "singleItem": true}, + {"partitionKey": "root", "start": "b", "end": "d", "tombstones": true} +] +''') +print(response.headers) +print(response.text) + + +print("-- DeleteBatch") +response = requests.post('http://localhost:3812/alex?delete', + auth=auth, + data=''' +[ + {"partitionKey": "root", "start": "b", "end": "c"} +] +''') +print(response.headers) +print(response.text) + +print("-- ReadBatch") +response = requests.post('http://localhost:3812/alex?search', + auth=auth, + data=''' +[ + {"partitionKey": "root"} +] +''') +print(response.headers) +print(response.text) diff --git a/nix/common.nix b/nix/common.nix index 5cd15c8a..aa59cdc0 100644 --- a/nix/common.nix +++ b/nix/common.nix @@ -4,18 +4,16 @@ rec { */ pkgsSrc = fetchTarball { # As of 2021-10-04 - url ="https://github.com/NixOS/nixpkgs/archive/b27d18a412b071f5d7991d1648cfe78ee7afe68a.tar.gz"; + url = "https://github.com/NixOS/nixpkgs/archive/b27d18a412b071f5d7991d1648cfe78ee7afe68a.tar.gz"; sha256 = "1xy9zpypqfxs5gcq5dcla4bfkhxmh5nzn9dyqkr03lqycm9wg5cr"; }; cargo2nixSrc = fetchGit { - # As of 2022-03-17 - url = "https://github.com/superboum/cargo2nix"; - ref = "main"; - rev = "bcbf3ba99e9e01a61eb83a24624419c2dd9dec64"; + # As of 2022-08-29, stacking two patches: superboum@dedup_propagate and Alexis211@fix_fetchcrategit + url = "https://github.com/Alexis211/cargo2nix"; + ref = "fix_fetchcrategit"; + rev = "4b31c0cc05b6394916d46e9289f51263d81973b9"; }; - - /* * Shared objects */ diff --git a/nix/compile.nix b/nix/compile.nix new file mode 100644 index 00000000..adb07886 --- /dev/null +++ b/nix/compile.nix @@ -0,0 +1,244 @@ +{ + system ? builtins.currentSystem, + target ? null, + compiler ? "rustc", + release ? false, + git_version ? null, +}: + +with import ./common.nix; + +let + log = v: builtins.trace v v; + + pkgs = import pkgsSrc { + inherit system; + ${ if target == null then null else "crossSystem" } = { config = target; }; + overlays = [ cargo2nixOverlay ]; + }; + + /* + Rust and Nix triples are not the same. Cargo2nix has a dedicated library + to convert Nix triples to Rust ones. We need this conversion as we want to + set later options linked to our (rust) target in a generic way. Not only + the triple terminology is different, but also the "roles" are named differently. + Nix uses a build/host/target terminology where Nix's "host" maps to Cargo's "target". + */ + rustTarget = log (pkgs.rustBuilder.rustLib.rustTriple pkgs.stdenv.hostPlatform); + + /* + Cargo2nix is built for rustOverlay which installs Rust from Mozilla releases. + We want our own Rust to avoid incompatibilities, like we had with musl 1.2.0. + rustc was built with musl < 1.2.0 and nix shipped musl >= 1.2.0 which lead to compilation breakage. + So we want a Rust release that is bound to our Nix repository to avoid these problems. + See here for more info: https://musl.libc.org/time64.html + Because Cargo2nix does not support the Rust environment shipped by NixOS, + we emulate the structure of the Rust object created by rustOverlay. + In practise, rustOverlay ships rustc+cargo in a single derivation while + NixOS ships them in separate ones. We reunite them with symlinkJoin. + */ + rustChannel = { + rustc = pkgs.symlinkJoin { + name = "rust-channel"; + paths = [ + pkgs.rustPlatform.rust.cargo + pkgs.rustPlatform.rust.rustc + ]; + }; + clippy = pkgs.symlinkJoin { + name = "clippy-channel"; + paths = [ + pkgs.rustPlatform.rust.cargo + pkgs.rustPlatform.rust.rustc + pkgs.clippy + ]; + }; + }.${compiler}; + + clippyBuilder = pkgs.writeScriptBin "clippy" '' + #!${pkgs.stdenv.shell} + . ${cargo2nixSrc + "/overlay/utils.sh"} + isBuildScript= + args=("$@") + for i in "''${!args[@]}"; do + if [ "xmetadata=" = "x''${args[$i]::9}" ]; then + args[$i]=metadata=$NIX_RUST_METADATA + elif [ "x--crate-name" = "x''${args[$i]}" ] && [ "xbuild_script_" = "x''${args[$i+1]::13}" ]; then + isBuildScript=1 + fi + done + if [ "$isBuildScript" ]; then + args+=($NIX_RUST_BUILD_LINK_FLAGS) + else + args+=($NIX_RUST_LINK_FLAGS) + fi + touch invoke.log + echo "''${args[@]}" >>invoke.log + + exec ${rustChannel}/bin/clippy-driver --deny warnings "''${args[@]}" + ''; + + buildEnv = (drv: { + rustc = drv.setBuildEnv; + clippy = '' + ${drv.setBuildEnv or "" } + echo + echo --- BUILDING WITH CLIPPY --- + echo + + export RUSTC=${clippyBuilder}/bin/clippy + ''; + }.${compiler}); + + /* + Cargo2nix provides many overrides by default, you can take inspiration from them: + https://github.com/cargo2nix/cargo2nix/blob/master/overlay/overrides.nix + + You can have a complete list of the available options by looking at the overriden object, mkcrate: + https://github.com/cargo2nix/cargo2nix/blob/master/overlay/mkcrate.nix + */ + overrides = pkgs.rustBuilder.overrides.all ++ [ + /* + [1] We add some logic to compile our crates with clippy, it provides us many additional lints + + [2] We need to alter Nix hardening to make static binaries: PIE, + Position Independent Executables seems to be supported only on amd64. Having + this flag set either 1. make our executables crash or 2. compile as dynamic on some platforms. + Here, we deactivate it. Later (find `codegenOpts`), we reactivate it for supported targets + (only amd64 curently) through the `-static-pie` flag. + PIE is a feature used by ASLR, which helps mitigate security issues. + Learn more about Nix Hardening at: https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/cc-wrapper/add-hardening.sh + + [3] We want to inject the git version while keeping the build deterministic. + As we do not want to consider the .git folder as part of the input source, + we ask the user (the CI often) to pass the value to Nix. + + [4] We ship some parts of the code disabled by default by putting them behind a flag. + It speeds up the compilation (when the feature is not required) and released crates have less dependency by default (less attack surface, disk space, etc.). + But we want to ship these additional features when we release Garage. + In the end, we chose to exclude all features from debug builds while putting (all of) them in the release builds. + + [5] We don't want libsodium-sys and zstd-sys to try to use pkgconfig to build against a system library. + However the features to do so get activated for some reason (due to a bug in cargo2nix?), + so disable them manually here. + */ + (pkgs.rustBuilder.rustLib.makeOverride { + name = "garage"; + overrideAttrs = drv: + (if git_version != null then { + /* [3] */ preConfigure = '' + ${drv.preConfigure or ""} + export GIT_VERSION="${git_version}" + ''; + } else {}) + // + { + /* [1] */ setBuildEnv = (buildEnv drv); + /* [2] */ hardeningDisable = [ "pie" ]; + }; + overrideArgs = old: { + /* [4] */ features = [ "bundled-libs" "sled" "metrics" "k2v" ] + ++ (if release then [ "kubernetes-discovery" "telemetry-otlp" "lmdb" "sqlite" ] else []); + }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "garage_rpc"; + overrideAttrs = drv: { /* [1] */ setBuildEnv = (buildEnv drv); }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "garage_db"; + overrideAttrs = drv: { /* [1] */ setBuildEnv = (buildEnv drv); }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "garage_util"; + overrideAttrs = drv: { /* [1] */ setBuildEnv = (buildEnv drv); }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "garage_table"; + overrideAttrs = drv: { /* [1] */ setBuildEnv = (buildEnv drv); }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "garage_block"; + overrideAttrs = drv: { /* [1] */ setBuildEnv = (buildEnv drv); }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "garage_model"; + overrideAttrs = drv: { /* [1] */ setBuildEnv = (buildEnv drv); }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "garage_api"; + overrideAttrs = drv: { /* [1] */ setBuildEnv = (buildEnv drv); }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "garage_web"; + overrideAttrs = drv: { /* [1] */ setBuildEnv = (buildEnv drv); }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "k2v-client"; + overrideAttrs = drv: { /* [1] */ setBuildEnv = (buildEnv drv); }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "libsodium-sys"; + overrideArgs = old: { + features = [ ]; /* [5] */ + }; + }) + + (pkgs.rustBuilder.rustLib.makeOverride { + name = "zstd-sys"; + overrideArgs = old: { + features = [ ]; /* [5] */ + }; + }) + ]; + + packageFun = import ../Cargo.nix; + + /* + We compile fully static binaries with musl to simplify deployment on most systems. + When possible, we reactivate PIE hardening (see above). + + Also, if you set the RUSTFLAGS environment variable, the following parameters will + be ignored. + + For more information on static builds, please refer to Rust's RFC 1721. + https://rust-lang.github.io/rfcs/1721-crt-static.html#specifying-dynamicstatic-c-runtime-linkage + */ + + codegenOpts = { + "armv6l-unknown-linux-musleabihf" = [ "target-feature=+crt-static" "link-arg=-static" ]; /* compile as dynamic with static-pie */ + "aarch64-unknown-linux-musl" = [ "target-feature=+crt-static" "link-arg=-static" ]; /* segfault with static-pie */ + "i686-unknown-linux-musl" = [ "target-feature=+crt-static" "link-arg=-static" ]; /* segfault with static-pie */ + "x86_64-unknown-linux-musl" = [ "target-feature=+crt-static" "link-arg=-static-pie" ]; + }; + +in + /* + The following definition is not elegant as we use a low level function of Cargo2nix + that enables us to pass our custom rustChannel object. We need this low level definition + to pass Nix's Rust toolchains instead of Mozilla's one. + + target is mandatory but must be kept to null to allow cargo2nix to set it to the appropriate value + for each crate. + */ + pkgs.rustBuilder.makePackageSet { + inherit packageFun rustChannel release codegenOpts; + packageOverrides = overrides; + target = null; + + buildRustPackages = pkgs.buildPackages.rustBuilder.makePackageSet { + inherit rustChannel packageFun codegenOpts; + packageOverrides = overrides; + target = null; + }; + } diff --git a/nix/manifest-tool.nix b/nix/manifest-tool.nix new file mode 100644 index 00000000..182ccc0e --- /dev/null +++ b/nix/manifest-tool.nix @@ -0,0 +1,23 @@ +pkgs: +pkgs.buildGoModule rec { + pname = "manifest-tool"; + version = "2.0.5"; + + src = pkgs.fetchFromGitHub { + owner = "estesp"; + repo = "manifest-tool"; + rev = "v${version}"; + sha256 = "hjCGKnE0yrlnF/VIzOwcDzmQX3Wft+21KCny/opqdLg="; + } + "/v2"; + + vendorSha256 = null; + + checkPhase = "true"; + + meta = with pkgs.lib; { + description = "Command line tool to create and query container image manifest list/indexes"; + homepage = "https://github.com/estesp/manifest-tool"; + license = licenses.asl20; + platforms = platforms.linux; + }; +} diff --git a/nix/nix.conf b/nix/nix.conf index 871efb10..6abf96b3 100644 --- a/nix/nix.conf +++ b/nix/nix.conf @@ -1,4 +1,9 @@ substituters = https://cache.nixos.org https://nix.web.deuxfleurs.fr trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= nix.web.deuxfleurs.fr:eTGL6kvaQn6cDR/F9lDYUIP9nCVR/kkshYfLDJf1yKs= max-jobs = auto -cores = 4 +cores = 0 +log-lines = 200 +filter-syscalls = false +sandbox = false +keep-outputs = true +keep-derivations = true diff --git a/script/dev-cluster.sh b/script/dev-cluster.sh index fa0a950e..c7fbe08d 100755 --- a/script/dev-cluster.sh +++ b/script/dev-cluster.sh @@ -11,7 +11,7 @@ PATH="${GARAGE_DEBUG}:${GARAGE_RELEASE}:${NIX_RELEASE}:$PATH" FANCYCOLORS=("41m" "42m" "44m" "45m" "100m" "104m") export RUST_BACKTRACE=1 -export RUST_LOG=garage=info,garage_api=debug +export RUST_LOG=garage=info,garage_api=debug,netapp=trace MAIN_LABEL="\e[${FANCYCOLORS[0]}[main]\e[49m" WHICH_GARAGE=$(which garage || exit 1) diff --git a/script/helm/README.md b/script/helm/README.md new file mode 100644 index 00000000..5f919a23 --- /dev/null +++ b/script/helm/README.md @@ -0,0 +1,3 @@ +# Garage helm3 chart + +Documentation is located [here](/doc/book/cookbook/kubernetes.md). diff --git a/script/helm/garage/.helmignore b/script/helm/garage/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/script/helm/garage/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/script/helm/garage/Chart.yaml b/script/helm/garage/Chart.yaml new file mode 100644 index 00000000..56598ea4 --- /dev/null +++ b/script/helm/garage/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: garage +description: S3-compatible object store for small self-hosted geo-distributed deployments + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "v0.7.2.1" diff --git a/script/helm/garage/templates/_helpers.tpl b/script/helm/garage/templates/_helpers.tpl new file mode 100644 index 00000000..037a5f1c --- /dev/null +++ b/script/helm/garage/templates/_helpers.tpl @@ -0,0 +1,88 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "garage.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "garage.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create the name of the rpc secret +*/}} +{{- define "garage.rpcSecretName" -}} +{{- printf "%s-rpc-secret" (include "garage.fullname" .) -}} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "garage.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "garage.labels" -}} +helm.sh/chart: {{ include "garage.chart" . }} +{{ include "garage.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "garage.selectorLabels" -}} +app.kubernetes.io/name: {{ include "garage.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "garage.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "garage.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* + Returns given number of random Hex characters. + In practice, it generates up to 100 randAlphaNum strings + that are filtered from non-hex characters and augmented + to the resulting string that is finally trimmed down. +*/}} +{{- define "jupyterhub.randHex" -}} + {{- $result := "" }} + {{- range $i := until 100 }} + {{- if lt (len $result) . }} + {{- $rand_list := randAlphaNum . | splitList "" -}} + {{- $reduced_list := without $rand_list "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" }} + {{- $rand_string := join "" $reduced_list }} + {{- $result = print $result $rand_string -}} + {{- end }} + {{- end }} + {{- $result | trunc . }} +{{- end }} diff --git a/script/helm/garage/templates/clusterrole.yaml b/script/helm/garage/templates/clusterrole.yaml new file mode 100644 index 00000000..fa3e6405 --- /dev/null +++ b/script/helm/garage/templates/clusterrole.yaml @@ -0,0 +1,28 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: manage-crds-{{ .Release.Namespace }}-{{ .Release.Name }} + labels: + {{- include "garage.labels" . | nindent 4 }} +rules: +- apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "list", "watch", "create", "patch"] +- apiGroups: ["deuxfleurs.fr"] + resources: ["garagenodes"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: allow-crds-for-{{ .Release.Namespace }}-{{ .Release.Name }} + labels: + {{- include "garage.labels" . | nindent 4 }} +subjects: +- kind: ServiceAccount + name: {{ include "garage.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: manage-crds-{{ .Release.Namespace }}-{{ .Release.Name }} + apiGroup: rbac.authorization.k8s.io
\ No newline at end of file diff --git a/script/helm/garage/templates/configmap.yaml b/script/helm/garage/templates/configmap.yaml new file mode 100644 index 00000000..e33a4dbd --- /dev/null +++ b/script/helm/garage/templates/configmap.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "garage.fullname" . }}-config +data: + garage.toml: |- + metadata_dir = "{{ .Values.garage.metadataDir }}" + data_dir = "{{ .Values.garage.dataDir }}" + + replication_mode = "{{ .Values.garage.replicationMode }}" + + rpc_bind_addr = "{{ .Values.garage.rpcBindAddr }}" + # rpc_secret will be populated by the init container from a k8s secret object + rpc_secret = "__RPC_SECRET_REPLACE__" + + bootstrap_peers = {{ .Values.garage.bootstrapPeers }} + + kubernetes_namespace = "{{ .Release.Namespace }}" + kubernetes_service_name = "{{ include "garage.fullname" . }}" + kubernetes_skip_crd = {{ .Values.garage.kubernetesSkipCrd }} + + [s3_api] + s3_region = "{{ .Values.garage.s3.api.region }}" + api_bind_addr = "[::]:3900" + root_domain = "{{ .Values.garage.s3.api.rootDomain }}" + + [s3_web] + bind_addr = "[::]:3902" + root_domain = "{{ .Values.garage.s3.web.rootDomain }}" + index = "{{ .Values.garage.s3.web.index }}"
\ No newline at end of file diff --git a/script/helm/garage/templates/ingress.yaml b/script/helm/garage/templates/ingress.yaml new file mode 100644 index 00000000..c4ee5a3f --- /dev/null +++ b/script/helm/garage/templates/ingress.yaml @@ -0,0 +1,123 @@ +{{- if .Values.ingress.s3.api.enabled -}} +{{- $fullName := include "garage.fullname" . -}} +{{- $svcPort := .Values.service.s3.api.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.s3.api.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.s3.api.annotations "kubernetes.io/ingress.class" .Values.ingress.s3.api.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-s3-api + labels: + {{- include "garage.labels" . | nindent 4 }} + {{- with .Values.ingress.s3.api.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.s3.api.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.s3.api.className }} + {{- end }} + {{- if .Values.ingress.s3.api.tls }} + tls: + {{- range .Values.ingress.s3.api.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.s3.api.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +--- +{{- if .Values.ingress.s3.web.enabled -}} +{{- $fullName := include "garage.fullname" . -}} +{{- $svcPort := .Values.service.s3.web.port -}} +{{- if and .Values.ingress.s3.web.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.s3.web.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.s3.web.annotations "kubernetes.io/ingress.class" .Values.ingress.s3.web.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-s3-web + labels: + {{- include "garage.labels" . | nindent 4 }} + {{- with .Values.ingress.s3.web.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.s3.web.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.s3.web.className }} + {{- end }} + {{- if .Values.ingress.s3.web.tls }} + tls: + {{- range .Values.ingress.s3.web.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.s3.web.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/script/helm/garage/templates/secret.yaml b/script/helm/garage/templates/secret.yaml new file mode 100644 index 00000000..54749424 --- /dev/null +++ b/script/helm/garage/templates/secret.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "garage.rpcSecretName" . }} + labels: + {{- include "garage.labels" . | nindent 4 }} +type: Opaque +data: + {{/* retrieve the secret data using lookup function and when not exists, return an empty dictionary / map as result */}} + {{- $prevSecret := (lookup "v1" "Secret" .Release.Namespace (include "garage.rpcSecretName" .)) | default dict }} + {{- $prevSecretData := $prevSecret.data | default dict }} + {{- $prevRpcSecret := $prevSecretData.rpcSecret | default "" | b64dec }} + {{/* Priority is: 1. from values, 2. previous value, 3. generate random */}} + rpcSecret: {{ .Values.garage.rpcSecret | default $prevRpcSecret | default (include "jupyterhub.randHex" 64) | b64enc | quote }} diff --git a/script/helm/garage/templates/service.yaml b/script/helm/garage/templates/service.yaml new file mode 100644 index 00000000..2bfff99d --- /dev/null +++ b/script/helm/garage/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "garage.fullname" . }} + labels: + {{- include "garage.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.s3.api.port }} + targetPort: 3900 + protocol: TCP + name: s3-api + - port: {{ .Values.service.s3.web.port }} + targetPort: 3902 + protocol: TCP + name: s3-web + selector: + {{- include "garage.selectorLabels" . | nindent 4 }} diff --git a/script/helm/garage/templates/serviceaccount.yaml b/script/helm/garage/templates/serviceaccount.yaml new file mode 100644 index 00000000..a0a89a33 --- /dev/null +++ b/script/helm/garage/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "garage.serviceAccountName" . }} + labels: + {{- include "garage.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/script/helm/garage/templates/statefulset.yaml b/script/helm/garage/templates/statefulset.yaml new file mode 100644 index 00000000..bda40117 --- /dev/null +++ b/script/helm/garage/templates/statefulset.yaml @@ -0,0 +1,116 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "garage.fullname" . }} + labels: + {{- include "garage.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "garage.selectorLabels" . | nindent 6 }} + serviceName: {{ include "garage.fullname" . }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "garage.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "garage.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + # Copies garage.toml from configmap to temporary etc volume and replaces RPC secret placeholder + - name: {{ .Chart.Name }}-init + image: busybox:1.28 + command: ["sh", "-c", "sed \"s/__RPC_SECRET_REPLACE__/$RPC_SECRET/\" /mnt/garage.toml > /mnt/etc/garage.toml"] + env: + - name: RPC_SECRET + valueFrom: + secretKeyRef: + name: {{ include "garage.rpcSecretName" . }} + key: rpcSecret + volumeMounts: + - name: configmap + mountPath: /mnt/garage.toml + subPath: garage.toml + - name: etc + mountPath: /mnt/etc + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: 3900 + name: s3-api + - containerPort: 3902 + name: web-api + volumeMounts: + - name: meta + mountPath: /mnt/meta + - name: data + mountPath: /mnt/data + - name: etc + mountPath: /etc/garage.toml + subPath: garage.toml + # TODO + # livenessProbe: + # httpGet: + # path: / + # port: 3900 + # readinessProbe: + # httpGet: + # path: / + # port: 3900 + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + - name: configmap + configMap: + name: {{ include "garage.fullname" . }}-config + - name: etc + emptyDir: {} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: meta + spec: + accessModes: [ "ReadWriteOnce" ] + {{- if hasKey .Values.persistence.meta "storageClass" }} + storageClassName: {{ .Values.persistence.meta.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.meta.size | quote }} + - metadata: + name: data + spec: + accessModes: [ "ReadWriteOnce" ] + {{- if hasKey .Values.persistence.data "storageClass" }} + storageClassName: {{ .Values.persistence.data.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.data.size | quote }} + {{- end }} diff --git a/script/helm/garage/values.yaml b/script/helm/garage/values.yaml new file mode 100644 index 00000000..08d0c09b --- /dev/null +++ b/script/helm/garage/values.yaml @@ -0,0 +1,142 @@ +# Default values for garage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Garage configuration. These values go to garage.toml +garage: + metadataDir: "/mnt/meta" + dataDir: "/mnt/data" + # Default to 3 replicas, see the replication_mode section at + # https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/ + replicationMode: "3" + rpcBindAddr: "[::]:3901" + # If not given, a random secret will be generated and stored in a Secret object + rpcSecret: "" + # This is not required if you use the integrated kubernetes discovery + bootstrapPeers: [] + kubernetesSkipCrd: false + s3: + api: + region: "garage" + rootDomain: ".s3.garage.tld" + web: + rootDomain: ".web.garage.tld" + index: "index.html" + +# Data persistence +persistence: + enabled: true + meta: + # storageClass: "fast-storage-class" + size: 100Mi + data: + # storageClass: "slow-storage-class" + size: 100Mi + +# Number of StatefulSet replicas/garage nodes to start +replicaCount: 3 + +image: + repository: dxflrs/amd64_garage + # please prefer using the chart version and not this tag + tag: "" + pullPolicy: IfNotPresent + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: + # The default security context is heavily restricted + # feel free to tune it to your requirements + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +service: + # You can rely on any service to expose your cluster + # - ClusterIP (+ Ingress) + # - NodePort (+ Ingress) + # - LoadBalancer + type: ClusterIP + s3: + api: + port: 3900 + web: + port: 3902 + # NOTE: the admin API is excluded for now as it is not consistent across nodes +ingress: + s3: + api: + enabled: true + # Rely either on the className or the annotation below but not both + # replace "nginx" by an Ingress controller + # you can find examples here https://kubernetes.io/docs/concepts/services-networking/ingress-controllers + className: "nginx" + annotations: + # kubernetes.io/ingress.class: "nginx" + # kubernetes.io/tls-acme: "true" + hosts: + - host: "s3.garage.tld" # garage S3 API endpoint + paths: + - path: / + pathType: Prefix + - host: "*.s3.garage.tld" # garage S3 API endpoint, DNS style bucket access + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: my-garage-cluster-tls + # hosts: + # - kubernetes.docker.internal + web: + enabled: true + className: "nginx" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: "*.web.garage.tld" # wildcard website access with bucket name prefix + paths: + - path: / + pathType: Prefix + - host: "mywebpage.example.com" # specific bucket access with FQDN bucket + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: my-garage-cluster-tls + # hosts: + # - kubernetes.docker.internal + +resources: {} + # The following are indicative for a small-size deployement, for anything serious double them. + # limits: + # cpu: 100m + # memory: 1024Mi + # requests: + # cpu: 100m + # memory: 512Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/script/not-dynamic.sh b/script/not-dynamic.sh new file mode 100755 index 00000000..b9a13070 --- /dev/null +++ b/script/not-dynamic.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -e + +if [ "$#" -ne 1 ]; then + echo "[fail] usage: $0 binary" + exit 2 +fi + +if file $1 | grep 'dynamically linked' 2>&1; then + echo "[fail] $1 is dynamic" + exit 1 +fi +echo "[ok] $1 is probably static" @@ -1,8 +1,5 @@ { system ? builtins.currentSystem, - rust ? true, - integration ? true, - release ? true, }: with import ./nix/common.nix; @@ -13,12 +10,63 @@ let overlays = [ cargo2nixOverlay ]; }; kaniko = (import ./nix/kaniko.nix) pkgs; + manifest-tool = (import ./nix/manifest-tool.nix) pkgs; winscp = (import ./nix/winscp.nix) pkgs; in + { -pkgs.mkShell { - shellHook = '' + /* --- Rust Shell --- + * Use it to compile Garage + */ + rust = pkgs.mkShell { + shellHook = '' +function refresh_toolchain { + nix copy \ + --to 's3://nix?endpoint=garage.deuxfleurs.fr®ion=garage&secret-key=/etc/nix/signing-key.sec' \ + $(nix-store -qR \ + $(nix-build --quiet --no-build-output --no-out-link nix/toolchain.nix)) +} + ''; + + nativeBuildInputs = [ + #pkgs.rustPlatform.rust.rustc + pkgs.rustPlatform.rust.cargo + #pkgs.clippy + pkgs.rustfmt + #pkgs.perl + #pkgs.protobuf + #pkgs.pkg-config + #pkgs.openssl + pkgs.file + #cargo2nix.packages.x86_64-linux.cargo2nix + ]; + }; + + /* --- Integration shell --- + * Use it to test Garage with common S3 clients + */ + integration = pkgs.mkShell { + nativeBuildInputs = [ + winscp + pkgs.s3cmd + pkgs.awscli2 + pkgs.minio-client + pkgs.rclone + pkgs.socat + pkgs.psmisc + pkgs.which + pkgs.openssl + pkgs.curl + pkgs.jq + ]; + }; + + /* --- Release shell --- + * A shell built to make releasing easier + */ + release = pkgs.mkShell { + shellHook = '' function to_s3 { aws \ --endpoint-url https://garage.deuxfleurs.fr \ @@ -37,6 +85,34 @@ function to_docker { --verbosity=debug } +function multiarch_docker { + manifest-tool push from-spec <(cat <<EOF +image: dxflrs/garage:''${CONTAINER_TAG} +manifests: + - + image: dxflrs/arm64_garage:''${CONTAINER_TAG} + platform: + architecture: arm64 + os: linux + - + image: dxflrs/amd64_garage:''${CONTAINER_TAG} + platform: + architecture: amd64 + os: linux + - + image: dxflrs/386_garage:''${CONTAINER_TAG} + platform: + architecture: 386 + os: linux + - + image: dxflrs/arm_garage:''${CONTAINER_TAG} + platform: + architecture: arm + os: linux +EOF + ) +} + function refresh_index { aws \ --endpoint-url https://garage.deuxfleurs.fr \ @@ -62,43 +138,13 @@ function refresh_index { result/share/_releases.html \ s3://garagehq.deuxfleurs.fr/ } + ''; + nativeBuildInputs = [ + pkgs.awscli2 + kaniko + manifest-tool + ]; + }; + } -function refresh_toolchain { - nix copy \ - --to 's3://nix?endpoint=garage.deuxfleurs.fr®ion=garage&secret-key=/etc/nix/signing-key.sec' \ - $(nix-store -qR \ - $(nix-build --quiet --no-build-output --no-out-link nix/toolchain.nix)) -} - ''; - nativeBuildInputs = - (if rust then [ - pkgs.rustPlatform.rust.rustc - pkgs.rustPlatform.rust.cargo - pkgs.clippy - pkgs.rustfmt - pkgs.perl - pkgs.protobuf - cargo2nix.packages.x86_64-linux.cargo2nix - ] else []) - ++ - (if integration then [ - winscp - pkgs.s3cmd - pkgs.awscli2 - pkgs.minio-client - pkgs.rclone - pkgs.socat - pkgs.psmisc - pkgs.which - pkgs.openssl - pkgs.curl - pkgs.jq - ] else []) - ++ - (if release then [ - pkgs.awscli2 - kaniko - ] else []) - ; -} diff --git a/src/admin/Cargo.toml b/src/admin/Cargo.toml deleted file mode 100644 index 2db4bb08..00000000 --- a/src/admin/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "garage_admin" -version = "0.7.0" -authors = ["Maximilien Richer <code@mricher.fr>"] -edition = "2018" -license = "AGPL-3.0" -description = "Administration and metrics REST HTTP server for Garage" -repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage" - -[lib] -path = "lib.rs" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -garage_util = { version = "0.7.0", path = "../util" } - -hex = "0.4" - -futures = "0.3" -futures-util = "0.3" -http = "0.2" -hyper = "0.14" -tracing = "0.1.30" - -opentelemetry = { version = "0.17", features = [ "rt-tokio" ] } -opentelemetry-prometheus = "0.10" -opentelemetry-otlp = "0.10" -prometheus = "0.13" diff --git a/src/admin/lib.rs b/src/admin/lib.rs deleted file mode 100644 index b5b0775b..00000000 --- a/src/admin/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Crate for handling the admin and metric HTTP APIs -#[macro_use] -extern crate tracing; - -pub mod metrics; -pub mod tracing_setup; diff --git a/src/admin/metrics.rs b/src/admin/metrics.rs deleted file mode 100644 index 7edc36c6..00000000 --- a/src/admin/metrics.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::convert::Infallible; -use std::net::SocketAddr; -use std::sync::Arc; -use std::time::SystemTime; - -use futures::future::*; -use hyper::{ - header::CONTENT_TYPE, - service::{make_service_fn, service_fn}, - Body, Method, Request, Response, Server, -}; - -use opentelemetry::{ - global, - metrics::{BoundCounter, BoundValueRecorder}, - trace::{FutureExt, TraceContextExt, Tracer}, - Context, -}; -use opentelemetry_prometheus::PrometheusExporter; - -use prometheus::{Encoder, TextEncoder}; - -use garage_util::error::Error as GarageError; -use garage_util::metrics::*; - -// serve_req on metric endpoint -async fn serve_req( - req: Request<Body>, - admin_server: Arc<AdminServer>, -) -> Result<Response<Body>, hyper::Error> { - debug!("Receiving request at path {}", req.uri()); - let request_start = SystemTime::now(); - - admin_server.metrics.http_counter.add(1); - - let response = match (req.method(), req.uri().path()) { - (&Method::GET, "/metrics") => { - let mut buffer = vec![]; - let encoder = TextEncoder::new(); - - let tracer = opentelemetry::global::tracer("garage"); - let metric_families = tracer.in_span("admin/gather_metrics", |_| { - admin_server.exporter.registry().gather() - }); - - encoder.encode(&metric_families, &mut buffer).unwrap(); - admin_server - .metrics - .http_body_gauge - .record(buffer.len() as u64); - - Response::builder() - .status(200) - .header(CONTENT_TYPE, encoder.format_type()) - .body(Body::from(buffer)) - .unwrap() - } - _ => Response::builder() - .status(404) - .body(Body::from("Not implemented")) - .unwrap(), - }; - - admin_server - .metrics - .http_req_histogram - .record(request_start.elapsed().map_or(0.0, |d| d.as_secs_f64())); - Ok(response) -} - -// AdminServer hold the admin server internal admin_server and the metric exporter -pub struct AdminServer { - exporter: PrometheusExporter, - metrics: AdminServerMetrics, -} - -// GarageMetricadmin_server holds the metrics counter definition for Garage -// FIXME: we would rather have that split up among the different libraries? -struct AdminServerMetrics { - http_counter: BoundCounter<u64>, - http_body_gauge: BoundValueRecorder<u64>, - http_req_histogram: BoundValueRecorder<f64>, -} - -impl AdminServer { - /// init initilialize the AdminServer and background metric server - pub fn init() -> AdminServer { - let exporter = opentelemetry_prometheus::exporter().init(); - let meter = global::meter("garage/admin_server"); - AdminServer { - exporter, - metrics: AdminServerMetrics { - http_counter: meter - .u64_counter("admin.http_requests_total") - .with_description("Total number of HTTP requests made.") - .init() - .bind(&[]), - http_body_gauge: meter - .u64_value_recorder("admin.http_response_size_bytes") - .with_description("The metrics HTTP response sizes in bytes.") - .init() - .bind(&[]), - http_req_histogram: meter - .f64_value_recorder("admin.http_request_duration_seconds") - .with_description("The HTTP request latencies in seconds.") - .init() - .bind(&[]), - }, - } - } - /// run execute the admin server on the designated HTTP port and listen for requests - pub async fn run( - self, - bind_addr: SocketAddr, - shutdown_signal: impl Future<Output = ()>, - ) -> Result<(), GarageError> { - let admin_server = Arc::new(self); - // For every connection, we must make a `Service` to handle all - // incoming HTTP requests on said connection. - let make_svc = make_service_fn(move |_conn| { - let admin_server = admin_server.clone(); - // This is the `Service` that will handle the connection. - // `service_fn` is a helper to convert a function that - // returns a Response into a `Service`. - async move { - Ok::<_, Infallible>(service_fn(move |req| { - let tracer = opentelemetry::global::tracer("garage"); - let span = tracer - .span_builder("admin/request") - .with_trace_id(gen_trace_id()) - .start(&tracer); - - serve_req(req, admin_server.clone()) - .with_context(Context::current_with_span(span)) - })) - } - }); - - let server = Server::bind(&bind_addr).serve(make_svc); - let graceful = server.with_graceful_shutdown(shutdown_signal); - info!("Admin server listening on http://{}", bind_addr); - - graceful.await?; - Ok(()) - } -} diff --git a/src/api/Cargo.toml b/src/api/Cargo.toml index 5e96b081..7c3ed43b 100644 --- a/src/api/Cargo.toml +++ b/src/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "garage_api" -version = "0.7.0" +version = "0.8.0" authors = ["Alex Auvolat <alex@adnab.me>"] edition = "2018" license = "AGPL-3.0" @@ -14,28 +14,31 @@ path = "lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -garage_model = { version = "0.7.0", path = "../model" } -garage_table = { version = "0.7.0", path = "../table" } -garage_block = { version = "0.7.0", path = "../block" } -garage_util = { version = "0.7.0", path = "../util" } +garage_model = { version = "0.8.0", path = "../model" } +garage_table = { version = "0.8.0", path = "../table" } +garage_block = { version = "0.8.0", path = "../block" } +garage_util = { version = "0.8.0", path = "../util" } +garage_rpc = { version = "0.8.0", path = "../rpc" } +async-trait = "0.1.7" base64 = "0.13" bytes = "1.0" chrono = "0.4" -crypto-mac = "0.10" +crypto-common = "0.1" err-derive = "0.3" hex = "0.4" -hmac = "0.10" +hmac = "0.12" idna = "0.2" tracing = "0.1.30" -md-5 = "0.9" +md-5 = "0.10" nom = "7.1" -sha2 = "0.9" +sha2 = "0.10" futures = "0.3" futures-util = "0.3" pin-project = "1.0" tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] } +tokio-stream = "0.1" form_urlencoded = "1.0.0" http = "0.2" @@ -52,3 +55,9 @@ quick-xml = { version = "0.21", features = [ "serialize" ] } url = "2.1" opentelemetry = "0.17" +opentelemetry-prometheus = { version = "0.10", optional = true } +prometheus = { version = "0.13", optional = true } + +[features] +k2v = [ "garage_util/k2v", "garage_model/k2v" ] +metrics = [ "opentelemetry-prometheus", "prometheus" ] diff --git a/src/api/admin/api_server.rs b/src/api/admin/api_server.rs new file mode 100644 index 00000000..0816bda1 --- /dev/null +++ b/src/api/admin/api_server.rs @@ -0,0 +1,209 @@ +use std::net::SocketAddr; +use std::sync::Arc; + +use async_trait::async_trait; + +use futures::future::Future; +use http::header::{ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ALLOW}; +use hyper::{Body, Request, Response}; + +use opentelemetry::trace::SpanRef; + +#[cfg(feature = "metrics")] +use opentelemetry_prometheus::PrometheusExporter; +#[cfg(feature = "metrics")] +use prometheus::{Encoder, TextEncoder}; + +use garage_model::garage::Garage; +use garage_util::error::Error as GarageError; + +use crate::generic_server::*; + +use crate::admin::bucket::*; +use crate::admin::cluster::*; +use crate::admin::error::*; +use crate::admin::key::*; +use crate::admin::router::{Authorization, Endpoint}; + +pub struct AdminApiServer { + garage: Arc<Garage>, + #[cfg(feature = "metrics")] + exporter: PrometheusExporter, + metrics_token: Option<String>, + admin_token: Option<String>, +} + +impl AdminApiServer { + pub fn new( + garage: Arc<Garage>, + #[cfg(feature = "metrics")] exporter: PrometheusExporter, + ) -> Self { + let cfg = &garage.config.admin; + let metrics_token = cfg + .metrics_token + .as_ref() + .map(|tok| format!("Bearer {}", tok)); + let admin_token = cfg + .admin_token + .as_ref() + .map(|tok| format!("Bearer {}", tok)); + Self { + garage, + #[cfg(feature = "metrics")] + exporter, + metrics_token, + admin_token, + } + } + + pub async fn run( + self, + bind_addr: SocketAddr, + shutdown_signal: impl Future<Output = ()>, + ) -> Result<(), GarageError> { + let region = self.garage.config.s3_api.s3_region.clone(); + ApiServer::new(region, self) + .run_server(bind_addr, shutdown_signal) + .await + } + + fn handle_options(&self, _req: &Request<Body>) -> Result<Response<Body>, Error> { + Ok(Response::builder() + .status(204) + .header(ALLOW, "OPTIONS, GET, POST") + .header(ACCESS_CONTROL_ALLOW_METHODS, "OPTIONS, GET, POST") + .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .body(Body::empty())?) + } + + fn handle_metrics(&self) -> Result<Response<Body>, Error> { + #[cfg(feature = "metrics")] + { + use opentelemetry::trace::Tracer; + + let mut buffer = vec![]; + let encoder = TextEncoder::new(); + + let tracer = opentelemetry::global::tracer("garage"); + let metric_families = tracer.in_span("admin/gather_metrics", |_| { + self.exporter.registry().gather() + }); + + encoder + .encode(&metric_families, &mut buffer) + .ok_or_internal_error("Could not serialize metrics")?; + + Ok(Response::builder() + .status(200) + .header(http::header::CONTENT_TYPE, encoder.format_type()) + .body(Body::from(buffer))?) + } + #[cfg(not(feature = "metrics"))] + Err(Error::bad_request( + "Garage was built without the metrics feature".to_string(), + )) + } +} + +#[async_trait] +impl ApiHandler for AdminApiServer { + const API_NAME: &'static str = "admin"; + const API_NAME_DISPLAY: &'static str = "Admin"; + + type Endpoint = Endpoint; + type Error = Error; + + fn parse_endpoint(&self, req: &Request<Body>) -> Result<Endpoint, Error> { + Endpoint::from_request(req) + } + + async fn handle( + &self, + req: Request<Body>, + endpoint: Endpoint, + ) -> Result<Response<Body>, Error> { + let expected_auth_header = + match endpoint.authorization_type() { + Authorization::MetricsToken => self.metrics_token.as_ref(), + Authorization::AdminToken => match &self.admin_token { + None => return Err(Error::forbidden( + "Admin token isn't configured, admin API access is disabled for security.", + )), + Some(t) => Some(t), + }, + }; + + if let Some(h) = expected_auth_header { + match req.headers().get("Authorization") { + None => return Err(Error::forbidden("Authorization token must be provided")), + Some(v) => { + let authorized = v.to_str().map(|hv| hv.trim() == h).unwrap_or(false); + if !authorized { + return Err(Error::forbidden("Invalid authorization token provided")); + } + } + } + } + + match endpoint { + Endpoint::Options => self.handle_options(&req), + Endpoint::Metrics => self.handle_metrics(), + Endpoint::GetClusterStatus => handle_get_cluster_status(&self.garage).await, + Endpoint::ConnectClusterNodes => handle_connect_cluster_nodes(&self.garage, req).await, + // Layout + Endpoint::GetClusterLayout => handle_get_cluster_layout(&self.garage).await, + Endpoint::UpdateClusterLayout => handle_update_cluster_layout(&self.garage, req).await, + Endpoint::ApplyClusterLayout => handle_apply_cluster_layout(&self.garage, req).await, + Endpoint::RevertClusterLayout => handle_revert_cluster_layout(&self.garage, req).await, + // Keys + Endpoint::ListKeys => handle_list_keys(&self.garage).await, + Endpoint::GetKeyInfo { id, search } => { + handle_get_key_info(&self.garage, id, search).await + } + Endpoint::CreateKey => handle_create_key(&self.garage, req).await, + Endpoint::ImportKey => handle_import_key(&self.garage, req).await, + Endpoint::UpdateKey { id } => handle_update_key(&self.garage, id, req).await, + Endpoint::DeleteKey { id } => handle_delete_key(&self.garage, id).await, + // Buckets + Endpoint::ListBuckets => handle_list_buckets(&self.garage).await, + Endpoint::GetBucketInfo { id, global_alias } => { + handle_get_bucket_info(&self.garage, id, global_alias).await + } + Endpoint::CreateBucket => handle_create_bucket(&self.garage, req).await, + Endpoint::DeleteBucket { id } => handle_delete_bucket(&self.garage, id).await, + Endpoint::UpdateBucket { id } => handle_update_bucket(&self.garage, id, req).await, + // Bucket-key permissions + Endpoint::BucketAllowKey => { + handle_bucket_change_key_perm(&self.garage, req, true).await + } + Endpoint::BucketDenyKey => { + handle_bucket_change_key_perm(&self.garage, req, false).await + } + // Bucket aliasing + Endpoint::GlobalAliasBucket { id, alias } => { + handle_global_alias_bucket(&self.garage, id, alias).await + } + Endpoint::GlobalUnaliasBucket { id, alias } => { + handle_global_unalias_bucket(&self.garage, id, alias).await + } + Endpoint::LocalAliasBucket { + id, + access_key_id, + alias, + } => handle_local_alias_bucket(&self.garage, id, access_key_id, alias).await, + Endpoint::LocalUnaliasBucket { + id, + access_key_id, + alias, + } => handle_local_unalias_bucket(&self.garage, id, access_key_id, alias).await, + } + } +} + +impl ApiEndpoint for Endpoint { + fn name(&self) -> &'static str { + Endpoint::name(self) + } + + fn add_span_attributes(&self, _span: SpanRef<'_>) {} +} diff --git a/src/api/admin/bucket.rs b/src/api/admin/bucket.rs new file mode 100644 index 00000000..ac8a8a40 --- /dev/null +++ b/src/api/admin/bucket.rs @@ -0,0 +1,580 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use hyper::{Body, Request, Response, StatusCode}; +use serde::{Deserialize, Serialize}; + +use garage_util::crdt::*; +use garage_util::data::*; +use garage_util::time::*; + +use garage_table::*; + +use garage_model::bucket_alias_table::*; +use garage_model::bucket_table::*; +use garage_model::garage::Garage; +use garage_model::permission::*; +use garage_model::s3::object_table::*; + +use crate::admin::error::*; +use crate::admin::key::ApiBucketKeyPerm; +use crate::common_error::CommonError; +use crate::helpers::{json_ok_response, parse_json_body}; + +pub async fn handle_list_buckets(garage: &Arc<Garage>) -> Result<Response<Body>, Error> { + let buckets = garage + .bucket_table + .get_range( + &EmptyKey, + None, + Some(DeletedFilter::NotDeleted), + 10000, + EnumerationOrder::Forward, + ) + .await?; + + let res = buckets + .into_iter() + .map(|b| { + let state = b.state.as_option().unwrap(); + ListBucketResultItem { + id: hex::encode(b.id), + global_aliases: state + .aliases + .items() + .iter() + .filter(|(_, _, a)| *a) + .map(|(n, _, _)| n.to_string()) + .collect::<Vec<_>>(), + local_aliases: state + .local_aliases + .items() + .iter() + .filter(|(_, _, a)| *a) + .map(|((k, n), _, _)| BucketLocalAlias { + access_key_id: k.to_string(), + alias: n.to_string(), + }) + .collect::<Vec<_>>(), + } + }) + .collect::<Vec<_>>(); + + Ok(json_ok_response(&res)?) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct ListBucketResultItem { + id: String, + global_aliases: Vec<String>, + local_aliases: Vec<BucketLocalAlias>, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct BucketLocalAlias { + access_key_id: String, + alias: String, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ApiBucketQuotas { + max_size: Option<u64>, + max_objects: Option<u64>, +} + +pub async fn handle_get_bucket_info( + garage: &Arc<Garage>, + id: Option<String>, + global_alias: Option<String>, +) -> Result<Response<Body>, Error> { + let bucket_id = match (id, global_alias) { + (Some(id), None) => parse_bucket_id(&id)?, + (None, Some(ga)) => garage + .bucket_helper() + .resolve_global_bucket_name(&ga) + .await? + .ok_or_else(|| HelperError::NoSuchBucket(ga.to_string()))?, + _ => { + return Err(Error::bad_request( + "Either id or globalAlias must be provided (but not both)", + )); + } + }; + + bucket_info_results(garage, bucket_id).await +} + +async fn bucket_info_results( + garage: &Arc<Garage>, + bucket_id: Uuid, +) -> Result<Response<Body>, Error> { + let bucket = garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + + let counters = garage + .object_counter_table + .table + .get(&bucket_id, &EmptyKey) + .await? + .map(|x| x.filtered_values(&garage.system.ring.borrow())) + .unwrap_or_default(); + + let mut relevant_keys = HashMap::new(); + for (k, _) in bucket + .state + .as_option() + .unwrap() + .authorized_keys + .items() + .iter() + { + if let Some(key) = garage + .key_table + .get(&EmptyKey, k) + .await? + .filter(|k| !k.is_deleted()) + { + if !key.state.is_deleted() { + relevant_keys.insert(k.clone(), key); + } + } + } + for ((k, _), _, _) in bucket + .state + .as_option() + .unwrap() + .local_aliases + .items() + .iter() + { + if relevant_keys.contains_key(k) { + continue; + } + if let Some(key) = garage.key_table.get(&EmptyKey, k).await? { + if !key.state.is_deleted() { + relevant_keys.insert(k.clone(), key); + } + } + } + + let state = bucket.state.as_option().unwrap(); + + let quotas = state.quotas.get(); + let res = + GetBucketInfoResult { + id: hex::encode(&bucket.id), + global_aliases: state + .aliases + .items() + .iter() + .filter(|(_, _, a)| *a) + .map(|(n, _, _)| n.to_string()) + .collect::<Vec<_>>(), + website_access: state.website_config.get().is_some(), + website_config: state.website_config.get().clone().map(|wsc| { + GetBucketInfoWebsiteResult { + index_document: wsc.index_document, + error_document: wsc.error_document, + } + }), + keys: relevant_keys + .into_iter() + .map(|(_, key)| { + let p = key.state.as_option().unwrap(); + GetBucketInfoKey { + access_key_id: key.key_id, + name: p.name.get().to_string(), + permissions: p + .authorized_buckets + .get(&bucket.id) + .map(|p| ApiBucketKeyPerm { + read: p.allow_read, + write: p.allow_write, + owner: p.allow_owner, + }) + .unwrap_or_default(), + bucket_local_aliases: p + .local_aliases + .items() + .iter() + .filter(|(_, _, b)| *b == Some(bucket.id)) + .map(|(n, _, _)| n.to_string()) + .collect::<Vec<_>>(), + } + }) + .collect::<Vec<_>>(), + objects: counters.get(OBJECTS).cloned().unwrap_or_default(), + bytes: counters.get(BYTES).cloned().unwrap_or_default(), + unfinshed_uploads: counters + .get(UNFINISHED_UPLOADS) + .cloned() + .unwrap_or_default(), + quotas: ApiBucketQuotas { + max_size: quotas.max_size, + max_objects: quotas.max_objects, + }, + }; + + Ok(json_ok_response(&res)?) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct GetBucketInfoResult { + id: String, + global_aliases: Vec<String>, + website_access: bool, + #[serde(default)] + website_config: Option<GetBucketInfoWebsiteResult>, + keys: Vec<GetBucketInfoKey>, + objects: i64, + bytes: i64, + unfinshed_uploads: i64, + quotas: ApiBucketQuotas, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct GetBucketInfoWebsiteResult { + index_document: String, + error_document: Option<String>, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct GetBucketInfoKey { + access_key_id: String, + name: String, + permissions: ApiBucketKeyPerm, + bucket_local_aliases: Vec<String>, +} + +pub async fn handle_create_bucket( + garage: &Arc<Garage>, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let req = parse_json_body::<CreateBucketRequest>(req).await?; + + if let Some(ga) = &req.global_alias { + if !is_valid_bucket_name(ga) { + return Err(Error::bad_request(format!( + "{}: {}", + ga, INVALID_BUCKET_NAME_MESSAGE + ))); + } + + if let Some(alias) = garage.bucket_alias_table.get(&EmptyKey, ga).await? { + if alias.state.get().is_some() { + return Err(CommonError::BucketAlreadyExists.into()); + } + } + } + + if let Some(la) = &req.local_alias { + if !is_valid_bucket_name(&la.alias) { + return Err(Error::bad_request(format!( + "{}: {}", + la.alias, INVALID_BUCKET_NAME_MESSAGE + ))); + } + + let key = garage + .key_helper() + .get_existing_key(&la.access_key_id) + .await?; + let state = key.state.as_option().unwrap(); + if matches!(state.local_aliases.get(&la.alias), Some(_)) { + return Err(Error::bad_request("Local alias already exists")); + } + } + + let bucket = Bucket::new(); + garage.bucket_table.insert(&bucket).await?; + + if let Some(ga) = &req.global_alias { + garage + .bucket_helper() + .set_global_bucket_alias(bucket.id, ga) + .await?; + } + + if let Some(la) = &req.local_alias { + garage + .bucket_helper() + .set_local_bucket_alias(bucket.id, &la.access_key_id, &la.alias) + .await?; + + if la.allow.read || la.allow.write || la.allow.owner { + garage + .bucket_helper() + .set_bucket_key_permissions( + bucket.id, + &la.access_key_id, + BucketKeyPerm { + timestamp: now_msec(), + allow_read: la.allow.read, + allow_write: la.allow.write, + allow_owner: la.allow.owner, + }, + ) + .await?; + } + } + + bucket_info_results(garage, bucket.id).await +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CreateBucketRequest { + global_alias: Option<String>, + local_alias: Option<CreateBucketLocalAlias>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct CreateBucketLocalAlias { + access_key_id: String, + alias: String, + #[serde(default)] + allow: ApiBucketKeyPerm, +} + +pub async fn handle_delete_bucket( + garage: &Arc<Garage>, + id: String, +) -> Result<Response<Body>, Error> { + let helper = garage.bucket_helper(); + + let bucket_id = parse_bucket_id(&id)?; + + let mut bucket = helper.get_existing_bucket(bucket_id).await?; + let state = bucket.state.as_option().unwrap(); + + // Check bucket is empty + if !helper.is_bucket_empty(bucket_id).await? { + return Err(CommonError::BucketNotEmpty.into()); + } + + // --- done checking, now commit --- + // 1. delete authorization from keys that had access + for (key_id, perm) in bucket.authorized_keys() { + if perm.is_any() { + helper + .set_bucket_key_permissions(bucket.id, key_id, BucketKeyPerm::NO_PERMISSIONS) + .await?; + } + } + // 2. delete all local aliases + for ((key_id, alias), _, active) in state.local_aliases.items().iter() { + if *active { + helper + .unset_local_bucket_alias(bucket.id, key_id, alias) + .await?; + } + } + // 3. delete all global aliases + for (alias, _, active) in state.aliases.items().iter() { + if *active { + helper.purge_global_bucket_alias(bucket.id, alias).await?; + } + } + + // 4. delete bucket + bucket.state = Deletable::delete(); + garage.bucket_table.insert(&bucket).await?; + + Ok(Response::builder() + .status(StatusCode::NO_CONTENT) + .body(Body::empty())?) +} + +pub async fn handle_update_bucket( + garage: &Arc<Garage>, + id: String, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let req = parse_json_body::<UpdateBucketRequest>(req).await?; + let bucket_id = parse_bucket_id(&id)?; + + let mut bucket = garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + + let state = bucket.state.as_option_mut().unwrap(); + + if let Some(wa) = req.website_access { + if wa.enabled { + state.website_config.update(Some(WebsiteConfig { + index_document: wa.index_document.ok_or_bad_request( + "Please specify indexDocument when enabling website access.", + )?, + error_document: wa.error_document, + })); + } else { + if wa.index_document.is_some() || wa.error_document.is_some() { + return Err(Error::bad_request( + "Cannot specify indexDocument or errorDocument when disabling website access.", + )); + } + state.website_config.update(None); + } + } + + if let Some(q) = req.quotas { + state.quotas.update(BucketQuotas { + max_size: q.max_size, + max_objects: q.max_objects, + }); + } + + garage.bucket_table.insert(&bucket).await?; + + bucket_info_results(garage, bucket_id).await +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct UpdateBucketRequest { + website_access: Option<UpdateBucketWebsiteAccess>, + quotas: Option<ApiBucketQuotas>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct UpdateBucketWebsiteAccess { + enabled: bool, + index_document: Option<String>, + error_document: Option<String>, +} + +// ---- BUCKET/KEY PERMISSIONS ---- + +pub async fn handle_bucket_change_key_perm( + garage: &Arc<Garage>, + req: Request<Body>, + new_perm_flag: bool, +) -> Result<Response<Body>, Error> { + let req = parse_json_body::<BucketKeyPermChangeRequest>(req).await?; + + let bucket_id = parse_bucket_id(&req.bucket_id)?; + + let bucket = garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + let state = bucket.state.as_option().unwrap(); + + let key = garage + .key_helper() + .get_existing_key(&req.access_key_id) + .await?; + + let mut perm = state + .authorized_keys + .get(&key.key_id) + .cloned() + .unwrap_or(BucketKeyPerm::NO_PERMISSIONS); + + if req.permissions.read { + perm.allow_read = new_perm_flag; + } + if req.permissions.write { + perm.allow_write = new_perm_flag; + } + if req.permissions.owner { + perm.allow_owner = new_perm_flag; + } + + garage + .bucket_helper() + .set_bucket_key_permissions(bucket.id, &key.key_id, perm) + .await?; + + bucket_info_results(garage, bucket.id).await +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct BucketKeyPermChangeRequest { + bucket_id: String, + access_key_id: String, + permissions: ApiBucketKeyPerm, +} + +// ---- BUCKET ALIASES ---- + +pub async fn handle_global_alias_bucket( + garage: &Arc<Garage>, + bucket_id: String, + alias: String, +) -> Result<Response<Body>, Error> { + let bucket_id = parse_bucket_id(&bucket_id)?; + + garage + .bucket_helper() + .set_global_bucket_alias(bucket_id, &alias) + .await?; + + bucket_info_results(garage, bucket_id).await +} + +pub async fn handle_global_unalias_bucket( + garage: &Arc<Garage>, + bucket_id: String, + alias: String, +) -> Result<Response<Body>, Error> { + let bucket_id = parse_bucket_id(&bucket_id)?; + + garage + .bucket_helper() + .unset_global_bucket_alias(bucket_id, &alias) + .await?; + + bucket_info_results(garage, bucket_id).await +} + +pub async fn handle_local_alias_bucket( + garage: &Arc<Garage>, + bucket_id: String, + access_key_id: String, + alias: String, +) -> Result<Response<Body>, Error> { + let bucket_id = parse_bucket_id(&bucket_id)?; + + garage + .bucket_helper() + .set_local_bucket_alias(bucket_id, &access_key_id, &alias) + .await?; + + bucket_info_results(garage, bucket_id).await +} + +pub async fn handle_local_unalias_bucket( + garage: &Arc<Garage>, + bucket_id: String, + access_key_id: String, + alias: String, +) -> Result<Response<Body>, Error> { + let bucket_id = parse_bucket_id(&bucket_id)?; + + garage + .bucket_helper() + .unset_local_bucket_alias(bucket_id, &access_key_id, &alias) + .await?; + + bucket_info_results(garage, bucket_id).await +} + +// ---- HELPER ---- + +fn parse_bucket_id(id: &str) -> Result<Uuid, Error> { + let id_hex = hex::decode(&id).ok_or_bad_request("Invalid bucket id")?; + Ok(Uuid::try_from(&id_hex).ok_or_bad_request("Invalid bucket id")?) +} diff --git a/src/api/admin/cluster.rs b/src/api/admin/cluster.rs new file mode 100644 index 00000000..99c6e332 --- /dev/null +++ b/src/api/admin/cluster.rs @@ -0,0 +1,193 @@ +use std::collections::HashMap; +use std::net::SocketAddr; +use std::sync::Arc; + +use hyper::{Body, Request, Response, StatusCode}; +use serde::{Deserialize, Serialize}; + +use garage_util::crdt::*; +use garage_util::data::*; + +use garage_rpc::layout::*; + +use garage_model::garage::Garage; + +use crate::admin::error::*; +use crate::helpers::{json_ok_response, parse_json_body}; + +pub async fn handle_get_cluster_status(garage: &Arc<Garage>) -> Result<Response<Body>, Error> { + let res = GetClusterStatusResponse { + node: hex::encode(garage.system.id), + garage_version: garage_util::version::garage_version(), + garage_features: garage_util::version::garage_features(), + db_engine: garage.db.engine(), + known_nodes: garage + .system + .get_known_nodes() + .into_iter() + .map(|i| { + ( + hex::encode(i.id), + KnownNodeResp { + addr: i.addr, + is_up: i.is_up, + last_seen_secs_ago: i.last_seen_secs_ago, + hostname: i.status.hostname, + }, + ) + }) + .collect(), + layout: get_cluster_layout(garage), + }; + + Ok(json_ok_response(&res)?) +} + +pub async fn handle_connect_cluster_nodes( + garage: &Arc<Garage>, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let req = parse_json_body::<Vec<String>>(req).await?; + + let res = futures::future::join_all(req.iter().map(|node| garage.system.connect(node))) + .await + .into_iter() + .map(|r| match r { + Ok(()) => ConnectClusterNodesResponse { + success: true, + error: None, + }, + Err(e) => ConnectClusterNodesResponse { + success: false, + error: Some(format!("{}", e)), + }, + }) + .collect::<Vec<_>>(); + + Ok(json_ok_response(&res)?) +} + +pub async fn handle_get_cluster_layout(garage: &Arc<Garage>) -> Result<Response<Body>, Error> { + let res = get_cluster_layout(garage); + + Ok(json_ok_response(&res)?) +} + +fn get_cluster_layout(garage: &Arc<Garage>) -> GetClusterLayoutResponse { + let layout = garage.system.get_cluster_layout(); + + GetClusterLayoutResponse { + version: layout.version, + roles: layout + .roles + .items() + .iter() + .filter(|(_, _, v)| v.0.is_some()) + .map(|(k, _, v)| (hex::encode(k), v.0.clone())) + .collect(), + staged_role_changes: layout + .staging + .items() + .iter() + .filter(|(k, _, v)| layout.roles.get(k) != Some(v)) + .map(|(k, _, v)| (hex::encode(k), v.0.clone())) + .collect(), + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct GetClusterStatusResponse { + node: String, + garage_version: &'static str, + garage_features: Option<&'static [&'static str]>, + db_engine: String, + known_nodes: HashMap<String, KnownNodeResp>, + layout: GetClusterLayoutResponse, +} + +#[derive(Serialize)] +struct ConnectClusterNodesResponse { + success: bool, + error: Option<String>, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct GetClusterLayoutResponse { + version: u64, + roles: HashMap<String, Option<NodeRole>>, + staged_role_changes: HashMap<String, Option<NodeRole>>, +} + +#[derive(Serialize)] +struct KnownNodeResp { + addr: SocketAddr, + is_up: bool, + last_seen_secs_ago: Option<u64>, + hostname: String, +} + +pub async fn handle_update_cluster_layout( + garage: &Arc<Garage>, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let updates = parse_json_body::<UpdateClusterLayoutRequest>(req).await?; + + let mut layout = garage.system.get_cluster_layout(); + + let mut roles = layout.roles.clone(); + roles.merge(&layout.staging); + + for (node, role) in updates { + let node = hex::decode(node).ok_or_bad_request("Invalid node identifier")?; + let node = Uuid::try_from(&node).ok_or_bad_request("Invalid node identifier")?; + + layout + .staging + .merge(&roles.update_mutator(node, NodeRoleV(role))); + } + + garage.system.update_cluster_layout(&layout).await?; + + Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty())?) +} + +pub async fn handle_apply_cluster_layout( + garage: &Arc<Garage>, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let param = parse_json_body::<ApplyRevertLayoutRequest>(req).await?; + + let layout = garage.system.get_cluster_layout(); + let layout = layout.apply_staged_changes(Some(param.version))?; + garage.system.update_cluster_layout(&layout).await?; + + Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty())?) +} + +pub async fn handle_revert_cluster_layout( + garage: &Arc<Garage>, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let param = parse_json_body::<ApplyRevertLayoutRequest>(req).await?; + + let layout = garage.system.get_cluster_layout(); + let layout = layout.revert_staged_changes(Some(param.version))?; + garage.system.update_cluster_layout(&layout).await?; + + Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty())?) +} + +type UpdateClusterLayoutRequest = HashMap<String, Option<NodeRole>>; + +#[derive(Deserialize)] +struct ApplyRevertLayoutRequest { + version: u64, +} diff --git a/src/api/admin/error.rs b/src/api/admin/error.rs new file mode 100644 index 00000000..ed1a07bd --- /dev/null +++ b/src/api/admin/error.rs @@ -0,0 +1,97 @@ +use err_derive::Error; +use hyper::header::HeaderValue; +use hyper::{Body, HeaderMap, StatusCode}; + +pub use garage_model::helper::error::Error as HelperError; + +use crate::common_error::CommonError; +pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError}; +use crate::generic_server::ApiError; +use crate::helpers::CustomApiErrorBody; + +/// Errors of this crate +#[derive(Debug, Error)] +pub enum Error { + #[error(display = "{}", _0)] + /// Error from common error + Common(CommonError), + + // Category: cannot process + /// The API access key does not exist + #[error(display = "Access key not found: {}", _0)] + NoSuchAccessKey(String), + + /// In Import key, the key already exists + #[error( + display = "Key {} already exists in data store. Even if it is deleted, we can't let you create a new key with the same ID. Sorry.", + _0 + )] + KeyAlreadyExists(String), +} + +impl<T> From<T> for Error +where + CommonError: From<T>, +{ + fn from(err: T) -> Self { + Error::Common(CommonError::from(err)) + } +} + +impl CommonErrorDerivative for Error {} + +impl From<HelperError> for Error { + fn from(err: HelperError) -> Self { + match err { + HelperError::Internal(i) => Self::Common(CommonError::InternalError(i)), + HelperError::BadRequest(b) => Self::Common(CommonError::BadRequest(b)), + HelperError::InvalidBucketName(n) => Self::Common(CommonError::InvalidBucketName(n)), + HelperError::NoSuchBucket(n) => Self::Common(CommonError::NoSuchBucket(n)), + HelperError::NoSuchAccessKey(n) => Self::NoSuchAccessKey(n), + } + } +} + +impl Error { + fn code(&self) -> &'static str { + match self { + Error::Common(c) => c.aws_code(), + Error::NoSuchAccessKey(_) => "NoSuchAccessKey", + Error::KeyAlreadyExists(_) => "KeyAlreadyExists", + } + } +} + +impl ApiError for Error { + /// Get the HTTP status code that best represents the meaning of the error for the client + fn http_status_code(&self) -> StatusCode { + match self { + Error::Common(c) => c.http_status_code(), + Error::NoSuchAccessKey(_) => StatusCode::NOT_FOUND, + Error::KeyAlreadyExists(_) => StatusCode::CONFLICT, + } + } + + fn add_http_headers(&self, header_map: &mut HeaderMap<HeaderValue>) { + use hyper::header; + header_map.append(header::CONTENT_TYPE, "application/json".parse().unwrap()); + } + + fn http_body(&self, garage_region: &str, path: &str) -> Body { + let error = CustomApiErrorBody { + code: self.code().to_string(), + message: format!("{}", self), + path: path.to_string(), + region: garage_region.to_string(), + }; + Body::from(serde_json::to_string_pretty(&error).unwrap_or_else(|_| { + r#" +{ + "code": "InternalError", + "message": "JSON encoding of error failed" +} + "# + .into() + })) + } +} diff --git a/src/api/admin/key.rs b/src/api/admin/key.rs new file mode 100644 index 00000000..2bbabb7b --- /dev/null +++ b/src/api/admin/key.rs @@ -0,0 +1,256 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use hyper::{Body, Request, Response, StatusCode}; +use serde::{Deserialize, Serialize}; + +use garage_table::*; + +use garage_model::garage::Garage; +use garage_model::key_table::*; + +use crate::admin::error::*; +use crate::helpers::{json_ok_response, parse_json_body}; + +pub async fn handle_list_keys(garage: &Arc<Garage>) -> Result<Response<Body>, Error> { + let res = garage + .key_table + .get_range( + &EmptyKey, + None, + Some(KeyFilter::Deleted(DeletedFilter::NotDeleted)), + 10000, + EnumerationOrder::Forward, + ) + .await? + .iter() + .map(|k| ListKeyResultItem { + id: k.key_id.to_string(), + name: k.params().unwrap().name.get().clone(), + }) + .collect::<Vec<_>>(); + + Ok(json_ok_response(&res)?) +} + +#[derive(Serialize)] +struct ListKeyResultItem { + id: String, + name: String, +} + +pub async fn handle_get_key_info( + garage: &Arc<Garage>, + id: Option<String>, + search: Option<String>, +) -> Result<Response<Body>, Error> { + let key = if let Some(id) = id { + garage.key_helper().get_existing_key(&id).await? + } else if let Some(search) = search { + garage + .key_helper() + .get_existing_matching_key(&search) + .await? + } else { + unreachable!(); + }; + + key_info_results(garage, key).await +} + +pub async fn handle_create_key( + garage: &Arc<Garage>, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let req = parse_json_body::<CreateKeyRequest>(req).await?; + + let key = Key::new(&req.name); + garage.key_table.insert(&key).await?; + + key_info_results(garage, key).await +} + +#[derive(Deserialize)] +struct CreateKeyRequest { + name: String, +} + +pub async fn handle_import_key( + garage: &Arc<Garage>, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let req = parse_json_body::<ImportKeyRequest>(req).await?; + + let prev_key = garage.key_table.get(&EmptyKey, &req.access_key_id).await?; + if prev_key.is_some() { + return Err(Error::KeyAlreadyExists(req.access_key_id.to_string())); + } + + let imported_key = Key::import(&req.access_key_id, &req.secret_access_key, &req.name); + garage.key_table.insert(&imported_key).await?; + + key_info_results(garage, imported_key).await +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ImportKeyRequest { + access_key_id: String, + secret_access_key: String, + name: String, +} + +pub async fn handle_update_key( + garage: &Arc<Garage>, + id: String, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let req = parse_json_body::<UpdateKeyRequest>(req).await?; + + let mut key = garage.key_helper().get_existing_key(&id).await?; + + let key_state = key.state.as_option_mut().unwrap(); + + if let Some(new_name) = req.name { + key_state.name.update(new_name); + } + if let Some(allow) = req.allow { + if allow.create_bucket { + key_state.allow_create_bucket.update(true); + } + } + if let Some(deny) = req.deny { + if deny.create_bucket { + key_state.allow_create_bucket.update(false); + } + } + + garage.key_table.insert(&key).await?; + + key_info_results(garage, key).await +} + +#[derive(Deserialize)] +struct UpdateKeyRequest { + name: Option<String>, + allow: Option<KeyPerm>, + deny: Option<KeyPerm>, +} + +pub async fn handle_delete_key(garage: &Arc<Garage>, id: String) -> Result<Response<Body>, Error> { + let mut key = garage.key_helper().get_existing_key(&id).await?; + + key.state.as_option().unwrap(); + + garage.key_helper().delete_key(&mut key).await?; + + Ok(Response::builder() + .status(StatusCode::NO_CONTENT) + .body(Body::empty())?) +} + +async fn key_info_results(garage: &Arc<Garage>, key: Key) -> Result<Response<Body>, Error> { + let mut relevant_buckets = HashMap::new(); + + let key_state = key.state.as_option().unwrap(); + + for id in key_state + .authorized_buckets + .items() + .iter() + .map(|(id, _)| id) + .chain( + key_state + .local_aliases + .items() + .iter() + .filter_map(|(_, _, v)| v.as_ref()), + ) { + if !relevant_buckets.contains_key(id) { + if let Some(b) = garage.bucket_table.get(&EmptyKey, id).await? { + if b.state.as_option().is_some() { + relevant_buckets.insert(*id, b); + } + } + } + } + + let res = GetKeyInfoResult { + name: key_state.name.get().clone(), + access_key_id: key.key_id.clone(), + secret_access_key: key_state.secret_key.clone(), + permissions: KeyPerm { + create_bucket: *key_state.allow_create_bucket.get(), + }, + buckets: relevant_buckets + .into_iter() + .map(|(_, bucket)| { + let state = bucket.state.as_option().unwrap(); + KeyInfoBucketResult { + id: hex::encode(bucket.id), + global_aliases: state + .aliases + .items() + .iter() + .filter(|(_, _, a)| *a) + .map(|(n, _, _)| n.to_string()) + .collect::<Vec<_>>(), + local_aliases: state + .local_aliases + .items() + .iter() + .filter(|((k, _), _, a)| *a && *k == key.key_id) + .map(|((_, n), _, _)| n.to_string()) + .collect::<Vec<_>>(), + permissions: key_state + .authorized_buckets + .get(&bucket.id) + .map(|p| ApiBucketKeyPerm { + read: p.allow_read, + write: p.allow_write, + owner: p.allow_owner, + }) + .unwrap_or_default(), + } + }) + .collect::<Vec<_>>(), + }; + + Ok(json_ok_response(&res)?) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct GetKeyInfoResult { + name: String, + access_key_id: String, + secret_access_key: String, + permissions: KeyPerm, + buckets: Vec<KeyInfoBucketResult>, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct KeyPerm { + #[serde(default)] + create_bucket: bool, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct KeyInfoBucketResult { + id: String, + global_aliases: Vec<String>, + local_aliases: Vec<String>, + permissions: ApiBucketKeyPerm, +} + +#[derive(Serialize, Deserialize, Default)] +pub(crate) struct ApiBucketKeyPerm { + #[serde(default)] + pub(crate) read: bool, + #[serde(default)] + pub(crate) write: bool, + #[serde(default)] + pub(crate) owner: bool, +} diff --git a/src/api/admin/mod.rs b/src/api/admin/mod.rs new file mode 100644 index 00000000..c4857c10 --- /dev/null +++ b/src/api/admin/mod.rs @@ -0,0 +1,7 @@ +pub mod api_server; +mod error; +mod router; + +mod bucket; +mod cluster; +mod key; diff --git a/src/api/admin/router.rs b/src/api/admin/router.rs new file mode 100644 index 00000000..3eee8b67 --- /dev/null +++ b/src/api/admin/router.rs @@ -0,0 +1,145 @@ +use std::borrow::Cow; + +use hyper::{Method, Request}; + +use crate::admin::error::*; +use crate::router_macros::*; + +pub enum Authorization { + MetricsToken, + AdminToken, +} + +router_match! {@func + +/// List of all Admin API endpoints. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Endpoint { + Options, + Metrics, + GetClusterStatus, + ConnectClusterNodes, + // Layout + GetClusterLayout, + UpdateClusterLayout, + ApplyClusterLayout, + RevertClusterLayout, + // Keys + ListKeys, + CreateKey, + ImportKey, + GetKeyInfo { + id: Option<String>, + search: Option<String>, + }, + DeleteKey { + id: String, + }, + UpdateKey { + id: String, + }, + // Buckets + ListBuckets, + CreateBucket, + GetBucketInfo { + id: Option<String>, + global_alias: Option<String>, + }, + DeleteBucket { + id: String, + }, + UpdateBucket { + id: String, + }, + // Bucket-Key Permissions + BucketAllowKey, + BucketDenyKey, + // Bucket aliases + GlobalAliasBucket { + id: String, + alias: String, + }, + GlobalUnaliasBucket { + id: String, + alias: String, + }, + LocalAliasBucket { + id: String, + access_key_id: String, + alias: String, + }, + LocalUnaliasBucket { + id: String, + access_key_id: String, + alias: String, + }, +}} + +impl Endpoint { + /// Determine which S3 endpoint a request is for using the request, and a bucket which was + /// possibly extracted from the Host header. + /// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets + pub fn from_request<T>(req: &Request<T>) -> Result<Self, Error> { + let uri = req.uri(); + let path = uri.path(); + let query = uri.query(); + + let mut query = QueryParameters::from_query(query.unwrap_or_default())?; + + let res = router_match!(@gen_path_parser (req.method(), path, query) [ + OPTIONS _ => Options, + GET "/metrics" => Metrics, + GET "/v0/status" => GetClusterStatus, + POST "/v0/connect" => ConnectClusterNodes, + // Layout endpoints + GET "/v0/layout" => GetClusterLayout, + POST "/v0/layout" => UpdateClusterLayout, + POST "/v0/layout/apply" => ApplyClusterLayout, + POST "/v0/layout/revert" => RevertClusterLayout, + // API key endpoints + GET "/v0/key" if id => GetKeyInfo (query_opt::id, query_opt::search), + GET "/v0/key" if search => GetKeyInfo (query_opt::id, query_opt::search), + POST "/v0/key" if id => UpdateKey (query::id), + POST "/v0/key" => CreateKey, + POST "/v0/key/import" => ImportKey, + DELETE "/v0/key" if id => DeleteKey (query::id), + GET "/v0/key" => ListKeys, + // Bucket endpoints + GET "/v0/bucket" if id => GetBucketInfo (query_opt::id, query_opt::global_alias), + GET "/v0/bucket" if global_alias => GetBucketInfo (query_opt::id, query_opt::global_alias), + GET "/v0/bucket" => ListBuckets, + POST "/v0/bucket" => CreateBucket, + DELETE "/v0/bucket" if id => DeleteBucket (query::id), + PUT "/v0/bucket" if id => UpdateBucket (query::id), + // Bucket-key permissions + POST "/v0/bucket/allow" => BucketAllowKey, + POST "/v0/bucket/deny" => BucketDenyKey, + // Bucket aliases + PUT "/v0/bucket/alias/global" => GlobalAliasBucket (query::id, query::alias), + DELETE "/v0/bucket/alias/global" => GlobalUnaliasBucket (query::id, query::alias), + PUT "/v0/bucket/alias/local" => LocalAliasBucket (query::id, query::access_key_id, query::alias), + DELETE "/v0/bucket/alias/local" => LocalUnaliasBucket (query::id, query::access_key_id, query::alias), + ]); + + if let Some(message) = query.nonempty_message() { + debug!("Unused query parameter: {}", message) + } + + Ok(res) + } + /// Get the kind of authorization which is required to perform the operation. + pub fn authorization_type(&self) -> Authorization { + match self { + Self::Metrics => Authorization::MetricsToken, + _ => Authorization::AdminToken, + } + } +} + +generateQueryParameters! { + "id" => id, + "search" => search, + "globalAlias" => global_alias, + "alias" => alias, + "accessKeyId" => access_key_id +} diff --git a/src/api/api_server.rs b/src/api/api_server.rs deleted file mode 100644 index e7b86d9e..00000000 --- a/src/api/api_server.rs +++ /dev/null @@ -1,645 +0,0 @@ -use std::net::SocketAddr; -use std::sync::Arc; - -use chrono::{DateTime, NaiveDateTime, Utc}; -use futures::future::Future; -use futures::prelude::*; -use hyper::header; -use hyper::server::conn::AddrStream; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Method, Request, Response, Server}; - -use opentelemetry::{ - global, - metrics::{Counter, ValueRecorder}, - trace::{FutureExt, TraceContextExt, Tracer}, - Context, KeyValue, -}; - -use garage_util::data::*; -use garage_util::error::Error as GarageError; -use garage_util::metrics::{gen_trace_id, RecordDuration}; - -use garage_model::garage::Garage; -use garage_model::key_table::Key; - -use garage_table::util::*; - -use crate::error::*; -use crate::signature::compute_scope; -use crate::signature::payload::check_payload_signature; -use crate::signature::streaming::SignedPayloadStream; -use crate::signature::LONG_DATETIME; - -use crate::helpers::*; -use crate::s3_bucket::*; -use crate::s3_copy::*; -use crate::s3_cors::*; -use crate::s3_delete::*; -use crate::s3_get::*; -use crate::s3_list::*; -use crate::s3_post_object::handle_post_object; -use crate::s3_put::*; -use crate::s3_router::{Authorization, Endpoint}; -use crate::s3_website::*; - -struct ApiMetrics { - request_counter: Counter<u64>, - error_counter: Counter<u64>, - request_duration: ValueRecorder<f64>, -} - -impl ApiMetrics { - fn new() -> Self { - let meter = global::meter("garage/api"); - Self { - request_counter: meter - .u64_counter("api.request_counter") - .with_description("Number of API calls to the various S3 API endpoints") - .init(), - error_counter: meter - .u64_counter("api.error_counter") - .with_description( - "Number of API calls to the various S3 API endpoints that resulted in errors", - ) - .init(), - request_duration: meter - .f64_value_recorder("api.request_duration") - .with_description("Duration of API calls to the various S3 API endpoints") - .init(), - } - } -} - -/// Run the S3 API server -pub async fn run_api_server( - garage: Arc<Garage>, - shutdown_signal: impl Future<Output = ()>, -) -> Result<(), GarageError> { - let addr = &garage.config.s3_api.api_bind_addr; - - let metrics = Arc::new(ApiMetrics::new()); - - let service = make_service_fn(|conn: &AddrStream| { - let garage = garage.clone(); - let metrics = metrics.clone(); - - let client_addr = conn.remote_addr(); - async move { - Ok::<_, GarageError>(service_fn(move |req: Request<Body>| { - let garage = garage.clone(); - let metrics = metrics.clone(); - - handler(garage, metrics, req, client_addr) - })) - } - }); - - let server = Server::bind(addr).serve(service); - - let graceful = server.with_graceful_shutdown(shutdown_signal); - info!("API server listening on http://{}", addr); - - graceful.await?; - Ok(()) -} - -async fn handler( - garage: Arc<Garage>, - metrics: Arc<ApiMetrics>, - req: Request<Body>, - addr: SocketAddr, -) -> Result<Response<Body>, GarageError> { - let uri = req.uri().clone(); - info!("{} {} {}", addr, req.method(), uri); - debug!("{:?}", req); - - let tracer = opentelemetry::global::tracer("garage"); - let span = tracer - .span_builder("S3 API call (unknown)") - .with_trace_id(gen_trace_id()) - .with_attributes(vec![ - KeyValue::new("method", format!("{}", req.method())), - KeyValue::new("uri", req.uri().to_string()), - ]) - .start(&tracer); - - let res = handler_stage2(garage.clone(), metrics, req) - .with_context(Context::current_with_span(span)) - .await; - - match res { - Ok(x) => { - debug!("{} {:?}", x.status(), x.headers()); - Ok(x) - } - Err(e) => { - let body: Body = Body::from(e.aws_xml(&garage.config.s3_api.s3_region, uri.path())); - let mut http_error_builder = Response::builder() - .status(e.http_status_code()) - .header("Content-Type", "application/xml"); - - if let Some(header_map) = http_error_builder.headers_mut() { - e.add_headers(header_map) - } - - let http_error = http_error_builder.body(body)?; - - if e.http_status_code().is_server_error() { - warn!("Response: error {}, {}", e.http_status_code(), e); - } else { - info!("Response: error {}, {}", e.http_status_code(), e); - } - Ok(http_error) - } - } -} - -async fn handler_stage2( - garage: Arc<Garage>, - metrics: Arc<ApiMetrics>, - req: Request<Body>, -) -> Result<Response<Body>, Error> { - let authority = req - .headers() - .get(header::HOST) - .ok_or_bad_request("Host header required")? - .to_str()?; - - let host = authority_to_host(authority)?; - - let bucket_name = garage - .config - .s3_api - .root_domain - .as_ref() - .and_then(|root_domain| host_to_bucket(&host, root_domain)); - - let (endpoint, bucket_name) = Endpoint::from_request(&req, bucket_name.map(ToOwned::to_owned))?; - debug!("Endpoint: {:?}", endpoint); - - let current_context = Context::current(); - let current_span = current_context.span(); - current_span.update_name::<String>(format!("S3 API {}", endpoint.name())); - current_span.set_attribute(KeyValue::new("endpoint", endpoint.name())); - current_span.set_attribute(KeyValue::new( - "bucket", - bucket_name.clone().unwrap_or_default(), - )); - - let metrics_tags = &[KeyValue::new("api_endpoint", endpoint.name())]; - - let res = handler_stage3(garage, req, endpoint, bucket_name) - .record_duration(&metrics.request_duration, &metrics_tags[..]) - .await; - - metrics.request_counter.add(1, &metrics_tags[..]); - - let status_code = match &res { - Ok(r) => r.status(), - Err(e) => e.http_status_code(), - }; - if status_code.is_client_error() || status_code.is_server_error() { - metrics.error_counter.add( - 1, - &[ - metrics_tags[0].clone(), - KeyValue::new("status_code", status_code.as_str().to_string()), - ], - ); - } - - res -} - -async fn handler_stage3( - garage: Arc<Garage>, - req: Request<Body>, - endpoint: Endpoint, - bucket_name: Option<String>, -) -> Result<Response<Body>, Error> { - // Some endpoints are processed early, before we even check for an API key - if let Endpoint::PostObject = endpoint { - return handle_post_object(garage, req, bucket_name.unwrap()).await; - } - if let Endpoint::Options = endpoint { - return handle_options_s3api(garage, &req, bucket_name).await; - } - - let (api_key, mut content_sha256) = check_payload_signature(&garage, &req).await?; - let api_key = api_key.ok_or_else(|| { - Error::Forbidden("Garage does not support anonymous access yet".to_string()) - })?; - - let req = match req.headers().get("x-amz-content-sha256") { - Some(header) if header == "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" => { - let signature = content_sha256 - .take() - .ok_or_bad_request("No signature provided")?; - - let secret_key = &api_key - .state - .as_option() - .ok_or_internal_error("Deleted key state")? - .secret_key; - - let date = req - .headers() - .get("x-amz-date") - .ok_or_bad_request("Missing X-Amz-Date field")? - .to_str()?; - let date: NaiveDateTime = NaiveDateTime::parse_from_str(date, LONG_DATETIME) - .ok_or_bad_request("Invalid date")?; - let date: DateTime<Utc> = DateTime::from_utc(date, Utc); - - let scope = compute_scope(&date, &garage.config.s3_api.s3_region); - let signing_hmac = crate::signature::signing_hmac( - &date, - secret_key, - &garage.config.s3_api.s3_region, - "s3", - ) - .ok_or_internal_error("Unable to build signing HMAC")?; - - req.map(move |body| { - Body::wrap_stream( - SignedPayloadStream::new( - body.map_err(Error::from), - signing_hmac, - date, - &scope, - signature, - ) - .map_err(Error::from), - ) - }) - } - _ => req, - }; - - let bucket_name = match bucket_name { - None => return handle_request_without_bucket(garage, req, api_key, endpoint).await, - Some(bucket) => bucket.to_string(), - }; - - // Special code path for CreateBucket API endpoint - if let Endpoint::CreateBucket {} = endpoint { - return handle_create_bucket(&garage, req, content_sha256, api_key, bucket_name).await; - } - - let bucket_id = resolve_bucket(&garage, &bucket_name, &api_key).await?; - let bucket = garage - .bucket_table - .get(&EmptyKey, &bucket_id) - .await? - .filter(|b| !b.state.is_deleted()) - .ok_or(Error::NoSuchBucket)?; - - let allowed = match endpoint.authorization_type() { - Authorization::Read => api_key.allow_read(&bucket_id), - Authorization::Write => api_key.allow_write(&bucket_id), - Authorization::Owner => api_key.allow_owner(&bucket_id), - _ => unreachable!(), - }; - - if !allowed { - return Err(Error::Forbidden( - "Operation is not allowed for this key.".to_string(), - )); - } - - // Look up what CORS rule might apply to response. - // Requests for methods different than GET, HEAD or POST - // are always preflighted, i.e. the browser should make - // an OPTIONS call before to check it is allowed - let matching_cors_rule = match *req.method() { - Method::GET | Method::HEAD | Method::POST => find_matching_cors_rule(&bucket, &req)?, - _ => None, - }; - - let resp = match endpoint { - Endpoint::HeadObject { - key, part_number, .. - } => handle_head(garage, &req, bucket_id, &key, part_number).await, - Endpoint::GetObject { - key, part_number, .. - } => handle_get(garage, &req, bucket_id, &key, part_number).await, - Endpoint::UploadPart { - key, - part_number, - upload_id, - } => { - handle_put_part( - garage, - req, - bucket_id, - &key, - part_number, - &upload_id, - content_sha256, - ) - .await - } - Endpoint::CopyObject { key } => handle_copy(garage, &api_key, &req, bucket_id, &key).await, - Endpoint::UploadPartCopy { - key, - part_number, - upload_id, - } => { - handle_upload_part_copy( - garage, - &api_key, - &req, - bucket_id, - &key, - part_number, - &upload_id, - ) - .await - } - Endpoint::PutObject { key } => { - handle_put(garage, req, bucket_id, &key, content_sha256).await - } - Endpoint::AbortMultipartUpload { key, upload_id } => { - handle_abort_multipart_upload(garage, bucket_id, &key, &upload_id).await - } - Endpoint::DeleteObject { key, .. } => handle_delete(garage, bucket_id, &key).await, - Endpoint::CreateMultipartUpload { key } => { - handle_create_multipart_upload(garage, &req, &bucket_name, bucket_id, &key).await - } - Endpoint::CompleteMultipartUpload { key, upload_id } => { - handle_complete_multipart_upload( - garage, - req, - &bucket_name, - bucket_id, - &key, - &upload_id, - content_sha256, - ) - .await - } - Endpoint::CreateBucket {} => unreachable!(), - Endpoint::HeadBucket {} => { - let empty_body: Body = Body::from(vec![]); - let response = Response::builder().body(empty_body).unwrap(); - Ok(response) - } - Endpoint::DeleteBucket {} => { - handle_delete_bucket(&garage, bucket_id, bucket_name, api_key).await - } - Endpoint::GetBucketLocation {} => handle_get_bucket_location(garage), - Endpoint::GetBucketVersioning {} => handle_get_bucket_versioning(), - Endpoint::ListObjects { - delimiter, - encoding_type, - marker, - max_keys, - prefix, - } => { - handle_list( - garage, - &ListObjectsQuery { - common: ListQueryCommon { - bucket_name, - bucket_id, - delimiter: delimiter.map(|d| d.to_string()), - page_size: max_keys.map(|p| p.clamp(1, 1000)).unwrap_or(1000), - prefix: prefix.unwrap_or_default(), - urlencode_resp: encoding_type.map(|e| e == "url").unwrap_or(false), - }, - is_v2: false, - marker, - continuation_token: None, - start_after: None, - }, - ) - .await - } - Endpoint::ListObjectsV2 { - delimiter, - encoding_type, - max_keys, - prefix, - continuation_token, - start_after, - list_type, - .. - } => { - if list_type == "2" { - handle_list( - garage, - &ListObjectsQuery { - common: ListQueryCommon { - bucket_name, - bucket_id, - delimiter: delimiter.map(|d| d.to_string()), - page_size: max_keys.map(|p| p.clamp(1, 1000)).unwrap_or(1000), - urlencode_resp: encoding_type.map(|e| e == "url").unwrap_or(false), - prefix: prefix.unwrap_or_default(), - }, - is_v2: true, - marker: None, - continuation_token, - start_after, - }, - ) - .await - } else { - Err(Error::BadRequest(format!( - "Invalid endpoint: list-type={}", - list_type - ))) - } - } - Endpoint::ListMultipartUploads { - delimiter, - encoding_type, - key_marker, - max_uploads, - prefix, - upload_id_marker, - } => { - handle_list_multipart_upload( - garage, - &ListMultipartUploadsQuery { - common: ListQueryCommon { - bucket_name, - bucket_id, - delimiter: delimiter.map(|d| d.to_string()), - page_size: max_uploads.map(|p| p.clamp(1, 1000)).unwrap_or(1000), - prefix: prefix.unwrap_or_default(), - urlencode_resp: encoding_type.map(|e| e == "url").unwrap_or(false), - }, - key_marker, - upload_id_marker, - }, - ) - .await - } - Endpoint::ListParts { - key, - max_parts, - part_number_marker, - upload_id, - } => { - handle_list_parts( - garage, - &ListPartsQuery { - bucket_name, - bucket_id, - key, - upload_id, - part_number_marker: part_number_marker.map(|p| p.clamp(1, 10000)), - max_parts: max_parts.map(|p| p.clamp(1, 1000)).unwrap_or(1000), - }, - ) - .await - } - Endpoint::DeleteObjects {} => { - handle_delete_objects(garage, bucket_id, req, content_sha256).await - } - Endpoint::GetBucketWebsite {} => handle_get_website(&bucket).await, - Endpoint::PutBucketWebsite {} => { - handle_put_website(garage, bucket_id, req, content_sha256).await - } - Endpoint::DeleteBucketWebsite {} => handle_delete_website(garage, bucket_id).await, - Endpoint::GetBucketCors {} => handle_get_cors(&bucket).await, - Endpoint::PutBucketCors {} => handle_put_cors(garage, bucket_id, req, content_sha256).await, - Endpoint::DeleteBucketCors {} => handle_delete_cors(garage, bucket_id).await, - endpoint => Err(Error::NotImplemented(endpoint.name().to_owned())), - }; - - // If request was a success and we have a CORS rule that applies to it, - // add the corresponding CORS headers to the response - let mut resp_ok = resp?; - if let Some(rule) = matching_cors_rule { - add_cors_headers(&mut resp_ok, rule) - .ok_or_internal_error("Invalid bucket CORS configuration")?; - } - - Ok(resp_ok) -} - -async fn handle_request_without_bucket( - garage: Arc<Garage>, - _req: Request<Body>, - api_key: Key, - endpoint: Endpoint, -) -> Result<Response<Body>, Error> { - match endpoint { - Endpoint::ListBuckets => handle_list_buckets(&garage, &api_key).await, - endpoint => Err(Error::NotImplemented(endpoint.name().to_owned())), - } -} - -#[allow(clippy::ptr_arg)] -pub async fn resolve_bucket( - garage: &Garage, - bucket_name: &String, - api_key: &Key, -) -> Result<Uuid, Error> { - let api_key_params = api_key - .state - .as_option() - .ok_or_internal_error("Key should not be deleted at this point")?; - - if let Some(Some(bucket_id)) = api_key_params.local_aliases.get(bucket_name) { - Ok(*bucket_id) - } else { - Ok(garage - .bucket_helper() - .resolve_global_bucket_name(bucket_name) - .await? - .ok_or(Error::NoSuchBucket)?) - } -} - -/// Extract the bucket name and the key name from an HTTP path and possibly a bucket provided in -/// the host header of the request -/// -/// S3 internally manages only buckets and keys. This function splits -/// an HTTP path to get the corresponding bucket name and key. -pub fn parse_bucket_key<'a>( - path: &'a str, - host_bucket: Option<&'a str>, -) -> Result<(&'a str, Option<&'a str>), Error> { - let path = path.trim_start_matches('/'); - - if let Some(bucket) = host_bucket { - if !path.is_empty() { - return Ok((bucket, Some(path))); - } else { - return Ok((bucket, None)); - } - } - - let (bucket, key) = match path.find('/') { - Some(i) => { - let key = &path[i + 1..]; - if !key.is_empty() { - (&path[..i], Some(key)) - } else { - (&path[..i], None) - } - } - None => (path, None), - }; - if bucket.is_empty() { - return Err(Error::BadRequest("No bucket specified".to_string())); - } - Ok((bucket, key)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_bucket_containing_a_key() -> Result<(), Error> { - let (bucket, key) = parse_bucket_key("/my_bucket/a/super/file.jpg", None)?; - assert_eq!(bucket, "my_bucket"); - assert_eq!(key.expect("key must be set"), "a/super/file.jpg"); - Ok(()) - } - - #[test] - fn parse_bucket_containing_no_key() -> Result<(), Error> { - let (bucket, key) = parse_bucket_key("/my_bucket/", None)?; - assert_eq!(bucket, "my_bucket"); - assert!(key.is_none()); - let (bucket, key) = parse_bucket_key("/my_bucket", None)?; - assert_eq!(bucket, "my_bucket"); - assert!(key.is_none()); - Ok(()) - } - - #[test] - fn parse_bucket_containing_no_bucket() { - let parsed = parse_bucket_key("", None); - assert!(parsed.is_err()); - let parsed = parse_bucket_key("/", None); - assert!(parsed.is_err()); - let parsed = parse_bucket_key("////", None); - assert!(parsed.is_err()); - } - - #[test] - fn parse_bucket_with_vhost_and_key() -> Result<(), Error> { - let (bucket, key) = parse_bucket_key("/a/super/file.jpg", Some("my-bucket"))?; - assert_eq!(bucket, "my-bucket"); - assert_eq!(key.expect("key must be set"), "a/super/file.jpg"); - Ok(()) - } - - #[test] - fn parse_bucket_with_vhost_no_key() -> Result<(), Error> { - let (bucket, key) = parse_bucket_key("", Some("my-bucket"))?; - assert_eq!(bucket, "my-bucket"); - assert!(key.is_none()); - let (bucket, key) = parse_bucket_key("/", Some("my-bucket"))?; - assert_eq!(bucket, "my-bucket"); - assert!(key.is_none()); - Ok(()) - } -} diff --git a/src/api/common_error.rs b/src/api/common_error.rs new file mode 100644 index 00000000..20f9f266 --- /dev/null +++ b/src/api/common_error.rs @@ -0,0 +1,177 @@ +use err_derive::Error; +use hyper::StatusCode; + +use garage_util::error::Error as GarageError; + +/// Errors of this crate +#[derive(Debug, Error)] +pub enum CommonError { + // ---- INTERNAL ERRORS ---- + /// Error related to deeper parts of Garage + #[error(display = "Internal error: {}", _0)] + InternalError(#[error(source)] GarageError), + + /// Error related to Hyper + #[error(display = "Internal error (Hyper error): {}", _0)] + Hyper(#[error(source)] hyper::Error), + + /// Error related to HTTP + #[error(display = "Internal error (HTTP error): {}", _0)] + Http(#[error(source)] http::Error), + + // ---- GENERIC CLIENT ERRORS ---- + /// Proper authentication was not provided + #[error(display = "Forbidden: {}", _0)] + Forbidden(String), + + /// Generic bad request response with custom message + #[error(display = "Bad request: {}", _0)] + BadRequest(String), + + // ---- SPECIFIC ERROR CONDITIONS ---- + // These have to be error codes referenced in the S3 spec here: + // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList + /// The bucket requested don't exists + #[error(display = "Bucket not found: {}", _0)] + NoSuchBucket(String), + + /// Tried to create a bucket that already exist + #[error(display = "Bucket already exists")] + BucketAlreadyExists, + + /// Tried to delete a non-empty bucket + #[error(display = "Tried to delete a non-empty bucket")] + BucketNotEmpty, + + // Category: bad request + /// Bucket name is not valid according to AWS S3 specs + #[error(display = "Invalid bucket name: {}", _0)] + InvalidBucketName(String), +} + +impl CommonError { + pub fn http_status_code(&self) -> StatusCode { + match self { + CommonError::InternalError( + GarageError::Timeout + | GarageError::RemoteError(_) + | GarageError::Quorum(_, _, _, _), + ) => StatusCode::SERVICE_UNAVAILABLE, + CommonError::InternalError(_) | CommonError::Hyper(_) | CommonError::Http(_) => { + StatusCode::INTERNAL_SERVER_ERROR + } + CommonError::BadRequest(_) => StatusCode::BAD_REQUEST, + CommonError::Forbidden(_) => StatusCode::FORBIDDEN, + CommonError::NoSuchBucket(_) => StatusCode::NOT_FOUND, + CommonError::BucketNotEmpty | CommonError::BucketAlreadyExists => StatusCode::CONFLICT, + CommonError::InvalidBucketName(_) => StatusCode::BAD_REQUEST, + } + } + + pub fn aws_code(&self) -> &'static str { + match self { + CommonError::Forbidden(_) => "AccessDenied", + CommonError::InternalError( + GarageError::Timeout + | GarageError::RemoteError(_) + | GarageError::Quorum(_, _, _, _), + ) => "ServiceUnavailable", + CommonError::InternalError(_) | CommonError::Hyper(_) | CommonError::Http(_) => { + "InternalError" + } + CommonError::BadRequest(_) => "InvalidRequest", + CommonError::NoSuchBucket(_) => "NoSuchBucket", + CommonError::BucketAlreadyExists => "BucketAlreadyExists", + CommonError::BucketNotEmpty => "BucketNotEmpty", + CommonError::InvalidBucketName(_) => "InvalidBucketName", + } + } + + pub fn bad_request<M: ToString>(msg: M) -> Self { + CommonError::BadRequest(msg.to_string()) + } +} + +pub trait CommonErrorDerivative: From<CommonError> { + fn internal_error<M: ToString>(msg: M) -> Self { + Self::from(CommonError::InternalError(GarageError::Message( + msg.to_string(), + ))) + } + + fn bad_request<M: ToString>(msg: M) -> Self { + Self::from(CommonError::BadRequest(msg.to_string())) + } + + fn forbidden<M: ToString>(msg: M) -> Self { + Self::from(CommonError::Forbidden(msg.to_string())) + } +} + +/// Trait to map error to the Bad Request error code +pub trait OkOrBadRequest { + type S; + fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<Self::S, CommonError>; +} + +impl<T, E> OkOrBadRequest for Result<T, E> +where + E: std::fmt::Display, +{ + type S = T; + fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> { + match self { + Ok(x) => Ok(x), + Err(e) => Err(CommonError::BadRequest(format!( + "{}: {}", + reason.as_ref(), + e + ))), + } + } +} + +impl<T> OkOrBadRequest for Option<T> { + type S = T; + fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> { + match self { + Some(x) => Ok(x), + None => Err(CommonError::BadRequest(reason.as_ref().to_string())), + } + } +} + +/// Trait to map an error to an Internal Error code +pub trait OkOrInternalError { + type S; + fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<Self::S, CommonError>; +} + +impl<T, E> OkOrInternalError for Result<T, E> +where + E: std::fmt::Display, +{ + type S = T; + fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> { + match self { + Ok(x) => Ok(x), + Err(e) => Err(CommonError::InternalError(GarageError::Message(format!( + "{}: {}", + reason.as_ref(), + e + )))), + } + } +} + +impl<T> OkOrInternalError for Option<T> { + type S = T; + fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<T, CommonError> { + match self { + Some(x) => Ok(x), + None => Err(CommonError::InternalError(GarageError::Message( + reason.as_ref().to_string(), + ))), + } + } +} diff --git a/src/api/generic_server.rs b/src/api/generic_server.rs new file mode 100644 index 00000000..62fe4e5a --- /dev/null +++ b/src/api/generic_server.rs @@ -0,0 +1,211 @@ +use std::net::SocketAddr; +use std::sync::Arc; + +use async_trait::async_trait; + +use futures::future::Future; + +use hyper::header::HeaderValue; +use hyper::server::conn::AddrStream; +use hyper::service::{make_service_fn, service_fn}; +use hyper::{Body, Request, Response, Server}; +use hyper::{HeaderMap, StatusCode}; + +use opentelemetry::{ + global, + metrics::{Counter, ValueRecorder}, + trace::{FutureExt, SpanRef, TraceContextExt, Tracer}, + Context, KeyValue, +}; + +use garage_util::error::Error as GarageError; +use garage_util::metrics::{gen_trace_id, RecordDuration}; + +pub(crate) trait ApiEndpoint: Send + Sync + 'static { + fn name(&self) -> &'static str; + fn add_span_attributes(&self, span: SpanRef<'_>); +} + +pub trait ApiError: std::error::Error + Send + Sync + 'static { + fn http_status_code(&self) -> StatusCode; + fn add_http_headers(&self, header_map: &mut HeaderMap<HeaderValue>); + fn http_body(&self, garage_region: &str, path: &str) -> Body; +} + +#[async_trait] +pub(crate) trait ApiHandler: Send + Sync + 'static { + const API_NAME: &'static str; + const API_NAME_DISPLAY: &'static str; + + type Endpoint: ApiEndpoint; + type Error: ApiError; + + fn parse_endpoint(&self, r: &Request<Body>) -> Result<Self::Endpoint, Self::Error>; + async fn handle( + &self, + req: Request<Body>, + endpoint: Self::Endpoint, + ) -> Result<Response<Body>, Self::Error>; +} + +pub(crate) struct ApiServer<A: ApiHandler> { + region: String, + api_handler: A, + + // Metrics + request_counter: Counter<u64>, + error_counter: Counter<u64>, + request_duration: ValueRecorder<f64>, +} + +impl<A: ApiHandler> ApiServer<A> { + pub fn new(region: String, api_handler: A) -> Arc<Self> { + let meter = global::meter("garage/api"); + Arc::new(Self { + region, + api_handler, + request_counter: meter + .u64_counter(format!("api.{}.request_counter", A::API_NAME)) + .with_description(format!( + "Number of API calls to the various {} API endpoints", + A::API_NAME_DISPLAY + )) + .init(), + error_counter: meter + .u64_counter(format!("api.{}.error_counter", A::API_NAME)) + .with_description(format!( + "Number of API calls to the various {} API endpoints that resulted in errors", + A::API_NAME_DISPLAY + )) + .init(), + request_duration: meter + .f64_value_recorder(format!("api.{}.request_duration", A::API_NAME)) + .with_description(format!( + "Duration of API calls to the various {} API endpoints", + A::API_NAME_DISPLAY + )) + .init(), + }) + } + + pub async fn run_server( + self: Arc<Self>, + bind_addr: SocketAddr, + shutdown_signal: impl Future<Output = ()>, + ) -> Result<(), GarageError> { + let service = make_service_fn(|conn: &AddrStream| { + let this = self.clone(); + + let client_addr = conn.remote_addr(); + async move { + Ok::<_, GarageError>(service_fn(move |req: Request<Body>| { + let this = this.clone(); + + this.handler(req, client_addr) + })) + } + }); + + let server = Server::bind(&bind_addr).serve(service); + + let graceful = server.with_graceful_shutdown(shutdown_signal); + info!( + "{} API server listening on http://{}", + A::API_NAME_DISPLAY, + bind_addr + ); + + graceful.await?; + Ok(()) + } + + async fn handler( + self: Arc<Self>, + req: Request<Body>, + addr: SocketAddr, + ) -> Result<Response<Body>, GarageError> { + let uri = req.uri().clone(); + info!("{} {} {}", addr, req.method(), uri); + debug!("{:?}", req); + + let tracer = opentelemetry::global::tracer("garage"); + let span = tracer + .span_builder(format!("{} API call (unknown)", A::API_NAME_DISPLAY)) + .with_trace_id(gen_trace_id()) + .with_attributes(vec![ + KeyValue::new("method", format!("{}", req.method())), + KeyValue::new("uri", req.uri().to_string()), + ]) + .start(&tracer); + + let res = self + .handler_stage2(req) + .with_context(Context::current_with_span(span)) + .await; + + match res { + Ok(x) => { + debug!("{} {:?}", x.status(), x.headers()); + Ok(x) + } + Err(e) => { + let body: Body = e.http_body(&self.region, uri.path()); + let mut http_error_builder = Response::builder().status(e.http_status_code()); + + if let Some(header_map) = http_error_builder.headers_mut() { + e.add_http_headers(header_map) + } + + let http_error = http_error_builder.body(body)?; + + if e.http_status_code().is_server_error() { + warn!("Response: error {}, {}", e.http_status_code(), e); + } else { + info!("Response: error {}, {}", e.http_status_code(), e); + } + Ok(http_error) + } + } + } + + async fn handler_stage2(&self, req: Request<Body>) -> Result<Response<Body>, A::Error> { + let endpoint = self.api_handler.parse_endpoint(&req)?; + debug!("Endpoint: {}", endpoint.name()); + + let current_context = Context::current(); + let current_span = current_context.span(); + current_span.update_name::<String>(format!( + "{} API {}", + A::API_NAME_DISPLAY, + endpoint.name() + )); + current_span.set_attribute(KeyValue::new("endpoint", endpoint.name())); + endpoint.add_span_attributes(current_span); + + let metrics_tags = &[KeyValue::new("api_endpoint", endpoint.name())]; + + let res = self + .api_handler + .handle(req, endpoint) + .record_duration(&self.request_duration, &metrics_tags[..]) + .await; + + self.request_counter.add(1, &metrics_tags[..]); + + let status_code = match &res { + Ok(r) => r.status(), + Err(e) => e.http_status_code(), + }; + if status_code.is_client_error() || status_code.is_server_error() { + self.error_counter.add( + 1, + &[ + metrics_tags[0].clone(), + KeyValue::new("status_code", status_code.as_str().to_string()), + ], + ); + } + + res + } +} diff --git a/src/api/helpers.rs b/src/api/helpers.rs index c2709bb3..642dbc42 100644 --- a/src/api/helpers.rs +++ b/src/api/helpers.rs @@ -1,5 +1,21 @@ -use crate::Error; +use hyper::{Body, Request, Response}; use idna::domain_to_unicode; +use serde::{Deserialize, Serialize}; + +use crate::common_error::{CommonError as Error, *}; + +/// What kind of authorization is required to perform a given action +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Authorization { + /// No authorization is required + None, + /// Having Read permission on bucket + Read, + /// Having Write permission on bucket + Write, + /// Having Owner permission on bucket + Owner, +} /// Host to bucket /// @@ -31,7 +47,7 @@ pub fn authority_to_host(authority: &str) -> Result<String, Error> { let mut iter = authority.chars().enumerate(); let (_, first_char) = iter .next() - .ok_or_else(|| Error::BadRequest("Authority is empty".to_string()))?; + .ok_or_else(|| Error::bad_request("Authority is empty".to_string()))?; let split = match first_char { '[' => { @@ -39,7 +55,7 @@ pub fn authority_to_host(authority: &str) -> Result<String, Error> { match iter.next() { Some((_, ']')) => iter.next(), _ => { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "Authority {} has an illegal format", authority ))) @@ -52,7 +68,7 @@ pub fn authority_to_host(authority: &str) -> Result<String, Error> { let authority = match split { Some((i, ':')) => Ok(&authority[..i]), None => Ok(authority), - Some((_, _)) => Err(Error::BadRequest(format!( + Some((_, _)) => Err(Error::bad_request(format!( "Authority {} has an illegal format", authority ))), @@ -60,11 +76,135 @@ pub fn authority_to_host(authority: &str) -> Result<String, Error> { authority.map(|h| domain_to_unicode(h).0) } +/// Extract the bucket name and the key name from an HTTP path and possibly a bucket provided in +/// the host header of the request +/// +/// S3 internally manages only buckets and keys. This function splits +/// an HTTP path to get the corresponding bucket name and key. +pub fn parse_bucket_key<'a>( + path: &'a str, + host_bucket: Option<&'a str>, +) -> Result<(&'a str, Option<&'a str>), Error> { + let path = path.trim_start_matches('/'); + + if let Some(bucket) = host_bucket { + if !path.is_empty() { + return Ok((bucket, Some(path))); + } else { + return Ok((bucket, None)); + } + } + + let (bucket, key) = match path.find('/') { + Some(i) => { + let key = &path[i + 1..]; + if !key.is_empty() { + (&path[..i], Some(key)) + } else { + (&path[..i], None) + } + } + None => (path, None), + }; + if bucket.is_empty() { + return Err(Error::bad_request("No bucket specified")); + } + Ok((bucket, key)) +} + +const UTF8_BEFORE_LAST_CHAR: char = '\u{10FFFE}'; + +/// Compute the key after the prefix +pub fn key_after_prefix(pfx: &str) -> Option<String> { + let mut next = pfx.to_string(); + while !next.is_empty() { + let tail = next.pop().unwrap(); + if tail >= char::MAX { + continue; + } + + // Circumvent a limitation of RangeFrom that overflow earlier than needed + // See: https://doc.rust-lang.org/core/ops/struct.RangeFrom.html + let new_tail = if tail == UTF8_BEFORE_LAST_CHAR { + char::MAX + } else { + (tail..).nth(1).unwrap() + }; + + next.push(new_tail); + return Some(next); + } + + None +} + +pub async fn parse_json_body<T: for<'de> Deserialize<'de>>(req: Request<Body>) -> Result<T, Error> { + let body = hyper::body::to_bytes(req.into_body()).await?; + let resp: T = serde_json::from_slice(&body).ok_or_bad_request("Invalid JSON")?; + Ok(resp) +} + +pub fn json_ok_response<T: Serialize>(res: &T) -> Result<Response<Body>, Error> { + let resp_json = serde_json::to_string_pretty(res).map_err(garage_util::error::Error::from)?; + Ok(Response::builder() + .status(hyper::StatusCode::OK) + .header(http::header::CONTENT_TYPE, "application/json") + .body(Body::from(resp_json))?) +} + #[cfg(test)] mod tests { use super::*; #[test] + fn parse_bucket_containing_a_key() -> Result<(), Error> { + let (bucket, key) = parse_bucket_key("/my_bucket/a/super/file.jpg", None)?; + assert_eq!(bucket, "my_bucket"); + assert_eq!(key.expect("key must be set"), "a/super/file.jpg"); + Ok(()) + } + + #[test] + fn parse_bucket_containing_no_key() -> Result<(), Error> { + let (bucket, key) = parse_bucket_key("/my_bucket/", None)?; + assert_eq!(bucket, "my_bucket"); + assert!(key.is_none()); + let (bucket, key) = parse_bucket_key("/my_bucket", None)?; + assert_eq!(bucket, "my_bucket"); + assert!(key.is_none()); + Ok(()) + } + + #[test] + fn parse_bucket_containing_no_bucket() { + let parsed = parse_bucket_key("", None); + assert!(parsed.is_err()); + let parsed = parse_bucket_key("/", None); + assert!(parsed.is_err()); + let parsed = parse_bucket_key("////", None); + assert!(parsed.is_err()); + } + + #[test] + fn parse_bucket_with_vhost_and_key() -> Result<(), Error> { + let (bucket, key) = parse_bucket_key("/a/super/file.jpg", Some("my-bucket"))?; + assert_eq!(bucket, "my-bucket"); + assert_eq!(key.expect("key must be set"), "a/super/file.jpg"); + Ok(()) + } + + #[test] + fn parse_bucket_with_vhost_no_key() -> Result<(), Error> { + let (bucket, key) = parse_bucket_key("", Some("my-bucket"))?; + assert_eq!(bucket, "my-bucket"); + assert!(key.is_none()); + let (bucket, key) = parse_bucket_key("/", Some("my-bucket"))?; + assert_eq!(bucket, "my-bucket"); + assert!(key.is_none()); + Ok(()) + } + + #[test] fn authority_to_host_with_port() -> Result<(), Error> { let domain = authority_to_host("[::1]:3902")?; assert_eq!(domain, "[::1]"); @@ -111,4 +251,47 @@ mod tests { assert_eq!(host_to_bucket("not-garage.tld", "garage.tld"), None); assert_eq!(host_to_bucket("not-garage.tld", ".garage.tld"), None); } + + #[test] + fn test_key_after_prefix() { + use std::iter::FromIterator; + + assert_eq!(UTF8_BEFORE_LAST_CHAR as u32, (char::MAX as u32) - 1); + assert_eq!(key_after_prefix("a/b/").unwrap().as_str(), "a/b0"); + assert_eq!(key_after_prefix("€").unwrap().as_str(), "₭"); + assert_eq!( + key_after_prefix("").unwrap().as_str(), + String::from(char::from_u32(0x10FFFE).unwrap()) + ); + + // When the last character is the biggest UTF8 char + let a = String::from_iter(['a', char::MAX].iter()); + assert_eq!(key_after_prefix(a.as_str()).unwrap().as_str(), "b"); + + // When all characters are the biggest UTF8 char + let b = String::from_iter([char::MAX; 3].iter()); + assert!(key_after_prefix(b.as_str()).is_none()); + + // Check utf8 surrogates + let c = String::from('\u{D7FF}'); + assert_eq!( + key_after_prefix(c.as_str()).unwrap().as_str(), + String::from('\u{E000}') + ); + + // Check the character before the biggest one + let d = String::from('\u{10FFFE}'); + assert_eq!( + key_after_prefix(d.as_str()).unwrap().as_str(), + String::from(char::MAX) + ); + } +} + +#[derive(Serialize)] +pub(crate) struct CustomApiErrorBody { + pub(crate) code: String, + pub(crate) message: String, + pub(crate) region: String, + pub(crate) path: String, } diff --git a/src/api/k2v/api_server.rs b/src/api/k2v/api_server.rs new file mode 100644 index 00000000..084867b5 --- /dev/null +++ b/src/api/k2v/api_server.rs @@ -0,0 +1,190 @@ +use std::net::SocketAddr; +use std::sync::Arc; + +use async_trait::async_trait; + +use futures::future::Future; +use hyper::{Body, Method, Request, Response}; + +use opentelemetry::{trace::SpanRef, KeyValue}; + +use garage_util::error::Error as GarageError; + +use garage_model::garage::Garage; + +use crate::generic_server::*; +use crate::k2v::error::*; + +use crate::signature::payload::check_payload_signature; +use crate::signature::streaming::*; + +use crate::helpers::*; +use crate::k2v::batch::*; +use crate::k2v::index::*; +use crate::k2v::item::*; +use crate::k2v::router::Endpoint; +use crate::s3::cors::*; + +pub struct K2VApiServer { + garage: Arc<Garage>, +} + +pub(crate) struct K2VApiEndpoint { + bucket_name: String, + endpoint: Endpoint, +} + +impl K2VApiServer { + pub async fn run( + garage: Arc<Garage>, + bind_addr: SocketAddr, + s3_region: String, + shutdown_signal: impl Future<Output = ()>, + ) -> Result<(), GarageError> { + ApiServer::new(s3_region, K2VApiServer { garage }) + .run_server(bind_addr, shutdown_signal) + .await + } +} + +#[async_trait] +impl ApiHandler for K2VApiServer { + const API_NAME: &'static str = "k2v"; + const API_NAME_DISPLAY: &'static str = "K2V"; + + type Endpoint = K2VApiEndpoint; + type Error = Error; + + fn parse_endpoint(&self, req: &Request<Body>) -> Result<K2VApiEndpoint, Error> { + let (endpoint, bucket_name) = Endpoint::from_request(req)?; + + Ok(K2VApiEndpoint { + bucket_name, + endpoint, + }) + } + + async fn handle( + &self, + req: Request<Body>, + endpoint: K2VApiEndpoint, + ) -> Result<Response<Body>, Error> { + let K2VApiEndpoint { + bucket_name, + endpoint, + } = endpoint; + let garage = self.garage.clone(); + + // The OPTIONS method is procesed early, before we even check for an API key + if let Endpoint::Options = endpoint { + return Ok(handle_options_s3api(garage, &req, Some(bucket_name)) + .await + .ok_or_bad_request("Error handling OPTIONS")?); + } + + let (api_key, mut content_sha256) = check_payload_signature(&garage, "k2v", &req).await?; + let api_key = api_key + .ok_or_else(|| Error::forbidden("Garage does not support anonymous access yet"))?; + + let req = parse_streaming_body( + &api_key, + req, + &mut content_sha256, + &garage.config.s3_api.s3_region, + "k2v", + )?; + + let bucket_id = garage + .bucket_helper() + .resolve_bucket(&bucket_name, &api_key) + .await?; + let bucket = garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + + let allowed = match endpoint.authorization_type() { + Authorization::Read => api_key.allow_read(&bucket_id), + Authorization::Write => api_key.allow_write(&bucket_id), + Authorization::Owner => api_key.allow_owner(&bucket_id), + _ => unreachable!(), + }; + + if !allowed { + return Err(Error::forbidden("Operation is not allowed for this key.")); + } + + // Look up what CORS rule might apply to response. + // Requests for methods different than GET, HEAD or POST + // are always preflighted, i.e. the browser should make + // an OPTIONS call before to check it is allowed + let matching_cors_rule = match *req.method() { + Method::GET | Method::HEAD | Method::POST => find_matching_cors_rule(&bucket, &req) + .ok_or_internal_error("Error looking up CORS rule")?, + _ => None, + }; + + let resp = match endpoint { + Endpoint::DeleteItem { + partition_key, + sort_key, + } => handle_delete_item(garage, req, bucket_id, &partition_key, &sort_key).await, + Endpoint::InsertItem { + partition_key, + sort_key, + } => handle_insert_item(garage, req, bucket_id, &partition_key, &sort_key).await, + Endpoint::ReadItem { + partition_key, + sort_key, + } => handle_read_item(garage, &req, bucket_id, &partition_key, &sort_key).await, + Endpoint::PollItem { + partition_key, + sort_key, + causality_token, + timeout, + } => { + handle_poll_item( + garage, + &req, + bucket_id, + partition_key, + sort_key, + causality_token, + timeout, + ) + .await + } + Endpoint::ReadIndex { + prefix, + start, + end, + limit, + reverse, + } => handle_read_index(garage, bucket_id, prefix, start, end, limit, reverse).await, + Endpoint::InsertBatch {} => handle_insert_batch(garage, bucket_id, req).await, + Endpoint::ReadBatch {} => handle_read_batch(garage, bucket_id, req).await, + Endpoint::DeleteBatch {} => handle_delete_batch(garage, bucket_id, req).await, + Endpoint::Options => unreachable!(), + }; + + // If request was a success and we have a CORS rule that applies to it, + // add the corresponding CORS headers to the response + let mut resp_ok = resp?; + if let Some(rule) = matching_cors_rule { + add_cors_headers(&mut resp_ok, rule) + .ok_or_internal_error("Invalid bucket CORS configuration")?; + } + + Ok(resp_ok) + } +} + +impl ApiEndpoint for K2VApiEndpoint { + fn name(&self) -> &'static str { + self.endpoint.name() + } + + fn add_span_attributes(&self, span: SpanRef<'_>) { + span.set_attribute(KeyValue::new("bucket", self.bucket_name.clone())); + } +} diff --git a/src/api/k2v/batch.rs b/src/api/k2v/batch.rs new file mode 100644 index 00000000..db9901cf --- /dev/null +++ b/src/api/k2v/batch.rs @@ -0,0 +1,363 @@ +use std::sync::Arc; + +use hyper::{Body, Request, Response, StatusCode}; +use serde::{Deserialize, Serialize}; + +use garage_util::data::*; +use garage_util::error::Error as GarageError; + +use garage_table::{EnumerationOrder, TableSchema}; + +use garage_model::garage::Garage; +use garage_model::k2v::causality::*; +use garage_model::k2v::item_table::*; + +use crate::helpers::*; +use crate::k2v::error::*; +use crate::k2v::range::read_range; + +pub async fn handle_insert_batch( + garage: Arc<Garage>, + bucket_id: Uuid, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let items = parse_json_body::<Vec<InsertBatchItem>>(req).await?; + + let mut items2 = vec![]; + for it in items { + let ct = it + .ct + .map(|s| CausalContext::parse(&s)) + .transpose() + .ok_or_bad_request("Invalid causality token")?; + let v = match it.v { + Some(vs) => { + DvvsValue::Value(base64::decode(vs).ok_or_bad_request("Invalid base64 value")?) + } + None => DvvsValue::Deleted, + }; + items2.push((it.pk, it.sk, ct, v)); + } + + garage.k2v.rpc.insert_batch(bucket_id, items2).await?; + + Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty())?) +} + +pub async fn handle_read_batch( + garage: Arc<Garage>, + bucket_id: Uuid, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let queries = parse_json_body::<Vec<ReadBatchQuery>>(req).await?; + + let resp_results = futures::future::join_all( + queries + .into_iter() + .map(|q| handle_read_batch_query(&garage, bucket_id, q)), + ) + .await; + + let mut resps: Vec<ReadBatchResponse> = vec![]; + for resp in resp_results { + resps.push(resp?); + } + + let resp_json = serde_json::to_string_pretty(&resps).map_err(GarageError::from)?; + Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::from(resp_json))?) +} + +async fn handle_read_batch_query( + garage: &Arc<Garage>, + bucket_id: Uuid, + query: ReadBatchQuery, +) -> Result<ReadBatchResponse, Error> { + let partition = K2VItemPartition { + bucket_id, + partition_key: query.partition_key.clone(), + }; + + let filter = ItemFilter { + exclude_only_tombstones: !query.tombstones, + conflicts_only: query.conflicts_only, + }; + + let (items, more, next_start) = if query.single_item { + if query.prefix.is_some() || query.end.is_some() || query.limit.is_some() || query.reverse { + return Err(Error::bad_request("Batch query parameters 'prefix', 'end', 'limit' and 'reverse' must not be set when singleItem is true.")); + } + let sk = query + .start + .as_ref() + .ok_or_bad_request("start should be specified if single_item is set")?; + let item = garage + .k2v + .item_table + .get(&partition, sk) + .await? + .filter(|e| K2VItemTable::matches_filter(e, &filter)); + match item { + Some(i) => (vec![ReadBatchResponseItem::from(i)], false, None), + None => (vec![], false, None), + } + } else { + let (items, more, next_start) = read_range( + &garage.k2v.item_table, + &partition, + &query.prefix, + &query.start, + &query.end, + query.limit, + Some(filter), + EnumerationOrder::from_reverse(query.reverse), + ) + .await?; + + let items = items + .into_iter() + .map(ReadBatchResponseItem::from) + .collect::<Vec<_>>(); + + (items, more, next_start) + }; + + Ok(ReadBatchResponse { + partition_key: query.partition_key, + prefix: query.prefix, + start: query.start, + end: query.end, + limit: query.limit, + reverse: query.reverse, + single_item: query.single_item, + conflicts_only: query.conflicts_only, + tombstones: query.tombstones, + items, + more, + next_start, + }) +} + +pub async fn handle_delete_batch( + garage: Arc<Garage>, + bucket_id: Uuid, + req: Request<Body>, +) -> Result<Response<Body>, Error> { + let queries = parse_json_body::<Vec<DeleteBatchQuery>>(req).await?; + + let resp_results = futures::future::join_all( + queries + .into_iter() + .map(|q| handle_delete_batch_query(&garage, bucket_id, q)), + ) + .await; + + let mut resps: Vec<DeleteBatchResponse> = vec![]; + for resp in resp_results { + resps.push(resp?); + } + + let resp_json = serde_json::to_string_pretty(&resps).map_err(GarageError::from)?; + Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::from(resp_json))?) +} + +async fn handle_delete_batch_query( + garage: &Arc<Garage>, + bucket_id: Uuid, + query: DeleteBatchQuery, +) -> Result<DeleteBatchResponse, Error> { + let partition = K2VItemPartition { + bucket_id, + partition_key: query.partition_key.clone(), + }; + + let filter = ItemFilter { + exclude_only_tombstones: true, + conflicts_only: false, + }; + + let deleted_items = if query.single_item { + if query.prefix.is_some() || query.end.is_some() { + return Err(Error::bad_request("Batch query parameters 'prefix' and 'end' must not be set when singleItem is true.")); + } + let sk = query + .start + .as_ref() + .ok_or_bad_request("start should be specified if single_item is set")?; + let item = garage + .k2v + .item_table + .get(&partition, sk) + .await? + .filter(|e| K2VItemTable::matches_filter(e, &filter)); + match item { + Some(i) => { + let cc = i.causal_context(); + garage + .k2v + .rpc + .insert( + bucket_id, + i.partition.partition_key, + i.sort_key, + Some(cc), + DvvsValue::Deleted, + ) + .await?; + 1 + } + None => 0, + } + } else { + let (items, more, _next_start) = read_range( + &garage.k2v.item_table, + &partition, + &query.prefix, + &query.start, + &query.end, + None, + Some(filter), + EnumerationOrder::Forward, + ) + .await?; + assert!(!more); + + // TODO delete items + let items = items + .into_iter() + .map(|i| { + let cc = i.causal_context(); + ( + i.partition.partition_key, + i.sort_key, + Some(cc), + DvvsValue::Deleted, + ) + }) + .collect::<Vec<_>>(); + let n = items.len(); + + garage.k2v.rpc.insert_batch(bucket_id, items).await?; + + n + }; + + Ok(DeleteBatchResponse { + partition_key: query.partition_key, + prefix: query.prefix, + start: query.start, + end: query.end, + single_item: query.single_item, + deleted_items, + }) +} + +#[derive(Deserialize)] +struct InsertBatchItem { + pk: String, + sk: String, + ct: Option<String>, + v: Option<String>, +} + +#[derive(Deserialize)] +struct ReadBatchQuery { + #[serde(rename = "partitionKey")] + partition_key: String, + #[serde(default)] + prefix: Option<String>, + #[serde(default)] + start: Option<String>, + #[serde(default)] + end: Option<String>, + #[serde(default)] + limit: Option<u64>, + #[serde(default)] + reverse: bool, + #[serde(default, rename = "singleItem")] + single_item: bool, + #[serde(default, rename = "conflictsOnly")] + conflicts_only: bool, + #[serde(default)] + tombstones: bool, +} + +#[derive(Serialize)] +struct ReadBatchResponse { + #[serde(rename = "partitionKey")] + partition_key: String, + prefix: Option<String>, + start: Option<String>, + end: Option<String>, + limit: Option<u64>, + reverse: bool, + #[serde(rename = "singleItem")] + single_item: bool, + #[serde(rename = "conflictsOnly")] + conflicts_only: bool, + tombstones: bool, + + items: Vec<ReadBatchResponseItem>, + more: bool, + #[serde(rename = "nextStart")] + next_start: Option<String>, +} + +#[derive(Serialize)] +struct ReadBatchResponseItem { + sk: String, + ct: String, + v: Vec<Option<String>>, +} + +impl ReadBatchResponseItem { + fn from(i: K2VItem) -> Self { + let ct = i.causal_context().serialize(); + let v = i + .values() + .iter() + .map(|v| match v { + DvvsValue::Value(x) => Some(base64::encode(x)), + DvvsValue::Deleted => None, + }) + .collect::<Vec<_>>(); + Self { + sk: i.sort_key, + ct, + v, + } + } +} + +#[derive(Deserialize)] +struct DeleteBatchQuery { + #[serde(rename = "partitionKey")] + partition_key: String, + #[serde(default)] + prefix: Option<String>, + #[serde(default)] + start: Option<String>, + #[serde(default)] + end: Option<String>, + #[serde(default, rename = "singleItem")] + single_item: bool, +} + +#[derive(Serialize)] +struct DeleteBatchResponse { + #[serde(rename = "partitionKey")] + partition_key: String, + prefix: Option<String>, + start: Option<String>, + end: Option<String>, + #[serde(rename = "singleItem")] + single_item: bool, + + #[serde(rename = "deletedItems")] + deleted_items: usize, +} diff --git a/src/api/k2v/error.rs b/src/api/k2v/error.rs new file mode 100644 index 00000000..42491466 --- /dev/null +++ b/src/api/k2v/error.rs @@ -0,0 +1,135 @@ +use err_derive::Error; +use hyper::header::HeaderValue; +use hyper::{Body, HeaderMap, StatusCode}; + +use garage_model::helper::error::Error as HelperError; + +use crate::common_error::CommonError; +pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError}; +use crate::generic_server::ApiError; +use crate::helpers::CustomApiErrorBody; +use crate::signature::error::Error as SignatureError; + +/// Errors of this crate +#[derive(Debug, Error)] +pub enum Error { + #[error(display = "{}", _0)] + /// Error from common error + Common(CommonError), + + // Category: cannot process + /// Authorization Header Malformed + #[error(display = "Authorization header malformed, expected scope: {}", _0)] + AuthorizationHeaderMalformed(String), + + /// The object requested don't exists + #[error(display = "Key not found")] + NoSuchKey, + + /// Some base64 encoded data was badly encoded + #[error(display = "Invalid base64: {}", _0)] + InvalidBase64(#[error(source)] base64::DecodeError), + + /// The client sent a header with invalid value + #[error(display = "Invalid header value: {}", _0)] + InvalidHeader(#[error(source)] hyper::header::ToStrError), + + /// The client asked for an invalid return format (invalid Accept header) + #[error(display = "Not acceptable: {}", _0)] + NotAcceptable(String), + + /// The request contained an invalid UTF-8 sequence in its path or in other parameters + #[error(display = "Invalid UTF-8: {}", _0)] + InvalidUtf8Str(#[error(source)] std::str::Utf8Error), +} + +impl<T> From<T> for Error +where + CommonError: From<T>, +{ + fn from(err: T) -> Self { + Error::Common(CommonError::from(err)) + } +} + +impl CommonErrorDerivative for Error {} + +impl From<HelperError> for Error { + fn from(err: HelperError) -> Self { + match err { + HelperError::Internal(i) => Self::Common(CommonError::InternalError(i)), + HelperError::BadRequest(b) => Self::Common(CommonError::BadRequest(b)), + HelperError::InvalidBucketName(n) => Self::Common(CommonError::InvalidBucketName(n)), + HelperError::NoSuchBucket(n) => Self::Common(CommonError::NoSuchBucket(n)), + e => Self::Common(CommonError::BadRequest(format!("{}", e))), + } + } +} + +impl From<SignatureError> for Error { + fn from(err: SignatureError) -> Self { + match err { + SignatureError::Common(c) => Self::Common(c), + SignatureError::AuthorizationHeaderMalformed(c) => { + Self::AuthorizationHeaderMalformed(c) + } + SignatureError::InvalidUtf8Str(i) => Self::InvalidUtf8Str(i), + SignatureError::InvalidHeader(h) => Self::InvalidHeader(h), + } + } +} + +impl Error { + /// This returns a keyword for the corresponding error. + /// Here, these keywords are not necessarily those from AWS S3, + /// as we are building a custom API + fn code(&self) -> &'static str { + match self { + Error::Common(c) => c.aws_code(), + Error::NoSuchKey => "NoSuchKey", + Error::NotAcceptable(_) => "NotAcceptable", + Error::AuthorizationHeaderMalformed(_) => "AuthorizationHeaderMalformed", + Error::InvalidBase64(_) => "InvalidBase64", + Error::InvalidHeader(_) => "InvalidHeaderValue", + Error::InvalidUtf8Str(_) => "InvalidUtf8String", + } + } +} + +impl ApiError for Error { + /// Get the HTTP status code that best represents the meaning of the error for the client + fn http_status_code(&self) -> StatusCode { + match self { + Error::Common(c) => c.http_status_code(), + Error::NoSuchKey => StatusCode::NOT_FOUND, + Error::NotAcceptable(_) => StatusCode::NOT_ACCEPTABLE, + Error::AuthorizationHeaderMalformed(_) + | Error::InvalidBase64(_) + | Error::InvalidHeader(_) + | Error::InvalidUtf8Str(_) => StatusCode::BAD_REQUEST, + } + } + + fn add_http_headers(&self, header_map: &mut HeaderMap<HeaderValue>) { + use hyper::header; + header_map.append(header::CONTENT_TYPE, "application/json".parse().unwrap()); + } + + fn http_body(&self, garage_region: &str, path: &str) -> Body { + let error = CustomApiErrorBody { + code: self.code().to_string(), + message: format!("{}", self), + path: path.to_string(), + region: garage_region.to_string(), + }; + Body::from(serde_json::to_string_pretty(&error).unwrap_or_else(|_| { + r#" +{ + "code": "InternalError", + "message": "JSON encoding of error failed" +} + "# + .into() + })) + } +} diff --git a/src/api/k2v/index.rs b/src/api/k2v/index.rs new file mode 100644 index 00000000..210950bf --- /dev/null +++ b/src/api/k2v/index.rs @@ -0,0 +1,100 @@ +use std::sync::Arc; + +use hyper::{Body, Response, StatusCode}; +use serde::Serialize; + +use garage_util::data::*; +use garage_util::error::Error as GarageError; + +use garage_rpc::ring::Ring; +use garage_table::util::*; + +use garage_model::garage::Garage; +use garage_model::k2v::item_table::{BYTES, CONFLICTS, ENTRIES, VALUES}; + +use crate::k2v::error::*; +use crate::k2v::range::read_range; + +pub async fn handle_read_index( + garage: Arc<Garage>, + bucket_id: Uuid, + prefix: Option<String>, + start: Option<String>, + end: Option<String>, + limit: Option<u64>, + reverse: Option<bool>, +) -> Result<Response<Body>, Error> { + let reverse = reverse.unwrap_or(false); + + let ring: Arc<Ring> = garage.system.ring.borrow().clone(); + + let (partition_keys, more, next_start) = read_range( + &garage.k2v.counter_table.table, + &bucket_id, + &prefix, + &start, + &end, + limit, + Some((DeletedFilter::NotDeleted, ring.layout.node_id_vec.clone())), + EnumerationOrder::from_reverse(reverse), + ) + .await?; + + let s_entries = ENTRIES.to_string(); + let s_conflicts = CONFLICTS.to_string(); + let s_values = VALUES.to_string(); + let s_bytes = BYTES.to_string(); + + let resp = ReadIndexResponse { + prefix, + start, + end, + limit, + reverse, + partition_keys: partition_keys + .into_iter() + .map(|part| { + let vals = part.filtered_values(&ring); + ReadIndexResponseEntry { + pk: part.sk, + entries: *vals.get(&s_entries).unwrap_or(&0), + conflicts: *vals.get(&s_conflicts).unwrap_or(&0), + values: *vals.get(&s_values).unwrap_or(&0), + bytes: *vals.get(&s_bytes).unwrap_or(&0), + } + }) + .collect::<Vec<_>>(), + more, + next_start, + }; + + let resp_json = serde_json::to_string_pretty(&resp).map_err(GarageError::from)?; + Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::from(resp_json))?) +} + +#[derive(Serialize)] +struct ReadIndexResponse { + prefix: Option<String>, + start: Option<String>, + end: Option<String>, + limit: Option<u64>, + reverse: bool, + + #[serde(rename = "partitionKeys")] + partition_keys: Vec<ReadIndexResponseEntry>, + + more: bool, + #[serde(rename = "nextStart")] + next_start: Option<String>, +} + +#[derive(Serialize)] +struct ReadIndexResponseEntry { + pk: String, + entries: i64, + conflicts: i64, + values: i64, + bytes: i64, +} diff --git a/src/api/k2v/item.rs b/src/api/k2v/item.rs new file mode 100644 index 00000000..836d386f --- /dev/null +++ b/src/api/k2v/item.rs @@ -0,0 +1,230 @@ +use std::sync::Arc; + +use http::header; + +use hyper::{Body, Request, Response, StatusCode}; + +use garage_util::data::*; + +use garage_model::garage::Garage; +use garage_model::k2v::causality::*; +use garage_model::k2v::item_table::*; + +use crate::k2v::error::*; + +pub const X_GARAGE_CAUSALITY_TOKEN: &str = "X-Garage-Causality-Token"; + +pub enum ReturnFormat { + Json, + Binary, + Either, +} + +impl ReturnFormat { + pub fn from(req: &Request<Body>) -> Result<Self, Error> { + let accept = match req.headers().get(header::ACCEPT) { + Some(a) => a.to_str()?, + None => return Ok(Self::Json), + }; + + let accept = accept.split(',').map(|s| s.trim()).collect::<Vec<_>>(); + let accept_json = accept.contains(&"application/json") || accept.contains(&"*/*"); + let accept_binary = accept.contains(&"application/octet-stream") || accept.contains(&"*/*"); + + match (accept_json, accept_binary) { + (true, true) => Ok(Self::Either), + (true, false) => Ok(Self::Json), + (false, true) => Ok(Self::Binary), + (false, false) => Err(Error::NotAcceptable("Invalid Accept: header value, must contain either application/json or application/octet-stream (or both)".into())), + } + } + + pub fn make_response(&self, item: &K2VItem) -> Result<Response<Body>, Error> { + let vals = item.values(); + + if vals.is_empty() { + return Err(Error::NoSuchKey); + } + + let ct = item.causal_context().serialize(); + match self { + Self::Binary if vals.len() > 1 => Ok(Response::builder() + .header(X_GARAGE_CAUSALITY_TOKEN, ct) + .status(StatusCode::CONFLICT) + .body(Body::empty())?), + Self::Binary => { + assert!(vals.len() == 1); + Self::make_binary_response(ct, vals[0]) + } + Self::Either if vals.len() == 1 => Self::make_binary_response(ct, vals[0]), + _ => Self::make_json_response(ct, &vals[..]), + } + } + + fn make_binary_response(ct: String, v: &DvvsValue) -> Result<Response<Body>, Error> { + match v { + DvvsValue::Deleted => Ok(Response::builder() + .header(X_GARAGE_CAUSALITY_TOKEN, ct) + .header(header::CONTENT_TYPE, "application/octet-stream") + .status(StatusCode::NO_CONTENT) + .body(Body::empty())?), + DvvsValue::Value(v) => Ok(Response::builder() + .header(X_GARAGE_CAUSALITY_TOKEN, ct) + .header(header::CONTENT_TYPE, "application/octet-stream") + .status(StatusCode::OK) + .body(Body::from(v.to_vec()))?), + } + } + + fn make_json_response(ct: String, v: &[&DvvsValue]) -> Result<Response<Body>, Error> { + let items = v + .iter() + .map(|v| match v { + DvvsValue::Deleted => serde_json::Value::Null, + DvvsValue::Value(v) => serde_json::Value::String(base64::encode(v)), + }) + .collect::<Vec<_>>(); + let json_body = + serde_json::to_string_pretty(&items).ok_or_internal_error("JSON encoding error")?; + Ok(Response::builder() + .header(X_GARAGE_CAUSALITY_TOKEN, ct) + .header(header::CONTENT_TYPE, "application/json") + .status(StatusCode::OK) + .body(Body::from(json_body))?) + } +} + +/// Handle ReadItem request +#[allow(clippy::ptr_arg)] +pub async fn handle_read_item( + garage: Arc<Garage>, + req: &Request<Body>, + bucket_id: Uuid, + partition_key: &str, + sort_key: &String, +) -> Result<Response<Body>, Error> { + let format = ReturnFormat::from(req)?; + + let item = garage + .k2v + .item_table + .get( + &K2VItemPartition { + bucket_id, + partition_key: partition_key.to_string(), + }, + sort_key, + ) + .await? + .ok_or(Error::NoSuchKey)?; + + format.make_response(&item) +} + +pub async fn handle_insert_item( + garage: Arc<Garage>, + req: Request<Body>, + bucket_id: Uuid, + partition_key: &str, + sort_key: &str, +) -> Result<Response<Body>, Error> { + let causal_context = req + .headers() + .get(X_GARAGE_CAUSALITY_TOKEN) + .map(|s| s.to_str()) + .transpose()? + .map(CausalContext::parse) + .transpose() + .ok_or_bad_request("Invalid causality token")?; + + let body = hyper::body::to_bytes(req.into_body()).await?; + let value = DvvsValue::Value(body.to_vec()); + + garage + .k2v + .rpc + .insert( + bucket_id, + partition_key.to_string(), + sort_key.to_string(), + causal_context, + value, + ) + .await?; + + Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty())?) +} + +pub async fn handle_delete_item( + garage: Arc<Garage>, + req: Request<Body>, + bucket_id: Uuid, + partition_key: &str, + sort_key: &str, +) -> Result<Response<Body>, Error> { + let causal_context = req + .headers() + .get(X_GARAGE_CAUSALITY_TOKEN) + .map(|s| s.to_str()) + .transpose()? + .map(CausalContext::parse) + .transpose() + .ok_or_bad_request("Invalid causality token")?; + + let value = DvvsValue::Deleted; + + garage + .k2v + .rpc + .insert( + bucket_id, + partition_key.to_string(), + sort_key.to_string(), + causal_context, + value, + ) + .await?; + + Ok(Response::builder() + .status(StatusCode::NO_CONTENT) + .body(Body::empty())?) +} + +/// Handle ReadItem request +#[allow(clippy::ptr_arg)] +pub async fn handle_poll_item( + garage: Arc<Garage>, + req: &Request<Body>, + bucket_id: Uuid, + partition_key: String, + sort_key: String, + causality_token: String, + timeout_secs: Option<u64>, +) -> Result<Response<Body>, Error> { + let format = ReturnFormat::from(req)?; + + let causal_context = + CausalContext::parse(&causality_token).ok_or_bad_request("Invalid causality token")?; + + let item = garage + .k2v + .rpc + .poll( + bucket_id, + partition_key, + sort_key, + causal_context, + timeout_secs.unwrap_or(300) * 1000, + ) + .await?; + + if let Some(item) = item { + format.make_response(&item) + } else { + Ok(Response::builder() + .status(StatusCode::NOT_MODIFIED) + .body(Body::empty())?) + } +} diff --git a/src/api/k2v/mod.rs b/src/api/k2v/mod.rs new file mode 100644 index 00000000..b6a8c5cf --- /dev/null +++ b/src/api/k2v/mod.rs @@ -0,0 +1,9 @@ +pub mod api_server; +mod error; +mod router; + +mod batch; +mod index; +mod item; + +mod range; diff --git a/src/api/k2v/range.rs b/src/api/k2v/range.rs new file mode 100644 index 00000000..bb9d3be5 --- /dev/null +++ b/src/api/k2v/range.rs @@ -0,0 +1,100 @@ +//! Utility module for retrieving ranges of items in Garage tables +//! Implements parameters (prefix, start, end, limit) as specified +//! for endpoints ReadIndex, ReadBatch and DeleteBatch + +use std::sync::Arc; + +use garage_table::replication::TableShardedReplication; +use garage_table::*; + +use crate::helpers::key_after_prefix; +use crate::k2v::error::*; + +/// Read range in a Garage table. +/// Returns (entries, more?, nextStart) +#[allow(clippy::too_many_arguments)] +pub(crate) async fn read_range<F>( + table: &Arc<Table<F, TableShardedReplication>>, + partition_key: &F::P, + prefix: &Option<String>, + start: &Option<String>, + end: &Option<String>, + limit: Option<u64>, + filter: Option<F::Filter>, + enumeration_order: EnumerationOrder, +) -> Result<(Vec<F::E>, bool, Option<String>), Error> +where + F: TableSchema<S = String> + 'static, +{ + let (mut start, mut start_ignore) = match (prefix, start) { + (None, None) => (None, false), + (None, Some(s)) => (Some(s.clone()), false), + (Some(p), Some(s)) => { + if !s.starts_with(p) { + return Err(Error::bad_request(format!( + "Start key '{}' does not start with prefix '{}'", + s, p + ))); + } + (Some(s.clone()), false) + } + (Some(p), None) if enumeration_order == EnumerationOrder::Reverse => { + let start = key_after_prefix(p) + .ok_or_internal_error("Sorry, can't list this prefix in reverse order")?; + (Some(start), true) + } + (Some(p), None) => (Some(p.clone()), false), + }; + + let mut entries = vec![]; + loop { + let n_get = std::cmp::min( + 1000, + limit.map(|x| x as usize).unwrap_or(usize::MAX - 10) - entries.len() + 2, + ); + let get_ret = table + .get_range( + partition_key, + start.clone(), + filter.clone(), + n_get, + enumeration_order, + ) + .await?; + + let get_ret_len = get_ret.len(); + + for entry in get_ret { + if start_ignore && Some(entry.sort_key()) == start.as_ref() { + continue; + } + if let Some(p) = prefix { + if !entry.sort_key().starts_with(p) { + return Ok((entries, false, None)); + } + } + if let Some(e) = end { + let is_finished = match enumeration_order { + EnumerationOrder::Forward => entry.sort_key() >= e, + EnumerationOrder::Reverse => entry.sort_key() <= e, + }; + if is_finished { + return Ok((entries, false, None)); + } + } + if let Some(l) = limit { + if entries.len() >= l as usize { + return Ok((entries, true, Some(entry.sort_key().clone()))); + } + } + entries.push(entry); + } + + if get_ret_len < n_get { + return Ok((entries, false, None)); + } + + start = Some(entries.last().unwrap().sort_key().clone()); + start_ignore = true; + } +} diff --git a/src/api/k2v/router.rs b/src/api/k2v/router.rs new file mode 100644 index 00000000..50e6965b --- /dev/null +++ b/src/api/k2v/router.rs @@ -0,0 +1,252 @@ +use crate::k2v::error::*; + +use std::borrow::Cow; + +use hyper::{Method, Request}; + +use crate::helpers::Authorization; +use crate::router_macros::{generateQueryParameters, router_match}; + +router_match! {@func + + +/// List of all K2V API endpoints. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Endpoint { + DeleteBatch { + }, + DeleteItem { + partition_key: String, + sort_key: String, + }, + InsertBatch { + }, + InsertItem { + partition_key: String, + sort_key: String, + }, + Options, + PollItem { + partition_key: String, + sort_key: String, + causality_token: String, + timeout: Option<u64>, + }, + ReadBatch { + }, + ReadIndex { + prefix: Option<String>, + start: Option<String>, + end: Option<String>, + limit: Option<u64>, + reverse: Option<bool>, + }, + ReadItem { + partition_key: String, + sort_key: String, + }, +}} + +impl Endpoint { + /// Determine which S3 endpoint a request is for using the request, and a bucket which was + /// possibly extracted from the Host header. + /// Returns Self plus bucket name, if endpoint is not Endpoint::ListBuckets + pub fn from_request<T>(req: &Request<T>) -> Result<(Self, String), Error> { + let uri = req.uri(); + let path = uri.path().trim_start_matches('/'); + let query = uri.query(); + + let (bucket, partition_key) = path + .split_once('/') + .map(|(b, p)| (b.to_owned(), p.trim_start_matches('/'))) + .unwrap_or((path.to_owned(), "")); + + if bucket.is_empty() { + return Err(Error::bad_request("Missing bucket name")); + } + + if *req.method() == Method::OPTIONS { + return Ok((Self::Options, bucket)); + } + + let partition_key = percent_encoding::percent_decode_str(partition_key) + .decode_utf8()? + .into_owned(); + + let mut query = QueryParameters::from_query(query.unwrap_or_default())?; + + let method_search = Method::from_bytes(b"SEARCH").unwrap(); + let res = match *req.method() { + Method::GET => Self::from_get(partition_key, &mut query)?, + //&Method::HEAD => Self::from_head(partition_key, &mut query)?, + Method::POST => Self::from_post(partition_key, &mut query)?, + Method::PUT => Self::from_put(partition_key, &mut query)?, + Method::DELETE => Self::from_delete(partition_key, &mut query)?, + _ if req.method() == method_search => Self::from_search(partition_key, &mut query)?, + _ => return Err(Error::bad_request("Unknown method")), + }; + + if let Some(message) = query.nonempty_message() { + debug!("Unused query parameter: {}", message) + } + Ok((res, bucket)) + } + + /// Determine which endpoint a request is for, knowing it is a GET. + fn from_get(partition_key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { + router_match! { + @gen_parser + (query.keyword.take().unwrap_or_default().as_ref(), partition_key, query, None), + key: [ + EMPTY if causality_token => PollItem (query::sort_key, query::causality_token, opt_parse::timeout), + EMPTY => ReadItem (query::sort_key), + ], + no_key: [ + EMPTY => ReadIndex (query_opt::prefix, query_opt::start, query_opt::end, opt_parse::limit, opt_parse::reverse), + ] + } + } + + /// Determine which endpoint a request is for, knowing it is a SEARCH. + fn from_search(partition_key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { + router_match! { + @gen_parser + (query.keyword.take().unwrap_or_default().as_ref(), partition_key, query, None), + key: [ + ], + no_key: [ + EMPTY => ReadBatch, + ] + } + } + + /* + /// Determine which endpoint a request is for, knowing it is a HEAD. + fn from_head(partition_key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { + router_match! { + @gen_parser + (query.keyword.take().unwrap_or_default().as_ref(), partition_key, query, None), + key: [ + EMPTY => HeadObject(opt_parse::part_number, query_opt::version_id), + ], + no_key: [ + EMPTY => HeadBucket, + ] + } + } + */ + + /// Determine which endpoint a request is for, knowing it is a POST. + fn from_post(partition_key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { + router_match! { + @gen_parser + (query.keyword.take().unwrap_or_default().as_ref(), partition_key, query, None), + key: [ + ], + no_key: [ + EMPTY => InsertBatch, + DELETE => DeleteBatch, + SEARCH => ReadBatch, + ] + } + } + + /// Determine which endpoint a request is for, knowing it is a PUT. + fn from_put(partition_key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { + router_match! { + @gen_parser + (query.keyword.take().unwrap_or_default().as_ref(), partition_key, query, None), + key: [ + EMPTY => InsertItem (query::sort_key), + + ], + no_key: [ + ] + } + } + + /// Determine which endpoint a request is for, knowing it is a DELETE. + fn from_delete(partition_key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { + router_match! { + @gen_parser + (query.keyword.take().unwrap_or_default().as_ref(), partition_key, query, None), + key: [ + EMPTY => DeleteItem (query::sort_key), + ], + no_key: [ + ] + } + } + + /// Get the partition key the request target. Returns None for requests which don't use a partition key. + #[allow(dead_code)] + pub fn get_partition_key(&self) -> Option<&str> { + router_match! { + @extract + self, + partition_key, + [ + DeleteItem, + InsertItem, + PollItem, + ReadItem, + ] + } + } + + /// Get the sort key the request target. Returns None for requests which don't use a sort key. + #[allow(dead_code)] + pub fn get_sort_key(&self) -> Option<&str> { + router_match! { + @extract + self, + sort_key, + [ + DeleteItem, + InsertItem, + PollItem, + ReadItem, + ] + } + } + + /// Get the kind of authorization which is required to perform the operation. + pub fn authorization_type(&self) -> Authorization { + let readonly = router_match! { + @match + self, + [ + PollItem, + ReadBatch, + ReadIndex, + ReadItem, + ] + }; + if readonly { + Authorization::Read + } else { + Authorization::Write + } + } +} + +// parameter name => struct field +generateQueryParameters! { + "prefix" => prefix, + "start" => start, + "causality_token" => causality_token, + "end" => end, + "limit" => limit, + "reverse" => reverse, + "sort_key" => sort_key, + "timeout" => timeout +} + +mod keywords { + //! This module contain all query parameters with no associated value + //! used to differentiate endpoints. + pub const EMPTY: &str = ""; + + pub const DELETE: &str = "delete"; + pub const SEARCH: &str = "search"; +} diff --git a/src/api/lib.rs b/src/api/lib.rs index de60ec53..370dfd7a 100644 --- a/src/api/lib.rs +++ b/src/api/lib.rs @@ -2,26 +2,16 @@ #[macro_use] extern crate tracing; -pub mod error; -pub use error::Error; +pub mod common_error; mod encoding; - -mod api_server; -pub use api_server::run_api_server; - +pub mod generic_server; +pub mod helpers; +mod router_macros; /// This mode is public only to help testing. Don't expect stability here pub mod signature; -pub mod helpers; -mod s3_bucket; -mod s3_copy; -pub mod s3_cors; -mod s3_delete; -pub mod s3_get; -mod s3_list; -mod s3_post_object; -mod s3_put; -mod s3_router; -mod s3_website; -mod s3_xml; +pub mod admin; +#[cfg(feature = "k2v")] +pub mod k2v; +pub mod s3; diff --git a/src/api/router_macros.rs b/src/api/router_macros.rs new file mode 100644 index 00000000..4c593300 --- /dev/null +++ b/src/api/router_macros.rs @@ -0,0 +1,213 @@ +/// This macro is used to generate very repetitive match {} blocks in this module +/// It is _not_ made to be used anywhere else +macro_rules! router_match { + (@match $enum:expr , [ $($endpoint:ident,)* ]) => {{ + // usage: router_match {@match my_enum, [ VariantWithField1, VariantWithField2 ..] } + // returns true if the variant was one of the listed variants, false otherwise. + use Endpoint::*; + match $enum { + $( + $endpoint { .. } => true, + )* + _ => false + } + }}; + (@extract $enum:expr , $param:ident, [ $($endpoint:ident,)* ]) => {{ + // usage: router_match {@extract my_enum, field_name, [ VariantWithField1, VariantWithField2 ..] } + // returns Some(field_value), or None if the variant was not one of the listed variants. + use Endpoint::*; + match $enum { + $( + $endpoint {$param, ..} => Some($param), + )* + _ => None + } + }}; + (@gen_path_parser ($method:expr, $reqpath:expr, $query:expr) + [ + $($meth:ident $path:pat $(if $required:ident)? => $api:ident $(($($conv:ident :: $param:ident),*))?,)* + ]) => {{ + { + use Endpoint::*; + match ($method, $reqpath) { + $( + (&Method::$meth, $path) if true $(&& $query.$required.is_some())? => $api { + $($( + $param: router_match!(@@parse_param $query, $conv, $param), + )*)? + }, + )* + (m, p) => { + return Err(Error::bad_request(format!( + "Unknown API endpoint: {} {}", + m, p + ))) + } + } + } + }}; + (@gen_parser ($keyword:expr, $key:ident, $query:expr, $header:expr), + key: [$($kw_k:ident $(if $required_k:ident)? $(header $header_k:expr)? => $api_k:ident $(($($conv_k:ident :: $param_k:ident),*))?,)*], + no_key: [$($kw_nk:ident $(if $required_nk:ident)? $(if_header $header_nk:expr)? => $api_nk:ident $(($($conv_nk:ident :: $param_nk:ident),*))?,)*]) => {{ + // usage: router_match {@gen_parser (keyword, key, query, header), + // key: [ + // SOME_KEYWORD => VariantWithKey, + // ... + // ], + // no_key: [ + // SOME_KEYWORD => VariantWithoutKey, + // ... + // ] + // } + // See in from_{method} for more detailed usage. + use Endpoint::*; + use keywords::*; + match ($keyword, !$key.is_empty()){ + $( + ($kw_k, true) if true $(&& $query.$required_k.is_some())? $(&& $header.contains_key($header_k))? => Ok($api_k { + $key, + $($( + $param_k: router_match!(@@parse_param $query, $conv_k, $param_k), + )*)? + }), + )* + $( + ($kw_nk, false) $(if $query.$required_nk.is_some())? $(if $header.contains($header_nk))? => Ok($api_nk { + $($( + $param_nk: router_match!(@@parse_param $query, $conv_nk, $param_nk), + )*)? + }), + )* + (kw, _) => Err(Error::bad_request(format!("Invalid endpoint: {}", kw))) + } + }}; + + (@@parse_param $query:expr, query_opt, $param:ident) => {{ + // extract optional query parameter + $query.$param.take().map(|param| param.into_owned()) + }}; + (@@parse_param $query:expr, query, $param:ident) => {{ + // extract mendatory query parameter + $query.$param.take().ok_or_bad_request("Missing argument for endpoint")?.into_owned() + }}; + (@@parse_param $query:expr, opt_parse, $param:ident) => {{ + // extract and parse optional query parameter + // missing parameter is file, however parse error is reported as an error + $query.$param + .take() + .map(|param| param.parse()) + .transpose() + .map_err(|_| Error::bad_request("Failed to parse query parameter"))? + }}; + (@@parse_param $query:expr, parse, $param:ident) => {{ + // extract and parse mandatory query parameter + // both missing and un-parseable parameters are reported as errors + $query.$param.take().ok_or_bad_request("Missing argument for endpoint")? + .parse() + .map_err(|_| Error::bad_request("Failed to parse query parameter"))? + }}; + (@func + $(#[$doc:meta])* + pub enum Endpoint { + $( + $(#[$outer:meta])* + $variant:ident $({ + $($name:ident: $ty:ty,)* + })?, + )* + }) => { + $(#[$doc])* + pub enum Endpoint { + $( + $(#[$outer])* + $variant $({ + $($name: $ty, )* + })?, + )* + } + impl Endpoint { + pub fn name(&self) -> &'static str { + match self { + $(Endpoint::$variant $({ $($name: _,)* .. })? => stringify!($variant),)* + } + } + } + }; + (@if ($($cond:tt)+) then ($($then:tt)*) else ($($else:tt)*)) => { + $($then)* + }; + (@if () then ($($then:tt)*) else ($($else:tt)*)) => { + $($else)* + }; +} + +/// This macro is used to generate part of the code in this module. It must be called only one, and +/// is useless outside of this module. +macro_rules! generateQueryParameters { + ( $($rest:expr => $name:ident),* ) => { + /// Struct containing all query parameters used in endpoints. Think of it as an HashMap, + /// but with keys statically known. + #[derive(Debug, Default)] + struct QueryParameters<'a> { + keyword: Option<Cow<'a, str>>, + $( + $name: Option<Cow<'a, str>>, + )* + } + + impl<'a> QueryParameters<'a> { + /// Build this struct from the query part of an URI. + fn from_query(query: &'a str) -> Result<Self, Error> { + let mut res: Self = Default::default(); + for (k, v) in url::form_urlencoded::parse(query.as_bytes()) { + let repeated = match k.as_ref() { + $( + $rest => if !v.is_empty() { + res.$name.replace(v).is_some() + } else { + false + }, + )* + _ => { + if k.starts_with("response-") || k.starts_with("X-Amz-") { + false + } else if v.as_ref().is_empty() { + if res.keyword.replace(k).is_some() { + return Err(Error::bad_request("Multiple keywords")); + } + continue; + } else { + debug!("Received an unknown query parameter: '{}'", k); + false + } + } + }; + if repeated { + return Err(Error::bad_request(format!( + "Query parameter repeated: '{}'", + k + ))); + } + } + Ok(res) + } + + /// Get an error message in case not all parameters where used when extracting them to + /// build an Enpoint variant + fn nonempty_message(&self) -> Option<&str> { + if self.keyword.is_some() { + Some("Keyword not used") + } $( + else if self.$name.is_some() { + Some(concat!("'", $rest, "'")) + } + )* else { + None + } + } + } + } +} + +pub(crate) use generateQueryParameters; +pub(crate) use router_match; diff --git a/src/api/s3/api_server.rs b/src/api/s3/api_server.rs new file mode 100644 index 00000000..27837297 --- /dev/null +++ b/src/api/s3/api_server.rs @@ -0,0 +1,390 @@ +use std::net::SocketAddr; +use std::sync::Arc; + +use async_trait::async_trait; + +use futures::future::Future; +use hyper::header; +use hyper::{Body, Request, Response}; + +use opentelemetry::{trace::SpanRef, KeyValue}; + +use garage_util::error::Error as GarageError; + +use garage_model::garage::Garage; +use garage_model::key_table::Key; + +use crate::generic_server::*; +use crate::s3::error::*; + +use crate::signature::payload::check_payload_signature; +use crate::signature::streaming::*; + +use crate::helpers::*; +use crate::s3::bucket::*; +use crate::s3::copy::*; +use crate::s3::cors::*; +use crate::s3::delete::*; +use crate::s3::get::*; +use crate::s3::list::*; +use crate::s3::post_object::handle_post_object; +use crate::s3::put::*; +use crate::s3::router::Endpoint; +use crate::s3::website::*; + +pub struct S3ApiServer { + garage: Arc<Garage>, +} + +pub(crate) struct S3ApiEndpoint { + bucket_name: Option<String>, + endpoint: Endpoint, +} + +impl S3ApiServer { + pub async fn run( + garage: Arc<Garage>, + addr: SocketAddr, + s3_region: String, + shutdown_signal: impl Future<Output = ()>, + ) -> Result<(), GarageError> { + ApiServer::new(s3_region, S3ApiServer { garage }) + .run_server(addr, shutdown_signal) + .await + } + + async fn handle_request_without_bucket( + &self, + _req: Request<Body>, + api_key: Key, + endpoint: Endpoint, + ) -> Result<Response<Body>, Error> { + match endpoint { + Endpoint::ListBuckets => handle_list_buckets(&self.garage, &api_key).await, + endpoint => Err(Error::NotImplemented(endpoint.name().to_owned())), + } + } +} + +#[async_trait] +impl ApiHandler for S3ApiServer { + const API_NAME: &'static str = "s3"; + const API_NAME_DISPLAY: &'static str = "S3"; + + type Endpoint = S3ApiEndpoint; + type Error = Error; + + fn parse_endpoint(&self, req: &Request<Body>) -> Result<S3ApiEndpoint, Error> { + let authority = req + .headers() + .get(header::HOST) + .ok_or_bad_request("Host header required")? + .to_str()?; + + let host = authority_to_host(authority)?; + + let bucket_name = self + .garage + .config + .s3_api + .root_domain + .as_ref() + .and_then(|root_domain| host_to_bucket(&host, root_domain)); + + let (endpoint, bucket_name) = + Endpoint::from_request(req, bucket_name.map(ToOwned::to_owned))?; + + Ok(S3ApiEndpoint { + bucket_name, + endpoint, + }) + } + + async fn handle( + &self, + req: Request<Body>, + endpoint: S3ApiEndpoint, + ) -> Result<Response<Body>, Error> { + let S3ApiEndpoint { + bucket_name, + endpoint, + } = endpoint; + let garage = self.garage.clone(); + + // Some endpoints are processed early, before we even check for an API key + if let Endpoint::PostObject = endpoint { + return handle_post_object(garage, req, bucket_name.unwrap()).await; + } + if let Endpoint::Options = endpoint { + return handle_options_s3api(garage, &req, bucket_name).await; + } + + let (api_key, mut content_sha256) = check_payload_signature(&garage, "s3", &req).await?; + let api_key = api_key + .ok_or_else(|| Error::forbidden("Garage does not support anonymous access yet"))?; + + let req = parse_streaming_body( + &api_key, + req, + &mut content_sha256, + &garage.config.s3_api.s3_region, + "s3", + )?; + + let bucket_name = match bucket_name { + None => { + return self + .handle_request_without_bucket(req, api_key, endpoint) + .await + } + Some(bucket) => bucket.to_string(), + }; + + // Special code path for CreateBucket API endpoint + if let Endpoint::CreateBucket {} = endpoint { + return handle_create_bucket(&garage, req, content_sha256, api_key, bucket_name).await; + } + + let bucket_id = garage + .bucket_helper() + .resolve_bucket(&bucket_name, &api_key) + .await?; + let bucket = garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + + let allowed = match endpoint.authorization_type() { + Authorization::Read => api_key.allow_read(&bucket_id), + Authorization::Write => api_key.allow_write(&bucket_id), + Authorization::Owner => api_key.allow_owner(&bucket_id), + _ => unreachable!(), + }; + + if !allowed { + return Err(Error::forbidden("Operation is not allowed for this key.")); + } + + let matching_cors_rule = find_matching_cors_rule(&bucket, &req)?; + + let resp = match endpoint { + Endpoint::HeadObject { + key, part_number, .. + } => handle_head(garage, &req, bucket_id, &key, part_number).await, + Endpoint::GetObject { + key, part_number, .. + } => handle_get(garage, &req, bucket_id, &key, part_number).await, + Endpoint::UploadPart { + key, + part_number, + upload_id, + } => { + handle_put_part( + garage, + req, + bucket_id, + &key, + part_number, + &upload_id, + content_sha256, + ) + .await + } + Endpoint::CopyObject { key } => { + handle_copy(garage, &api_key, &req, bucket_id, &key).await + } + Endpoint::UploadPartCopy { + key, + part_number, + upload_id, + } => { + handle_upload_part_copy( + garage, + &api_key, + &req, + bucket_id, + &key, + part_number, + &upload_id, + ) + .await + } + Endpoint::PutObject { key } => { + handle_put(garage, req, &bucket, &key, content_sha256).await + } + Endpoint::AbortMultipartUpload { key, upload_id } => { + handle_abort_multipart_upload(garage, bucket_id, &key, &upload_id).await + } + Endpoint::DeleteObject { key, .. } => handle_delete(garage, bucket_id, &key).await, + Endpoint::CreateMultipartUpload { key } => { + handle_create_multipart_upload(garage, &req, &bucket_name, bucket_id, &key).await + } + Endpoint::CompleteMultipartUpload { key, upload_id } => { + handle_complete_multipart_upload( + garage, + req, + &bucket_name, + &bucket, + &key, + &upload_id, + content_sha256, + ) + .await + } + Endpoint::CreateBucket {} => unreachable!(), + Endpoint::HeadBucket {} => { + let empty_body: Body = Body::from(vec![]); + let response = Response::builder().body(empty_body).unwrap(); + Ok(response) + } + Endpoint::DeleteBucket {} => { + handle_delete_bucket(&garage, bucket_id, bucket_name, api_key).await + } + Endpoint::GetBucketLocation {} => handle_get_bucket_location(garage), + Endpoint::GetBucketVersioning {} => handle_get_bucket_versioning(), + Endpoint::ListObjects { + delimiter, + encoding_type, + marker, + max_keys, + prefix, + } => { + handle_list( + garage, + &ListObjectsQuery { + common: ListQueryCommon { + bucket_name, + bucket_id, + delimiter: delimiter.map(|d| d.to_string()), + page_size: max_keys.map(|p| p.clamp(1, 1000)).unwrap_or(1000), + prefix: prefix.unwrap_or_default(), + urlencode_resp: encoding_type.map(|e| e == "url").unwrap_or(false), + }, + is_v2: false, + marker, + continuation_token: None, + start_after: None, + }, + ) + .await + } + Endpoint::ListObjectsV2 { + delimiter, + encoding_type, + max_keys, + prefix, + continuation_token, + start_after, + list_type, + .. + } => { + if list_type == "2" { + handle_list( + garage, + &ListObjectsQuery { + common: ListQueryCommon { + bucket_name, + bucket_id, + delimiter: delimiter.map(|d| d.to_string()), + page_size: max_keys.map(|p| p.clamp(1, 1000)).unwrap_or(1000), + urlencode_resp: encoding_type.map(|e| e == "url").unwrap_or(false), + prefix: prefix.unwrap_or_default(), + }, + is_v2: true, + marker: None, + continuation_token, + start_after, + }, + ) + .await + } else { + Err(Error::bad_request(format!( + "Invalid endpoint: list-type={}", + list_type + ))) + } + } + Endpoint::ListMultipartUploads { + delimiter, + encoding_type, + key_marker, + max_uploads, + prefix, + upload_id_marker, + } => { + handle_list_multipart_upload( + garage, + &ListMultipartUploadsQuery { + common: ListQueryCommon { + bucket_name, + bucket_id, + delimiter: delimiter.map(|d| d.to_string()), + page_size: max_uploads.map(|p| p.clamp(1, 1000)).unwrap_or(1000), + prefix: prefix.unwrap_or_default(), + urlencode_resp: encoding_type.map(|e| e == "url").unwrap_or(false), + }, + key_marker, + upload_id_marker, + }, + ) + .await + } + Endpoint::ListParts { + key, + max_parts, + part_number_marker, + upload_id, + } => { + handle_list_parts( + garage, + &ListPartsQuery { + bucket_name, + bucket_id, + key, + upload_id, + part_number_marker: part_number_marker.map(|p| p.clamp(1, 10000)), + max_parts: max_parts.map(|p| p.clamp(1, 1000)).unwrap_or(1000), + }, + ) + .await + } + Endpoint::DeleteObjects {} => { + handle_delete_objects(garage, bucket_id, req, content_sha256).await + } + Endpoint::GetBucketWebsite {} => handle_get_website(&bucket).await, + Endpoint::PutBucketWebsite {} => { + handle_put_website(garage, bucket_id, req, content_sha256).await + } + Endpoint::DeleteBucketWebsite {} => handle_delete_website(garage, bucket_id).await, + Endpoint::GetBucketCors {} => handle_get_cors(&bucket).await, + Endpoint::PutBucketCors {} => { + handle_put_cors(garage, bucket_id, req, content_sha256).await + } + Endpoint::DeleteBucketCors {} => handle_delete_cors(garage, bucket_id).await, + endpoint => Err(Error::NotImplemented(endpoint.name().to_owned())), + }; + + // If request was a success and we have a CORS rule that applies to it, + // add the corresponding CORS headers to the response + let mut resp_ok = resp?; + if let Some(rule) = matching_cors_rule { + add_cors_headers(&mut resp_ok, rule) + .ok_or_internal_error("Invalid bucket CORS configuration")?; + } + + Ok(resp_ok) + } +} + +impl ApiEndpoint for S3ApiEndpoint { + fn name(&self) -> &'static str { + self.endpoint.name() + } + + fn add_span_attributes(&self, span: SpanRef<'_>) { + span.set_attribute(KeyValue::new( + "bucket", + self.bucket_name.clone().unwrap_or_default(), + )); + } +} diff --git a/src/api/s3_bucket.rs b/src/api/s3/bucket.rs index 8a5407d3..3ac6a6ec 100644 --- a/src/api/s3_bucket.rs +++ b/src/api/s3/bucket.rs @@ -7,15 +7,15 @@ use garage_model::bucket_alias_table::*; use garage_model::bucket_table::Bucket; use garage_model::garage::Garage; use garage_model::key_table::Key; -use garage_model::object_table::ObjectFilter; use garage_model::permission::BucketKeyPerm; use garage_table::util::*; use garage_util::crdt::*; use garage_util::data::*; use garage_util::time::*; -use crate::error::*; -use crate::s3_xml; +use crate::common_error::CommonError; +use crate::s3::error::*; +use crate::s3::xml as s3_xml; use crate::signature::verify_signed_content; pub fn handle_get_bucket_location(garage: Arc<Garage>) -> Result<Response<Body>, Error> { @@ -130,7 +130,7 @@ pub async fn handle_create_bucket( if let Some(location_constraint) = cmd { if location_constraint != garage.config.s3_api.s3_region { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "Cannot satisfy location constraint `{}`: buckets can only be created in region `{}`", location_constraint, garage.config.s3_api.s3_region @@ -158,12 +158,12 @@ pub async fn handle_create_bucket( // otherwise return a forbidden error. let kp = api_key.bucket_permissions(&bucket_id); if !(kp.allow_write || kp.allow_owner) { - return Err(Error::BucketAlreadyExists); + return Err(CommonError::BucketAlreadyExists.into()); } } else { // Create the bucket! if !is_valid_bucket_name(&bucket_name) { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "{}: {}", bucket_name, INVALID_BUCKET_NAME_MESSAGE ))); @@ -228,12 +228,8 @@ pub async fn handle_delete_bucket( // Delete bucket // Check bucket is empty - let objects = garage - .object_table - .get_range(&bucket_id, None, Some(ObjectFilter::IsData), 10) - .await?; - if !objects.is_empty() { - return Err(Error::BucketNotEmpty); + if !garage.bucket_helper().is_bucket_empty(bucket_id).await? { + return Err(CommonError::BucketNotEmpty.into()); } // --- done checking, now commit --- @@ -299,7 +295,6 @@ fn parse_create_bucket_xml(xml_bytes: &[u8]) -> Option<Option<String>> { let mut ret = None; for item in cbc.children() { - println!("{:?}", item); if item.has_tag_name("LocationConstraint") { if ret != None { return None; diff --git a/src/api/s3_copy.rs b/src/api/s3/copy.rs index fc4707e2..7eb6459d 100644 --- a/src/api/s3_copy.rs +++ b/src/api/s3/copy.rs @@ -5,23 +5,26 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use futures::{stream, stream::Stream, StreamExt, TryFutureExt}; use md5::{Digest as Md5Digest, Md5}; +use bytes::Bytes; use hyper::{Body, Request, Response}; use serde::Serialize; +use garage_rpc::netapp::bytes_buf::BytesBuf; +use garage_rpc::rpc_helper::OrderTag; use garage_table::*; use garage_util::data::*; use garage_util::time::*; -use garage_model::block_ref_table::*; use garage_model::garage::Garage; use garage_model::key_table::Key; -use garage_model::object_table::*; -use garage_model::version_table::*; +use garage_model::s3::block_ref_table::*; +use garage_model::s3::object_table::*; +use garage_model::s3::version_table::*; -use crate::api_server::{parse_bucket_key, resolve_bucket}; -use crate::error::*; -use crate::s3_put::{decode_upload_id, get_headers}; -use crate::s3_xml::{self, xmlns_tag}; +use crate::helpers::parse_bucket_key; +use crate::s3::error::*; +use crate::s3::put::{decode_upload_id, get_headers}; +use crate::s3::xml::{self as s3_xml, xmlns_tag}; pub async fn handle_copy( garage: Arc<Garage>, @@ -201,8 +204,8 @@ pub async fn handle_upload_part_copy( let mut ranges = http_range::HttpRange::parse(range_str, source_version_meta.size) .map_err(|e| (e, source_version_meta.size))?; if ranges.len() != 1 { - return Err(Error::BadRequest( - "Invalid x-amz-copy-source-range header: exactly 1 range must be given".into(), + return Err(Error::bad_request( + "Invalid x-amz-copy-source-range header: exactly 1 range must be given", )); } else { ranges.pop().unwrap() @@ -230,8 +233,8 @@ pub async fn handle_upload_part_copy( // This is only for small files, we don't bother handling this. // (in AWS UploadPartCopy works for parts at least 5MB which // is never the case of an inline object) - return Err(Error::BadRequest( - "Source object is too small (minimum part size is 5Mb)".into(), + return Err(Error::bad_request( + "Source object is too small (minimum part size is 5Mb)", )); } ObjectVersionData::FirstBlock(_meta, _first_block_hash) => (), @@ -250,7 +253,7 @@ pub async fn handle_upload_part_copy( // Check this part number hasn't yet been uploaded if let Some(dv) = dest_version { if dv.has_part_number(part_number) { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "Part number {} has already been uploaded", part_number ))); @@ -305,13 +308,18 @@ pub async fn handle_upload_part_copy( // if and only if the block returned is a block that already existed // in the Garage data store (thus we don't need to save it again). let garage2 = garage.clone(); + let order_stream = OrderTag::stream(); let source_blocks = stream::iter(blocks_to_copy) - .flat_map(|(block_hash, range_to_copy)| { + .enumerate() + .flat_map(|(i, (block_hash, range_to_copy))| { let garage3 = garage2.clone(); stream::once(async move { - let data = garage3.block_manager.rpc_get_block(&block_hash).await?; + let data = garage3 + .block_manager + .rpc_get_block(&block_hash, Some(order_stream.order(i as u64))) + .await?; match range_to_copy { - Some(r) => Ok((data[r].to_vec(), None)), + Some(r) => Ok((data.slice(r), None)), None => Ok((data, Some(block_hash))), } }) @@ -413,10 +421,13 @@ async fn get_copy_source( let copy_source = percent_encoding::percent_decode_str(copy_source).decode_utf8()?; let (source_bucket, source_key) = parse_bucket_key(©_source, None)?; - let source_bucket_id = resolve_bucket(garage, &source_bucket.to_string(), api_key).await?; + let source_bucket_id = garage + .bucket_helper() + .resolve_bucket(&source_bucket.to_string(), api_key) + .await?; if !api_key.allow_read(&source_bucket_id) { - return Err(Error::Forbidden(format!( + return Err(Error::forbidden(format!( "Reading from bucket {} not allowed for this key", source_bucket ))); @@ -536,8 +547,8 @@ impl CopyPreconditionHeaders { (None, None, None, Some(ims)) => v_date > *ims, (None, None, None, None) => true, _ => { - return Err(Error::BadRequest( - "Invalid combination of x-amz-copy-source-if-xxxxx headers".into(), + return Err(Error::bad_request( + "Invalid combination of x-amz-copy-source-if-xxxxx headers", )) } }; @@ -550,13 +561,13 @@ impl CopyPreconditionHeaders { } } -type BlockStreamItemOk = (Vec<u8>, Option<Hash>); +type BlockStreamItemOk = (Bytes, Option<Hash>); type BlockStreamItem = Result<BlockStreamItemOk, garage_util::error::Error>; struct Defragmenter<S: Stream<Item = BlockStreamItem>> { block_size: usize, block_stream: Pin<Box<stream::Peekable<S>>>, - buffer: Vec<u8>, + buffer: BytesBuf, hash: Option<Hash>, } @@ -565,7 +576,7 @@ impl<S: Stream<Item = BlockStreamItem>> Defragmenter<S> { Self { block_size, block_stream, - buffer: vec![], + buffer: BytesBuf::new(), hash: None, } } @@ -583,7 +594,7 @@ impl<S: Stream<Item = BlockStreamItem>> Defragmenter<S> { if self.buffer.is_empty() { let (next_block, next_block_hash) = self.block_stream.next().await.unwrap()?; - self.buffer = next_block; + self.buffer.extend(next_block); self.hash = next_block_hash; } else if self.buffer.len() + peeked_next_block.len() > self.block_size { break; @@ -594,11 +605,11 @@ impl<S: Stream<Item = BlockStreamItem>> Defragmenter<S> { } } - Ok((std::mem::take(&mut self.buffer), self.hash.take())) + Ok((self.buffer.take_all(), self.hash.take())) } } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct CopyObjectResult { #[serde(rename = "LastModified")] pub last_modified: s3_xml::Value, @@ -606,7 +617,7 @@ pub struct CopyObjectResult { pub etag: s3_xml::Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct CopyPartResult { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), @@ -619,7 +630,7 @@ pub struct CopyPartResult { #[cfg(test)] mod tests { use super::*; - use crate::s3_xml::to_xml_with_header; + use crate::s3::xml::to_xml_with_header; #[test] fn copy_object_result() -> Result<(), Error> { @@ -651,7 +662,6 @@ mod tests { last_modified: s3_xml::Value("2011-04-11T20:34:56.000Z".into()), etag: s3_xml::Value("\"9b2cf535f27731c974343645a3985328\"".into()), }; - println!("{}", to_xml_with_header(&v)?); assert_eq!(to_xml_with_header(&v)?, expected_retval); diff --git a/src/api/s3_cors.rs b/src/api/s3/cors.rs index ab77e23a..c7273464 100644 --- a/src/api/s3_cors.rs +++ b/src/api/s3/cors.rs @@ -9,13 +9,12 @@ use hyper::{header::HeaderName, Body, Method, Request, Response, StatusCode}; use serde::{Deserialize, Serialize}; -use crate::error::*; -use crate::s3_xml::{to_xml_with_header, xmlns_tag, IntValue, Value}; +use crate::s3::error::*; +use crate::s3::xml::{to_xml_with_header, xmlns_tag, IntValue, Value}; use crate::signature::verify_signed_content; use garage_model::bucket_table::{Bucket, CorsRule as GarageCorsRule}; use garage_model::garage::Garage; -use garage_table::*; use garage_util::data::*; pub async fn handle_get_cors(bucket: &Bucket) -> Result<Response<Body>, Error> { @@ -48,14 +47,11 @@ pub async fn handle_delete_cors( bucket_id: Uuid, ) -> Result<Response<Body>, Error> { let mut bucket = garage - .bucket_table - .get(&EmptyKey, &bucket_id) - .await? - .ok_or(Error::NoSuchBucket)?; + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; - let param = bucket - .params_mut() - .ok_or_internal_error("Bucket should not be deleted at this point")?; + let param = bucket.params_mut().unwrap(); param.cors_config.update(None); garage.bucket_table.insert(&bucket).await?; @@ -78,14 +74,11 @@ pub async fn handle_put_cors( } let mut bucket = garage - .bucket_table - .get(&EmptyKey, &bucket_id) - .await? - .ok_or(Error::NoSuchBucket)?; + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; - let param = bucket - .params_mut() - .ok_or_internal_error("Bucket should not be deleted at this point")?; + let param = bucket.params_mut().unwrap(); let conf: CorsConfiguration = from_reader(&body as &[u8])?; conf.validate()?; @@ -119,12 +112,7 @@ pub async fn handle_options_s3api( let helper = garage.bucket_helper(); let bucket_id = helper.resolve_global_bucket_name(&bn).await?; if let Some(id) = bucket_id { - let bucket = garage - .bucket_table - .get(&EmptyKey, &id) - .await? - .filter(|b| !b.state.is_deleted()) - .ok_or(Error::NoSuchBucket)?; + let bucket = garage.bucket_helper().get_existing_bucket(id).await?; handle_options_for_bucket(req, &bucket) } else { // If there is a bucket name in the request, but that name @@ -185,7 +173,7 @@ pub fn handle_options_for_bucket( } } - Err(Error::Forbidden("This CORS request is not allowed.".into())) + Err(Error::forbidden("This CORS request is not allowed.")) } pub fn find_matching_cors_rule<'a>( diff --git a/src/api/s3_delete.rs b/src/api/s3/delete.rs index b243d982..b337155f 100644 --- a/src/api/s3_delete.rs +++ b/src/api/s3/delete.rs @@ -6,10 +6,10 @@ use garage_util::data::*; use garage_util::time::*; use garage_model::garage::Garage; -use garage_model::object_table::*; +use garage_model::s3::object_table::*; -use crate::error::*; -use crate::s3_xml; +use crate::s3::error::*; +use crate::s3::xml as s3_xml; use crate::signature::verify_signed_content; async fn handle_delete_internal( @@ -64,14 +64,13 @@ pub async fn handle_delete( bucket_id: Uuid, key: &str, ) -> Result<Response<Body>, Error> { - let (_deleted_version, delete_marker_version) = - handle_delete_internal(&garage, bucket_id, key).await?; - - Ok(Response::builder() - .header("x-amz-version-id", hex::encode(delete_marker_version)) - .status(StatusCode::NO_CONTENT) - .body(Body::from(vec![])) - .unwrap()) + match handle_delete_internal(&garage, bucket_id, key).await { + Ok(_) | Err(Error::NoSuchKey) => Ok(Response::builder() + .status(StatusCode::NO_CONTENT) + .body(Body::from(vec![])) + .unwrap()), + Err(e) => Err(e), + } } pub async fn handle_delete_objects( diff --git a/src/api/error.rs b/src/api/s3/error.rs index f53ed1fd..67009d63 100644 --- a/src/api/error.rs +++ b/src/api/s3/error.rs @@ -2,34 +2,24 @@ use std::convert::TryInto; use err_derive::Error; use hyper::header::HeaderValue; -use hyper::{HeaderMap, StatusCode}; +use hyper::{Body, HeaderMap, StatusCode}; use garage_model::helper::error::Error as HelperError; -use garage_util::error::Error as GarageError; -use crate::s3_xml; +use crate::common_error::CommonError; +pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError}; +use crate::generic_server::ApiError; +use crate::s3::xml as s3_xml; +use crate::signature::error::Error as SignatureError; /// Errors of this crate #[derive(Debug, Error)] pub enum Error { - // Category: internal error - /// Error related to deeper parts of Garage - #[error(display = "Internal error: {}", _0)] - InternalError(#[error(source)] GarageError), - - /// Error related to Hyper - #[error(display = "Internal error (Hyper error): {}", _0)] - Hyper(#[error(source)] hyper::Error), - - /// Error related to HTTP - #[error(display = "Internal error (HTTP error): {}", _0)] - Http(#[error(source)] http::Error), + #[error(display = "{}", _0)] + /// Error from common error + Common(CommonError), // Category: cannot process - /// No proper api key was used, or the signature was invalid - #[error(display = "Forbidden: {}", _0)] - Forbidden(String), - /// Authorization Header Malformed #[error(display = "Authorization header malformed, expected scope: {}", _0)] AuthorizationHeaderMalformed(String), @@ -38,22 +28,10 @@ pub enum Error { #[error(display = "Key not found")] NoSuchKey, - /// The bucket requested don't exists - #[error(display = "Bucket not found")] - NoSuchBucket, - /// The multipart upload requested don't exists #[error(display = "Upload not found")] NoSuchUpload, - /// Tried to create a bucket that already exist - #[error(display = "Bucket already exists")] - BucketAlreadyExists, - - /// Tried to delete a non-empty bucket - #[error(display = "Tried to delete a non-empty bucket")] - BucketNotEmpty, - /// Precondition failed (e.g. x-amz-copy-source-if-match) #[error(display = "At least one of the preconditions you specified did not hold")] PreconditionFailed, @@ -80,10 +58,6 @@ pub enum Error { #[error(display = "Invalid UTF-8: {}", _0)] InvalidUtf8String(#[error(source)] std::string::FromUtf8Error), - /// Some base64 encoded data was badly encoded - #[error(display = "Invalid base64: {}", _0)] - InvalidBase64(#[error(source)] base64::DecodeError), - /// The client sent invalid XML data #[error(display = "Invalid XML: {}", _0)] InvalidXml(String), @@ -96,15 +70,34 @@ pub enum Error { #[error(display = "Invalid HTTP range: {:?}", _0)] InvalidRange(#[error(from)] (http_range::HttpRangeParseError, u64)), - /// The client sent an invalid request - #[error(display = "Bad request: {}", _0)] - BadRequest(String), - /// The client sent a request for an action not supported by garage #[error(display = "Unimplemented action: {}", _0)] NotImplemented(String), } +impl<T> From<T> for Error +where + CommonError: From<T>, +{ + fn from(err: T) -> Self { + Error::Common(CommonError::from(err)) + } +} + +impl CommonErrorDerivative for Error {} + +impl From<HelperError> for Error { + fn from(err: HelperError) -> Self { + match err { + HelperError::Internal(i) => Self::Common(CommonError::InternalError(i)), + HelperError::BadRequest(b) => Self::Common(CommonError::BadRequest(b)), + HelperError::InvalidBucketName(n) => Self::Common(CommonError::InvalidBucketName(n)), + HelperError::NoSuchBucket(n) => Self::Common(CommonError::NoSuchBucket(n)), + e => Self::bad_request(format!("{}", e)), + } + } +} + impl From<roxmltree::Error> for Error { fn from(err: roxmltree::Error) -> Self { Self::InvalidXml(format!("{}", err)) @@ -117,88 +110,71 @@ impl From<quick_xml::de::DeError> for Error { } } -impl From<HelperError> for Error { - fn from(err: HelperError) -> Self { +impl From<SignatureError> for Error { + fn from(err: SignatureError) -> Self { match err { - HelperError::Internal(i) => Self::InternalError(i), - HelperError::BadRequest(b) => Self::BadRequest(b), + SignatureError::Common(c) => Self::Common(c), + SignatureError::AuthorizationHeaderMalformed(c) => { + Self::AuthorizationHeaderMalformed(c) + } + SignatureError::InvalidUtf8Str(i) => Self::InvalidUtf8Str(i), + SignatureError::InvalidHeader(h) => Self::InvalidHeader(h), } } } impl From<multer::Error> for Error { fn from(err: multer::Error) -> Self { - Self::BadRequest(err.to_string()) + Self::bad_request(err) } } impl Error { - /// Get the HTTP status code that best represents the meaning of the error for the client - pub fn http_status_code(&self) -> StatusCode { - match self { - Error::NoSuchKey | Error::NoSuchBucket | Error::NoSuchUpload => StatusCode::NOT_FOUND, - Error::BucketNotEmpty | Error::BucketAlreadyExists => StatusCode::CONFLICT, - Error::PreconditionFailed => StatusCode::PRECONDITION_FAILED, - Error::Forbidden(_) => StatusCode::FORBIDDEN, - Error::InternalError( - GarageError::Timeout - | GarageError::RemoteError(_) - | GarageError::Quorum(_, _, _, _), - ) => StatusCode::SERVICE_UNAVAILABLE, - Error::InternalError(_) | Error::Hyper(_) | Error::Http(_) => { - StatusCode::INTERNAL_SERVER_ERROR - } - Error::InvalidRange(_) => StatusCode::RANGE_NOT_SATISFIABLE, - Error::NotImplemented(_) => StatusCode::NOT_IMPLEMENTED, - _ => StatusCode::BAD_REQUEST, - } - } - pub fn aws_code(&self) -> &'static str { match self { + Error::Common(c) => c.aws_code(), Error::NoSuchKey => "NoSuchKey", - Error::NoSuchBucket => "NoSuchBucket", Error::NoSuchUpload => "NoSuchUpload", - Error::BucketAlreadyExists => "BucketAlreadyExists", - Error::BucketNotEmpty => "BucketNotEmpty", Error::PreconditionFailed => "PreconditionFailed", Error::InvalidPart => "InvalidPart", Error::InvalidPartOrder => "InvalidPartOrder", Error::EntityTooSmall => "EntityTooSmall", - Error::Forbidden(_) => "AccessDenied", Error::AuthorizationHeaderMalformed(_) => "AuthorizationHeaderMalformed", Error::NotImplemented(_) => "NotImplemented", - Error::InternalError( - GarageError::Timeout - | GarageError::RemoteError(_) - | GarageError::Quorum(_, _, _, _), - ) => "ServiceUnavailable", - Error::InternalError(_) | Error::Hyper(_) | Error::Http(_) => "InternalError", - _ => "InvalidRequest", + Error::InvalidXml(_) => "MalformedXML", + Error::InvalidRange(_) => "InvalidRange", + Error::InvalidUtf8Str(_) | Error::InvalidUtf8String(_) | Error::InvalidHeader(_) => { + "InvalidRequest" + } } } +} - pub fn aws_xml(&self, garage_region: &str, path: &str) -> String { - let error = s3_xml::Error { - code: s3_xml::Value(self.aws_code().to_string()), - message: s3_xml::Value(format!("{}", self)), - resource: Some(s3_xml::Value(path.to_string())), - region: Some(s3_xml::Value(garage_region.to_string())), - }; - s3_xml::to_xml_with_header(&error).unwrap_or_else(|_| { - r#" -<?xml version="1.0" encoding="UTF-8"?> -<Error> - <Code>InternalError</Code> - <Message>XML encoding of error failed</Message> -</Error> - "# - .into() - }) +impl ApiError for Error { + /// Get the HTTP status code that best represents the meaning of the error for the client + fn http_status_code(&self) -> StatusCode { + match self { + Error::Common(c) => c.http_status_code(), + Error::NoSuchKey | Error::NoSuchUpload => StatusCode::NOT_FOUND, + Error::PreconditionFailed => StatusCode::PRECONDITION_FAILED, + Error::InvalidRange(_) => StatusCode::RANGE_NOT_SATISFIABLE, + Error::NotImplemented(_) => StatusCode::NOT_IMPLEMENTED, + Error::AuthorizationHeaderMalformed(_) + | Error::InvalidPart + | Error::InvalidPartOrder + | Error::EntityTooSmall + | Error::InvalidXml(_) + | Error::InvalidUtf8Str(_) + | Error::InvalidUtf8String(_) + | Error::InvalidHeader(_) => StatusCode::BAD_REQUEST, + } } - pub fn add_headers(&self, header_map: &mut HeaderMap<HeaderValue>) { + fn add_http_headers(&self, header_map: &mut HeaderMap<HeaderValue>) { use hyper::header; + + header_map.append(header::CONTENT_TYPE, "application/xml".parse().unwrap()); + #[allow(clippy::single_match)] match self { Error::InvalidRange((_, len)) => { @@ -212,68 +188,23 @@ impl Error { _ => (), } } -} - -/// Trait to map error to the Bad Request error code -pub trait OkOrBadRequest { - type S; - fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<Self::S, Error>; -} - -impl<T, E> OkOrBadRequest for Result<T, E> -where - E: std::fmt::Display, -{ - type S = T; - fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<T, Error> { - match self { - Ok(x) => Ok(x), - Err(e) => Err(Error::BadRequest(format!("{}: {}", reason.as_ref(), e))), - } - } -} - -impl<T> OkOrBadRequest for Option<T> { - type S = T; - fn ok_or_bad_request<M: AsRef<str>>(self, reason: M) -> Result<T, Error> { - match self { - Some(x) => Ok(x), - None => Err(Error::BadRequest(reason.as_ref().to_string())), - } - } -} - -/// Trait to map an error to an Internal Error code -pub trait OkOrInternalError { - type S; - fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<Self::S, Error>; -} - -impl<T, E> OkOrInternalError for Result<T, E> -where - E: std::fmt::Display, -{ - type S = T; - fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<T, Error> { - match self { - Ok(x) => Ok(x), - Err(e) => Err(Error::InternalError(GarageError::Message(format!( - "{}: {}", - reason.as_ref(), - e - )))), - } - } -} -impl<T> OkOrInternalError for Option<T> { - type S = T; - fn ok_or_internal_error<M: AsRef<str>>(self, reason: M) -> Result<T, Error> { - match self { - Some(x) => Ok(x), - None => Err(Error::InternalError(GarageError::Message( - reason.as_ref().to_string(), - ))), - } + fn http_body(&self, garage_region: &str, path: &str) -> Body { + let error = s3_xml::Error { + code: s3_xml::Value(self.aws_code().to_string()), + message: s3_xml::Value(format!("{}", self)), + resource: Some(s3_xml::Value(path.to_string())), + region: Some(s3_xml::Value(garage_region.to_string())), + }; + Body::from(s3_xml::to_xml_with_header(&error).unwrap_or_else(|_| { + r#" +<?xml version="1.0" encoding="UTF-8"?> +<Error> + <Code>InternalError</Code> + <Message>XML encoding of error failed</Message> +</Error> + "# + .into() + })) } } diff --git a/src/api/s3_get.rs b/src/api/s3/get.rs index 7f647e15..2a99551a 100644 --- a/src/api/s3_get.rs +++ b/src/api/s3/get.rs @@ -2,22 +2,25 @@ use std::sync::Arc; use std::time::{Duration, UNIX_EPOCH}; -use futures::stream::*; +use futures::future; +use futures::stream::{self, StreamExt}; use http::header::{ ACCEPT_RANGES, CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE, ETAG, IF_MODIFIED_SINCE, IF_NONE_MATCH, LAST_MODIFIED, RANGE, }; -use hyper::body::Bytes; use hyper::{Body, Request, Response, StatusCode}; +use tokio::sync::mpsc; +use garage_rpc::rpc_helper::{netapp::stream::ByteStream, OrderTag}; use garage_table::EmptyKey; use garage_util::data::*; +use garage_util::error::OkOrMessage; use garage_model::garage::Garage; -use garage_model::object_table::*; -use garage_model::version_table::*; +use garage_model::s3::object_table::*; +use garage_model::s3::version_table::*; -use crate::error::*; +use crate::s3::error::*; const X_AMZ_MP_PARTS_COUNT: &str = "x-amz-mp-parts-count"; @@ -210,8 +213,8 @@ pub async fn handle_get( match (part_number, parse_range_header(req, last_v_meta.size)?) { (Some(_), Some(_)) => { - return Err(Error::BadRequest( - "Cannot specify both partNumber and Range header".into(), + return Err(Error::bad_request( + "Cannot specify both partNumber and Range header", )); } (Some(pn), None) => { @@ -242,36 +245,56 @@ pub async fn handle_get( Ok(resp_builder.body(body)?) } ObjectVersionData::FirstBlock(_, first_block_hash) => { - let read_first_block = garage.block_manager.rpc_get_block(first_block_hash); - let get_next_blocks = garage.version_table.get(&last_v.uuid, &EmptyKey); - - let (first_block, version) = futures::try_join!(read_first_block, get_next_blocks)?; - let version = version.ok_or(Error::NoSuchKey)?; + let (tx, rx) = mpsc::channel(2); + + let order_stream = OrderTag::stream(); + let first_block_hash = *first_block_hash; + let version_uuid = last_v.uuid; + + tokio::spawn(async move { + match async { + let garage2 = garage.clone(); + let version_fut = tokio::spawn(async move { + garage2.version_table.get(&version_uuid, &EmptyKey).await + }); + + let stream_block_0 = garage + .block_manager + .rpc_get_block_streaming(&first_block_hash, Some(order_stream.order(0))) + .await?; + tx.send(stream_block_0) + .await + .ok_or_message("channel closed")?; + + let version = version_fut.await.unwrap()?.ok_or(Error::NoSuchKey)?; + for (i, (_, vb)) in version.blocks.items().iter().enumerate().skip(1) { + let stream_block_i = garage + .block_manager + .rpc_get_block_streaming(&vb.hash, Some(order_stream.order(i as u64))) + .await?; + tx.send(stream_block_i) + .await + .ok_or_message("channel closed")?; + } - let mut blocks = version - .blocks - .items() - .iter() - .map(|(_, vb)| (vb.hash, None)) - .collect::<Vec<_>>(); - blocks[0].1 = Some(first_block); - - let body_stream = futures::stream::iter(blocks) - .map(move |(hash, data_opt)| { - let garage = garage.clone(); - async move { - if let Some(data) = data_opt { - Ok(Bytes::from(data)) - } else { - garage - .block_manager - .rpc_get_block(&hash) - .await - .map(Bytes::from) - } + Ok::<(), Error>(()) + } + .await + { + Ok(()) => (), + Err(e) => { + let err = std::io::Error::new( + std::io::ErrorKind::Other, + format!("Error while getting object data: {}", e), + ); + let _ = tx + .send(Box::pin(stream::once(future::ready(Err(err))))) + .await; } - }) - .buffered(2); + } + }); + + let body_stream = tokio_stream::wrappers::ReceiverStream::new(rx).flatten(); let body = hyper::body::Body::wrap_stream(body_stream); Ok(resp_builder.body(body)?) @@ -302,9 +325,9 @@ async fn handle_get_range( let body: Body = Body::from(bytes[begin as usize..end as usize].to_vec()); Ok(resp_builder.body(body)?) } else { - None.ok_or_internal_error( + Err(Error::internal_error( "Requested range not present in inline bytes when it should have been", - ) + )) } } ObjectVersionData::FirstBlock(_meta, _first_block_hash) => { @@ -422,40 +445,79 @@ fn body_from_blocks_range( all_blocks.len(), 4 + ((end - begin) / std::cmp::max(all_blocks[0].1.size as u64, 1024)) as usize, )); - let mut true_offset = 0; + let mut block_offset: u64 = 0; for (_, b) in all_blocks.iter() { - if true_offset >= end { + if block_offset >= end { break; } // Keep only blocks that have an intersection with the requested range - if true_offset < end && true_offset + b.size > begin { - blocks.push((*b, true_offset)); + if block_offset < end && block_offset + b.size > begin { + blocks.push((*b, block_offset)); } - true_offset += b.size; + block_offset += b.size as u64; } + let order_stream = OrderTag::stream(); let body_stream = futures::stream::iter(blocks) - .map(move |(block, true_offset)| { + .enumerate() + .map(move |(i, (block, block_offset))| { let garage = garage.clone(); async move { - let data = garage.block_manager.rpc_get_block(&block.hash).await?; - let data = Bytes::from(data); - let start_in_block = if true_offset > begin { - 0 - } else { - begin - true_offset - }; - let end_in_block = if true_offset + block.size < end { - block.size - } else { - end - true_offset - }; - Result::<Bytes, Error>::Ok( - data.slice(start_in_block as usize..end_in_block as usize), - ) + garage + .block_manager + .rpc_get_block_streaming(&block.hash, Some(order_stream.order(i as u64))) + .await + .unwrap_or_else(|e| error_stream(i, e)) + .scan(block_offset, move |chunk_offset, chunk| { + let r = match chunk { + Ok(chunk_bytes) => { + let chunk_len = chunk_bytes.len() as u64; + let r = if *chunk_offset >= end { + // The current chunk is after the part we want to read. + // Returning None here will stop the scan, the rest of the + // stream will be ignored + None + } else if *chunk_offset + chunk_len <= begin { + // The current chunk is before the part we want to read. + // We return a None that will be removed by the filter_map + // below. + Some(None) + } else { + // The chunk has an intersection with the requested range + let start_in_chunk = if *chunk_offset > begin { + 0 + } else { + begin - *chunk_offset + }; + let end_in_chunk = if *chunk_offset + chunk_len < end { + chunk_len + } else { + end - *chunk_offset + }; + Some(Some(Ok(chunk_bytes + .slice(start_in_chunk as usize..end_in_chunk as usize)))) + }; + *chunk_offset += chunk_bytes.len() as u64; + r + } + Err(e) => Some(Some(Err(e))), + }; + futures::future::ready(r) + }) + .filter_map(futures::future::ready) } }) - .buffered(2); + .buffered(2) + .flatten(); hyper::body::Body::wrap_stream(body_stream) } + +fn error_stream(i: usize, e: garage_util::error::Error) -> ByteStream { + Box::pin(futures::stream::once(async move { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Could not get block {}: {}", i, e), + )) + })) +} diff --git a/src/api/s3_list.rs b/src/api/s3/list.rs index 5852fc1b..e5f486c8 100644 --- a/src/api/s3_list.rs +++ b/src/api/s3/list.rs @@ -10,15 +10,16 @@ use garage_util::error::Error as GarageError; use garage_util::time::*; use garage_model::garage::Garage; -use garage_model::object_table::*; -use garage_model::version_table::Version; +use garage_model::s3::object_table::*; +use garage_model::s3::version_table::Version; -use garage_table::EmptyKey; +use garage_table::{EmptyKey, EnumerationOrder}; use crate::encoding::*; -use crate::error::*; -use crate::s3_put; -use crate::s3_xml; +use crate::helpers::key_after_prefix; +use crate::s3::error::*; +use crate::s3::put as s3_put; +use crate::s3::xml as s3_xml; const DUMMY_NAME: &str = "Dummy Key"; const DUMMY_KEY: &str = "GKDummyKey"; @@ -66,8 +67,14 @@ pub async fn handle_list( let io = |bucket, key, count| { let t = &garage.object_table; async move { - t.get_range(&bucket, key, Some(ObjectFilter::IsData), count) - .await + t.get_range( + &bucket, + key, + Some(ObjectFilter::IsData), + count, + EnumerationOrder::Forward, + ) + .await } }; @@ -165,8 +172,14 @@ pub async fn handle_list_multipart_upload( let io = |bucket, key, count| { let t = &garage.object_table; async move { - t.get_range(&bucket, key, Some(ObjectFilter::IsUploading), count) - .await + t.get_range( + &bucket, + key, + Some(ObjectFilter::IsUploading), + count, + EnumerationOrder::Forward, + ) + .await } }; @@ -569,13 +582,19 @@ impl ListObjectsQuery { // representing the key to start with. (Some(token), _) => match &token[..1] { "[" => Ok(RangeBegin::IncludingKey { - key: String::from_utf8(base64::decode(token[1..].as_bytes())?)?, + key: String::from_utf8( + base64::decode(token[1..].as_bytes()) + .ok_or_bad_request("Invalid continuation token")?, + )?, fallback_key: None, }), "]" => Ok(RangeBegin::AfterKey { - key: String::from_utf8(base64::decode(token[1..].as_bytes())?)?, + key: String::from_utf8( + base64::decode(token[1..].as_bytes()) + .ok_or_bad_request("Invalid continuation token")?, + )?, }), - _ => Err(Error::BadRequest("Invalid continuation token".to_string())), + _ => Err(Error::bad_request("Invalid continuation token")), }, // StartAfter has defined semantics in the spec: @@ -923,39 +942,13 @@ fn uriencode_maybe(s: &str, yes: bool) -> s3_xml::Value { } } -const UTF8_BEFORE_LAST_CHAR: char = '\u{10FFFE}'; - -/// Compute the key after the prefix -fn key_after_prefix(pfx: &str) -> Option<String> { - let mut next = pfx.to_string(); - while !next.is_empty() { - let tail = next.pop().unwrap(); - if tail >= char::MAX { - continue; - } - - // Circumvent a limitation of RangeFrom that overflow earlier than needed - // See: https://doc.rust-lang.org/core/ops/struct.RangeFrom.html - let new_tail = if tail == UTF8_BEFORE_LAST_CHAR { - char::MAX - } else { - (tail..).nth(1).unwrap() - }; - - next.push(new_tail); - return Some(next); - } - - None -} - /* * Unit tests of this module */ #[cfg(test)] mod tests { use super::*; - use garage_model::version_table::*; + use garage_model::s3::version_table::*; use garage_util::*; use std::iter::FromIterator; @@ -1003,39 +996,6 @@ mod tests { } #[test] - fn test_key_after_prefix() { - assert_eq!(UTF8_BEFORE_LAST_CHAR as u32, (char::MAX as u32) - 1); - assert_eq!(key_after_prefix("a/b/").unwrap().as_str(), "a/b0"); - assert_eq!(key_after_prefix("€").unwrap().as_str(), "₭"); - assert_eq!( - key_after_prefix("").unwrap().as_str(), - String::from(char::from_u32(0x10FFFE).unwrap()) - ); - - // When the last character is the biggest UTF8 char - let a = String::from_iter(['a', char::MAX].iter()); - assert_eq!(key_after_prefix(a.as_str()).unwrap().as_str(), "b"); - - // When all characters are the biggest UTF8 char - let b = String::from_iter([char::MAX; 3].iter()); - assert!(key_after_prefix(b.as_str()).is_none()); - - // Check utf8 surrogates - let c = String::from('\u{D7FF}'); - assert_eq!( - key_after_prefix(c.as_str()).unwrap().as_str(), - String::from('\u{E000}') - ); - - // Check the character before the biggest one - let d = String::from('\u{10FFFE}'); - assert_eq!( - key_after_prefix(d.as_str()).unwrap().as_str(), - String::from(char::MAX) - ); - } - - #[test] fn test_common_prefixes() { let mut query = query(); let objs = objs(); diff --git a/src/api/s3/mod.rs b/src/api/s3/mod.rs new file mode 100644 index 00000000..7b56d4d8 --- /dev/null +++ b/src/api/s3/mod.rs @@ -0,0 +1,15 @@ +pub mod api_server; +pub mod error; + +mod bucket; +mod copy; +pub mod cors; +mod delete; +pub mod get; +mod list; +mod post_object; +mod put; +mod website; + +mod router; +pub mod xml; diff --git a/src/api/s3_post_object.rs b/src/api/s3/post_object.rs index 585e0304..d063faa4 100644 --- a/src/api/s3_post_object.rs +++ b/src/api/s3/post_object.rs @@ -14,16 +14,15 @@ use serde::Deserialize; use garage_model::garage::Garage; -use crate::api_server::resolve_bucket; -use crate::error::*; -use crate::s3_put::{get_headers, save_stream}; -use crate::s3_xml; +use crate::s3::error::*; +use crate::s3::put::{get_headers, save_stream}; +use crate::s3::xml as s3_xml; use crate::signature::payload::{parse_date, verify_v4}; pub async fn handle_post_object( garage: Arc<Garage>, req: Request<Body>, - bucket: String, + bucket_name: String, ) -> Result<Response<Body>, Error> { let boundary = req .headers() @@ -48,9 +47,7 @@ pub async fn handle_post_object( let field = if let Some(field) = multipart.next_field().await? { field } else { - return Err(Error::BadRequest( - "Request did not contain a file".to_owned(), - )); + return Err(Error::bad_request("Request did not contain a file")); }; let name: HeaderName = if let Some(Ok(name)) = field.name().map(TryInto::try_into) { name @@ -66,14 +63,14 @@ pub async fn handle_post_object( "tag" => (/* tag need to be reencoded, but we don't support them yet anyway */), "acl" => { if params.insert("x-amz-acl", content).is_some() { - return Err(Error::BadRequest( - "Field 'acl' provided more than one time".to_string(), + return Err(Error::bad_request( + "Field 'acl' provided more than one time", )); } } _ => { if params.insert(&name, content).is_some() { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "Field '{}' provided more than one time", name ))); @@ -90,9 +87,7 @@ pub async fn handle_post_object( .to_str()?; let credential = params .get("x-amz-credential") - .ok_or_else(|| { - Error::Forbidden("Garage does not support anonymous access yet".to_string()) - })? + .ok_or_else(|| Error::forbidden("Garage does not support anonymous access yet"))? .to_str()?; let policy = params .get("policy") @@ -119,17 +114,31 @@ pub async fn handle_post_object( }; let date = parse_date(date)?; - let api_key = verify_v4(&garage, credential, &date, signature, policy.as_bytes()).await?; + let api_key = verify_v4( + &garage, + "s3", + credential, + &date, + signature, + policy.as_bytes(), + ) + .await?; - let bucket_id = resolve_bucket(&garage, &bucket, &api_key).await?; + let bucket_id = garage + .bucket_helper() + .resolve_bucket(&bucket_name, &api_key) + .await?; if !api_key.allow_write(&bucket_id) { - return Err(Error::Forbidden( - "Operation is not allowed for this key.".to_string(), - )); + return Err(Error::forbidden("Operation is not allowed for this key.")); } - let decoded_policy = base64::decode(&policy)?; + let bucket = garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + + let decoded_policy = base64::decode(&policy).ok_or_bad_request("Invalid policy")?; let decoded_policy: Policy = serde_json::from_slice(&decoded_policy).ok_or_bad_request("Invalid policy")?; @@ -137,9 +146,7 @@ pub async fn handle_post_object( .ok_or_bad_request("Invalid expiration date")? .into(); if Utc::now() - expiration > Duration::zero() { - return Err(Error::BadRequest( - "Expiration date is in the paste".to_string(), - )); + return Err(Error::bad_request("Expiration date is in the paste")); } let mut conditions = decoded_policy.into_conditions()?; @@ -151,7 +158,7 @@ pub async fn handle_post_object( "policy" | "x-amz-signature" => (), // this is always accepted, as it's required to validate other fields "content-type" => { let conds = conditions.params.remove("content-type").ok_or_else(|| { - Error::BadRequest(format!("Key '{}' is not allowed in policy", param_key)) + Error::bad_request(format!("Key '{}' is not allowed in policy", param_key)) })?; for cond in conds { let ok = match cond { @@ -161,7 +168,7 @@ pub async fn handle_post_object( } }; if !ok { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "Key '{}' has value not allowed in policy", param_key ))); @@ -170,7 +177,7 @@ pub async fn handle_post_object( } "key" => { let conds = conditions.params.remove("key").ok_or_else(|| { - Error::BadRequest(format!("Key '{}' is not allowed in policy", param_key)) + Error::bad_request(format!("Key '{}' is not allowed in policy", param_key)) })?; for cond in conds { let ok = match cond { @@ -178,7 +185,7 @@ pub async fn handle_post_object( Operation::StartsWith(s) => key.starts_with(&s), }; if !ok { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "Key '{}' has value not allowed in policy", param_key ))); @@ -193,7 +200,7 @@ pub async fn handle_post_object( continue; } let conds = conditions.params.remove(¶m_key).ok_or_else(|| { - Error::BadRequest(format!("Key '{}' is not allowed in policy", param_key)) + Error::bad_request(format!("Key '{}' is not allowed in policy", param_key)) })?; for cond in conds { let ok = match cond { @@ -201,7 +208,7 @@ pub async fn handle_post_object( Operation::StartsWith(s) => value.to_str()?.starts_with(s.as_str()), }; if !ok { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "Key '{}' has value not allowed in policy", param_key ))); @@ -212,7 +219,7 @@ pub async fn handle_post_object( } if let Some((param_key, _)) = conditions.params.iter().next() { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "Key '{}' is required in policy, but no value was provided", param_key ))); @@ -225,7 +232,7 @@ pub async fn handle_post_object( garage, headers, StreamLimiter::new(stream, conditions.content_length), - bucket_id, + &bucket, &key, None, None, @@ -242,7 +249,7 @@ pub async fn handle_post_object( { target .query_pairs_mut() - .append_pair("bucket", &bucket) + .append_pair("bucket", &bucket_name) .append_pair("key", &key) .append_pair("etag", &etag); let target = target.to_string(); @@ -287,7 +294,7 @@ pub async fn handle_post_object( let xml = s3_xml::PostObject { xmlns: (), location: s3_xml::Value(location), - bucket: s3_xml::Value(bucket), + bucket: s3_xml::Value(bucket_name), key: s3_xml::Value(key), etag: s3_xml::Value(etag), }; @@ -318,7 +325,7 @@ impl Policy { match condition { PolicyCondition::Equal(map) => { if map.len() != 1 { - return Err(Error::BadRequest("Invalid policy item".to_owned())); + return Err(Error::bad_request("Invalid policy item")); } let (mut k, v) = map.into_iter().next().expect("size was verified"); k.make_ascii_lowercase(); @@ -326,7 +333,7 @@ impl Policy { } PolicyCondition::OtherOp([cond, mut key, value]) => { if key.remove(0) != '$' { - return Err(Error::BadRequest("Invalid policy item".to_owned())); + return Err(Error::bad_request("Invalid policy item")); } key.make_ascii_lowercase(); match cond.as_str() { @@ -339,7 +346,7 @@ impl Policy { .or_default() .push(Operation::StartsWith(value)); } - _ => return Err(Error::BadRequest("Invalid policy item".to_owned())), + _ => return Err(Error::bad_request("Invalid policy item")), } } PolicyCondition::SizeRange(key, min, max) => { @@ -347,7 +354,7 @@ impl Policy { length.0 = length.0.max(min); length.1 = length.1.min(max); } else { - return Err(Error::BadRequest("Invalid policy item".to_owned())); + return Err(Error::bad_request("Invalid policy item")); } } } @@ -412,15 +419,15 @@ where self.read += bytes.len() as u64; // optimization to fail early when we know before the end it's too long if self.length.end() < &self.read { - return Poll::Ready(Some(Err(Error::BadRequest( - "File size does not match policy".to_owned(), + return Poll::Ready(Some(Err(Error::bad_request( + "File size does not match policy", )))); } } Poll::Ready(None) => { if !self.length.contains(&self.read) { - return Poll::Ready(Some(Err(Error::BadRequest( - "File size does not match policy".to_owned(), + return Poll::Ready(Some(Err(Error::bad_request( + "File size does not match policy", )))); } } diff --git a/src/api/s3_put.rs b/src/api/s3/put.rs index ed0bf00b..97b8e4e3 100644 --- a/src/api/s3_put.rs +++ b/src/api/s3/put.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::sync::Arc; use futures::prelude::*; @@ -8,25 +8,34 @@ use hyper::{Request, Response}; use md5::{digest::generic_array::*, Digest as Md5Digest, Md5}; use sha2::Sha256; +use opentelemetry::{ + trace::{FutureExt as OtelFutureExt, TraceContextExt, Tracer}, + Context, +}; + +use garage_rpc::netapp::bytes_buf::BytesBuf; use garage_table::*; +use garage_util::async_hash::*; use garage_util::data::*; use garage_util::error::Error as GarageError; use garage_util::time::*; use garage_block::manager::INLINE_THRESHOLD; -use garage_model::block_ref_table::*; +use garage_model::bucket_table::Bucket; use garage_model::garage::Garage; -use garage_model::object_table::*; -use garage_model::version_table::*; +use garage_model::index_counter::CountedItem; +use garage_model::s3::block_ref_table::*; +use garage_model::s3::object_table::*; +use garage_model::s3::version_table::*; -use crate::error::*; -use crate::s3_xml; +use crate::s3::error::*; +use crate::s3::xml as s3_xml; use crate::signature::verify_signed_content; pub async fn handle_put( garage: Arc<Garage>, req: Request<Body>, - bucket_id: Uuid, + bucket: &Bucket, key: &str, content_sha256: Option<Hash>, ) -> Result<Response<Body>, Error> { @@ -46,7 +55,7 @@ pub async fn handle_put( garage, headers, body, - bucket_id, + bucket, key, content_md5, content_sha256, @@ -59,7 +68,7 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>( garage: Arc<Garage>, headers: ObjectVersionHeaders, body: S, - bucket_id: Uuid, + bucket: &Bucket, key: &str, content_md5: Option<String>, content_sha256: Option<FixedBytes32>, @@ -80,6 +89,7 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>( let data_md5sum_hex = hex::encode(data_md5sum); let data_sha256sum = sha256sum(&first_block[..]); + let size = first_block.len() as u64; ensure_checksum_matches( data_md5sum.as_slice(), @@ -88,20 +98,22 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>( content_sha256, )?; + check_quotas(&garage, bucket, key, size).await?; + let object_version = ObjectVersion { uuid: version_uuid, timestamp: version_timestamp, state: ObjectVersionState::Complete(ObjectVersionData::Inline( ObjectVersionMeta { headers, - size: first_block.len() as u64, + size, etag: data_md5sum_hex.clone(), }, - first_block, + first_block.to_vec(), )), }; - let object = Object::new(bucket_id, key.into(), vec![object_version]); + let object = Object::new(bucket.id, key.into(), vec![object_version]); garage.object_table.insert(&object).await?; return Ok((version_uuid, data_md5sum_hex)); @@ -114,36 +126,42 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>( timestamp: version_timestamp, state: ObjectVersionState::Uploading(headers.clone()), }; - let object = Object::new(bucket_id, key.into(), vec![object_version.clone()]); + let object = Object::new(bucket.id, key.into(), vec![object_version.clone()]); garage.object_table.insert(&object).await?; // Initialize corresponding entry in version table // Write this entry now, even with empty block list, // to prevent block_ref entries from being deleted (they can be deleted // if the reference a version that isn't found in the version table) - let version = Version::new(version_uuid, bucket_id, key.into(), false); + let version = Version::new(version_uuid, bucket.id, key.into(), false); garage.version_table.insert(&version).await?; // Transfer data and verify checksum - let first_block_hash = blake2sum(&first_block[..]); - let tx_result = read_and_put_blocks( - &garage, - &version, - 1, - first_block, - first_block_hash, - &mut chunker, - ) - .await - .and_then(|(total_size, data_md5sum, data_sha256sum)| { + let first_block_hash = async_blake2sum(first_block.clone()).await; + + let tx_result = (|| async { + let (total_size, data_md5sum, data_sha256sum) = read_and_put_blocks( + &garage, + &version, + 1, + first_block, + first_block_hash, + &mut chunker, + ) + .await?; + ensure_checksum_matches( data_md5sum.as_slice(), data_sha256sum, content_md5.as_deref(), content_sha256, - ) - .map(|()| (total_size, data_md5sum)) - }); + )?; + + check_quotas(&garage, bucket, key, total_size).await?; + + Ok((total_size, data_md5sum)) + })() + .await; // If something went wrong, clean up let (total_size, md5sum_arr) = match tx_result { @@ -151,7 +169,7 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>( Err(e) => { // Mark object as aborted, this will free the blocks further down object_version.state = ObjectVersionState::Aborted; - let object = Object::new(bucket_id, key.into(), vec![object_version.clone()]); + let object = Object::new(bucket.id, key.into(), vec![object_version.clone()]); garage.object_table.insert(&object).await?; return Err(e); } @@ -167,7 +185,7 @@ pub(crate) async fn save_stream<S: Stream<Item = Result<Bytes, Error>> + Unpin>( }, first_block_hash, )); - let object = Object::new(bucket_id, key.into(), vec![object_version]); + let object = Object::new(bucket.id, key.into(), vec![object_version]); garage.object_table.insert(&object).await?; Ok((version_uuid, md5sum_hex)) @@ -183,8 +201,8 @@ fn ensure_checksum_matches( ) -> Result<(), Error> { if let Some(expected_sha256) = content_sha256 { if expected_sha256 != data_sha256sum { - return Err(Error::BadRequest( - "Unable to validate x-amz-content-sha256".to_string(), + return Err(Error::bad_request( + "Unable to validate x-amz-content-sha256", )); } else { trace!("Successfully validated x-amz-content-sha256"); @@ -192,9 +210,7 @@ fn ensure_checksum_matches( } if let Some(expected_md5) = content_md5 { if expected_md5.trim_matches('"') != base64::encode(data_md5sum) { - return Err(Error::BadRequest( - "Unable to validate content-md5".to_string(), - )); + return Err(Error::bad_request("Unable to validate content-md5")); } else { trace!("Successfully validated content-md5"); } @@ -202,18 +218,85 @@ fn ensure_checksum_matches( Ok(()) } +/// Check that inserting this object with this size doesn't exceed bucket quotas +async fn check_quotas( + garage: &Arc<Garage>, + bucket: &Bucket, + key: &str, + size: u64, +) -> Result<(), Error> { + let quotas = bucket.state.as_option().unwrap().quotas.get(); + if quotas.max_objects.is_none() && quotas.max_size.is_none() { + return Ok(()); + }; + + let key = key.to_string(); + let (prev_object, counters) = futures::try_join!( + garage.object_table.get(&bucket.id, &key), + garage.object_counter_table.table.get(&bucket.id, &EmptyKey), + )?; + + let counters = counters + .map(|x| x.filtered_values(&garage.system.ring.borrow())) + .unwrap_or_default(); + + let (prev_cnt_obj, prev_cnt_size) = match prev_object { + Some(o) => { + let prev_cnt = o.counts().into_iter().collect::<HashMap<_, _>>(); + ( + prev_cnt.get(OBJECTS).cloned().unwrap_or_default(), + prev_cnt.get(BYTES).cloned().unwrap_or_default(), + ) + } + None => (0, 0), + }; + let cnt_obj_diff = 1 - prev_cnt_obj; + let cnt_size_diff = size as i64 - prev_cnt_size; + + if let Some(mo) = quotas.max_objects { + let current_objects = counters.get(OBJECTS).cloned().unwrap_or_default(); + if cnt_obj_diff > 0 && current_objects + cnt_obj_diff > mo as i64 { + return Err(Error::forbidden(format!( + "Object quota is reached, maximum objects for this bucket: {}", + mo + ))); + } + } + + if let Some(ms) = quotas.max_size { + let current_size = counters.get(BYTES).cloned().unwrap_or_default(); + if cnt_size_diff > 0 && current_size + cnt_size_diff > ms as i64 { + return Err(Error::forbidden(format!( + "Bucket size quota is reached, maximum total size of objects for this bucket: {}. The bucket is already {} bytes, and this object would add {} bytes.", + ms, current_size, size + ))); + } + } + + Ok(()) +} + async fn read_and_put_blocks<S: Stream<Item = Result<Bytes, Error>> + Unpin>( garage: &Garage, version: &Version, part_number: u64, - first_block: Vec<u8>, + first_block: Bytes, first_block_hash: Hash, chunker: &mut StreamChunker<S>, ) -> Result<(u64, GenericArray<u8, typenum::U16>, Hash), Error> { - let mut md5hasher = Md5::new(); - let mut sha256hasher = Sha256::new(); - md5hasher.update(&first_block[..]); - sha256hasher.update(&first_block[..]); + let tracer = opentelemetry::global::tracer("garage"); + + let md5hasher = AsyncHasher::<Md5>::new(); + let sha256hasher = AsyncHasher::<Sha256>::new(); + + futures::future::join( + md5hasher.update(first_block.clone()), + sha256hasher.update(first_block.clone()), + ) + .with_context(Context::current_with_span( + tracer.start("Hash first block (md5, sha256)"), + )) + .await; let mut next_offset = first_block.len(); let mut put_curr_version_block = put_block_meta( @@ -235,9 +318,15 @@ async fn read_and_put_blocks<S: Stream<Item = Result<Bytes, Error>> + Unpin>( chunker.next(), )?; if let Some(block) = next_block { - md5hasher.update(&block[..]); - sha256hasher.update(&block[..]); - let block_hash = blake2sum(&block[..]); + let (_, _, block_hash) = futures::future::join3( + md5hasher.update(block.clone()), + sha256hasher.update(block.clone()), + async_blake2sum(block.clone()), + ) + .with_context(Context::current_with_span( + tracer.start("Hash block (md5, sha256, blake2)"), + )) + .await; let block_len = block.len(); put_curr_version_block = put_block_meta( garage, @@ -255,9 +344,9 @@ async fn read_and_put_blocks<S: Stream<Item = Result<Bytes, Error>> + Unpin>( } let total_size = next_offset as u64; - let data_md5sum = md5hasher.finalize(); + let data_md5sum = md5hasher.finalize().await; - let data_sha256sum = sha256hasher.finalize(); + let data_sha256sum = sha256hasher.finalize().await; let data_sha256sum = Hash::try_from(&data_sha256sum[..]).unwrap(); Ok((total_size, data_md5sum, data_sha256sum)) @@ -297,7 +386,7 @@ struct StreamChunker<S: Stream<Item = Result<Bytes, Error>>> { stream: S, read_all: bool, block_size: usize, - buf: VecDeque<u8>, + buf: BytesBuf, } impl<S: Stream<Item = Result<Bytes, Error>> + Unpin> StreamChunker<S> { @@ -306,11 +395,11 @@ impl<S: Stream<Item = Result<Bytes, Error>> + Unpin> StreamChunker<S> { stream, read_all: false, block_size, - buf: VecDeque::with_capacity(2 * block_size), + buf: BytesBuf::new(), } } - async fn next(&mut self) -> Result<Option<Vec<u8>>, Error> { + async fn next(&mut self) -> Result<Option<Bytes>, Error> { while !self.read_all && self.buf.len() < self.block_size { if let Some(block) = self.stream.next().await { let bytes = block?; @@ -323,12 +412,8 @@ impl<S: Stream<Item = Result<Bytes, Error>> + Unpin> StreamChunker<S> { if self.buf.is_empty() { Ok(None) - } else if self.buf.len() <= self.block_size { - let block = self.buf.drain(..).collect::<Vec<u8>>(); - Ok(Some(block)) } else { - let block = self.buf.drain(..self.block_size).collect::<Vec<u8>>(); - Ok(Some(block)) + Ok(Some(self.buf.take_max(self.block_size))) } } } @@ -428,7 +513,7 @@ pub async fn handle_put_part( // Check part hasn't already been uploaded if let Some(v) = version { if v.has_part_number(part_number) { - return Err(Error::BadRequest(format!( + return Err(Error::bad_request(format!( "Part number {} has already been uploaded", part_number ))); @@ -437,7 +522,9 @@ pub async fn handle_put_part( // Copy block to store let version = Version::new(version_uuid, bucket_id, key, false); - let first_block_hash = blake2sum(&first_block[..]); + + let first_block_hash = async_blake2sum(first_block.clone()).await; + let (_, data_md5sum, data_sha256sum) = read_and_put_blocks( &garage, &version, @@ -475,7 +562,7 @@ pub async fn handle_complete_multipart_upload( garage: Arc<Garage>, req: Request<Body>, bucket_name: &str, - bucket_id: Uuid, + bucket: &Bucket, key: &str, upload_id: &str, content_sha256: Option<Hash>, @@ -499,7 +586,7 @@ pub async fn handle_complete_multipart_upload( // Get object and version let key = key.to_string(); let (object, version) = futures::try_join!( - garage.object_table.get(&bucket_id, &key), + garage.object_table.get(&bucket.id, &key), garage.version_table.get(&version_uuid, &EmptyKey), )?; @@ -513,7 +600,7 @@ pub async fn handle_complete_multipart_upload( let version = version.ok_or(Error::NoSuchKey)?; if version.blocks.is_empty() { - return Err(Error::BadRequest("No data was uploaded".to_string())); + return Err(Error::bad_request("No data was uploaded")); } let headers = match object_version.state { @@ -574,8 +661,8 @@ pub async fn handle_complete_multipart_upload( .map(|x| x.part_number) .eq(block_parts.into_iter()); if !same_parts { - return Err(Error::BadRequest( - "Part numbers in block list and part list do not match. This can happen if a part was partially uploaded. Please abort the multipart upload and try again.".into(), + return Err(Error::bad_request( + "Part numbers in block list and part list do not match. This can happen if a part was partially uploaded. Please abort the multipart upload and try again." )); } @@ -592,6 +679,14 @@ pub async fn handle_complete_multipart_upload( // Calculate total size of final object let total_size = version.blocks.items().iter().map(|x| x.1.size).sum(); + if let Err(e) = check_quotas(&garage, bucket, &key, total_size).await { + object_version.state = ObjectVersionState::Aborted; + let final_object = Object::new(bucket.id, key.clone(), vec![object_version]); + garage.object_table.insert(&final_object).await?; + + return Err(e); + } + // Write final object version object_version.state = ObjectVersionState::Complete(ObjectVersionData::FirstBlock( ObjectVersionMeta { @@ -602,7 +697,7 @@ pub async fn handle_complete_multipart_upload( version.blocks.items()[0].1.hash, )); - let final_object = Object::new(bucket_id, key.clone(), vec![object_version]); + let final_object = Object::new(bucket.id, key.clone(), vec![object_version]); garage.object_table.insert(&final_object).await?; // Send response saying ok we're done diff --git a/src/api/s3_router.rs b/src/api/s3/router.rs index 95a7eceb..44f581ff 100644 --- a/src/api/s3_router.rs +++ b/src/api/s3/router.rs @@ -1,131 +1,13 @@ -use crate::error::{Error, OkOrBadRequest}; - use std::borrow::Cow; use hyper::header::HeaderValue; use hyper::{HeaderMap, Method, Request}; -/// This macro is used to generate very repetitive match {} blocks in this module -/// It is _not_ made to be used anywhere else -macro_rules! s3_match { - (@match $enum:expr , [ $($endpoint:ident,)* ]) => {{ - // usage: s3_match {@match my_enum, [ VariantWithField1, VariantWithField2 ..] } - // returns true if the variant was one of the listed variants, false otherwise. - use Endpoint::*; - match $enum { - $( - $endpoint { .. } => true, - )* - _ => false - } - }}; - (@extract $enum:expr , $param:ident, [ $($endpoint:ident,)* ]) => {{ - // usage: s3_match {@extract my_enum, field_name, [ VariantWithField1, VariantWithField2 ..] } - // returns Some(field_value), or None if the variant was not one of the listed variants. - use Endpoint::*; - match $enum { - $( - $endpoint {$param, ..} => Some($param), - )* - _ => None - } - }}; - (@gen_parser ($keyword:expr, $key:expr, $query:expr, $header:expr), - key: [$($kw_k:ident $(if $required_k:ident)? $(header $header_k:expr)? => $api_k:ident $(($($conv_k:ident :: $param_k:ident),*))?,)*], - no_key: [$($kw_nk:ident $(if $required_nk:ident)? $(if_header $header_nk:expr)? => $api_nk:ident $(($($conv_nk:ident :: $param_nk:ident),*))?,)*]) => {{ - // usage: s3_match {@gen_parser (keyword, key, query, header), - // key: [ - // SOME_KEYWORD => VariantWithKey, - // ... - // ], - // no_key: [ - // SOME_KEYWORD => VariantWithoutKey, - // ... - // ] - // } - // See in from_{method} for more detailed usage. - use Endpoint::*; - use keywords::*; - match ($keyword, !$key.is_empty()){ - $( - ($kw_k, true) if true $(&& $query.$required_k.is_some())? $(&& $header.contains_key($header_k))? => Ok($api_k { - key: $key, - $($( - $param_k: s3_match!(@@parse_param $query, $conv_k, $param_k), - )*)? - }), - )* - $( - ($kw_nk, false) $(if $query.$required_nk.is_some())? $(if $header.contains($header_nk))? => Ok($api_nk { - $($( - $param_nk: s3_match!(@@parse_param $query, $conv_nk, $param_nk), - )*)? - }), - )* - (kw, _) => Err(Error::BadRequest(format!("Invalid endpoint: {}", kw))) - } - }}; +use crate::helpers::Authorization; +use crate::router_macros::{generateQueryParameters, router_match}; +use crate::s3::error::*; - (@@parse_param $query:expr, query_opt, $param:ident) => {{ - // extract optional query parameter - $query.$param.take().map(|param| param.into_owned()) - }}; - (@@parse_param $query:expr, query, $param:ident) => {{ - // extract mendatory query parameter - $query.$param.take().ok_or_bad_request("Missing argument for endpoint")?.into_owned() - }}; - (@@parse_param $query:expr, opt_parse, $param:ident) => {{ - // extract and parse optional query parameter - // missing parameter is file, however parse error is reported as an error - $query.$param - .take() - .map(|param| param.parse()) - .transpose() - .map_err(|_| Error::BadRequest("Failed to parse query parameter".to_owned()))? - }}; - (@@parse_param $query:expr, parse, $param:ident) => {{ - // extract and parse mandatory query parameter - // both missing and un-parseable parameters are reported as errors - $query.$param.take().ok_or_bad_request("Missing argument for endpoint")? - .parse() - .map_err(|_| Error::BadRequest("Failed to parse query parameter".to_owned()))? - }}; - (@func - $(#[$doc:meta])* - pub enum Endpoint { - $( - $(#[$outer:meta])* - $variant:ident $({ - $($name:ident: $ty:ty,)* - })?, - )* - }) => { - $(#[$doc])* - pub enum Endpoint { - $( - $(#[$outer])* - $variant $({ - $($name: $ty, )* - })?, - )* - } - impl Endpoint { - pub fn name(&self) -> &'static str { - match self { - $(Endpoint::$variant $({ $($name: _,)* .. })? => stringify!($variant),)* - } - } - } - }; - (@if ($($cond:tt)+) then ($($then:tt)*) else ($($else:tt)*)) => { - $($then)* - }; - (@if () then ($($then:tt)*) else ($($else:tt)*)) => { - $($else)* - }; -} - -s3_match! {@func +router_match! {@func /// List of all S3 API endpoints. /// @@ -460,7 +342,7 @@ impl Endpoint { Method::POST => Self::from_post(key, &mut query)?, Method::PUT => Self::from_put(key, &mut query, req.headers())?, Method::DELETE => Self::from_delete(key, &mut query)?, - _ => return Err(Error::BadRequest("Unknown method".to_owned())), + _ => return Err(Error::bad_request("Unknown method")), }; if let Some(message) = query.nonempty_message() { @@ -471,7 +353,7 @@ impl Endpoint { /// Determine which endpoint a request is for, knowing it is a GET. fn from_get(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { - s3_match! { + router_match! { @gen_parser (query.keyword.take().unwrap_or_default().as_ref(), key, query, None), key: [ @@ -528,7 +410,7 @@ impl Endpoint { /// Determine which endpoint a request is for, knowing it is a HEAD. fn from_head(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { - s3_match! { + router_match! { @gen_parser (query.keyword.take().unwrap_or_default().as_ref(), key, query, None), key: [ @@ -542,7 +424,7 @@ impl Endpoint { /// Determine which endpoint a request is for, knowing it is a POST. fn from_post(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { - s3_match! { + router_match! { @gen_parser (query.keyword.take().unwrap_or_default().as_ref(), key, query, None), key: [ @@ -564,7 +446,7 @@ impl Endpoint { query: &mut QueryParameters<'_>, headers: &HeaderMap<HeaderValue>, ) -> Result<Self, Error> { - s3_match! { + router_match! { @gen_parser (query.keyword.take().unwrap_or_default().as_ref(), key, query, headers), key: [ @@ -606,7 +488,7 @@ impl Endpoint { /// Determine which endpoint a request is for, knowing it is a DELETE. fn from_delete(key: String, query: &mut QueryParameters<'_>) -> Result<Self, Error> { - s3_match! { + router_match! { @gen_parser (query.keyword.take().unwrap_or_default().as_ref(), key, query, None), key: [ @@ -636,7 +518,7 @@ impl Endpoint { /// Get the key the request target. Returns None for requests which don't use a key. #[allow(dead_code)] pub fn get_key(&self) -> Option<&str> { - s3_match! { + router_match! { @extract self, key, @@ -673,7 +555,7 @@ impl Endpoint { if let Endpoint::ListBuckets = self { return Authorization::None; }; - let readonly = s3_match! { + let readonly = router_match! { @match self, [ @@ -717,7 +599,7 @@ impl Endpoint { SelectObjectContent, ] }; - let owner = s3_match! { + let owner = router_match! { @match self, [ @@ -740,87 +622,6 @@ impl Endpoint { } } -/// What kind of authorization is required to perform a given action -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Authorization { - /// No authorization is required - None, - /// Having Read permission on bucket - Read, - /// Having Write permission on bucket - Write, - /// Having Owner permission on bucket - Owner, -} - -/// This macro is used to generate part of the code in this module. It must be called only one, and -/// is useless outside of this module. -macro_rules! generateQueryParameters { - ( $($rest:expr => $name:ident),* ) => { - /// Struct containing all query parameters used in endpoints. Think of it as an HashMap, - /// but with keys statically known. - #[derive(Debug, Default)] - struct QueryParameters<'a> { - keyword: Option<Cow<'a, str>>, - $( - $name: Option<Cow<'a, str>>, - )* - } - - impl<'a> QueryParameters<'a> { - /// Build this struct from the query part of an URI. - fn from_query(query: &'a str) -> Result<Self, Error> { - let mut res: Self = Default::default(); - for (k, v) in url::form_urlencoded::parse(query.as_bytes()) { - let repeated = match k.as_ref() { - $( - $rest => if !v.is_empty() { - res.$name.replace(v).is_some() - } else { - false - }, - )* - _ => { - if k.starts_with("response-") || k.starts_with("X-Amz-") { - false - } else if v.as_ref().is_empty() { - if res.keyword.replace(k).is_some() { - return Err(Error::BadRequest("Multiple keywords".to_owned())); - } - continue; - } else { - debug!("Received an unknown query parameter: '{}'", k); - false - } - } - }; - if repeated { - return Err(Error::BadRequest(format!( - "Query parameter repeated: '{}'", - k - ))); - } - } - Ok(res) - } - - /// Get an error message in case not all parameters where used when extracting them to - /// build an Enpoint variant - fn nonempty_message(&self) -> Option<&str> { - if self.keyword.is_some() { - Some("Keyword not used") - } $( - else if self.$name.is_some() { - Some(concat!("'", $rest, "'")) - } - )* else { - None - } - } - } - } -} - // parameter name => struct field generateQueryParameters! { "continuation-token" => continuation_token, diff --git a/src/api/s3_website.rs b/src/api/s3/website.rs index b464dd45..77738971 100644 --- a/src/api/s3_website.rs +++ b/src/api/s3/website.rs @@ -4,13 +4,12 @@ use std::sync::Arc; use hyper::{Body, Request, Response, StatusCode}; use serde::{Deserialize, Serialize}; -use crate::error::*; -use crate::s3_xml::{to_xml_with_header, xmlns_tag, IntValue, Value}; +use crate::s3::error::*; +use crate::s3::xml::{to_xml_with_header, xmlns_tag, IntValue, Value}; use crate::signature::verify_signed_content; use garage_model::bucket_table::*; use garage_model::garage::Garage; -use garage_table::*; use garage_util::data::*; pub async fn handle_get_website(bucket: &Bucket) -> Result<Response<Body>, Error> { @@ -47,14 +46,11 @@ pub async fn handle_delete_website( bucket_id: Uuid, ) -> Result<Response<Body>, Error> { let mut bucket = garage - .bucket_table - .get(&EmptyKey, &bucket_id) - .await? - .ok_or(Error::NoSuchBucket)?; + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; - let param = bucket - .params_mut() - .ok_or_internal_error("Bucket should not be deleted at this point")?; + let param = bucket.params_mut().unwrap(); param.website_config.update(None); garage.bucket_table.insert(&bucket).await?; @@ -77,14 +73,11 @@ pub async fn handle_put_website( } let mut bucket = garage - .bucket_table - .get(&EmptyKey, &bucket_id) - .await? - .ok_or(Error::NoSuchBucket)?; + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; - let param = bucket - .params_mut() - .ok_or_internal_error("Bucket should not be deleted at this point")?; + let param = bucket.params_mut().unwrap(); let conf: WebsiteConfiguration = from_reader(&body as &[u8])?; conf.validate()?; @@ -176,8 +169,8 @@ impl WebsiteConfiguration { || self.index_document.is_some() || self.routing_rules.is_some()) { - return Err(Error::BadRequest( - "Bad XML: can't have RedirectAllRequestsTo and other fields".to_owned(), + return Err(Error::bad_request( + "Bad XML: can't have RedirectAllRequestsTo and other fields", )); } if let Some(ref ed) = self.error_document { @@ -222,8 +215,8 @@ impl WebsiteConfiguration { impl Key { pub fn validate(&self) -> Result<(), Error> { if self.key.0.is_empty() { - Err(Error::BadRequest( - "Bad XML: error document specified but empty".to_owned(), + Err(Error::bad_request( + "Bad XML: error document specified but empty", )) } else { Ok(()) @@ -234,8 +227,8 @@ impl Key { impl Suffix { pub fn validate(&self) -> Result<(), Error> { if self.suffix.0.is_empty() | self.suffix.0.contains('/') { - Err(Error::BadRequest( - "Bad XML: index document is empty or contains /".to_owned(), + Err(Error::bad_request( + "Bad XML: index document is empty or contains /", )) } else { Ok(()) @@ -247,7 +240,7 @@ impl Target { pub fn validate(&self) -> Result<(), Error> { if let Some(ref protocol) = self.protocol { if protocol.0 != "http" && protocol.0 != "https" { - return Err(Error::BadRequest("Bad XML: invalid protocol".to_owned())); + return Err(Error::bad_request("Bad XML: invalid protocol")); } } Ok(()) @@ -269,19 +262,19 @@ impl Redirect { pub fn validate(&self, has_prefix: bool) -> Result<(), Error> { if self.replace_prefix.is_some() { if self.replace_full.is_some() { - return Err(Error::BadRequest( - "Bad XML: both ReplaceKeyPrefixWith and ReplaceKeyWith are set".to_owned(), + return Err(Error::bad_request( + "Bad XML: both ReplaceKeyPrefixWith and ReplaceKeyWith are set", )); } if !has_prefix { - return Err(Error::BadRequest( - "Bad XML: ReplaceKeyPrefixWith is set, but KeyPrefixEquals isn't".to_owned(), + return Err(Error::bad_request( + "Bad XML: ReplaceKeyPrefixWith is set, but KeyPrefixEquals isn't", )); } } if let Some(ref protocol) = self.protocol { if protocol.0 != "http" && protocol.0 != "https" { - return Err(Error::BadRequest("Bad XML: invalid protocol".to_owned())); + return Err(Error::bad_request("Bad XML: invalid protocol")); } } // TODO there are probably more invalide cases, but which ones? diff --git a/src/api/s3_xml.rs b/src/api/s3/xml.rs index 75ec4559..06f11288 100644 --- a/src/api/s3_xml.rs +++ b/src/api/s3/xml.rs @@ -1,7 +1,7 @@ use quick_xml::se::to_string; use serde::{Deserialize, Serialize, Serializer}; -use crate::Error as ApiError; +use crate::s3::error::Error as ApiError; pub fn to_xml_with_header<T: Serialize>(x: &T) -> Result<String, ApiError> { let mut xml = r#"<?xml version="1.0" encoding="UTF-8"?>"#.to_string(); @@ -25,7 +25,7 @@ impl From<&str> for Value { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct IntValue(#[serde(rename = "$value")] pub i64); -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct Bucket { #[serde(rename = "CreationDate")] pub creation_date: Value, @@ -33,7 +33,7 @@ pub struct Bucket { pub name: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct Owner { #[serde(rename = "DisplayName")] pub display_name: Value, @@ -41,13 +41,13 @@ pub struct Owner { pub id: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct BucketList { #[serde(rename = "Bucket")] pub entries: Vec<Bucket>, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct ListAllMyBucketsResult { #[serde(rename = "Buckets")] pub buckets: BucketList, @@ -55,7 +55,7 @@ pub struct ListAllMyBucketsResult { pub owner: Owner, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct LocationConstraint { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), @@ -63,7 +63,7 @@ pub struct LocationConstraint { pub region: String, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct Deleted { #[serde(rename = "Key")] pub key: Value, @@ -73,7 +73,7 @@ pub struct Deleted { pub delete_marker_version_id: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct Error { #[serde(rename = "Code")] pub code: Value, @@ -85,7 +85,7 @@ pub struct Error { pub region: Option<Value>, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct DeleteError { #[serde(rename = "Code")] pub code: Value, @@ -97,7 +97,7 @@ pub struct DeleteError { pub version_id: Option<Value>, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct DeleteResult { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), @@ -107,7 +107,7 @@ pub struct DeleteResult { pub errors: Vec<DeleteError>, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct InitiateMultipartUploadResult { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), @@ -119,7 +119,7 @@ pub struct InitiateMultipartUploadResult { pub upload_id: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct CompleteMultipartUploadResult { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), @@ -133,7 +133,7 @@ pub struct CompleteMultipartUploadResult { pub etag: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct Initiator { #[serde(rename = "DisplayName")] pub display_name: Value, @@ -141,7 +141,7 @@ pub struct Initiator { pub id: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct ListMultipartItem { #[serde(rename = "Initiated")] pub initiated: Value, @@ -157,7 +157,7 @@ pub struct ListMultipartItem { pub storage_class: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct ListMultipartUploadsResult { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), @@ -187,7 +187,7 @@ pub struct ListMultipartUploadsResult { pub encoding_type: Option<Value>, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct PartItem { #[serde(rename = "ETag")] pub etag: Value, @@ -199,7 +199,7 @@ pub struct PartItem { pub size: IntValue, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct ListPartsResult { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), @@ -227,7 +227,7 @@ pub struct ListPartsResult { pub storage_class: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct ListBucketItem { #[serde(rename = "Key")] pub key: Value, @@ -241,13 +241,13 @@ pub struct ListBucketItem { pub storage_class: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct CommonPrefix { #[serde(rename = "Prefix")] pub prefix: Value, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct ListBucketResult { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), @@ -281,7 +281,7 @@ pub struct ListBucketResult { pub common_prefixes: Vec<CommonPrefix>, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct VersioningConfiguration { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), @@ -289,7 +289,7 @@ pub struct VersioningConfiguration { pub status: Option<Value>, } -#[derive(Debug, Serialize, PartialEq)] +#[derive(Debug, Serialize, PartialEq, Eq)] pub struct PostObject { #[serde(serialize_with = "xmlns_tag")] pub xmlns: (), diff --git a/src/api/signature/error.rs b/src/api/signature/error.rs new file mode 100644 index 00000000..f5a067bd --- /dev/null +++ b/src/api/signature/error.rs @@ -0,0 +1,36 @@ +use err_derive::Error; + +use crate::common_error::CommonError; +pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError}; + +/// Errors of this crate +#[derive(Debug, Error)] +pub enum Error { + #[error(display = "{}", _0)] + /// Error from common error + Common(CommonError), + + /// Authorization Header Malformed + #[error(display = "Authorization header malformed, expected scope: {}", _0)] + AuthorizationHeaderMalformed(String), + + // Category: bad request + /// The request contained an invalid UTF-8 sequence in its path or in other parameters + #[error(display = "Invalid UTF-8: {}", _0)] + InvalidUtf8Str(#[error(source)] std::str::Utf8Error), + + /// The client sent a header with invalid value + #[error(display = "Invalid header value: {}", _0)] + InvalidHeader(#[error(source)] hyper::header::ToStrError), +} + +impl<T> From<T> for Error +where + CommonError: From<T>, +{ + fn from(err: T) -> Self { + Error::Common(CommonError::from(err)) + } +} + +impl CommonErrorDerivative for Error {} diff --git a/src/api/signature/mod.rs b/src/api/signature/mod.rs index ebdee6da..4b8b990f 100644 --- a/src/api/signature/mod.rs +++ b/src/api/signature/mod.rs @@ -1,14 +1,15 @@ use chrono::{DateTime, Utc}; -use hmac::{Hmac, Mac, NewMac}; +use hmac::{Hmac, Mac}; use sha2::Sha256; use garage_util::data::{sha256sum, Hash}; -use crate::error::*; - +pub mod error; pub mod payload; pub mod streaming; +use error::*; + pub const SHORT_DATE: &str = "%Y%m%d"; pub const LONG_DATETIME: &str = "%Y%m%dT%H%M%SZ"; @@ -16,7 +17,7 @@ type HmacSha256 = Hmac<Sha256>; pub fn verify_signed_content(expected_sha256: Hash, body: &[u8]) -> Result<(), Error> { if expected_sha256 != sha256sum(body) { - return Err(Error::BadRequest( + return Err(Error::bad_request( "Request content hash does not match signed hash".to_string(), )); } @@ -28,20 +29,25 @@ pub fn signing_hmac( secret_key: &str, region: &str, service: &str, -) -> Result<HmacSha256, crypto_mac::InvalidKeyLength> { +) -> Result<HmacSha256, crypto_common::InvalidLength> { let secret = String::from("AWS4") + secret_key; - let mut date_hmac = HmacSha256::new_varkey(secret.as_bytes())?; + let mut date_hmac = HmacSha256::new_from_slice(secret.as_bytes())?; date_hmac.update(datetime.format(SHORT_DATE).to_string().as_bytes()); - let mut region_hmac = HmacSha256::new_varkey(&date_hmac.finalize().into_bytes())?; + let mut region_hmac = HmacSha256::new_from_slice(&date_hmac.finalize().into_bytes())?; region_hmac.update(region.as_bytes()); - let mut service_hmac = HmacSha256::new_varkey(®ion_hmac.finalize().into_bytes())?; + let mut service_hmac = HmacSha256::new_from_slice(®ion_hmac.finalize().into_bytes())?; service_hmac.update(service.as_bytes()); - let mut signing_hmac = HmacSha256::new_varkey(&service_hmac.finalize().into_bytes())?; + let mut signing_hmac = HmacSha256::new_from_slice(&service_hmac.finalize().into_bytes())?; signing_hmac.update(b"aws4_request"); - let hmac = HmacSha256::new_varkey(&signing_hmac.finalize().into_bytes())?; + let hmac = HmacSha256::new_from_slice(&signing_hmac.finalize().into_bytes())?; Ok(hmac) } -pub fn compute_scope(datetime: &DateTime<Utc>, region: &str) -> String { - format!("{}/{}/s3/aws4_request", datetime.format(SHORT_DATE), region,) +pub fn compute_scope(datetime: &DateTime<Utc>, region: &str, service: &str) -> String { + format!( + "{}/{}/{}/aws4_request", + datetime.format(SHORT_DATE), + region, + service + ) } diff --git a/src/api/signature/payload.rs b/src/api/signature/payload.rs index 2a41b307..4c7934e5 100644 --- a/src/api/signature/payload.rs +++ b/src/api/signature/payload.rs @@ -11,14 +11,15 @@ use garage_util::data::Hash; use garage_model::garage::Garage; use garage_model::key_table::*; -use super::signing_hmac; -use super::{LONG_DATETIME, SHORT_DATE}; +use super::LONG_DATETIME; +use super::{compute_scope, signing_hmac}; use crate::encoding::uri_encode; -use crate::error::*; +use crate::signature::error::*; pub async fn check_payload_signature( garage: &Garage, + service: &str, request: &Request<Body>, ) -> Result<(Option<Key>, Option<Hash>), Error> { let mut headers = HashMap::new(); @@ -64,6 +65,7 @@ pub async fn check_payload_signature( let key = verify_v4( garage, + service, &authorization.credential, &authorization.date, &authorization.signature, @@ -103,7 +105,7 @@ fn parse_authorization( let (auth_kind, rest) = authorization.split_at(first_space); if auth_kind != "AWS4-HMAC-SHA256" { - return Err(Error::BadRequest("Unsupported authorization method".into())); + return Err(Error::bad_request("Unsupported authorization method")); } let mut auth_params = HashMap::new(); @@ -127,10 +129,11 @@ fn parse_authorization( let date = headers .get("x-amz-date") .ok_or_bad_request("Missing X-Amz-Date field") + .map_err(Error::from) .and_then(|d| parse_date(d))?; if Utc::now() - date > Duration::hours(24) { - return Err(Error::BadRequest("Date is too old".to_string())); + return Err(Error::bad_request("Date is too old".to_string())); } let auth = Authorization { @@ -154,7 +157,7 @@ fn parse_query_authorization( headers: &HashMap<String, String>, ) -> Result<Authorization, Error> { if algorithm != "AWS4-HMAC-SHA256" { - return Err(Error::BadRequest( + return Err(Error::bad_request( "Unsupported authorization method".to_string(), )); } @@ -177,10 +180,10 @@ fn parse_query_authorization( .get("x-amz-expires") .ok_or_bad_request("X-Amz-Expires not found in query parameters")? .parse() - .map_err(|_| Error::BadRequest("X-Amz-Expires is not a number".to_string()))?; + .map_err(|_| Error::bad_request("X-Amz-Expires is not a number".to_string()))?; if duration > 7 * 24 * 3600 { - return Err(Error::BadRequest( + return Err(Error::bad_request( "X-Amz-Exprires may not exceed a week".to_string(), )); } @@ -188,10 +191,11 @@ fn parse_query_authorization( let date = headers .get("x-amz-date") .ok_or_bad_request("Missing X-Amz-Date field") + .map_err(Error::from) .and_then(|d| parse_date(d))?; if Utc::now() - date > Duration::seconds(duration) { - return Err(Error::BadRequest("Date is too old".to_string())); + return Err(Error::bad_request("Date is too old".to_string())); } Ok(Authorization { @@ -281,6 +285,7 @@ pub fn parse_date(date: &str) -> Result<DateTime<Utc>, Error> { pub async fn verify_v4( garage: &Garage, + service: &str, credential: &str, date: &DateTime<Utc>, signature: &str, @@ -288,11 +293,7 @@ pub async fn verify_v4( ) -> Result<Key, Error> { let (key_id, scope) = parse_credential(credential)?; - let scope_expected = format!( - "{}/{}/s3/aws4_request", - date.format(SHORT_DATE), - garage.config.s3_api.s3_region - ); + let scope_expected = compute_scope(date, &garage.config.s3_api.s3_region, service); if scope != scope_expected { return Err(Error::AuthorizationHeaderMalformed(scope.to_string())); } @@ -302,20 +303,20 @@ pub async fn verify_v4( .get(&EmptyKey, &key_id) .await? .filter(|k| !k.state.is_deleted()) - .ok_or_else(|| Error::Forbidden(format!("No such key: {}", &key_id)))?; + .ok_or_else(|| Error::forbidden(format!("No such key: {}", &key_id)))?; let key_p = key.params().unwrap(); let mut hmac = signing_hmac( date, &key_p.secret_key, &garage.config.s3_api.s3_region, - "s3", + service, ) .ok_or_internal_error("Unable to build signing HMAC")?; hmac.update(payload); let our_signature = hex::encode(hmac.finalize().into_bytes()); if signature != our_signature { - return Err(Error::Forbidden("Invalid signature".to_string())); + return Err(Error::forbidden("Invalid signature".to_string())); } Ok(key) diff --git a/src/api/signature/streaming.rs b/src/api/signature/streaming.rs index 969a45d6..c8358c4f 100644 --- a/src/api/signature/streaming.rs +++ b/src/api/signature/streaming.rs @@ -1,18 +1,67 @@ use std::pin::Pin; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, NaiveDateTime, Utc}; use futures::prelude::*; use futures::task; +use garage_model::key_table::Key; +use hmac::Mac; use hyper::body::Bytes; +use hyper::{Body, Request}; use garage_util::data::Hash; -use hmac::Mac; - -use super::sha256sum; -use super::HmacSha256; -use super::LONG_DATETIME; -use crate::error::*; +use super::{compute_scope, sha256sum, HmacSha256, LONG_DATETIME}; + +use crate::signature::error::*; + +pub fn parse_streaming_body( + api_key: &Key, + req: Request<Body>, + content_sha256: &mut Option<Hash>, + region: &str, + service: &str, +) -> Result<Request<Body>, Error> { + match req.headers().get("x-amz-content-sha256") { + Some(header) if header == "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" => { + let signature = content_sha256 + .take() + .ok_or_bad_request("No signature provided")?; + + let secret_key = &api_key + .state + .as_option() + .ok_or_internal_error("Deleted key state")? + .secret_key; + + let date = req + .headers() + .get("x-amz-date") + .ok_or_bad_request("Missing X-Amz-Date field")? + .to_str()?; + let date: NaiveDateTime = NaiveDateTime::parse_from_str(date, LONG_DATETIME) + .ok_or_bad_request("Invalid date")?; + let date: DateTime<Utc> = DateTime::from_utc(date, Utc); + + let scope = compute_scope(&date, region, service); + let signing_hmac = crate::signature::signing_hmac(&date, secret_key, region, service) + .ok_or_internal_error("Unable to build signing HMAC")?; + + Ok(req.map(move |body| { + Body::wrap_stream( + SignedPayloadStream::new( + body.map_err(Error::from), + signing_hmac, + date, + &scope, + signature, + ) + .map_err(Error::from), + ) + })) + } + _ => Ok(req), + } +} /// Result of `sha256("")` const EMPTY_STRING_HEX_DIGEST: &str = @@ -38,7 +87,7 @@ fn compute_streaming_payload_signature( let mut hmac = signing_hmac.clone(); hmac.update(string_to_sign.as_bytes()); - Hash::try_from(&hmac.finalize().into_bytes()).ok_or_internal_error("Invalid signature") + Ok(Hash::try_from(&hmac.finalize().into_bytes()).ok_or_internal_error("Invalid signature")?) } mod payload { @@ -114,10 +163,10 @@ impl From<SignedPayloadStreamError> for Error { match err { SignedPayloadStreamError::Stream(e) => e, SignedPayloadStreamError::InvalidSignature => { - Error::BadRequest("Invalid payload signature".into()) + Error::bad_request("Invalid payload signature") } SignedPayloadStreamError::Message(e) => { - Error::BadRequest(format!("Chunk format error: {}", e)) + Error::bad_request(format!("Chunk format error: {}", e)) } } } @@ -295,7 +344,7 @@ mod tests { .with_timezone(&Utc); let secret_key = "test"; let region = "test"; - let scope = crate::signature::compute_scope(&datetime, region); + let scope = crate::signature::compute_scope(&datetime, region, "s3"); let signing_hmac = crate::signature::signing_hmac(&datetime, secret_key, region, "s3").unwrap(); diff --git a/src/block/Cargo.toml b/src/block/Cargo.toml index 9cba69ee..cd409001 100644 --- a/src/block/Cargo.toml +++ b/src/block/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "garage_block" -version = "0.7.0" +version = "0.8.0" authors = ["Alex Auvolat <alex@adnab.me>"] edition = "2018" license = "AGPL-3.0" @@ -14,20 +14,22 @@ path = "lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -garage_rpc = { version = "0.7.0", path = "../rpc" } -garage_util = { version = "0.7.0", path = "../util" } -garage_table = { version = "0.7.0", path = "../table" } +garage_db = { version = "0.8.0", path = "../db" } +garage_rpc = { version = "0.8.0", path = "../rpc" } +garage_util = { version = "0.8.0", path = "../util" } +garage_table = { version = "0.8.0", path = "../table" } opentelemetry = "0.17" +arc-swap = "1.5" async-trait = "0.1.7" bytes = "1.0" hex = "0.4" tracing = "0.1.30" rand = "0.8" -zstd = { version = "0.9", default-features = false } -sled = "0.34" +async-compression = { version = "0.3", features = ["tokio", "zstd"] } +zstd = { version = "0.9", default-features = false } rmp-serde = "0.15" serde = { version = "1.0", default-features = false, features = ["derive", "rc"] } @@ -36,3 +38,7 @@ serde_bytes = "0.11" futures = "0.3" futures-util = "0.3" tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] } +tokio-util = { version = "0.6", features = ["io"] } + +[features] +system-libs = [ "zstd/pkg-config" ] diff --git a/src/block/block.rs b/src/block/block.rs index 4d3fbcb8..935aa900 100644 --- a/src/block/block.rs +++ b/src/block/block.rs @@ -1,16 +1,22 @@ +use bytes::Bytes; use serde::{Deserialize, Serialize}; use zstd::stream::{decode_all as zstd_decode, Encoder}; use garage_util::data::*; use garage_util::error::*; +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +pub enum DataBlockHeader { + Plain, + Compressed, +} + /// A possibly compressed block of data -#[derive(Debug, Serialize, Deserialize)] pub enum DataBlock { /// Uncompressed data - Plain(#[serde(with = "serde_bytes")] Vec<u8>), + Plain(Bytes), /// Data compressed with zstd - Compressed(#[serde(with = "serde_bytes")] Vec<u8>), + Compressed(Bytes), } impl DataBlock { @@ -30,7 +36,7 @@ impl DataBlock { /// Get the buffer, possibly decompressing it, and verify it's integrity. /// For Plain block, data is compared to hash, for Compressed block, zstd checksumming system /// is used instead. - pub fn verify_get(self, hash: Hash) -> Result<Vec<u8>, Error> { + pub fn verify_get(self, hash: Hash) -> Result<Bytes, Error> { match self { DataBlock::Plain(data) => { if blake2sum(&data) == hash { @@ -39,9 +45,9 @@ impl DataBlock { Err(Error::CorruptData(hash)) } } - DataBlock::Compressed(data) => { - zstd_decode(&data[..]).map_err(|_| Error::CorruptData(hash)) - } + DataBlock::Compressed(data) => zstd_decode(&data[..]) + .map_err(|_| Error::CorruptData(hash)) + .map(Bytes::from), } } @@ -61,13 +67,31 @@ impl DataBlock { } } - pub fn from_buffer(data: Vec<u8>, level: Option<i32>) -> DataBlock { - if let Some(level) = level { - if let Ok(data) = zstd_encode(&data[..], level) { - return DataBlock::Compressed(data); + pub async fn from_buffer(data: Bytes, level: Option<i32>) -> DataBlock { + tokio::task::spawn_blocking(move || { + if let Some(level) = level { + if let Ok(data) = zstd_encode(&data[..], level) { + return DataBlock::Compressed(data.into()); + } } + DataBlock::Plain(data) + }) + .await + .unwrap() + } + + pub fn into_parts(self) -> (DataBlockHeader, Bytes) { + match self { + DataBlock::Plain(data) => (DataBlockHeader::Plain, data), + DataBlock::Compressed(data) => (DataBlockHeader::Compressed, data), + } + } + + pub fn from_parts(h: DataBlockHeader, bytes: Bytes) -> Self { + match h { + DataBlockHeader::Plain => DataBlock::Plain(bytes), + DataBlockHeader::Compressed => DataBlock::Compressed(bytes), } - DataBlock::Plain(data) } } diff --git a/src/block/lib.rs b/src/block/lib.rs index dc685657..d2814f77 100644 --- a/src/block/lib.rs +++ b/src/block/lib.rs @@ -2,6 +2,8 @@ extern crate tracing; pub mod manager; +pub mod repair; +pub mod resync; mod block; mod metrics; diff --git a/src/block/manager.rs b/src/block/manager.rs index 1c04a335..7f439b96 100644 --- a/src/block/manager.rs +++ b/src/block/manager.rs @@ -1,29 +1,32 @@ -use std::convert::TryInto; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; +use std::pin::Pin; use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; +use bytes::Bytes; use serde::{Deserialize, Serialize}; -use futures::future::*; -use futures::select; +use futures::Stream; +use futures_util::stream::StreamExt; use tokio::fs; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::sync::{watch, Mutex, Notify}; +use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader}; +use tokio::sync::{mpsc, Mutex, MutexGuard}; use opentelemetry::{ trace::{FutureExt as OtelFutureExt, TraceContextExt, Tracer}, - Context, KeyValue, + Context, }; +use garage_rpc::rpc_helper::netapp::stream::{stream_asyncread, ByteStream}; + +use garage_db as db; + use garage_util::data::*; use garage_util::error::*; use garage_util::metrics::RecordDuration; -use garage_util::sled_counter::SledCountedTree; -use garage_util::time::*; -use garage_util::tranquilizer::Tranquilizer; +use garage_rpc::rpc_helper::OrderTag; use garage_rpc::system::System; use garage_rpc::*; @@ -32,24 +35,12 @@ use garage_table::replication::{TableReplication, TableShardedReplication}; use crate::block::*; use crate::metrics::*; use crate::rc::*; +use crate::repair::*; +use crate::resync::*; /// Size under which data will be stored inlined in database instead of as files pub const INLINE_THRESHOLD: usize = 3072; -// Timeout for RPCs that read and write blocks to remote nodes -const BLOCK_RW_TIMEOUT: Duration = Duration::from_secs(30); -// Timeout for RPCs that ask other nodes whether they need a copy -// of a given block before we delete it locally -const NEED_BLOCK_QUERY_TIMEOUT: Duration = Duration::from_secs(5); - -// The delay between the time where a resync operation fails -// and the time when it is retried, with exponential backoff -// (multiplied by 2, 4, 8, 16, etc. for every consecutive failure). -const RESYNC_RETRY_DELAY: Duration = Duration::from_secs(60); -// The minimum retry delay is 60 seconds = 1 minute -// The maximum retry delay is 60 seconds * 2^6 = 60 seconds << 6 = 64 minutes (~1 hour) -const RESYNC_RETRY_DELAY_MAX_BACKOFF_POWER: u64 = 6; - // The delay between the moment when the reference counter // drops to zero, and the moment where we allow ourselves // to delete the block locally. @@ -60,12 +51,12 @@ pub(crate) const BLOCK_GC_DELAY: Duration = Duration::from_secs(600); pub enum BlockRpc { Ok, /// Message to ask for a block of data, by hash - GetBlock(Hash), + GetBlock(Hash, Option<OrderTag>), /// Message to send a block of data, either because requested, of for first delivery of new /// block PutBlock { hash: Hash, - data: DataBlock, + header: DataBlockHeader, }, /// Ask other node if they should have this block, but don't actually have it NeedBlockQuery(Hash), @@ -85,20 +76,18 @@ pub struct BlockManager { pub data_dir: PathBuf, compression_level: Option<i32>, - background_tranquility: u32, - mutation_lock: Mutex<BlockManagerLocked>, + mutation_lock: [Mutex<BlockManagerLocked>; 256], - rc: BlockRc, + pub(crate) rc: BlockRc, + pub resync: BlockResyncManager, - resync_queue: SledCountedTree, - resync_notify: Notify, - resync_errors: SledCountedTree, + pub(crate) system: Arc<System>, + pub(crate) endpoint: Arc<Endpoint<BlockRpc, Self>>, - system: Arc<System>, - endpoint: Arc<Endpoint<BlockRpc, Self>>, + pub(crate) metrics: BlockManagerMetrics, - metrics: BlockManagerMetrics, + tx_scrub_command: mpsc::Sender<ScrubWorkerCommand>, } // This custom struct contains functions that must only be ran @@ -108,10 +97,9 @@ struct BlockManagerLocked(); impl BlockManager { pub fn new( - db: &sled::Db, + db: &db::Db, data_dir: PathBuf, compression_level: Option<i32>, - background_tranquility: u32, replication: TableShardedReplication, system: Arc<System>, ) -> Arc<Self> { @@ -120,215 +108,323 @@ impl BlockManager { .expect("Unable to open block_local_rc tree"); let rc = BlockRc::new(rc); - let resync_queue = db - .open_tree("block_local_resync_queue") - .expect("Unable to open block_local_resync_queue tree"); - let resync_queue = SledCountedTree::new(resync_queue); - - let resync_errors = db - .open_tree("block_local_resync_errors") - .expect("Unable to open block_local_resync_errors tree"); - let resync_errors = SledCountedTree::new(resync_errors); + let resync = BlockResyncManager::new(db, &system); let endpoint = system .netapp - .endpoint("garage_model/block.rs/Rpc".to_string()); + .endpoint("garage_block/manager.rs/Rpc".to_string()); - let manager_locked = BlockManagerLocked(); + let metrics = BlockManagerMetrics::new(resync.queue.clone(), resync.errors.clone()); - let metrics = BlockManagerMetrics::new(resync_queue.clone(), resync_errors.clone()); + let (scrub_tx, scrub_rx) = mpsc::channel(1); let block_manager = Arc::new(Self { replication, data_dir, compression_level, - background_tranquility, - mutation_lock: Mutex::new(manager_locked), + mutation_lock: [(); 256].map(|_| Mutex::new(BlockManagerLocked())), rc, - resync_queue, - resync_notify: Notify::new(), - resync_errors, + resync, system, endpoint, metrics, + tx_scrub_command: scrub_tx, }); block_manager.endpoint.set_handler(block_manager.clone()); - block_manager.clone().spawn_background_worker(); + // Spawn a bunch of resync workers + for index in 0..MAX_RESYNC_WORKERS { + let worker = ResyncWorker::new(index, block_manager.clone()); + block_manager.system.background.spawn_worker(worker); + } + + // Spawn scrub worker + let scrub_worker = ScrubWorker::new(block_manager.clone(), scrub_rx); + block_manager.system.background.spawn_worker(scrub_worker); block_manager } /// Ask nodes that might have a (possibly compressed) block for it - async fn rpc_get_raw_block(&self, hash: &Hash) -> Result<DataBlock, Error> { + /// Return it as a stream with a header + async fn rpc_get_raw_block_streaming( + &self, + hash: &Hash, + order_tag: Option<OrderTag>, + ) -> Result<(DataBlockHeader, ByteStream), Error> { let who = self.replication.read_nodes(hash); - let resps = self - .system - .rpc - .try_call_many( - &self.endpoint, - &who[..], - BlockRpc::GetBlock(*hash), - RequestStrategy::with_priority(PRIO_NORMAL) - .with_quorum(1) - .with_timeout(BLOCK_RW_TIMEOUT) - .interrupt_after_quorum(true), - ) - .await?; + let who = self.system.rpc.request_order(&who); + + for node in who.iter() { + let node_id = NodeID::from(*node); + let rpc = self.endpoint.call_streaming( + &node_id, + BlockRpc::GetBlock(*hash, order_tag), + PRIO_NORMAL | PRIO_SECONDARY, + ); + tokio::select! { + res = rpc => { + let res = match res { + Ok(res) => res, + Err(e) => { + debug!("Node {:?} returned error: {}", node, e); + continue; + } + }; + let (header, stream) = match res.into_parts() { + (Ok(BlockRpc::PutBlock { hash: _, header }), Some(stream)) => (header, stream), + _ => { + debug!("Node {:?} returned a malformed response", node); + continue; + } + }; + return Ok((header, stream)); + } + _ = tokio::time::sleep(self.system.rpc.rpc_timeout()) => { + debug!("Node {:?} didn't return block in time, trying next.", node); + } + }; + } - for resp in resps { - if let BlockRpc::PutBlock { data, .. } = resp { - return Ok(data); - } + Err(Error::Message(format!( + "Unable to read block {:?}: no node returned a valid block", + hash + ))) + } + + /// Ask nodes that might have a (possibly compressed) block for it + /// Return its entire body + pub(crate) async fn rpc_get_raw_block( + &self, + hash: &Hash, + order_tag: Option<OrderTag>, + ) -> Result<DataBlock, Error> { + let who = self.replication.read_nodes(hash); + let who = self.system.rpc.request_order(&who); + + for node in who.iter() { + let node_id = NodeID::from(*node); + let rpc = self.endpoint.call_streaming( + &node_id, + BlockRpc::GetBlock(*hash, order_tag), + PRIO_NORMAL | PRIO_SECONDARY, + ); + tokio::select! { + res = rpc => { + let res = match res { + Ok(res) => res, + Err(e) => { + debug!("Node {:?} returned error: {}", node, e); + continue; + } + }; + let (header, stream) = match res.into_parts() { + (Ok(BlockRpc::PutBlock { hash: _, header }), Some(stream)) => (header, stream), + _ => { + debug!("Node {:?} returned a malformed response", node); + continue; + } + }; + match read_stream_to_end(stream).await { + Ok(bytes) => return Ok(DataBlock::from_parts(header, bytes)), + Err(e) => { + debug!("Error reading stream from node {:?}: {}", node, e); + } + } + } + _ = tokio::time::sleep(self.system.rpc.rpc_timeout()) => { + debug!("Node {:?} didn't return block in time, trying next.", node); + } + }; } + Err(Error::Message(format!( - "Unable to read block {:?}: no valid blocks returned", + "Unable to read block {:?}: no node returned a valid block", hash ))) } // ---- Public interface ---- + /// Ask nodes that might have a block for it, + /// return it as a stream + pub async fn rpc_get_block_streaming( + &self, + hash: &Hash, + order_tag: Option<OrderTag>, + ) -> Result< + Pin<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send + Sync + 'static>>, + Error, + > { + let (header, stream) = self.rpc_get_raw_block_streaming(hash, order_tag).await?; + match header { + DataBlockHeader::Plain => Ok(stream), + DataBlockHeader::Compressed => { + // Too many things, I hate it. + let reader = stream_asyncread(stream); + let reader = BufReader::new(reader); + let reader = async_compression::tokio::bufread::ZstdDecoder::new(reader); + Ok(Box::pin(tokio_util::io::ReaderStream::new(reader))) + } + } + } + /// Ask nodes that might have a block for it - pub async fn rpc_get_block(&self, hash: &Hash) -> Result<Vec<u8>, Error> { - self.rpc_get_raw_block(hash).await?.verify_get(*hash) + pub async fn rpc_get_block( + &self, + hash: &Hash, + order_tag: Option<OrderTag>, + ) -> Result<Bytes, Error> { + self.rpc_get_raw_block(hash, order_tag) + .await? + .verify_get(*hash) } /// Send block to nodes that should have it - pub async fn rpc_put_block(&self, hash: Hash, data: Vec<u8>) -> Result<(), Error> { + pub async fn rpc_put_block(&self, hash: Hash, data: Bytes) -> Result<(), Error> { let who = self.replication.write_nodes(&hash); - let data = DataBlock::from_buffer(data, self.compression_level); + + let (header, bytes) = DataBlock::from_buffer(data, self.compression_level) + .await + .into_parts(); + let put_block_rpc = + Req::new(BlockRpc::PutBlock { hash, header })?.with_stream_from_buffer(bytes); + self.system .rpc .try_call_many( &self.endpoint, &who[..], - BlockRpc::PutBlock { hash, data }, - RequestStrategy::with_priority(PRIO_NORMAL) - .with_quorum(self.replication.write_quorum()) - .with_timeout(BLOCK_RW_TIMEOUT), + put_block_rpc, + RequestStrategy::with_priority(PRIO_NORMAL | PRIO_SECONDARY) + .with_quorum(self.replication.write_quorum()), ) .await?; - Ok(()) - } - /// Launch the repair procedure on the data store - /// - /// This will list all blocks locally present, as well as those - /// that are required because of refcount > 0, and will try - /// to fix any mismatch between the two. - pub async fn repair_data_store(&self, must_exit: &watch::Receiver<bool>) -> Result<(), Error> { - // 1. Repair blocks from RC table. - for (i, entry) in self.rc.rc.iter().enumerate() { - let (hash, _) = entry?; - let hash = Hash::try_from(&hash[..]).unwrap(); - self.put_to_resync(&hash, Duration::from_secs(0))?; - if i & 0xFF == 0 && *must_exit.borrow() { - return Ok(()); - } - } - - // 2. Repair blocks actually on disk - // Lists all blocks on disk and adds them to the resync queue. - // This allows us to find blocks we are storing but don't actually need, - // so that we can offload them if necessary and then delete them locally. - self.for_each_file( - (), - move |_, hash| async move { - self.put_to_resync(&hash, Duration::from_secs(0)) - .map_err(Into::into) - }, - must_exit, - ) - .await - } - - /// Verify integrity of each block on disk. Use `speed_limit` to limit the load generated by - /// this function. - pub async fn scrub_data_store( - &self, - must_exit: &watch::Receiver<bool>, - tranquility: u32, - ) -> Result<(), Error> { - let tranquilizer = Tranquilizer::new(30); - self.for_each_file( - tranquilizer, - move |mut tranquilizer, hash| async move { - let _ = self.read_block(&hash).await; - tranquilizer.tranquilize(tranquility).await; - Ok(tranquilizer) - }, - must_exit, - ) - .await - } - - /// Get lenght of resync queue - pub fn resync_queue_len(&self) -> usize { - self.resync_queue.len() + Ok(()) } - /// Get number of blocks that have an error - pub fn resync_errors_len(&self) -> usize { - self.resync_errors.len() + /// Get number of items in the refcount table + pub fn rc_len(&self) -> Result<usize, Error> { + Ok(self.rc.rc.len()?) } - /// Get number of items in the refcount table - pub fn rc_len(&self) -> usize { - self.rc.rc.len() + /// Send command to start/stop/manager scrub worker + pub async fn send_scrub_command(&self, cmd: ScrubWorkerCommand) { + let _ = self.tx_scrub_command.send(cmd).await; } //// ----- Managing the reference counter ---- /// Increment the number of time a block is used, putting it to resynchronization if it is /// required, but not known - pub fn block_incref(&self, hash: &Hash) -> Result<(), Error> { - if self.rc.block_incref(hash)? { + pub fn block_incref( + self: &Arc<Self>, + tx: &mut db::Transaction, + hash: Hash, + ) -> db::TxOpResult<()> { + if self.rc.block_incref(tx, &hash)? { // When the reference counter is incremented, there is // normally a node that is responsible for sending us the // data of the block. However that operation may fail, // so in all cases we add the block here to the todo list // to check later that it arrived correctly, and if not // we will fecth it from someone. - self.put_to_resync(hash, 2 * BLOCK_RW_TIMEOUT)?; + let this = self.clone(); + tokio::spawn(async move { + if let Err(e) = this + .resync + .put_to_resync(&hash, 2 * this.system.rpc.rpc_timeout()) + { + error!("Block {:?} could not be put in resync queue: {}.", hash, e); + } + }); } Ok(()) } /// Decrement the number of time a block is used - pub fn block_decref(&self, hash: &Hash) -> Result<(), Error> { - if self.rc.block_decref(hash)? { + pub fn block_decref( + self: &Arc<Self>, + tx: &mut db::Transaction, + hash: Hash, + ) -> db::TxOpResult<()> { + if self.rc.block_decref(tx, &hash)? { // When the RC is decremented, it might drop to zero, // indicating that we don't need the block. // There is a delay before we garbage collect it; // make sure that it is handled in the resync loop // after that delay has passed. - self.put_to_resync(hash, BLOCK_GC_DELAY + Duration::from_secs(10))?; + let this = self.clone(); + tokio::spawn(async move { + if let Err(e) = this + .resync + .put_to_resync(&hash, BLOCK_GC_DELAY + Duration::from_secs(10)) + { + error!("Block {:?} could not be put in resync queue: {}.", hash, e); + } + }); } Ok(()) } // ---- Reading and writing blocks locally ---- + async fn handle_put_block( + &self, + hash: Hash, + header: DataBlockHeader, + stream: Option<ByteStream>, + ) -> Result<(), Error> { + let stream = stream.ok_or_message("missing stream")?; + let bytes = read_stream_to_end(stream).await?; + let data = DataBlock::from_parts(header, bytes); + self.write_block(&hash, &data).await + } + /// Write a block to disk - async fn write_block(&self, hash: &Hash, data: &DataBlock) -> Result<BlockRpc, Error> { + pub(crate) async fn write_block(&self, hash: &Hash, data: &DataBlock) -> Result<(), Error> { + let tracer = opentelemetry::global::tracer("garage"); + let write_size = data.inner_buffer().len() as u64; - let res = self - .mutation_lock - .lock() + self.lock_mutate(hash) .await .write_block(hash, data, self) .bound_record_duration(&self.metrics.block_write_duration) + .with_context(Context::current_with_span( + tracer.start("BlockManagerLocked::write_block"), + )) .await?; self.metrics.bytes_written.add(write_size); - Ok(res) + Ok(()) + } + + async fn handle_get_block(&self, hash: &Hash, order_tag: Option<OrderTag>) -> Resp<BlockRpc> { + let block = match self.read_block(hash).await { + Ok(data) => data, + Err(e) => return Resp::new(Err(e)), + }; + + let (header, data) = block.into_parts(); + + let resp = Resp::new(Ok(BlockRpc::PutBlock { + hash: *hash, + header, + })) + .with_stream_from_buffer(data); + + if let Some(order_tag) = order_tag { + resp.with_order_tag(order_tag) + } else { + resp + } } /// Read block from disk, verifying it's integrity - async fn read_block(&self, hash: &Hash) -> Result<BlockRpc, Error> { + pub(crate) async fn read_block(&self, hash: &Hash) -> Result<DataBlock, Error> { let data = self .read_block_internal(hash) .bound_record_duration(&self.metrics.block_read_duration) @@ -338,7 +434,7 @@ impl BlockManager { .bytes_read .add(data.inner_buffer().len() as u64); - Ok(BlockRpc::PutBlock { hash: *hash, data }) + Ok(data) } async fn read_block_internal(&self, hash: &Hash) -> Result<DataBlock, Error> { @@ -347,7 +443,8 @@ impl BlockManager { Ok(c) => c, Err(e) => { // Not found but maybe we should have had it ?? - self.put_to_resync(hash, 2 * BLOCK_RW_TIMEOUT)?; + self.resync + .put_to_resync(hash, 2 * self.system.rpc.rpc_timeout())?; return Err(Into::into(e)); } }; @@ -361,37 +458,47 @@ impl BlockManager { drop(f); let data = if compressed { - DataBlock::Compressed(data) + DataBlock::Compressed(data.into()) } else { - DataBlock::Plain(data) + DataBlock::Plain(data.into()) }; if data.verify(*hash).is_err() { self.metrics.corruption_counter.add(1); - self.mutation_lock - .lock() + self.lock_mutate(hash) .await .move_block_to_corrupted(hash, self) .await?; - self.put_to_resync(hash, Duration::from_millis(0))?; + self.resync.put_to_resync(hash, Duration::from_millis(0))?; return Err(Error::CorruptData(*hash)); } Ok(data) } - /// Check if this node should have a block, but don't actually have it - async fn need_block(&self, hash: &Hash) -> Result<bool, Error> { - let BlockStatus { exists, needed } = self - .mutation_lock - .lock() + /// Check if this node has a block and whether it needs it + pub(crate) async fn check_block_status(&self, hash: &Hash) -> Result<BlockStatus, Error> { + self.lock_mutate(hash) .await .check_block_status(hash, self) - .await?; + .await + } + + /// Check if this node should have a block, but don't actually have it + async fn need_block(&self, hash: &Hash) -> Result<bool, Error> { + let BlockStatus { exists, needed } = self.check_block_status(hash).await?; Ok(needed.is_nonzero() && !exists) } + /// Delete block if it is not needed anymore + pub(crate) async fn delete_if_unneeded(&self, hash: &Hash) -> Result<(), Error> { + self.lock_mutate(hash) + .await + .delete_if_unneeded(hash, self) + .await + } + /// Utility: gives the path of the directory in which a block should be found fn block_dir(&self, hash: &Hash) -> PathBuf { let mut path = self.data_dir.clone(); @@ -419,431 +526,38 @@ impl BlockManager { fs::metadata(&path).await.map(|_| false).map_err(Into::into) } - // ---- Resync loop ---- - - // This part manages a queue of blocks that need to be - // "resynchronized", i.e. that need to have a check that - // they are at present if we need them, or that they are - // deleted once the garbage collection delay has passed. - // - // Here are some explanations on how the resync queue works. - // There are two Sled trees that are used to have information - // about the status of blocks that need to be resynchronized: - // - // - resync_queue: a tree that is ordered first by a timestamp - // (in milliseconds since Unix epoch) that is the time at which - // the resync must be done, and second by block hash. - // The key in this tree is just: - // concat(timestamp (8 bytes), hash (32 bytes)) - // The value is the same 32-byte hash. - // - // - resync_errors: a tree that indicates for each block - // if the last resync resulted in an error, and if so, - // the following two informations (see the ErrorCounter struct): - // - how many consecutive resync errors for this block? - // - when was the last try? - // These two informations are used to implement an - // exponential backoff retry strategy. - // The key in this tree is the 32-byte hash of the block, - // and the value is the encoded ErrorCounter value. - // - // We need to have these two trees, because the resync queue - // is not just a queue of items to process, but a set of items - // that are waiting a specific delay until we can process them - // (the delay being necessary both internally for the exponential - // backoff strategy, and exposed as a parameter when adding items - // to the queue, e.g. to wait until the GC delay has passed). - // This is why we need one tree ordered by time, and one - // ordered by identifier of item to be processed (block hash). - // - // When the worker wants to process an item it takes from - // resync_queue, it checks in resync_errors that if there is an - // exponential back-off delay to await, it has passed before we - // process the item. If not, the item in the queue is skipped - // (but added back for later processing after the time of the - // delay). - // - // An alternative that would have seemed natural is to - // only add items to resync_queue with a processing time that is - // after the delay, but there are several issues with this: - // - This requires to synchronize updates to resync_queue and - // resync_errors (with the current model, there is only one thread, - // the worker thread, that accesses resync_errors, - // so no need to synchronize) by putting them both in a lock. - // This would mean that block_incref might need to take a lock - // before doing its thing, meaning it has much more chances of - // not completing successfully if something bad happens to Garage. - // Currently Garage is not able to recover from block_incref that - // doesn't complete successfully, because it is necessary to ensure - // the consistency between the state of the block manager and - // information in the BlockRef table. - // - If a resync fails, we put that block in the resync_errors table, - // and also add it back to resync_queue to be processed after - // the exponential back-off delay, - // but maybe the block is already scheduled to be resynced again - // at another time that is before the exponential back-off delay, - // and we have no way to check that easily. This means that - // in all cases, we need to check the resync_errors table - // in the resync loop at the time when a block is popped from - // the resync_queue. - // Overall, the current design is therefore simpler and more robust - // because it tolerates inconsistencies between the resync_queue - // and resync_errors table (items being scheduled in resync_queue - // for times that are earlier than the exponential back-off delay - // is a natural condition that is handled properly). - - fn spawn_background_worker(self: Arc<Self>) { - // Launch a background workers for background resync loop processing - let background = self.system.background.clone(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_secs(10)).await; - background.spawn_worker("block resync worker".into(), move |must_exit| { - self.resync_loop(must_exit) - }); - }); - } - - fn put_to_resync(&self, hash: &Hash, delay: Duration) -> Result<(), sled::Error> { - let when = now_msec() + delay.as_millis() as u64; - self.put_to_resync_at(hash, when) - } - - fn put_to_resync_at(&self, hash: &Hash, when: u64) -> Result<(), sled::Error> { - trace!("Put resync_queue: {} {:?}", when, hash); - let mut key = u64::to_be_bytes(when).to_vec(); - key.extend(hash.as_ref()); - self.resync_queue.insert(key, hash.as_ref())?; - self.resync_notify.notify_waiters(); - Ok(()) - } - - async fn resync_loop(self: Arc<Self>, mut must_exit: watch::Receiver<bool>) { - let mut tranquilizer = Tranquilizer::new(30); - - while !*must_exit.borrow() { - match self.resync_iter(&mut must_exit).await { - Ok(true) => { - tranquilizer.tranquilize(self.background_tranquility).await; - } - Ok(false) => { - tranquilizer.reset(); - } - Err(e) => { - // The errors that we have here are only Sled errors - // We don't really know how to handle them so just ¯\_(ツ)_/¯ - // (there is kind of an assumption that Sled won't error on us, - // if it does there is not much we can do -- TODO should we just panic?) - error!( - "Could not do a resync iteration: {} (this is a very bad error)", - e - ); - tranquilizer.reset(); - } - } - } - } - - // The result of resync_iter is: - // - Ok(true) -> a block was processed (successfully or not) - // - Ok(false) -> no block was processed, but we are ready for the next iteration - // - Err(_) -> a Sled error occurred when reading/writing from resync_queue/resync_errors - async fn resync_iter( - &self, - must_exit: &mut watch::Receiver<bool>, - ) -> Result<bool, sled::Error> { - if let Some(first_pair_res) = self.resync_queue.iter().next() { - let (time_bytes, hash_bytes) = first_pair_res?; - - let time_msec = u64::from_be_bytes(time_bytes[0..8].try_into().unwrap()); - let now = now_msec(); - - if now >= time_msec { - let hash = Hash::try_from(&hash_bytes[..]).unwrap(); - - if let Some(ec) = self.resync_errors.get(hash.as_slice())? { - let ec = ErrorCounter::decode(ec); - if now < ec.next_try() { - // if next retry after an error is not yet, - // don't do resync and return early, but still - // make sure the item is still in queue at expected time - self.put_to_resync_at(&hash, ec.next_try())?; - // ec.next_try() > now >= time_msec, so this remove - // is not removing the one we added just above - // (we want to do the remove after the insert to ensure - // that the item is not lost if we crash in-between) - self.resync_queue.remove(time_bytes)?; - return Ok(false); - } - } - - let tracer = opentelemetry::global::tracer("garage"); - let trace_id = gen_uuid(); - let span = tracer - .span_builder("Resync block") - .with_trace_id( - opentelemetry::trace::TraceId::from_hex(&hex::encode( - &trace_id.as_slice()[..16], - )) - .unwrap(), - ) - .with_attributes(vec![KeyValue::new("block", format!("{:?}", hash))]) - .start(&tracer); - - let res = self - .resync_block(&hash) - .with_context(Context::current_with_span(span)) - .bound_record_duration(&self.metrics.resync_duration) - .await; - - self.metrics.resync_counter.add(1); - - if let Err(e) = &res { - self.metrics.resync_error_counter.add(1); - warn!("Error when resyncing {:?}: {}", hash, e); - - let err_counter = match self.resync_errors.get(hash.as_slice())? { - Some(ec) => ErrorCounter::decode(ec).add1(now + 1), - None => ErrorCounter::new(now + 1), - }; - - self.resync_errors - .insert(hash.as_slice(), err_counter.encode())?; - - self.put_to_resync_at(&hash, err_counter.next_try())?; - // err_counter.next_try() >= now + 1 > now, - // the entry we remove from the queue is not - // the entry we inserted with put_to_resync_at - self.resync_queue.remove(time_bytes)?; - } else { - self.resync_errors.remove(hash.as_slice())?; - self.resync_queue.remove(time_bytes)?; - } - - Ok(true) - } else { - let delay = tokio::time::sleep(Duration::from_millis(time_msec - now)); - select! { - _ = delay.fuse() => {}, - _ = self.resync_notify.notified().fuse() => {}, - _ = must_exit.changed().fuse() => {}, - } - Ok(false) - } - } else { - // Here we wait either for a notification that an item has been - // added to the queue, or for a constant delay of 10 secs to expire. - // The delay avoids a race condition where the notification happens - // between the time we checked the queue and the first poll - // to resync_notify.notified(): if that happens, we'll just loop - // back 10 seconds later, which is fine. - let delay = tokio::time::sleep(Duration::from_secs(10)); - select! { - _ = delay.fuse() => {}, - _ = self.resync_notify.notified().fuse() => {}, - _ = must_exit.changed().fuse() => {}, - } - Ok(false) - } - } - - async fn resync_block(&self, hash: &Hash) -> Result<(), Error> { - let BlockStatus { exists, needed } = self - .mutation_lock + async fn lock_mutate(&self, hash: &Hash) -> MutexGuard<'_, BlockManagerLocked> { + let tracer = opentelemetry::global::tracer("garage"); + self.mutation_lock[hash.as_slice()[0] as usize] .lock() + .with_context(Context::current_with_span( + tracer.start("Acquire mutation_lock"), + )) .await - .check_block_status(hash, self) - .await?; - - if exists != needed.is_needed() || exists != needed.is_nonzero() { - debug!( - "Resync block {:?}: exists {}, nonzero rc {}, deletable {}", - hash, - exists, - needed.is_nonzero(), - needed.is_deletable(), - ); - } - - if exists && needed.is_deletable() { - info!("Resync block {:?}: offloading and deleting", hash); - - let mut who = self.replication.write_nodes(hash); - if who.len() < self.replication.write_quorum() { - return Err(Error::Message("Not trying to offload block because we don't have a quorum of nodes to write to".to_string())); - } - who.retain(|id| *id != self.system.id); - - let msg = Arc::new(BlockRpc::NeedBlockQuery(*hash)); - let who_needs_fut = who.iter().map(|to| { - self.system.rpc.call_arc( - &self.endpoint, - *to, - msg.clone(), - RequestStrategy::with_priority(PRIO_BACKGROUND) - .with_timeout(NEED_BLOCK_QUERY_TIMEOUT), - ) - }); - let who_needs_resps = join_all(who_needs_fut).await; - - let mut need_nodes = vec![]; - for (node, needed) in who.iter().zip(who_needs_resps.into_iter()) { - match needed.err_context("NeedBlockQuery RPC")? { - BlockRpc::NeedBlockReply(needed) => { - if needed { - need_nodes.push(*node); - } - } - m => { - return Err(Error::unexpected_rpc_message(m)); - } - } - } - - if !need_nodes.is_empty() { - trace!( - "Block {:?} needed by {} nodes, sending", - hash, - need_nodes.len() - ); - - for node in need_nodes.iter() { - self.metrics - .resync_send_counter - .add(1, &[KeyValue::new("to", format!("{:?}", node))]); - } - - let put_block_message = self.read_block(hash).await?; - self.system - .rpc - .try_call_many( - &self.endpoint, - &need_nodes[..], - put_block_message, - RequestStrategy::with_priority(PRIO_BACKGROUND) - .with_quorum(need_nodes.len()) - .with_timeout(BLOCK_RW_TIMEOUT), - ) - .await - .err_context("PutBlock RPC")?; - } - info!( - "Deleting unneeded block {:?}, offload finished ({} / {})", - hash, - need_nodes.len(), - who.len() - ); - - self.mutation_lock - .lock() - .await - .delete_if_unneeded(hash, self) - .await?; - - self.rc.clear_deleted_block_rc(hash)?; - } - - if needed.is_nonzero() && !exists { - info!( - "Resync block {:?}: fetching absent but needed block (refcount > 0)", - hash - ); - - let block_data = self.rpc_get_raw_block(hash).await?; - - self.metrics.resync_recv_counter.add(1); - - self.write_block(hash, &block_data).await?; - } - - Ok(()) - } - - // ---- Utility: iteration on files in the data directory ---- - - async fn for_each_file<F, Fut, State>( - &self, - state: State, - mut f: F, - must_exit: &watch::Receiver<bool>, - ) -> Result<(), Error> - where - F: FnMut(State, Hash) -> Fut + Send, - Fut: Future<Output = Result<State, Error>> + Send, - State: Send, - { - self.for_each_file_rec(&self.data_dir, state, &mut f, must_exit) - .await - .map(|_| ()) - } - - fn for_each_file_rec<'a, F, Fut, State>( - &'a self, - path: &'a Path, - mut state: State, - f: &'a mut F, - must_exit: &'a watch::Receiver<bool>, - ) -> BoxFuture<'a, Result<State, Error>> - where - F: FnMut(State, Hash) -> Fut + Send, - Fut: Future<Output = Result<State, Error>> + Send, - State: Send + 'a, - { - async move { - let mut ls_data_dir = fs::read_dir(path).await?; - while let Some(data_dir_ent) = ls_data_dir.next_entry().await? { - if *must_exit.borrow() { - break; - } - - let name = data_dir_ent.file_name(); - let name = if let Ok(n) = name.into_string() { - n - } else { - continue; - }; - let ent_type = data_dir_ent.file_type().await?; - - let name = name.strip_suffix(".zst").unwrap_or(&name); - if name.len() == 2 && hex::decode(&name).is_ok() && ent_type.is_dir() { - state = self - .for_each_file_rec(&data_dir_ent.path(), state, f, must_exit) - .await?; - } else if name.len() == 64 { - let hash_bytes = if let Ok(h) = hex::decode(&name) { - h - } else { - continue; - }; - let mut hash = [0u8; 32]; - hash.copy_from_slice(&hash_bytes[..]); - state = f(state, hash.into()).await?; - } - } - Ok(state) - } - .boxed() } } #[async_trait] -impl EndpointHandler<BlockRpc> for BlockManager { - async fn handle( - self: &Arc<Self>, - message: &BlockRpc, - _from: NodeID, - ) -> Result<BlockRpc, Error> { - match message { - BlockRpc::PutBlock { hash, data } => self.write_block(hash, data).await, - BlockRpc::GetBlock(h) => self.read_block(h).await, - BlockRpc::NeedBlockQuery(h) => self.need_block(h).await.map(BlockRpc::NeedBlockReply), - m => Err(Error::unexpected_rpc_message(m)), +impl StreamingEndpointHandler<BlockRpc> for BlockManager { + async fn handle(self: &Arc<Self>, mut message: Req<BlockRpc>, _from: NodeID) -> Resp<BlockRpc> { + match message.msg() { + BlockRpc::PutBlock { hash, header } => Resp::new( + self.handle_put_block(*hash, *header, message.take_stream()) + .await + .map(|_| BlockRpc::Ok), + ), + BlockRpc::GetBlock(h, order_tag) => self.handle_get_block(h, *order_tag).await, + BlockRpc::NeedBlockQuery(h) => { + Resp::new(self.need_block(h).await.map(BlockRpc::NeedBlockReply)) + } + m => Resp::new(Err(Error::unexpected_rpc_message(m))), } } } -struct BlockStatus { - exists: bool, - needed: RcEntry, +pub(crate) struct BlockStatus { + pub(crate) exists: bool, + pub(crate) needed: RcEntry, } impl BlockManagerLocked { @@ -863,7 +577,7 @@ impl BlockManagerLocked { hash: &Hash, data: &DataBlock, mgr: &BlockManager, - ) -> Result<BlockRpc, Error> { + ) -> Result<(), Error> { let compressed = data.is_compressed(); let data = data.inner_buffer(); @@ -874,8 +588,8 @@ impl BlockManagerLocked { fs::create_dir_all(&directory).await?; let to_delete = match (mgr.is_block_compressed(hash).await, compressed) { - (Ok(true), _) => return Ok(BlockRpc::Ok), - (Ok(false), false) => return Ok(BlockRpc::Ok), + (Ok(true), _) => return Ok(()), + (Ok(false), false) => return Ok(()), (Ok(false), true) => { let path_to_delete = path.clone(); path.set_extension("zst"); @@ -914,7 +628,7 @@ impl BlockManagerLocked { dir.sync_all().await?; drop(dir); - Ok(BlockRpc::Ok) + Ok(()) } async fn move_block_to_corrupted(&self, hash: &Hash, mgr: &BlockManager) -> Result<(), Error> { @@ -949,49 +663,16 @@ impl BlockManagerLocked { } } -/// Counts the number of errors when resyncing a block, -/// and the time of the last try. -/// Used to implement exponential backoff. -#[derive(Clone, Copy, Debug)] -struct ErrorCounter { - errors: u64, - last_try: u64, -} - -impl ErrorCounter { - fn new(now: u64) -> Self { - Self { - errors: 1, - last_try: now, - } +async fn read_stream_to_end(mut stream: ByteStream) -> Result<Bytes, Error> { + let mut parts: Vec<Bytes> = vec![]; + while let Some(part) = stream.next().await { + parts.push(part.ok_or_message("error in stream")?); } - fn decode(data: sled::IVec) -> Self { - Self { - errors: u64::from_be_bytes(data[0..8].try_into().unwrap()), - last_try: u64::from_be_bytes(data[8..16].try_into().unwrap()), - } - } - fn encode(&self) -> Vec<u8> { - [ - u64::to_be_bytes(self.errors), - u64::to_be_bytes(self.last_try), - ] + Ok(parts + .iter() + .map(|x| &x[..]) + .collect::<Vec<_>>() .concat() - } - - fn add1(self, now: u64) -> Self { - Self { - errors: self.errors + 1, - last_try: now, - } - } - - fn delay_msec(&self) -> u64 { - (RESYNC_RETRY_DELAY.as_millis() as u64) - << std::cmp::min(self.errors - 1, RESYNC_RETRY_DELAY_MAX_BACKOFF_POWER) - } - fn next_try(&self) -> u64 { - self.last_try + self.delay_msec() - } + .into()) } diff --git a/src/block/metrics.rs b/src/block/metrics.rs index f0f541a3..477add66 100644 --- a/src/block/metrics.rs +++ b/src/block/metrics.rs @@ -1,6 +1,6 @@ use opentelemetry::{global, metrics::*}; -use garage_util::sled_counter::SledCountedTree; +use garage_db::counted_tree_hack::CountedTree; /// TableMetrics reference all counter used for metrics pub struct BlockManagerMetrics { @@ -23,7 +23,7 @@ pub struct BlockManagerMetrics { } impl BlockManagerMetrics { - pub fn new(resync_queue: SledCountedTree, resync_errors: SledCountedTree) -> Self { + pub fn new(resync_queue: CountedTree, resync_errors: CountedTree) -> Self { let meter = global::meter("garage_model/block"); Self { _resync_queue_len: meter diff --git a/src/block/rc.rs b/src/block/rc.rs index ec3ea44e..ce6defad 100644 --- a/src/block/rc.rs +++ b/src/block/rc.rs @@ -1,5 +1,7 @@ use std::convert::TryInto; +use garage_db as db; + use garage_util::data::*; use garage_util::error::*; use garage_util::time::*; @@ -7,31 +9,41 @@ use garage_util::time::*; use crate::manager::BLOCK_GC_DELAY; pub struct BlockRc { - pub(crate) rc: sled::Tree, + pub(crate) rc: db::Tree, } impl BlockRc { - pub(crate) fn new(rc: sled::Tree) -> Self { + pub(crate) fn new(rc: db::Tree) -> Self { Self { rc } } /// Increment the reference counter associated to a hash. /// Returns true if the RC goes from zero to nonzero. - pub(crate) fn block_incref(&self, hash: &Hash) -> Result<bool, Error> { - let old_rc = self - .rc - .fetch_and_update(&hash, |old| RcEntry::parse_opt(old).increment().serialize())?; - let old_rc = RcEntry::parse_opt(old_rc); + pub(crate) fn block_incref( + &self, + tx: &mut db::Transaction, + hash: &Hash, + ) -> db::TxOpResult<bool> { + let old_rc = RcEntry::parse_opt(tx.get(&self.rc, &hash)?); + match old_rc.increment().serialize() { + Some(x) => tx.insert(&self.rc, &hash, x)?, + None => unreachable!(), + }; Ok(old_rc.is_zero()) } /// Decrement the reference counter associated to a hash. /// Returns true if the RC is now zero. - pub(crate) fn block_decref(&self, hash: &Hash) -> Result<bool, Error> { - let new_rc = self - .rc - .update_and_fetch(&hash, |old| RcEntry::parse_opt(old).decrement().serialize())?; - let new_rc = RcEntry::parse_opt(new_rc); + pub(crate) fn block_decref( + &self, + tx: &mut db::Transaction, + hash: &Hash, + ) -> db::TxOpResult<bool> { + let new_rc = RcEntry::parse_opt(tx.get(&self.rc, &hash)?).decrement(); + match new_rc.serialize() { + Some(x) => tx.insert(&self.rc, &hash, x)?, + None => tx.remove(&self.rc, &hash)?, + }; Ok(matches!(new_rc, RcEntry::Deletable { .. })) } @@ -44,12 +56,15 @@ impl BlockRc { /// deletion time has passed pub(crate) fn clear_deleted_block_rc(&self, hash: &Hash) -> Result<(), Error> { let now = now_msec(); - self.rc.update_and_fetch(&hash, |rcval| { - let updated = match RcEntry::parse_opt(rcval) { - RcEntry::Deletable { at_time } if now > at_time => RcEntry::Absent, - v => v, + self.rc.db().transaction(|mut tx| { + let rcval = RcEntry::parse_opt(tx.get(&self.rc, &hash)?); + match rcval { + RcEntry::Deletable { at_time } if now > at_time => { + tx.remove(&self.rc, &hash)?; + } + _ => (), }; - updated.serialize() + tx.commit(()) })?; Ok(()) } diff --git a/src/block/repair.rs b/src/block/repair.rs new file mode 100644 index 00000000..e2884b69 --- /dev/null +++ b/src/block/repair.rs @@ -0,0 +1,466 @@ +use core::ops::Bound; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; + +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use tokio::fs; +use tokio::select; +use tokio::sync::mpsc; +use tokio::sync::watch; + +use garage_util::background::*; +use garage_util::data::*; +use garage_util::error::*; +use garage_util::persister::Persister; +use garage_util::time::*; +use garage_util::tranquilizer::Tranquilizer; + +use crate::manager::*; + +// Full scrub every 30 days +const SCRUB_INTERVAL: Duration = Duration::from_secs(3600 * 24 * 30); +// Scrub tranquility is initially set to 4, but can be changed in the CLI +// and the updated version is persisted over Garage restarts +const INITIAL_SCRUB_TRANQUILITY: u32 = 4; + +// ---- ---- ---- +// FIRST KIND OF REPAIR: FINDING MISSING BLOCKS/USELESS BLOCKS +// This is a one-shot repair operation that can be launched, +// checks everything, and then exits. +// ---- ---- ---- + +pub struct RepairWorker { + manager: Arc<BlockManager>, + next_start: Option<Hash>, + block_iter: Option<BlockStoreIterator>, +} + +impl RepairWorker { + pub fn new(manager: Arc<BlockManager>) -> Self { + Self { + manager, + next_start: None, + block_iter: None, + } + } +} + +#[async_trait] +impl Worker for RepairWorker { + fn name(&self) -> String { + "Block repair worker".into() + } + + fn info(&self) -> Option<String> { + match self.block_iter.as_ref() { + None => { + let idx_bytes = self + .next_start + .as_ref() + .map(|x| x.as_slice()) + .unwrap_or(&[]); + let idx_bytes = if idx_bytes.len() > 4 { + &idx_bytes[..4] + } else { + idx_bytes + }; + Some(format!("Phase 1: {}", hex::encode(idx_bytes))) + } + Some(bi) => Some(format!("Phase 2: {:.2}% done", bi.progress() * 100.)), + } + } + + async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + match self.block_iter.as_mut() { + None => { + // Phase 1: Repair blocks from RC table. + + // We have to do this complicated two-step process where we first read a bunch + // of hashes from the RC table, and then insert them in the to-resync queue, + // because of SQLite. Basically, as long as we have an iterator on a DB table, + // we can't do anything else on the DB. The naive approach (which we had previously) + // of just iterating on the RC table and inserting items one to one in the resync + // queue can't work here, it would just provoke a deadlock in the SQLite adapter code. + // This is mostly because the Rust bindings for SQLite assume a worst-case scenario + // where SQLite is not compiled in thread-safe mode, so we have to wrap everything + // in a mutex (see db/sqlite_adapter.rs and discussion in PR #322). + // TODO: maybe do this with tokio::task::spawn_blocking ? + let mut batch_of_hashes = vec![]; + let start_bound = match self.next_start.as_ref() { + None => Bound::Unbounded, + Some(x) => Bound::Excluded(x.as_slice()), + }; + for entry in self + .manager + .rc + .rc + .range::<&[u8], _>((start_bound, Bound::Unbounded))? + { + let (hash, _) = entry?; + let hash = Hash::try_from(&hash[..]).unwrap(); + batch_of_hashes.push(hash); + if batch_of_hashes.len() >= 1000 { + break; + } + } + if batch_of_hashes.is_empty() { + // move on to phase 2 + self.block_iter = Some(BlockStoreIterator::new(&self.manager)); + return Ok(WorkerState::Busy); + } + + for hash in batch_of_hashes.into_iter() { + self.manager + .resync + .put_to_resync(&hash, Duration::from_secs(0))?; + self.next_start = Some(hash) + } + + Ok(WorkerState::Busy) + } + Some(bi) => { + // Phase 2: Repair blocks actually on disk + // Lists all blocks on disk and adds them to the resync queue. + // This allows us to find blocks we are storing but don't actually need, + // so that we can offload them if necessary and then delete them locally. + if let Some(hash) = bi.next().await? { + self.manager + .resync + .put_to_resync(&hash, Duration::from_secs(0))?; + Ok(WorkerState::Busy) + } else { + Ok(WorkerState::Done) + } + } + } + } + + async fn wait_for_work(&mut self, _must_exit: &watch::Receiver<bool>) -> WorkerState { + unreachable!() + } +} + +// ---- ---- ---- +// SECOND KIND OF REPAIR: SCRUBBING THE DATASTORE +// This is significantly more complex than the process above, +// as it is a continuously-running task that triggers automatically +// every SCRUB_INTERVAL, but can also be triggered manually +// and whose parameter (esp. speed) can be controlled at runtime. +// ---- ---- ---- + +pub struct ScrubWorker { + manager: Arc<BlockManager>, + rx_cmd: mpsc::Receiver<ScrubWorkerCommand>, + + work: ScrubWorkerState, + tranquilizer: Tranquilizer, + + persister: Persister<ScrubWorkerPersisted>, + persisted: ScrubWorkerPersisted, +} + +#[derive(Serialize, Deserialize)] +struct ScrubWorkerPersisted { + tranquility: u32, + time_last_complete_scrub: u64, + corruptions_detected: u64, +} + +enum ScrubWorkerState { + Running(BlockStoreIterator), + Paused(BlockStoreIterator, u64), // u64 = time when to resume scrub + Finished, +} + +impl Default for ScrubWorkerState { + fn default() -> Self { + ScrubWorkerState::Finished + } +} + +#[derive(Debug)] +pub enum ScrubWorkerCommand { + Start, + Pause(Duration), + Resume, + Cancel, + SetTranquility(u32), +} + +impl ScrubWorker { + pub fn new(manager: Arc<BlockManager>, rx_cmd: mpsc::Receiver<ScrubWorkerCommand>) -> Self { + let persister = Persister::new(&manager.system.metadata_dir, "scrub_info"); + let persisted = match persister.load() { + Ok(v) => v, + Err(_) => ScrubWorkerPersisted { + time_last_complete_scrub: 0, + tranquility: INITIAL_SCRUB_TRANQUILITY, + corruptions_detected: 0, + }, + }; + Self { + manager, + rx_cmd, + work: ScrubWorkerState::Finished, + tranquilizer: Tranquilizer::new(30), + persister, + persisted, + } + } + + async fn handle_cmd(&mut self, cmd: ScrubWorkerCommand) { + match cmd { + ScrubWorkerCommand::Start => { + self.work = match std::mem::take(&mut self.work) { + ScrubWorkerState::Finished => { + let iterator = BlockStoreIterator::new(&self.manager); + ScrubWorkerState::Running(iterator) + } + work => { + error!("Cannot start scrub worker: already running!"); + work + } + }; + } + ScrubWorkerCommand::Pause(dur) => { + self.work = match std::mem::take(&mut self.work) { + ScrubWorkerState::Running(it) | ScrubWorkerState::Paused(it, _) => { + ScrubWorkerState::Paused(it, now_msec() + dur.as_millis() as u64) + } + work => { + error!("Cannot pause scrub worker: not running!"); + work + } + }; + } + ScrubWorkerCommand::Resume => { + self.work = match std::mem::take(&mut self.work) { + ScrubWorkerState::Paused(it, _) => ScrubWorkerState::Running(it), + work => { + error!("Cannot resume scrub worker: not paused!"); + work + } + }; + } + ScrubWorkerCommand::Cancel => { + self.work = match std::mem::take(&mut self.work) { + ScrubWorkerState::Running(_) | ScrubWorkerState::Paused(_, _) => { + ScrubWorkerState::Finished + } + work => { + error!("Cannot cancel scrub worker: not running!"); + work + } + } + } + ScrubWorkerCommand::SetTranquility(t) => { + self.persisted.tranquility = t; + if let Err(e) = self.persister.save_async(&self.persisted).await { + error!("Could not save new tranquilitiy value: {}", e); + } + } + } + } +} + +#[async_trait] +impl Worker for ScrubWorker { + fn name(&self) -> String { + "Block scrub worker".into() + } + + fn info(&self) -> Option<String> { + let s = match &self.work { + ScrubWorkerState::Running(bsi) => format!( + "{:.2}% done (tranquility = {})", + bsi.progress() * 100., + self.persisted.tranquility + ), + ScrubWorkerState::Paused(bsi, rt) => { + format!( + "Paused, {:.2}% done, resumes at {}", + bsi.progress() * 100., + msec_to_rfc3339(*rt) + ) + } + ScrubWorkerState::Finished => format!( + "Last completed scrub: {}", + msec_to_rfc3339(self.persisted.time_last_complete_scrub) + ), + }; + Some(format!( + "{} ; corruptions detected: {}", + s, self.persisted.corruptions_detected + )) + } + + async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + match self.rx_cmd.try_recv() { + Ok(cmd) => self.handle_cmd(cmd).await, + Err(mpsc::error::TryRecvError::Disconnected) => return Ok(WorkerState::Done), + Err(mpsc::error::TryRecvError::Empty) => (), + }; + + match &mut self.work { + ScrubWorkerState::Running(bsi) => { + self.tranquilizer.reset(); + if let Some(hash) = bsi.next().await? { + match self.manager.read_block(&hash).await { + Err(Error::CorruptData(_)) => { + error!("Found corrupt data block during scrub: {:?}", hash); + self.persisted.corruptions_detected += 1; + self.persister.save_async(&self.persisted).await?; + } + Err(e) => return Err(e), + _ => (), + }; + Ok(self + .tranquilizer + .tranquilize_worker(self.persisted.tranquility)) + } else { + self.persisted.time_last_complete_scrub = now_msec(); + self.persister.save_async(&self.persisted).await?; + self.work = ScrubWorkerState::Finished; + self.tranquilizer.clear(); + Ok(WorkerState::Idle) + } + } + _ => Ok(WorkerState::Idle), + } + } + + async fn wait_for_work(&mut self, _must_exit: &watch::Receiver<bool>) -> WorkerState { + let (wait_until, command) = match &self.work { + ScrubWorkerState::Running(_) => return WorkerState::Busy, + ScrubWorkerState::Paused(_, resume_time) => (*resume_time, ScrubWorkerCommand::Resume), + ScrubWorkerState::Finished => ( + self.persisted.time_last_complete_scrub + SCRUB_INTERVAL.as_millis() as u64, + ScrubWorkerCommand::Start, + ), + }; + + let now = now_msec(); + if now >= wait_until { + self.handle_cmd(command).await; + return WorkerState::Busy; + } + let delay = Duration::from_millis(wait_until - now); + select! { + _ = tokio::time::sleep(delay) => self.handle_cmd(command).await, + cmd = self.rx_cmd.recv() => if let Some(cmd) = cmd { + self.handle_cmd(cmd).await; + } else { + return WorkerState::Done; + } + } + + match &self.work { + ScrubWorkerState::Running(_) => WorkerState::Busy, + _ => WorkerState::Idle, + } + } +} + +// ---- ---- ---- +// UTILITY FOR ENUMERATING THE BLOCK STORE +// ---- ---- ---- + +struct BlockStoreIterator { + path: Vec<ReadingDir>, +} + +enum ReadingDir { + Pending(PathBuf), + Read { + subpaths: Vec<fs::DirEntry>, + pos: usize, + }, +} + +impl BlockStoreIterator { + fn new(manager: &BlockManager) -> Self { + let root_dir = manager.data_dir.clone(); + Self { + path: vec![ReadingDir::Pending(root_dir)], + } + } + + /// Returns progress done, between 0 and 1 + fn progress(&self) -> f32 { + if self.path.is_empty() { + 1.0 + } else { + let mut ret = 0.0; + let mut next_div = 1; + for p in self.path.iter() { + match p { + ReadingDir::Pending(_) => break, + ReadingDir::Read { subpaths, pos } => { + next_div *= subpaths.len(); + ret += ((*pos - 1) as f32) / (next_div as f32); + } + } + } + ret + } + } + + async fn next(&mut self) -> Result<Option<Hash>, Error> { + loop { + let last_path = match self.path.last_mut() { + None => return Ok(None), + Some(lp) => lp, + }; + + if let ReadingDir::Pending(path) = last_path { + let mut reader = fs::read_dir(&path).await?; + let mut subpaths = vec![]; + while let Some(ent) = reader.next_entry().await? { + subpaths.push(ent); + } + *last_path = ReadingDir::Read { subpaths, pos: 0 }; + } + + let (subpaths, pos) = match *last_path { + ReadingDir::Read { + ref subpaths, + ref mut pos, + } => (subpaths, pos), + ReadingDir::Pending(_) => unreachable!(), + }; + + let data_dir_ent = match subpaths.get(*pos) { + None => { + self.path.pop(); + continue; + } + Some(ent) => { + *pos += 1; + ent + } + }; + + let name = data_dir_ent.file_name(); + let name = if let Ok(n) = name.into_string() { + n + } else { + continue; + }; + let ent_type = data_dir_ent.file_type().await?; + + let name = name.strip_suffix(".zst").unwrap_or(&name); + if name.len() == 2 && hex::decode(&name).is_ok() && ent_type.is_dir() { + let path = data_dir_ent.path(); + self.path.push(ReadingDir::Pending(path)); + } else if name.len() == 64 { + if let Ok(h) = hex::decode(&name) { + let mut hash = [0u8; 32]; + hash.copy_from_slice(&h); + return Ok(Some(hash.into())); + } + } + } + } +} diff --git a/src/block/resync.rs b/src/block/resync.rs new file mode 100644 index 00000000..ada3ac54 --- /dev/null +++ b/src/block/resync.rs @@ -0,0 +1,589 @@ +use std::collections::HashSet; +use std::convert::TryInto; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use arc_swap::ArcSwap; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +use tokio::select; +use tokio::sync::{watch, Notify}; + +use opentelemetry::{ + trace::{FutureExt as OtelFutureExt, TraceContextExt, Tracer}, + Context, KeyValue, +}; + +use garage_db as db; +use garage_db::counted_tree_hack::CountedTree; + +use garage_util::background::*; +use garage_util::data::*; +use garage_util::error::*; +use garage_util::metrics::RecordDuration; +use garage_util::persister::Persister; +use garage_util::time::*; +use garage_util::tranquilizer::Tranquilizer; + +use garage_rpc::system::System; +use garage_rpc::*; + +use garage_table::replication::TableReplication; + +use crate::manager::*; + +// The delay between the time where a resync operation fails +// and the time when it is retried, with exponential backoff +// (multiplied by 2, 4, 8, 16, etc. for every consecutive failure). +pub(crate) const RESYNC_RETRY_DELAY: Duration = Duration::from_secs(60); +// The minimum retry delay is 60 seconds = 1 minute +// The maximum retry delay is 60 seconds * 2^6 = 60 seconds << 6 = 64 minutes (~1 hour) +pub(crate) const RESYNC_RETRY_DELAY_MAX_BACKOFF_POWER: u64 = 6; + +// No more than 4 resync workers can be running in the system +pub(crate) const MAX_RESYNC_WORKERS: usize = 4; +// Resync tranquility is initially set to 2, but can be changed in the CLI +// and the updated version is persisted over Garage restarts +const INITIAL_RESYNC_TRANQUILITY: u32 = 2; + +pub struct BlockResyncManager { + pub(crate) queue: CountedTree, + pub(crate) notify: Notify, + pub(crate) errors: CountedTree, + + busy_set: BusySet, + + persister: Persister<ResyncPersistedConfig>, + persisted: ArcSwap<ResyncPersistedConfig>, +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +struct ResyncPersistedConfig { + n_workers: usize, + tranquility: u32, +} + +enum ResyncIterResult { + BusyDidSomething, + BusyDidNothing, + IdleFor(Duration), +} + +type BusySet = Arc<Mutex<HashSet<Vec<u8>>>>; + +struct BusyBlock { + time_bytes: Vec<u8>, + hash_bytes: Vec<u8>, + busy_set: BusySet, +} + +impl BlockResyncManager { + pub(crate) fn new(db: &db::Db, system: &System) -> Self { + let queue = db + .open_tree("block_local_resync_queue") + .expect("Unable to open block_local_resync_queue tree"); + let queue = CountedTree::new(queue).expect("Could not count block_local_resync_queue"); + + let errors = db + .open_tree("block_local_resync_errors") + .expect("Unable to open block_local_resync_errors tree"); + let errors = CountedTree::new(errors).expect("Could not count block_local_resync_errors"); + + let persister = Persister::new(&system.metadata_dir, "resync_cfg"); + let persisted = match persister.load() { + Ok(v) => v, + Err(_) => ResyncPersistedConfig { + n_workers: 1, + tranquility: INITIAL_RESYNC_TRANQUILITY, + }, + }; + + Self { + queue, + notify: Notify::new(), + errors, + busy_set: Arc::new(Mutex::new(HashSet::new())), + persister, + persisted: ArcSwap::new(Arc::new(persisted)), + } + } + + /// Get lenght of resync queue + pub fn queue_len(&self) -> Result<usize, Error> { + // This currently can't return an error because the CountedTree hack + // doesn't error on .len(), but this will change when we remove the hack + // (hopefully someday!) + Ok(self.queue.len()) + } + + /// Get number of blocks that have an error + pub fn errors_len(&self) -> Result<usize, Error> { + // (see queue_len comment) + Ok(self.errors.len()) + } + + // ---- Resync loop ---- + + // This part manages a queue of blocks that need to be + // "resynchronized", i.e. that need to have a check that + // they are at present if we need them, or that they are + // deleted once the garbage collection delay has passed. + // + // Here are some explanations on how the resync queue works. + // There are two Sled trees that are used to have information + // about the status of blocks that need to be resynchronized: + // + // - resync.queue: a tree that is ordered first by a timestamp + // (in milliseconds since Unix epoch) that is the time at which + // the resync must be done, and second by block hash. + // The key in this tree is just: + // concat(timestamp (8 bytes), hash (32 bytes)) + // The value is the same 32-byte hash. + // + // - resync.errors: a tree that indicates for each block + // if the last resync resulted in an error, and if so, + // the following two informations (see the ErrorCounter struct): + // - how many consecutive resync errors for this block? + // - when was the last try? + // These two informations are used to implement an + // exponential backoff retry strategy. + // The key in this tree is the 32-byte hash of the block, + // and the value is the encoded ErrorCounter value. + // + // We need to have these two trees, because the resync queue + // is not just a queue of items to process, but a set of items + // that are waiting a specific delay until we can process them + // (the delay being necessary both internally for the exponential + // backoff strategy, and exposed as a parameter when adding items + // to the queue, e.g. to wait until the GC delay has passed). + // This is why we need one tree ordered by time, and one + // ordered by identifier of item to be processed (block hash). + // + // When the worker wants to process an item it takes from + // resync.queue, it checks in resync.errors that if there is an + // exponential back-off delay to await, it has passed before we + // process the item. If not, the item in the queue is skipped + // (but added back for later processing after the time of the + // delay). + // + // An alternative that would have seemed natural is to + // only add items to resync.queue with a processing time that is + // after the delay, but there are several issues with this: + // - This requires to synchronize updates to resync.queue and + // resync.errors (with the current model, there is only one thread, + // the worker thread, that accesses resync.errors, + // so no need to synchronize) by putting them both in a lock. + // This would mean that block_incref might need to take a lock + // before doing its thing, meaning it has much more chances of + // not completing successfully if something bad happens to Garage. + // Currently Garage is not able to recover from block_incref that + // doesn't complete successfully, because it is necessary to ensure + // the consistency between the state of the block manager and + // information in the BlockRef table. + // - If a resync fails, we put that block in the resync.errors table, + // and also add it back to resync.queue to be processed after + // the exponential back-off delay, + // but maybe the block is already scheduled to be resynced again + // at another time that is before the exponential back-off delay, + // and we have no way to check that easily. This means that + // in all cases, we need to check the resync.errors table + // in the resync loop at the time when a block is popped from + // the resync.queue. + // Overall, the current design is therefore simpler and more robust + // because it tolerates inconsistencies between the resync.queue + // and resync.errors table (items being scheduled in resync.queue + // for times that are earlier than the exponential back-off delay + // is a natural condition that is handled properly). + + pub(crate) fn put_to_resync(&self, hash: &Hash, delay: Duration) -> db::Result<()> { + let when = now_msec() + delay.as_millis() as u64; + self.put_to_resync_at(hash, when) + } + + pub(crate) fn put_to_resync_at(&self, hash: &Hash, when: u64) -> db::Result<()> { + trace!("Put resync_queue: {} {:?}", when, hash); + let mut key = u64::to_be_bytes(when).to_vec(); + key.extend(hash.as_ref()); + self.queue.insert(key, hash.as_ref())?; + self.notify.notify_waiters(); + Ok(()) + } + + async fn resync_iter(&self, manager: &BlockManager) -> Result<ResyncIterResult, db::Error> { + if let Some(block) = self.get_block_to_resync()? { + let time_msec = u64::from_be_bytes(block.time_bytes[0..8].try_into().unwrap()); + let now = now_msec(); + + if now >= time_msec { + let hash = Hash::try_from(&block.hash_bytes[..]).unwrap(); + + if let Some(ec) = self.errors.get(hash.as_slice())? { + let ec = ErrorCounter::decode(&ec); + if now < ec.next_try() { + // if next retry after an error is not yet, + // don't do resync and return early, but still + // make sure the item is still in queue at expected time + self.put_to_resync_at(&hash, ec.next_try())?; + // ec.next_try() > now >= time_msec, so this remove + // is not removing the one we added just above + // (we want to do the remove after the insert to ensure + // that the item is not lost if we crash in-between) + self.queue.remove(&block.time_bytes)?; + return Ok(ResyncIterResult::BusyDidNothing); + } + } + + let tracer = opentelemetry::global::tracer("garage"); + let trace_id = gen_uuid(); + let span = tracer + .span_builder("Resync block") + .with_trace_id( + opentelemetry::trace::TraceId::from_hex(&hex::encode( + &trace_id.as_slice()[..16], + )) + .unwrap(), + ) + .with_attributes(vec![KeyValue::new("block", format!("{:?}", hash))]) + .start(&tracer); + + let res = self + .resync_block(manager, &hash) + .with_context(Context::current_with_span(span)) + .bound_record_duration(&manager.metrics.resync_duration) + .await; + + manager.metrics.resync_counter.add(1); + + if let Err(e) = &res { + manager.metrics.resync_error_counter.add(1); + warn!("Error when resyncing {:?}: {}", hash, e); + + let err_counter = match self.errors.get(hash.as_slice())? { + Some(ec) => ErrorCounter::decode(&ec).add1(now + 1), + None => ErrorCounter::new(now + 1), + }; + + self.errors.insert(hash.as_slice(), err_counter.encode())?; + + self.put_to_resync_at(&hash, err_counter.next_try())?; + // err_counter.next_try() >= now + 1 > now, + // the entry we remove from the queue is not + // the entry we inserted with put_to_resync_at + self.queue.remove(&block.time_bytes)?; + } else { + self.errors.remove(hash.as_slice())?; + self.queue.remove(&block.time_bytes)?; + } + + Ok(ResyncIterResult::BusyDidSomething) + } else { + Ok(ResyncIterResult::IdleFor(Duration::from_millis( + time_msec - now, + ))) + } + } else { + // Here we wait either for a notification that an item has been + // added to the queue, or for a constant delay of 10 secs to expire. + // The delay avoids a race condition where the notification happens + // between the time we checked the queue and the first poll + // to resync_notify.notified(): if that happens, we'll just loop + // back 10 seconds later, which is fine. + Ok(ResyncIterResult::IdleFor(Duration::from_secs(10))) + } + } + + fn get_block_to_resync(&self) -> Result<Option<BusyBlock>, db::Error> { + let mut busy = self.busy_set.lock().unwrap(); + for it in self.queue.iter()? { + let (time_bytes, hash_bytes) = it?; + if !busy.contains(&time_bytes) { + busy.insert(time_bytes.clone()); + return Ok(Some(BusyBlock { + time_bytes, + hash_bytes, + busy_set: self.busy_set.clone(), + })); + } + } + Ok(None) + } + + async fn resync_block(&self, manager: &BlockManager, hash: &Hash) -> Result<(), Error> { + let BlockStatus { exists, needed } = manager.check_block_status(hash).await?; + + if exists != needed.is_needed() || exists != needed.is_nonzero() { + debug!( + "Resync block {:?}: exists {}, nonzero rc {}, deletable {}", + hash, + exists, + needed.is_nonzero(), + needed.is_deletable(), + ); + } + + if exists && needed.is_deletable() { + info!("Resync block {:?}: offloading and deleting", hash); + + let mut who = manager.replication.write_nodes(hash); + if who.len() < manager.replication.write_quorum() { + return Err(Error::Message("Not trying to offload block because we don't have a quorum of nodes to write to".to_string())); + } + who.retain(|id| *id != manager.system.id); + + let who_needs_resps = manager + .system + .rpc + .call_many( + &manager.endpoint, + &who, + BlockRpc::NeedBlockQuery(*hash), + RequestStrategy::with_priority(PRIO_BACKGROUND), + ) + .await?; + + let mut need_nodes = vec![]; + for (node, needed) in who_needs_resps { + match needed.err_context("NeedBlockQuery RPC")? { + BlockRpc::NeedBlockReply(needed) => { + if needed { + need_nodes.push(node); + } + } + m => { + return Err(Error::unexpected_rpc_message(m)); + } + } + } + + if !need_nodes.is_empty() { + trace!( + "Block {:?} needed by {} nodes, sending", + hash, + need_nodes.len() + ); + + for node in need_nodes.iter() { + manager + .metrics + .resync_send_counter + .add(1, &[KeyValue::new("to", format!("{:?}", node))]); + } + + let block = manager.read_block(hash).await?; + let (header, bytes) = block.into_parts(); + let put_block_message = Req::new(BlockRpc::PutBlock { + hash: *hash, + header, + })? + .with_stream_from_buffer(bytes); + manager + .system + .rpc + .try_call_many( + &manager.endpoint, + &need_nodes[..], + put_block_message, + RequestStrategy::with_priority(PRIO_BACKGROUND) + .with_quorum(need_nodes.len()), + ) + .await + .err_context("PutBlock RPC")?; + } + info!( + "Deleting unneeded block {:?}, offload finished ({} / {})", + hash, + need_nodes.len(), + who.len() + ); + + manager.delete_if_unneeded(hash).await?; + + manager.rc.clear_deleted_block_rc(hash)?; + } + + if needed.is_nonzero() && !exists { + info!( + "Resync block {:?}: fetching absent but needed block (refcount > 0)", + hash + ); + + let block_data = manager.rpc_get_raw_block(hash, None).await?; + + manager.metrics.resync_recv_counter.add(1); + + manager.write_block(hash, &block_data).await?; + } + + Ok(()) + } + + async fn update_persisted( + &self, + update: impl Fn(&mut ResyncPersistedConfig), + ) -> Result<(), Error> { + let mut cfg: ResyncPersistedConfig = *self.persisted.load().as_ref(); + update(&mut cfg); + self.persister.save_async(&cfg).await?; + self.persisted.store(Arc::new(cfg)); + self.notify.notify_waiters(); + Ok(()) + } + + pub async fn set_n_workers(&self, n_workers: usize) -> Result<(), Error> { + if !(1..=MAX_RESYNC_WORKERS).contains(&n_workers) { + return Err(Error::Message(format!( + "Invalid number of resync workers, must be between 1 and {}", + MAX_RESYNC_WORKERS + ))); + } + self.update_persisted(|cfg| cfg.n_workers = n_workers).await + } + + pub async fn set_tranquility(&self, tranquility: u32) -> Result<(), Error> { + self.update_persisted(|cfg| cfg.tranquility = tranquility) + .await + } +} + +impl Drop for BusyBlock { + fn drop(&mut self) { + let mut busy = self.busy_set.lock().unwrap(); + busy.remove(&self.time_bytes); + } +} + +pub(crate) struct ResyncWorker { + index: usize, + manager: Arc<BlockManager>, + tranquilizer: Tranquilizer, + next_delay: Duration, +} + +impl ResyncWorker { + pub(crate) fn new(index: usize, manager: Arc<BlockManager>) -> Self { + Self { + index, + manager, + tranquilizer: Tranquilizer::new(30), + next_delay: Duration::from_secs(10), + } + } +} + +#[async_trait] +impl Worker for ResyncWorker { + fn name(&self) -> String { + format!("Block resync worker #{}", self.index + 1) + } + + fn info(&self) -> Option<String> { + let persisted = self.manager.resync.persisted.load(); + + if self.index >= persisted.n_workers { + return Some("(unused)".into()); + } + + let mut ret = vec![]; + ret.push(format!("tranquility = {}", persisted.tranquility)); + + let qlen = self.manager.resync.queue_len().unwrap_or(0); + if qlen > 0 { + ret.push(format!("{} blocks in queue", qlen)); + } + + let elen = self.manager.resync.errors_len().unwrap_or(0); + if elen > 0 { + ret.push(format!("{} blocks in error state", elen)); + } + + Some(ret.join(", ")) + } + + async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + if self.index >= self.manager.resync.persisted.load().n_workers { + return Ok(WorkerState::Idle); + } + + self.tranquilizer.reset(); + match self.manager.resync.resync_iter(&self.manager).await { + Ok(ResyncIterResult::BusyDidSomething) => Ok(self + .tranquilizer + .tranquilize_worker(self.manager.resync.persisted.load().tranquility)), + Ok(ResyncIterResult::BusyDidNothing) => Ok(WorkerState::Busy), + Ok(ResyncIterResult::IdleFor(delay)) => { + self.next_delay = delay; + Ok(WorkerState::Idle) + } + Err(e) => { + // The errors that we have here are only Sled errors + // We don't really know how to handle them so just ¯\_(ツ)_/¯ + // (there is kind of an assumption that Sled won't error on us, + // if it does there is not much we can do -- TODO should we just panic?) + // Here we just give the error to the worker manager, + // it will print it to the logs and increment a counter + Err(e.into()) + } + } + } + + async fn wait_for_work(&mut self, _must_exit: &watch::Receiver<bool>) -> WorkerState { + while self.index >= self.manager.resync.persisted.load().n_workers { + self.manager.resync.notify.notified().await + } + + select! { + _ = tokio::time::sleep(self.next_delay) => (), + _ = self.manager.resync.notify.notified() => (), + }; + + WorkerState::Busy + } +} + +/// Counts the number of errors when resyncing a block, +/// and the time of the last try. +/// Used to implement exponential backoff. +#[derive(Clone, Copy, Debug)] +struct ErrorCounter { + errors: u64, + last_try: u64, +} + +impl ErrorCounter { + fn new(now: u64) -> Self { + Self { + errors: 1, + last_try: now, + } + } + + fn decode(data: &[u8]) -> Self { + Self { + errors: u64::from_be_bytes(data[0..8].try_into().unwrap()), + last_try: u64::from_be_bytes(data[8..16].try_into().unwrap()), + } + } + fn encode(&self) -> Vec<u8> { + [ + u64::to_be_bytes(self.errors), + u64::to_be_bytes(self.last_try), + ] + .concat() + } + + fn add1(self, now: u64) -> Self { + Self { + errors: self.errors + 1, + last_try: now, + } + } + + fn delay_msec(&self) -> u64 { + (RESYNC_RETRY_DELAY.as_millis() as u64) + << std::cmp::min(self.errors - 1, RESYNC_RETRY_DELAY_MAX_BACKOFF_POWER) + } + fn next_try(&self) -> u64 { + self.last_try + self.delay_msec() + } +} diff --git a/src/db/Cargo.toml b/src/db/Cargo.toml new file mode 100644 index 00000000..62dda2ca --- /dev/null +++ b/src/db/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "garage_db" +version = "0.8.0" +authors = ["Alex Auvolat <alex@adnab.me>"] +edition = "2018" +license = "AGPL-3.0" +description = "Abstraction over multiple key/value storage engines that supports transactions" +repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage" +readme = "../../README.md" + +[lib] +path = "lib.rs" + +[[bin]] +name = "convert" +path = "bin/convert.rs" +required-features = ["cli"] + +[dependencies] +err-derive = "0.3" +hexdump = "0.1" +tracing = "0.1.30" + +heed = { version = "0.11", default-features = false, features = ["lmdb"], optional = true } +rusqlite = { version = "0.27", optional = true } +sled = { version = "0.34", optional = true } + +# cli deps +clap = { version = "3.1.18", optional = true, features = ["derive", "env"] } +pretty_env_logger = { version = "0.4", optional = true } + +[dev-dependencies] +mktemp = "0.4" + +[features] +bundled-libs = [ "rusqlite/bundled" ] +cli = ["clap", "pretty_env_logger"] +lmdb = [ "heed" ] +sqlite = [ "rusqlite" ] diff --git a/src/db/bin/convert.rs b/src/db/bin/convert.rs new file mode 100644 index 00000000..bbde2048 --- /dev/null +++ b/src/db/bin/convert.rs @@ -0,0 +1,69 @@ +use std::path::PathBuf; + +use garage_db::*; + +use clap::Parser; + +/// K2V command line interface +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Input DB path + #[clap(short = 'i')] + input_path: PathBuf, + /// Input DB engine + #[clap(short = 'a')] + input_engine: String, + + /// Output DB path + #[clap(short = 'o')] + output_path: PathBuf, + /// Output DB engine + #[clap(short = 'b')] + output_engine: String, +} + +fn main() { + let args = Args::parse(); + pretty_env_logger::init(); + + match do_conversion(args) { + Ok(()) => println!("Success!"), + Err(e) => eprintln!("Error: {}", e), + } +} + +fn do_conversion(args: Args) -> Result<()> { + let input = open_db(args.input_path, args.input_engine)?; + let output = open_db(args.output_path, args.output_engine)?; + output.import(&input)?; + Ok(()) +} + +fn open_db(path: PathBuf, engine: String) -> Result<Db> { + match engine.as_str() { + "sled" => { + let db = sled_adapter::sled::Config::default().path(&path).open()?; + Ok(sled_adapter::SledDb::init(db)) + } + "sqlite" | "sqlite3" | "rusqlite" => { + let db = sqlite_adapter::rusqlite::Connection::open(&path)?; + Ok(sqlite_adapter::SqliteDb::init(db)) + } + "lmdb" | "heed" => { + std::fs::create_dir_all(&path).map_err(|e| { + Error(format!("Unable to create LMDB data directory: {}", e).into()) + })?; + + let map_size = lmdb_adapter::recommended_map_size(); + + let db = lmdb_adapter::heed::EnvOpenOptions::new() + .max_dbs(100) + .map_size(map_size) + .open(&path) + .unwrap(); + Ok(lmdb_adapter::LmdbDb::init(db)) + } + e => Err(Error(format!("Invalid DB engine: {}", e).into())), + } +} diff --git a/src/db/counted_tree_hack.rs b/src/db/counted_tree_hack.rs new file mode 100644 index 00000000..bbe943a2 --- /dev/null +++ b/src/db/counted_tree_hack.rs @@ -0,0 +1,127 @@ +//! This hack allows a db tree to keep in RAM a counter of the number of entries +//! it contains, which is used to call .len() on it. This is usefull only for +//! the sled backend where .len() otherwise would have to traverse the whole +//! tree to count items. For sqlite and lmdb, this is mostly useless (but +//! hopefully not harmfull!). Note that a CountedTree cannot be part of a +//! transaction. + +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + +use crate::{Result, Tree, TxError, Value, ValueIter}; + +#[derive(Clone)] +pub struct CountedTree(Arc<CountedTreeInternal>); + +struct CountedTreeInternal { + tree: Tree, + len: AtomicUsize, +} + +impl CountedTree { + pub fn new(tree: Tree) -> Result<Self> { + let len = tree.len()?; + Ok(Self(Arc::new(CountedTreeInternal { + tree, + len: AtomicUsize::new(len), + }))) + } + + pub fn len(&self) -> usize { + self.0.len.load(Ordering::SeqCst) + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn get<K: AsRef<[u8]>>(&self, key: K) -> Result<Option<Value>> { + self.0.tree.get(key) + } + + pub fn first(&self) -> Result<Option<(Value, Value)>> { + self.0.tree.first() + } + + pub fn iter(&self) -> Result<ValueIter<'_>> { + self.0.tree.iter() + } + + // ---- writing functions ---- + + pub fn insert<K, V>(&self, key: K, value: V) -> Result<Option<Value>> + where + K: AsRef<[u8]>, + V: AsRef<[u8]>, + { + let old_val = self.0.tree.insert(key, value)?; + if old_val.is_none() { + self.0.len.fetch_add(1, Ordering::SeqCst); + } + Ok(old_val) + } + + pub fn remove<K: AsRef<[u8]>>(&self, key: K) -> Result<Option<Value>> { + let old_val = self.0.tree.remove(key)?; + if old_val.is_some() { + self.0.len.fetch_sub(1, Ordering::SeqCst); + } + Ok(old_val) + } + + pub fn compare_and_swap<K, OV, NV>( + &self, + key: K, + expected_old: Option<OV>, + new: Option<NV>, + ) -> Result<bool> + where + K: AsRef<[u8]>, + OV: AsRef<[u8]>, + NV: AsRef<[u8]>, + { + let old_some = expected_old.is_some(); + let new_some = new.is_some(); + + let tx_res = self.0.tree.db().transaction(|mut tx| { + let old_val = tx.get(&self.0.tree, &key)?; + let is_same = match (&old_val, &expected_old) { + (None, None) => true, + (Some(x), Some(y)) if x == y.as_ref() => true, + _ => false, + }; + if is_same { + match &new { + Some(v) => { + tx.insert(&self.0.tree, &key, v)?; + } + None => { + tx.remove(&self.0.tree, &key)?; + } + } + tx.commit(()) + } else { + tx.abort(()) + } + }); + + match tx_res { + Ok(()) => { + match (old_some, new_some) { + (false, true) => { + self.0.len.fetch_add(1, Ordering::SeqCst); + } + (true, false) => { + self.0.len.fetch_sub(1, Ordering::SeqCst); + } + _ => (), + } + Ok(true) + } + Err(TxError::Abort(())) => Ok(false), + Err(TxError::Db(e)) => Err(e), + } + } +} diff --git a/src/db/lib.rs b/src/db/lib.rs new file mode 100644 index 00000000..d96586be --- /dev/null +++ b/src/db/lib.rs @@ -0,0 +1,416 @@ +#[macro_use] +#[cfg(feature = "sqlite")] +extern crate tracing; + +#[cfg(not(any(feature = "lmdb", feature = "sled", feature = "sqlite")))] +compile_error!("Must activate the Cargo feature for at least one DB engine: lmdb, sled or sqlite."); + +#[cfg(feature = "lmdb")] +pub mod lmdb_adapter; +#[cfg(feature = "sled")] +pub mod sled_adapter; +#[cfg(feature = "sqlite")] +pub mod sqlite_adapter; + +pub mod counted_tree_hack; + +#[cfg(test)] +pub mod test; + +use core::ops::{Bound, RangeBounds}; + +use std::borrow::Cow; +use std::cell::Cell; +use std::sync::Arc; + +use err_derive::Error; + +#[derive(Clone)] +pub struct Db(pub(crate) Arc<dyn IDb>); + +pub struct Transaction<'a>(&'a mut dyn ITx); + +#[derive(Clone)] +pub struct Tree(Arc<dyn IDb>, usize); + +pub type Value = Vec<u8>; +pub type ValueIter<'a> = Box<dyn std::iter::Iterator<Item = Result<(Value, Value)>> + 'a>; +pub type TxValueIter<'a> = Box<dyn std::iter::Iterator<Item = TxOpResult<(Value, Value)>> + 'a>; + +// ---- + +#[derive(Debug, Error)] +#[error(display = "{}", _0)] +pub struct Error(pub Cow<'static, str>); + +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(Debug, Error)] +#[error(display = "{}", _0)] +pub struct TxOpError(pub(crate) Error); +pub type TxOpResult<T> = std::result::Result<T, TxOpError>; + +pub enum TxError<E> { + Abort(E), + Db(Error), +} +pub type TxResult<R, E> = std::result::Result<R, TxError<E>>; + +impl<E> From<TxOpError> for TxError<E> { + fn from(e: TxOpError) -> TxError<E> { + TxError::Db(e.0) + } +} + +pub fn unabort<R, E>(res: TxResult<R, E>) -> TxOpResult<std::result::Result<R, E>> { + match res { + Ok(v) => Ok(Ok(v)), + Err(TxError::Abort(e)) => Ok(Err(e)), + Err(TxError::Db(e)) => Err(TxOpError(e)), + } +} + +// ---- + +impl Db { + pub fn engine(&self) -> String { + self.0.engine() + } + + pub fn open_tree<S: AsRef<str>>(&self, name: S) -> Result<Tree> { + let tree_id = self.0.open_tree(name.as_ref())?; + Ok(Tree(self.0.clone(), tree_id)) + } + + pub fn list_trees(&self) -> Result<Vec<String>> { + self.0.list_trees() + } + + pub fn transaction<R, E, F>(&self, fun: F) -> TxResult<R, E> + where + F: Fn(Transaction<'_>) -> TxResult<R, E>, + { + let f = TxFn { + function: fun, + result: Cell::new(None), + }; + let tx_res = self.0.transaction(&f); + let ret = f + .result + .into_inner() + .expect("Transaction did not store result"); + + match tx_res { + Ok(()) => { + assert!(matches!(ret, Ok(_))); + ret + } + Err(TxError::Abort(())) => { + assert!(matches!(ret, Err(TxError::Abort(_)))); + ret + } + Err(TxError::Db(e2)) => match ret { + // Ok was stored -> the error occured when finalizing + // transaction + Ok(_) => Err(TxError::Db(e2)), + // An error was already stored: that's the one we want to + // return + Err(TxError::Db(e)) => Err(TxError::Db(e)), + _ => unreachable!(), + }, + } + } + + pub fn import(&self, other: &Db) -> Result<()> { + let existing_trees = self.list_trees()?; + if !existing_trees.is_empty() { + return Err(Error( + format!( + "destination database already contains data: {:?}", + existing_trees + ) + .into(), + )); + } + + let tree_names = other.list_trees()?; + for name in tree_names { + let tree = self.open_tree(&name)?; + if tree.len()? > 0 { + return Err(Error(format!("tree {} already contains data", name).into())); + } + + let ex_tree = other.open_tree(&name)?; + + let tx_res = self.transaction(|mut tx| { + let mut i = 0; + for item in ex_tree.iter().map_err(TxError::Abort)? { + let (k, v) = item.map_err(TxError::Abort)?; + tx.insert(&tree, k, v)?; + i += 1; + if i % 1000 == 0 { + println!("{}: imported {}", name, i); + } + } + tx.commit(i) + }); + let total = match tx_res { + Err(TxError::Db(e)) => return Err(e), + Err(TxError::Abort(e)) => return Err(e), + Ok(x) => x, + }; + + println!("{}: finished importing, {} items", name, total); + } + Ok(()) + } +} + +#[allow(clippy::len_without_is_empty)] +impl Tree { + #[inline] + pub fn db(&self) -> Db { + Db(self.0.clone()) + } + + #[inline] + pub fn get<T: AsRef<[u8]>>(&self, key: T) -> Result<Option<Value>> { + self.0.get(self.1, key.as_ref()) + } + #[inline] + pub fn len(&self) -> Result<usize> { + self.0.len(self.1) + } + + #[inline] + pub fn first(&self) -> Result<Option<(Value, Value)>> { + self.iter()?.next().transpose() + } + #[inline] + pub fn get_gt<T: AsRef<[u8]>>(&self, from: T) -> Result<Option<(Value, Value)>> { + self.range((Bound::Excluded(from), Bound::Unbounded))? + .next() + .transpose() + } + + /// Returns the old value if there was one + #[inline] + pub fn insert<T: AsRef<[u8]>, U: AsRef<[u8]>>( + &self, + key: T, + value: U, + ) -> Result<Option<Value>> { + self.0.insert(self.1, key.as_ref(), value.as_ref()) + } + /// Returns the old value if there was one + #[inline] + pub fn remove<T: AsRef<[u8]>>(&self, key: T) -> Result<Option<Value>> { + self.0.remove(self.1, key.as_ref()) + } + /// Clears all values from the tree + #[inline] + pub fn clear(&self) -> Result<()> { + self.0.clear(self.1) + } + + #[inline] + pub fn iter(&self) -> Result<ValueIter<'_>> { + self.0.iter(self.1) + } + #[inline] + pub fn iter_rev(&self) -> Result<ValueIter<'_>> { + self.0.iter_rev(self.1) + } + + #[inline] + pub fn range<K, R>(&self, range: R) -> Result<ValueIter<'_>> + where + K: AsRef<[u8]>, + R: RangeBounds<K>, + { + let sb = range.start_bound(); + let eb = range.end_bound(); + self.0.range(self.1, get_bound(sb), get_bound(eb)) + } + #[inline] + pub fn range_rev<K, R>(&self, range: R) -> Result<ValueIter<'_>> + where + K: AsRef<[u8]>, + R: RangeBounds<K>, + { + let sb = range.start_bound(); + let eb = range.end_bound(); + self.0.range_rev(self.1, get_bound(sb), get_bound(eb)) + } +} + +#[allow(clippy::len_without_is_empty)] +impl<'a> Transaction<'a> { + #[inline] + pub fn get<T: AsRef<[u8]>>(&self, tree: &Tree, key: T) -> TxOpResult<Option<Value>> { + self.0.get(tree.1, key.as_ref()) + } + #[inline] + pub fn len(&self, tree: &Tree) -> TxOpResult<usize> { + self.0.len(tree.1) + } + + /// Returns the old value if there was one + #[inline] + pub fn insert<T: AsRef<[u8]>, U: AsRef<[u8]>>( + &mut self, + tree: &Tree, + key: T, + value: U, + ) -> TxOpResult<Option<Value>> { + self.0.insert(tree.1, key.as_ref(), value.as_ref()) + } + /// Returns the old value if there was one + #[inline] + pub fn remove<T: AsRef<[u8]>>(&mut self, tree: &Tree, key: T) -> TxOpResult<Option<Value>> { + self.0.remove(tree.1, key.as_ref()) + } + + #[inline] + pub fn iter(&self, tree: &Tree) -> TxOpResult<TxValueIter<'_>> { + self.0.iter(tree.1) + } + #[inline] + pub fn iter_rev(&self, tree: &Tree) -> TxOpResult<TxValueIter<'_>> { + self.0.iter_rev(tree.1) + } + + #[inline] + pub fn range<K, R>(&self, tree: &Tree, range: R) -> TxOpResult<TxValueIter<'_>> + where + K: AsRef<[u8]>, + R: RangeBounds<K>, + { + let sb = range.start_bound(); + let eb = range.end_bound(); + self.0.range(tree.1, get_bound(sb), get_bound(eb)) + } + #[inline] + pub fn range_rev<K, R>(&self, tree: &Tree, range: R) -> TxOpResult<TxValueIter<'_>> + where + K: AsRef<[u8]>, + R: RangeBounds<K>, + { + let sb = range.start_bound(); + let eb = range.end_bound(); + self.0.range_rev(tree.1, get_bound(sb), get_bound(eb)) + } + + // ---- + + #[inline] + pub fn abort<R, E>(self, e: E) -> TxResult<R, E> { + Err(TxError::Abort(e)) + } + + #[inline] + pub fn commit<R, E>(self, r: R) -> TxResult<R, E> { + Ok(r) + } +} + +// ---- Internal interfaces + +pub(crate) trait IDb: Send + Sync { + fn engine(&self) -> String; + fn open_tree(&self, name: &str) -> Result<usize>; + fn list_trees(&self) -> Result<Vec<String>>; + + fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>>; + fn len(&self, tree: usize) -> Result<usize>; + + fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>>; + fn remove(&self, tree: usize, key: &[u8]) -> Result<Option<Value>>; + fn clear(&self, tree: usize) -> Result<()>; + + fn iter(&self, tree: usize) -> Result<ValueIter<'_>>; + fn iter_rev(&self, tree: usize) -> Result<ValueIter<'_>>; + + fn range<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> Result<ValueIter<'_>>; + fn range_rev<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> Result<ValueIter<'_>>; + + fn transaction(&self, f: &dyn ITxFn) -> TxResult<(), ()>; +} + +pub(crate) trait ITx { + fn get(&self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>>; + fn len(&self, tree: usize) -> TxOpResult<usize>; + + fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> TxOpResult<Option<Value>>; + fn remove(&mut self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>>; + + fn iter(&self, tree: usize) -> TxOpResult<TxValueIter<'_>>; + fn iter_rev(&self, tree: usize) -> TxOpResult<TxValueIter<'_>>; + + fn range<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> TxOpResult<TxValueIter<'_>>; + fn range_rev<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> TxOpResult<TxValueIter<'_>>; +} + +pub(crate) trait ITxFn { + fn try_on(&self, tx: &mut dyn ITx) -> TxFnResult; +} + +pub(crate) enum TxFnResult { + Ok, + Abort, + DbErr, +} + +struct TxFn<F, R, E> +where + F: Fn(Transaction<'_>) -> TxResult<R, E>, +{ + function: F, + result: Cell<Option<TxResult<R, E>>>, +} + +impl<F, R, E> ITxFn for TxFn<F, R, E> +where + F: Fn(Transaction<'_>) -> TxResult<R, E>, +{ + fn try_on(&self, tx: &mut dyn ITx) -> TxFnResult { + let res = (self.function)(Transaction(tx)); + let res2 = match &res { + Ok(_) => TxFnResult::Ok, + Err(TxError::Abort(_)) => TxFnResult::Abort, + Err(TxError::Db(_)) => TxFnResult::DbErr, + }; + self.result.set(Some(res)); + res2 + } +} + +// ---- + +fn get_bound<K: AsRef<[u8]>>(b: Bound<&K>) -> Bound<&[u8]> { + match b { + Bound::Included(v) => Bound::Included(v.as_ref()), + Bound::Excluded(v) => Bound::Excluded(v.as_ref()), + Bound::Unbounded => Bound::Unbounded, + } +} diff --git a/src/db/lmdb_adapter.rs b/src/db/lmdb_adapter.rs new file mode 100644 index 00000000..c036c990 --- /dev/null +++ b/src/db/lmdb_adapter.rs @@ -0,0 +1,350 @@ +use core::ops::Bound; +use core::ptr::NonNull; + +use std::collections::HashMap; +use std::convert::TryInto; +use std::sync::{Arc, RwLock}; + +use heed::types::ByteSlice; +use heed::{BytesDecode, Env, RoTxn, RwTxn, UntypedDatabase as Database}; + +use crate::{ + Db, Error, IDb, ITx, ITxFn, Result, TxError, TxFnResult, TxOpError, TxOpResult, TxResult, + TxValueIter, Value, ValueIter, +}; + +pub use heed; + +// -- err + +impl From<heed::Error> for Error { + fn from(e: heed::Error) -> Error { + Error(format!("LMDB: {}", e).into()) + } +} + +impl From<heed::Error> for TxOpError { + fn from(e: heed::Error) -> TxOpError { + TxOpError(e.into()) + } +} + +// -- db + +pub struct LmdbDb { + db: heed::Env, + trees: RwLock<(Vec<Database>, HashMap<String, usize>)>, +} + +impl LmdbDb { + pub fn init(db: Env) -> Db { + let s = Self { + db, + trees: RwLock::new((Vec::new(), HashMap::new())), + }; + Db(Arc::new(s)) + } + + fn get_tree(&self, i: usize) -> Result<Database> { + self.trees + .read() + .unwrap() + .0 + .get(i) + .cloned() + .ok_or_else(|| Error("invalid tree id".into())) + } +} + +impl IDb for LmdbDb { + fn engine(&self) -> String { + "LMDB (using Heed crate)".into() + } + + fn open_tree(&self, name: &str) -> Result<usize> { + let mut trees = self.trees.write().unwrap(); + if let Some(i) = trees.1.get(name) { + Ok(*i) + } else { + let tree = self.db.create_database(Some(name))?; + let i = trees.0.len(); + trees.0.push(tree); + trees.1.insert(name.to_string(), i); + Ok(i) + } + } + + fn list_trees(&self) -> Result<Vec<String>> { + let tree0 = match self.db.open_database::<heed::types::Str, ByteSlice>(None)? { + Some(x) => x, + None => return Ok(vec![]), + }; + + let mut ret = vec![]; + let tx = self.db.read_txn()?; + for item in tree0.iter(&tx)? { + let (tree_name, _) = item?; + ret.push(tree_name.to_string()); + } + drop(tx); + + let mut ret2 = vec![]; + for tree_name in ret { + if self + .db + .open_database::<ByteSlice, ByteSlice>(Some(&tree_name))? + .is_some() + { + ret2.push(tree_name); + } + } + + Ok(ret2) + } + + // ---- + + fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> { + let tree = self.get_tree(tree)?; + + let tx = self.db.read_txn()?; + let val = tree.get(&tx, key)?; + match val { + None => Ok(None), + Some(v) => Ok(Some(v.to_vec())), + } + } + + fn len(&self, tree: usize) -> Result<usize> { + let tree = self.get_tree(tree)?; + let tx = self.db.read_txn()?; + Ok(tree.len(&tx)?.try_into().unwrap()) + } + + fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>> { + let tree = self.get_tree(tree)?; + let mut tx = self.db.write_txn()?; + let old_val = tree.get(&tx, key)?.map(Vec::from); + tree.put(&mut tx, key, value)?; + tx.commit()?; + Ok(old_val) + } + + fn remove(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> { + let tree = self.get_tree(tree)?; + let mut tx = self.db.write_txn()?; + let old_val = tree.get(&tx, key)?.map(Vec::from); + tree.delete(&mut tx, key)?; + tx.commit()?; + Ok(old_val) + } + + fn clear(&self, tree: usize) -> Result<()> { + let tree = self.get_tree(tree)?; + let mut tx = self.db.write_txn()?; + tree.clear(&mut tx)?; + tx.commit()?; + Ok(()) + } + + fn iter(&self, tree: usize) -> Result<ValueIter<'_>> { + let tree = self.get_tree(tree)?; + let tx = self.db.read_txn()?; + TxAndIterator::make(tx, |tx| Ok(tree.iter(tx)?)) + } + + fn iter_rev(&self, tree: usize) -> Result<ValueIter<'_>> { + let tree = self.get_tree(tree)?; + let tx = self.db.read_txn()?; + TxAndIterator::make(tx, |tx| Ok(tree.rev_iter(tx)?)) + } + + fn range<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> Result<ValueIter<'_>> { + let tree = self.get_tree(tree)?; + let tx = self.db.read_txn()?; + TxAndIterator::make(tx, |tx| Ok(tree.range(tx, &(low, high))?)) + } + fn range_rev<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> Result<ValueIter<'_>> { + let tree = self.get_tree(tree)?; + let tx = self.db.read_txn()?; + TxAndIterator::make(tx, |tx| Ok(tree.rev_range(tx, &(low, high))?)) + } + + // ---- + + fn transaction(&self, f: &dyn ITxFn) -> TxResult<(), ()> { + let trees = self.trees.read().unwrap(); + let mut tx = LmdbTx { + trees: &trees.0[..], + tx: self + .db + .write_txn() + .map_err(Error::from) + .map_err(TxError::Db)?, + }; + + let res = f.try_on(&mut tx); + match res { + TxFnResult::Ok => { + tx.tx.commit().map_err(Error::from).map_err(TxError::Db)?; + Ok(()) + } + TxFnResult::Abort => { + tx.tx.abort().map_err(Error::from).map_err(TxError::Db)?; + Err(TxError::Abort(())) + } + TxFnResult::DbErr => { + tx.tx.abort().map_err(Error::from).map_err(TxError::Db)?; + Err(TxError::Db(Error( + "(this message will be discarded)".into(), + ))) + } + } + } +} + +// ---- + +struct LmdbTx<'a> { + trees: &'a [Database], + tx: RwTxn<'a, 'a>, +} + +impl<'a> LmdbTx<'a> { + fn get_tree(&self, i: usize) -> TxOpResult<&Database> { + self.trees.get(i).ok_or_else(|| { + TxOpError(Error( + "invalid tree id (it might have been openned after the transaction started)".into(), + )) + }) + } +} + +impl<'a> ITx for LmdbTx<'a> { + fn get(&self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> { + let tree = self.get_tree(tree)?; + match tree.get(&self.tx, key)? { + Some(v) => Ok(Some(v.to_vec())), + None => Ok(None), + } + } + fn len(&self, _tree: usize) -> TxOpResult<usize> { + unimplemented!(".len() in transaction not supported with LMDB backend") + } + + fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> TxOpResult<Option<Value>> { + let tree = *self.get_tree(tree)?; + let old_val = tree.get(&self.tx, key)?.map(Vec::from); + tree.put(&mut self.tx, key, value)?; + Ok(old_val) + } + fn remove(&mut self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> { + let tree = *self.get_tree(tree)?; + let old_val = tree.get(&self.tx, key)?.map(Vec::from); + tree.delete(&mut self.tx, key)?; + Ok(old_val) + } + + fn iter(&self, _tree: usize) -> TxOpResult<TxValueIter<'_>> { + unimplemented!("Iterators in transactions not supported with LMDB backend"); + } + fn iter_rev(&self, _tree: usize) -> TxOpResult<TxValueIter<'_>> { + unimplemented!("Iterators in transactions not supported with LMDB backend"); + } + + fn range<'r>( + &self, + _tree: usize, + _low: Bound<&'r [u8]>, + _high: Bound<&'r [u8]>, + ) -> TxOpResult<TxValueIter<'_>> { + unimplemented!("Iterators in transactions not supported with LMDB backend"); + } + fn range_rev<'r>( + &self, + _tree: usize, + _low: Bound<&'r [u8]>, + _high: Bound<&'r [u8]>, + ) -> TxOpResult<TxValueIter<'_>> { + unimplemented!("Iterators in transactions not supported with LMDB backend"); + } +} + +// ---- + +type IteratorItem<'a> = heed::Result<( + <ByteSlice as BytesDecode<'a>>::DItem, + <ByteSlice as BytesDecode<'a>>::DItem, +)>; + +struct TxAndIterator<'a, I> +where + I: Iterator<Item = IteratorItem<'a>> + 'a, +{ + tx: RoTxn<'a>, + iter: Option<I>, +} + +impl<'a, I> TxAndIterator<'a, I> +where + I: Iterator<Item = IteratorItem<'a>> + 'a, +{ + fn make<F>(tx: RoTxn<'a>, iterfun: F) -> Result<ValueIter<'a>> + where + F: FnOnce(&'a RoTxn<'a>) -> Result<I>, + { + let mut res = TxAndIterator { tx, iter: None }; + + let tx = unsafe { NonNull::from(&res.tx).as_ref() }; + res.iter = Some(iterfun(tx)?); + + Ok(Box::new(res)) + } +} + +impl<'a, I> Drop for TxAndIterator<'a, I> +where + I: Iterator<Item = IteratorItem<'a>> + 'a, +{ + fn drop(&mut self) { + drop(self.iter.take()); + } +} + +impl<'a, I> Iterator for TxAndIterator<'a, I> +where + I: Iterator<Item = IteratorItem<'a>> + 'a, +{ + type Item = Result<(Value, Value)>; + + fn next(&mut self) -> Option<Self::Item> { + match self.iter.as_mut().unwrap().next() { + None => None, + Some(Err(e)) => Some(Err(e.into())), + Some(Ok((k, v))) => Some(Ok((k.to_vec(), v.to_vec()))), + } + } +} + +// ---- + +#[cfg(target_pointer_width = "64")] +pub fn recommended_map_size() -> usize { + 1usize << 40 +} + +#[cfg(target_pointer_width = "32")] +pub fn recommended_map_size() -> usize { + warn!("LMDB is not recommended on 32-bit systems, database size will be limited"); + 1usize << 30 +} diff --git a/src/db/sled_adapter.rs b/src/db/sled_adapter.rs new file mode 100644 index 00000000..cf61867d --- /dev/null +++ b/src/db/sled_adapter.rs @@ -0,0 +1,266 @@ +use core::ops::Bound; + +use std::cell::Cell; +use std::collections::HashMap; +use std::sync::{Arc, RwLock}; + +use sled::transaction::{ + ConflictableTransactionError, TransactionError, Transactional, TransactionalTree, + UnabortableTransactionError, +}; + +use crate::{ + Db, Error, IDb, ITx, ITxFn, Result, TxError, TxFnResult, TxOpError, TxOpResult, TxResult, + TxValueIter, Value, ValueIter, +}; + +pub use sled; + +// -- err + +impl From<sled::Error> for Error { + fn from(e: sled::Error) -> Error { + Error(format!("Sled: {}", e).into()) + } +} + +impl From<sled::Error> for TxOpError { + fn from(e: sled::Error) -> TxOpError { + TxOpError(e.into()) + } +} + +// -- db + +pub struct SledDb { + db: sled::Db, + trees: RwLock<(Vec<sled::Tree>, HashMap<String, usize>)>, +} + +impl SledDb { + pub fn init(db: sled::Db) -> Db { + let s = Self { + db, + trees: RwLock::new((Vec::new(), HashMap::new())), + }; + Db(Arc::new(s)) + } + + fn get_tree(&self, i: usize) -> Result<sled::Tree> { + self.trees + .read() + .unwrap() + .0 + .get(i) + .cloned() + .ok_or_else(|| Error("invalid tree id".into())) + } +} + +impl IDb for SledDb { + fn engine(&self) -> String { + "Sled".into() + } + + fn open_tree(&self, name: &str) -> Result<usize> { + let mut trees = self.trees.write().unwrap(); + if let Some(i) = trees.1.get(name) { + Ok(*i) + } else { + let tree = self.db.open_tree(name)?; + let i = trees.0.len(); + trees.0.push(tree); + trees.1.insert(name.to_string(), i); + Ok(i) + } + } + + fn list_trees(&self) -> Result<Vec<String>> { + let mut trees = vec![]; + for name in self.db.tree_names() { + let name = std::str::from_utf8(&name) + .map_err(|e| Error(format!("{}", e).into()))? + .to_string(); + if name != "__sled__default" { + trees.push(name); + } + } + Ok(trees) + } + + // ---- + + fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> { + let tree = self.get_tree(tree)?; + let val = tree.get(key)?; + Ok(val.map(|x| x.to_vec())) + } + + fn len(&self, tree: usize) -> Result<usize> { + let tree = self.get_tree(tree)?; + Ok(tree.len()) + } + + fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>> { + let tree = self.get_tree(tree)?; + let old_val = tree.insert(key, value)?; + Ok(old_val.map(|x| x.to_vec())) + } + + fn remove(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> { + let tree = self.get_tree(tree)?; + let old_val = tree.remove(key)?; + Ok(old_val.map(|x| x.to_vec())) + } + + fn clear(&self, tree: usize) -> Result<()> { + let tree = self.get_tree(tree)?; + tree.clear()?; + Ok(()) + } + + fn iter(&self, tree: usize) -> Result<ValueIter<'_>> { + let tree = self.get_tree(tree)?; + Ok(Box::new(tree.iter().map(|v| { + v.map(|(x, y)| (x.to_vec(), y.to_vec())).map_err(Into::into) + }))) + } + + fn iter_rev(&self, tree: usize) -> Result<ValueIter<'_>> { + let tree = self.get_tree(tree)?; + Ok(Box::new(tree.iter().rev().map(|v| { + v.map(|(x, y)| (x.to_vec(), y.to_vec())).map_err(Into::into) + }))) + } + + fn range<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> Result<ValueIter<'_>> { + let tree = self.get_tree(tree)?; + Ok(Box::new(tree.range::<&'r [u8], _>((low, high)).map(|v| { + v.map(|(x, y)| (x.to_vec(), y.to_vec())).map_err(Into::into) + }))) + } + fn range_rev<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> Result<ValueIter<'_>> { + let tree = self.get_tree(tree)?; + Ok(Box::new(tree.range::<&'r [u8], _>((low, high)).rev().map( + |v| v.map(|(x, y)| (x.to_vec(), y.to_vec())).map_err(Into::into), + ))) + } + + // ---- + + fn transaction(&self, f: &dyn ITxFn) -> TxResult<(), ()> { + let trees = self.trees.read().unwrap(); + let res = trees.0.transaction(|txtrees| { + let mut tx = SledTx { + trees: txtrees, + err: Cell::new(None), + }; + match f.try_on(&mut tx) { + TxFnResult::Ok => { + assert!(tx.err.into_inner().is_none()); + Ok(()) + } + TxFnResult::Abort => { + assert!(tx.err.into_inner().is_none()); + Err(ConflictableTransactionError::Abort(())) + } + TxFnResult::DbErr => { + let e = tx.err.into_inner().expect("No DB error"); + Err(e.into()) + } + } + }); + match res { + Ok(()) => Ok(()), + Err(TransactionError::Abort(())) => Err(TxError::Abort(())), + Err(TransactionError::Storage(s)) => Err(TxError::Db(s.into())), + } + } +} + +// ---- + +struct SledTx<'a> { + trees: &'a [TransactionalTree], + err: Cell<Option<UnabortableTransactionError>>, +} + +impl<'a> SledTx<'a> { + fn get_tree(&self, i: usize) -> TxOpResult<&TransactionalTree> { + self.trees.get(i).ok_or_else(|| { + TxOpError(Error( + "invalid tree id (it might have been openned after the transaction started)".into(), + )) + }) + } + + fn save_error<R>( + &self, + v: std::result::Result<R, UnabortableTransactionError>, + ) -> TxOpResult<R> { + match v { + Ok(x) => Ok(x), + Err(e) => { + let txt = format!("{}", e); + self.err.set(Some(e)); + Err(TxOpError(Error(txt.into()))) + } + } + } +} + +impl<'a> ITx for SledTx<'a> { + fn get(&self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> { + let tree = self.get_tree(tree)?; + let tmp = self.save_error(tree.get(key))?; + Ok(tmp.map(|x| x.to_vec())) + } + fn len(&self, _tree: usize) -> TxOpResult<usize> { + unimplemented!(".len() in transaction not supported with Sled backend") + } + + fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> TxOpResult<Option<Value>> { + let tree = self.get_tree(tree)?; + let old_val = self.save_error(tree.insert(key, value))?; + Ok(old_val.map(|x| x.to_vec())) + } + fn remove(&mut self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> { + let tree = self.get_tree(tree)?; + let old_val = self.save_error(tree.remove(key))?; + Ok(old_val.map(|x| x.to_vec())) + } + + fn iter(&self, _tree: usize) -> TxOpResult<TxValueIter<'_>> { + unimplemented!("Iterators in transactions not supported with Sled backend"); + } + fn iter_rev(&self, _tree: usize) -> TxOpResult<TxValueIter<'_>> { + unimplemented!("Iterators in transactions not supported with Sled backend"); + } + + fn range<'r>( + &self, + _tree: usize, + _low: Bound<&'r [u8]>, + _high: Bound<&'r [u8]>, + ) -> TxOpResult<TxValueIter<'_>> { + unimplemented!("Iterators in transactions not supported with Sled backend"); + } + fn range_rev<'r>( + &self, + _tree: usize, + _low: Bound<&'r [u8]>, + _high: Bound<&'r [u8]>, + ) -> TxOpResult<TxValueIter<'_>> { + unimplemented!("Iterators in transactions not supported with Sled backend"); + } +} diff --git a/src/db/sqlite_adapter.rs b/src/db/sqlite_adapter.rs new file mode 100644 index 00000000..886fda6e --- /dev/null +++ b/src/db/sqlite_adapter.rs @@ -0,0 +1,508 @@ +use core::ops::Bound; + +use std::borrow::BorrowMut; +use std::marker::PhantomPinned; +use std::pin::Pin; +use std::ptr::NonNull; +use std::sync::{Arc, Mutex, MutexGuard}; + +use rusqlite::{params, Connection, Rows, Statement, Transaction}; + +use crate::{ + Db, Error, IDb, ITx, ITxFn, Result, TxError, TxFnResult, TxOpError, TxOpResult, TxResult, + TxValueIter, Value, ValueIter, +}; + +pub use rusqlite; + +// --- err + +impl From<rusqlite::Error> for Error { + fn from(e: rusqlite::Error) -> Error { + Error(format!("Sqlite: {}", e).into()) + } +} + +impl From<rusqlite::Error> for TxOpError { + fn from(e: rusqlite::Error) -> TxOpError { + TxOpError(e.into()) + } +} + +// -- db + +pub struct SqliteDb(Mutex<SqliteDbInner>); + +struct SqliteDbInner { + db: Connection, + trees: Vec<String>, +} + +impl SqliteDb { + pub fn init(db: rusqlite::Connection) -> Db { + let s = Self(Mutex::new(SqliteDbInner { + db, + trees: Vec::new(), + })); + Db(Arc::new(s)) + } +} + +impl SqliteDbInner { + fn get_tree(&self, i: usize) -> Result<&'_ str> { + self.trees + .get(i) + .map(String::as_str) + .ok_or_else(|| Error("invalid tree id".into())) + } + + fn internal_get(&self, tree: &str, key: &[u8]) -> Result<Option<Value>> { + let mut stmt = self + .db + .prepare(&format!("SELECT v FROM {} WHERE k = ?1", tree))?; + let mut res_iter = stmt.query([key])?; + match res_iter.next()? { + None => Ok(None), + Some(v) => Ok(Some(v.get::<_, Vec<u8>>(0)?)), + } + } +} + +impl IDb for SqliteDb { + fn engine(&self) -> String { + format!("sqlite3 v{} (using rusqlite crate)", rusqlite::version()) + } + + fn open_tree(&self, name: &str) -> Result<usize> { + let name = format!("tree_{}", name.replace(':', "_COLON_")); + let mut this = self.0.lock().unwrap(); + + if let Some(i) = this.trees.iter().position(|x| x == &name) { + Ok(i) + } else { + trace!("create table {}", name); + this.db.execute( + &format!( + "CREATE TABLE IF NOT EXISTS {} ( + k BLOB PRIMARY KEY, + v BLOB + )", + name + ), + [], + )?; + trace!("table created: {}, unlocking", name); + + let i = this.trees.len(); + this.trees.push(name.to_string()); + Ok(i) + } + } + + fn list_trees(&self) -> Result<Vec<String>> { + let mut trees = vec![]; + + trace!("list_trees: lock db"); + let this = self.0.lock().unwrap(); + trace!("list_trees: lock acquired"); + + let mut stmt = this.db.prepare( + "SELECT name FROM sqlite_schema WHERE type = 'table' AND name LIKE 'tree_%'", + )?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let name = row.get::<_, String>(0)?; + let name = name.replace("_COLON_", ":"); + let name = name.strip_prefix("tree_").unwrap().to_string(); + trees.push(name); + } + Ok(trees) + } + + // ---- + + fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> { + trace!("get {}: lock db", tree); + let this = self.0.lock().unwrap(); + trace!("get {}: lock acquired", tree); + + let tree = this.get_tree(tree)?; + this.internal_get(tree, key) + } + + fn len(&self, tree: usize) -> Result<usize> { + trace!("len {}: lock db", tree); + let this = self.0.lock().unwrap(); + trace!("len {}: lock acquired", tree); + + let tree = this.get_tree(tree)?; + let mut stmt = this.db.prepare(&format!("SELECT COUNT(*) FROM {}", tree))?; + let mut res_iter = stmt.query([])?; + match res_iter.next()? { + None => Ok(0), + Some(v) => Ok(v.get::<_, usize>(0)?), + } + } + + fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>> { + trace!("insert {}: lock db", tree); + let this = self.0.lock().unwrap(); + trace!("insert {}: lock acquired", tree); + + let tree = this.get_tree(tree)?; + let old_val = this.internal_get(tree, key)?; + + let sql = match &old_val { + Some(_) => format!("UPDATE {} SET v = ?2 WHERE k = ?1", tree), + None => format!("INSERT INTO {} (k, v) VALUES (?1, ?2)", tree), + }; + let n = this.db.execute(&sql, params![key, value])?; + assert_eq!(n, 1); + + Ok(old_val) + } + + fn remove(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> { + trace!("remove {}: lock db", tree); + let this = self.0.lock().unwrap(); + trace!("remove {}: lock acquired", tree); + + let tree = this.get_tree(tree)?; + let old_val = this.internal_get(tree, key)?; + + if old_val.is_some() { + let n = this + .db + .execute(&format!("DELETE FROM {} WHERE k = ?1", tree), params![key])?; + assert_eq!(n, 1); + } + + Ok(old_val) + } + + fn clear(&self, tree: usize) -> Result<()> { + trace!("clear {}: lock db", tree); + let this = self.0.lock().unwrap(); + trace!("clear {}: lock acquired", tree); + + let tree = this.get_tree(tree)?; + this.db.execute(&format!("DELETE FROM {}", tree), [])?; + Ok(()) + } + + fn iter(&self, tree: usize) -> Result<ValueIter<'_>> { + trace!("iter {}: lock db", tree); + let this = self.0.lock().unwrap(); + trace!("iter {}: lock acquired", tree); + + let tree = this.get_tree(tree)?; + let sql = format!("SELECT k, v FROM {} ORDER BY k ASC", tree); + DbValueIterator::make(this, &sql, []) + } + + fn iter_rev(&self, tree: usize) -> Result<ValueIter<'_>> { + trace!("iter_rev {}: lock db", tree); + let this = self.0.lock().unwrap(); + trace!("iter_rev {}: lock acquired", tree); + + let tree = this.get_tree(tree)?; + let sql = format!("SELECT k, v FROM {} ORDER BY k DESC", tree); + DbValueIterator::make(this, &sql, []) + } + + fn range<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> Result<ValueIter<'_>> { + trace!("range {}: lock db", tree); + let this = self.0.lock().unwrap(); + trace!("range {}: lock acquired", tree); + + let tree = this.get_tree(tree)?; + + let (bounds_sql, params) = bounds_sql(low, high); + let sql = format!("SELECT k, v FROM {} {} ORDER BY k ASC", tree, bounds_sql); + + let params = params + .iter() + .map(|x| x as &dyn rusqlite::ToSql) + .collect::<Vec<_>>(); + + DbValueIterator::make::<&[&dyn rusqlite::ToSql]>(this, &sql, params.as_ref()) + } + fn range_rev<'r>( + &self, + tree: usize, + low: Bound<&'r [u8]>, + high: Bound<&'r [u8]>, + ) -> Result<ValueIter<'_>> { + trace!("range_rev {}: lock db", tree); + let this = self.0.lock().unwrap(); + trace!("range_rev {}: lock acquired", tree); + + let tree = this.get_tree(tree)?; + + let (bounds_sql, params) = bounds_sql(low, high); + let sql = format!("SELECT k, v FROM {} {} ORDER BY k DESC", tree, bounds_sql); + + let params = params + .iter() + .map(|x| x as &dyn rusqlite::ToSql) + .collect::<Vec<_>>(); + + DbValueIterator::make::<&[&dyn rusqlite::ToSql]>(this, &sql, params.as_ref()) + } + + // ---- + + fn transaction(&self, f: &dyn ITxFn) -> TxResult<(), ()> { + trace!("transaction: lock db"); + let mut this = self.0.lock().unwrap(); + trace!("transaction: lock acquired"); + + let this_mut_ref: &mut SqliteDbInner = this.borrow_mut(); + + let mut tx = SqliteTx { + tx: this_mut_ref + .db + .transaction() + .map_err(Error::from) + .map_err(TxError::Db)?, + trees: &this_mut_ref.trees, + }; + let res = match f.try_on(&mut tx) { + TxFnResult::Ok => { + tx.tx.commit().map_err(Error::from).map_err(TxError::Db)?; + Ok(()) + } + TxFnResult::Abort => { + tx.tx.rollback().map_err(Error::from).map_err(TxError::Db)?; + Err(TxError::Abort(())) + } + TxFnResult::DbErr => { + tx.tx.rollback().map_err(Error::from).map_err(TxError::Db)?; + Err(TxError::Db(Error( + "(this message will be discarded)".into(), + ))) + } + }; + + trace!("transaction done"); + res + } +} + +// ---- + +struct SqliteTx<'a> { + tx: Transaction<'a>, + trees: &'a [String], +} + +impl<'a> SqliteTx<'a> { + fn get_tree(&self, i: usize) -> TxOpResult<&'_ str> { + self.trees.get(i).map(String::as_ref).ok_or_else(|| { + TxOpError(Error( + "invalid tree id (it might have been openned after the transaction started)".into(), + )) + }) + } + + fn internal_get(&self, tree: &str, key: &[u8]) -> TxOpResult<Option<Value>> { + let mut stmt = self + .tx + .prepare(&format!("SELECT v FROM {} WHERE k = ?1", tree))?; + let mut res_iter = stmt.query([key])?; + match res_iter.next()? { + None => Ok(None), + Some(v) => Ok(Some(v.get::<_, Vec<u8>>(0)?)), + } + } +} + +impl<'a> ITx for SqliteTx<'a> { + fn get(&self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> { + let tree = self.get_tree(tree)?; + self.internal_get(tree, key) + } + fn len(&self, tree: usize) -> TxOpResult<usize> { + let tree = self.get_tree(tree)?; + let mut stmt = self.tx.prepare(&format!("SELECT COUNT(*) FROM {}", tree))?; + let mut res_iter = stmt.query([])?; + match res_iter.next()? { + None => Ok(0), + Some(v) => Ok(v.get::<_, usize>(0)?), + } + } + + fn insert(&mut self, tree: usize, key: &[u8], value: &[u8]) -> TxOpResult<Option<Value>> { + let tree = self.get_tree(tree)?; + let old_val = self.internal_get(tree, key)?; + + let sql = match &old_val { + Some(_) => format!("UPDATE {} SET v = ?2 WHERE k = ?1", tree), + None => format!("INSERT INTO {} (k, v) VALUES (?1, ?2)", tree), + }; + let n = self.tx.execute(&sql, params![key, value])?; + assert_eq!(n, 1); + + Ok(old_val) + } + fn remove(&mut self, tree: usize, key: &[u8]) -> TxOpResult<Option<Value>> { + let tree = self.get_tree(tree)?; + let old_val = self.internal_get(tree, key)?; + + if old_val.is_some() { + let n = self + .tx + .execute(&format!("DELETE FROM {} WHERE k = ?1", tree), params![key])?; + assert_eq!(n, 1); + } + + Ok(old_val) + } + + fn iter(&self, _tree: usize) -> TxOpResult<TxValueIter<'_>> { + unimplemented!(); + } + fn iter_rev(&self, _tree: usize) -> TxOpResult<TxValueIter<'_>> { + unimplemented!(); + } + + fn range<'r>( + &self, + _tree: usize, + _low: Bound<&'r [u8]>, + _high: Bound<&'r [u8]>, + ) -> TxOpResult<TxValueIter<'_>> { + unimplemented!(); + } + fn range_rev<'r>( + &self, + _tree: usize, + _low: Bound<&'r [u8]>, + _high: Bound<&'r [u8]>, + ) -> TxOpResult<TxValueIter<'_>> { + unimplemented!(); + } +} + +// ---- + +struct DbValueIterator<'a> { + db: MutexGuard<'a, SqliteDbInner>, + stmt: Option<Statement<'a>>, + iter: Option<Rows<'a>>, + _pin: PhantomPinned, +} + +impl<'a> DbValueIterator<'a> { + fn make<P: rusqlite::Params>( + db: MutexGuard<'a, SqliteDbInner>, + sql: &str, + args: P, + ) -> Result<ValueIter<'a>> { + let res = DbValueIterator { + db, + stmt: None, + iter: None, + _pin: PhantomPinned, + }; + let mut boxed = Box::pin(res); + trace!("make iterator with sql: {}", sql); + + unsafe { + let db = NonNull::from(&boxed.db); + let stmt = db.as_ref().db.prepare(sql)?; + + let mut_ref: Pin<&mut DbValueIterator<'a>> = Pin::as_mut(&mut boxed); + Pin::get_unchecked_mut(mut_ref).stmt = Some(stmt); + + let mut stmt = NonNull::from(&boxed.stmt); + let iter = stmt.as_mut().as_mut().unwrap().query(args)?; + + let mut_ref: Pin<&mut DbValueIterator<'a>> = Pin::as_mut(&mut boxed); + Pin::get_unchecked_mut(mut_ref).iter = Some(iter); + } + + Ok(Box::new(DbValueIteratorPin(boxed))) + } +} + +impl<'a> Drop for DbValueIterator<'a> { + fn drop(&mut self) { + trace!("drop iter"); + drop(self.iter.take()); + drop(self.stmt.take()); + } +} + +struct DbValueIteratorPin<'a>(Pin<Box<DbValueIterator<'a>>>); + +impl<'a> Iterator for DbValueIteratorPin<'a> { + type Item = Result<(Value, Value)>; + + fn next(&mut self) -> Option<Self::Item> { + let next = unsafe { + let mut_ref: Pin<&mut DbValueIterator<'a>> = Pin::as_mut(&mut self.0); + Pin::get_unchecked_mut(mut_ref).iter.as_mut()?.next() + }; + let row = match next { + Err(e) => return Some(Err(e.into())), + Ok(None) => return None, + Ok(Some(r)) => r, + }; + let k = match row.get::<_, Vec<u8>>(0) { + Err(e) => return Some(Err(e.into())), + Ok(x) => x, + }; + let v = match row.get::<_, Vec<u8>>(1) { + Err(e) => return Some(Err(e.into())), + Ok(y) => y, + }; + Some(Ok((k, v))) + } +} + +// ---- + +fn bounds_sql<'r>(low: Bound<&'r [u8]>, high: Bound<&'r [u8]>) -> (String, Vec<Vec<u8>>) { + let mut sql = String::new(); + let mut params: Vec<Vec<u8>> = vec![]; + + match low { + Bound::Included(b) => { + sql.push_str(" WHERE k >= ?1"); + params.push(b.to_vec()); + } + Bound::Excluded(b) => { + sql.push_str(" WHERE k > ?1"); + params.push(b.to_vec()); + } + Bound::Unbounded => (), + }; + + match high { + Bound::Included(b) => { + if !params.is_empty() { + sql.push_str(" AND k <= ?2"); + } else { + sql.push_str(" WHERE k <= ?1"); + } + params.push(b.to_vec()); + } + Bound::Excluded(b) => { + if !params.is_empty() { + sql.push_str(" AND k < ?2"); + } else { + sql.push_str(" WHERE k < ?1"); + } + params.push(b.to_vec()); + } + Bound::Unbounded => (), + } + + (sql, params) +} diff --git a/src/db/test.rs b/src/db/test.rs new file mode 100644 index 00000000..cfcee643 --- /dev/null +++ b/src/db/test.rs @@ -0,0 +1,106 @@ +use crate::*; + +use crate::lmdb_adapter::LmdbDb; +use crate::sled_adapter::SledDb; +use crate::sqlite_adapter::SqliteDb; + +fn test_suite(db: Db) { + let tree = db.open_tree("tree").unwrap(); + + let ka: &[u8] = &b"test"[..]; + let kb: &[u8] = &b"zwello"[..]; + let kint: &[u8] = &b"tz"[..]; + let va: &[u8] = &b"plop"[..]; + let vb: &[u8] = &b"plip"[..]; + let vc: &[u8] = &b"plup"[..]; + + assert!(tree.insert(ka, va).unwrap().is_none()); + assert_eq!(tree.get(ka).unwrap().unwrap(), va); + + let res = db.transaction::<_, (), _>(|mut tx| { + assert_eq!(tx.get(&tree, ka).unwrap().unwrap(), va); + + assert_eq!(tx.insert(&tree, ka, vb).unwrap().unwrap(), va); + + assert_eq!(tx.get(&tree, ka).unwrap().unwrap(), vb); + + tx.commit(12) + }); + assert!(matches!(res, Ok(12))); + assert_eq!(tree.get(ka).unwrap().unwrap(), vb); + + let res = db.transaction::<(), _, _>(|mut tx| { + assert_eq!(tx.get(&tree, ka).unwrap().unwrap(), vb); + + assert_eq!(tx.insert(&tree, ka, vc).unwrap().unwrap(), vb); + + assert_eq!(tx.get(&tree, ka).unwrap().unwrap(), vc); + + tx.abort(42) + }); + assert!(matches!(res, Err(TxError::Abort(42)))); + assert_eq!(tree.get(ka).unwrap().unwrap(), vb); + + let mut iter = tree.iter().unwrap(); + let next = iter.next().unwrap().unwrap(); + assert_eq!((next.0.as_ref(), next.1.as_ref()), (ka, vb)); + assert!(iter.next().is_none()); + drop(iter); + + assert!(tree.insert(kb, vc).unwrap().is_none()); + assert_eq!(tree.get(kb).unwrap().unwrap(), vc); + + let mut iter = tree.iter().unwrap(); + let next = iter.next().unwrap().unwrap(); + assert_eq!((next.0.as_ref(), next.1.as_ref()), (ka, vb)); + let next = iter.next().unwrap().unwrap(); + assert_eq!((next.0.as_ref(), next.1.as_ref()), (kb, vc)); + assert!(iter.next().is_none()); + drop(iter); + + let mut iter = tree.range(kint..).unwrap(); + let next = iter.next().unwrap().unwrap(); + assert_eq!((next.0.as_ref(), next.1.as_ref()), (kb, vc)); + assert!(iter.next().is_none()); + drop(iter); + + let mut iter = tree.range_rev(..kint).unwrap(); + let next = iter.next().unwrap().unwrap(); + assert_eq!((next.0.as_ref(), next.1.as_ref()), (ka, vb)); + assert!(iter.next().is_none()); + drop(iter); + + let mut iter = tree.iter_rev().unwrap(); + let next = iter.next().unwrap().unwrap(); + assert_eq!((next.0.as_ref(), next.1.as_ref()), (kb, vc)); + let next = iter.next().unwrap().unwrap(); + assert_eq!((next.0.as_ref(), next.1.as_ref()), (ka, vb)); + assert!(iter.next().is_none()); + drop(iter); +} + +#[test] +fn test_lmdb_db() { + let path = mktemp::Temp::new_dir().unwrap(); + let db = heed::EnvOpenOptions::new() + .max_dbs(100) + .open(&path) + .unwrap(); + let db = LmdbDb::init(db); + test_suite(db); + drop(path); +} + +#[test] +fn test_sled_db() { + let path = mktemp::Temp::new_dir().unwrap(); + let db = SledDb::init(sled::open(path.to_path_buf()).unwrap()); + test_suite(db); + drop(path); +} + +#[test] +fn test_sqlite_db() { + let db = SqliteDb::init(rusqlite::Connection::open_in_memory().unwrap()); + test_suite(db); +} diff --git a/src/garage/Cargo.toml b/src/garage/Cargo.toml index 59f402ff..5ce40ff2 100644 --- a/src/garage/Cargo.toml +++ b/src/garage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "garage" -version = "0.7.0" +version = "0.8.0" authors = ["Alex Auvolat <alex@adnab.me>"] edition = "2018" license = "AGPL-3.0" @@ -21,25 +21,25 @@ path = "tests/lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -garage_api = { version = "0.7.0", path = "../api" } -garage_model = { version = "0.7.0", path = "../model" } -garage_rpc = { version = "0.7.0", path = "../rpc" } -garage_table = { version = "0.7.0", path = "../table" } -garage_util = { version = "0.7.0", path = "../util" } -garage_web = { version = "0.7.0", path = "../web" } -garage_admin = { version = "0.7.0", path = "../admin" } +garage_db = { version = "0.8.0", path = "../db" } +garage_api = { version = "0.8.0", path = "../api" } +garage_block = { version = "0.8.0", path = "../block" } +garage_model = { version = "0.8.0", path = "../model" } +garage_rpc = { version = "0.8.0", path = "../rpc" } +garage_table = { version = "0.8.0", path = "../table" } +garage_util = { version = "0.8.0", path = "../util" } +garage_web = { version = "0.8.0", path = "../web" } bytes = "1.0" -git-version = "0.3.4" +bytesize = "1.1" +timeago = "0.3" hex = "0.4" tracing = { version = "0.1.30", features = ["log-always"] } -pretty_env_logger = "0.4" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } rand = "0.8" async-trait = "0.1.7" sodiumoxide = { version = "0.2.5-0", package = "kuska-sodiumoxide" } -sled = "0.34" - rmp-serde = "0.15" serde = { version = "1.0", default-features = false, features = ["derive", "rc"] } serde_bytes = "0.11" @@ -50,16 +50,48 @@ futures = "0.3" futures-util = "0.3" tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] } -#netapp = { version = "0.3.2", git = "https://git.deuxfleurs.fr/lx/netapp" } -#netapp = { version = "0.4", path = "../../../netapp" } -netapp = "0.4" +netapp = "0.5" + +opentelemetry = { version = "0.17", features = [ "rt-tokio" ] } +opentelemetry-prometheus = { version = "0.10", optional = true } +opentelemetry-otlp = { version = "0.10", optional = true } +prometheus = { version = "0.13", optional = true } [dev-dependencies] aws-sdk-s3 = "0.8" chrono = "0.4" http = "0.2" -hmac = "0.10" +hmac = "0.12" hyper = { version = "0.14", features = ["client", "http1", "runtime"] } -sha2 = "0.9" +sha2 = "0.10" static_init = "1.0" +assert-json-diff = "2.0" +serde_json = "1.0" +base64 = "0.13" + + +[features] +default = [ "bundled-libs", "metrics", "sled" ] + +k2v = [ "garage_util/k2v", "garage_api/k2v" ] + +# Database engines, Sled is still our default even though we don't like it +sled = [ "garage_model/sled" ] +lmdb = [ "garage_model/lmdb" ] +sqlite = [ "garage_model/sqlite" ] + +# Automatic registration and discovery via Kubernetes API +kubernetes-discovery = [ "garage_rpc/kubernetes-discovery" ] +# Prometheus exporter (/metrics endpoint). +metrics = [ "garage_api/metrics", "opentelemetry-prometheus", "prometheus" ] +# Exporter for the OpenTelemetry Collector. +telemetry-otlp = [ "opentelemetry-otlp" ] + +# NOTE: bundled-libs and system-libs should be treat as mutually exclusive; +# exactly one of them should be enabled. + +# Use bundled libsqlite instead of linking against system-provided. +bundled-libs = [ "garage_db/bundled-libs" ] +# Link against system-provided libsodium and libzstd. +system-libs = [ "garage_block/system-libs", "garage_rpc/system-libs", "sodiumoxide/use-pkg-config" ] diff --git a/src/garage/admin.rs b/src/garage/admin.rs index 0b20bb20..802a8261 100644 --- a/src/garage/admin.rs +++ b/src/garage/admin.rs @@ -15,34 +15,45 @@ use garage_table::*; use garage_rpc::*; +use garage_block::repair::ScrubWorkerCommand; + use garage_model::bucket_alias_table::*; use garage_model::bucket_table::*; use garage_model::garage::Garage; use garage_model::helper::error::{Error, OkOrBadRequest}; use garage_model::key_table::*; use garage_model::migrate::Migrate; -use garage_model::object_table::ObjectFilter; use garage_model::permission::*; use crate::cli::*; -use crate::repair::Repair; +use crate::repair::online::launch_online_repair; pub const ADMIN_RPC_PATH: &str = "garage/admin_rpc.rs/Rpc"; #[derive(Debug, Serialize, Deserialize)] +#[allow(clippy::large_enum_variant)] pub enum AdminRpc { BucketOperation(BucketOperation), KeyOperation(KeyOperation), LaunchRepair(RepairOpt), Migrate(MigrateOpt), Stats(StatsOpt), + Worker(WorkerOpt), // Replies Ok(String), BucketList(Vec<Bucket>), - BucketInfo(Bucket, HashMap<String, Key>), + BucketInfo { + bucket: Bucket, + relevant_keys: HashMap<String, Key>, + counters: HashMap<String, i64>, + }, KeyList(Vec<(String, String)>), KeyInfo(Key, HashMap<Uuid, Bucket>), + WorkerList( + HashMap<usize, garage_util::background::WorkerInfo>, + WorkerListOpt, + ), } impl Rpc for AdminRpc { @@ -73,6 +84,7 @@ impl AdminRpcHandler { BucketOperation::Allow(query) => self.handle_bucket_allow(query).await, BucketOperation::Deny(query) => self.handle_bucket_deny(query).await, BucketOperation::Website(query) => self.handle_bucket_website(query).await, + BucketOperation::SetQuotas(query) => self.handle_bucket_set_quotas(query).await, } } @@ -80,8 +92,15 @@ impl AdminRpcHandler { let buckets = self .garage .bucket_table - .get_range(&EmptyKey, None, Some(DeletedFilter::NotDeleted), 10000) + .get_range( + &EmptyKey, + None, + Some(DeletedFilter::NotDeleted), + 10000, + EnumerationOrder::Forward, + ) .await?; + Ok(AdminRpc::BucketList(buckets)) } @@ -99,6 +118,15 @@ impl AdminRpcHandler { .get_existing_bucket(bucket_id) .await?; + let counters = self + .garage + .object_counter_table + .table + .get(&bucket_id, &EmptyKey) + .await? + .map(|x| x.filtered_values(&self.garage.system.ring.borrow())) + .unwrap_or_default(); + let mut relevant_keys = HashMap::new(); for (k, _) in bucket .state @@ -134,7 +162,11 @@ impl AdminRpcHandler { } } - Ok(AdminRpc::BucketInfo(bucket, relevant_keys)) + Ok(AdminRpc::BucketInfo { + bucket, + relevant_keys, + counters, + }) } #[allow(clippy::ptr_arg)] @@ -207,12 +239,7 @@ impl AdminRpcHandler { } // Check bucket is empty - let objects = self - .garage - .object_table - .get_range(&bucket_id, None, Some(ObjectFilter::IsData), 10) - .await?; - if !objects.is_empty() { + if !helper.is_bucket_empty(bucket_id).await? { return Err(Error::BadRequest(format!( "Bucket {} is not empty", query.name @@ -249,6 +276,7 @@ impl AdminRpcHandler { async fn handle_alias_bucket(&self, query: &AliasBucketOpt) -> Result<AdminRpc, Error> { let helper = self.garage.bucket_helper(); + let key_helper = self.garage.key_helper(); let bucket_id = helper .resolve_global_bucket_name(&query.existing_bucket) @@ -256,7 +284,7 @@ impl AdminRpcHandler { .ok_or_bad_request("Bucket not found")?; if let Some(key_pattern) = &query.local { - let key = helper.get_existing_matching_key(key_pattern).await?; + let key = key_helper.get_existing_matching_key(key_pattern).await?; helper .set_local_bucket_alias(bucket_id, &key.key_id, &query.new_name) @@ -278,9 +306,10 @@ impl AdminRpcHandler { async fn handle_unalias_bucket(&self, query: &UnaliasBucketOpt) -> Result<AdminRpc, Error> { let helper = self.garage.bucket_helper(); + let key_helper = self.garage.key_helper(); if let Some(key_pattern) = &query.local { - let key = helper.get_existing_matching_key(key_pattern).await?; + let key = key_helper.get_existing_matching_key(key_pattern).await?; let bucket_id = key .state @@ -319,12 +348,15 @@ impl AdminRpcHandler { async fn handle_bucket_allow(&self, query: &PermBucketOpt) -> Result<AdminRpc, Error> { let helper = self.garage.bucket_helper(); + let key_helper = self.garage.key_helper(); let bucket_id = helper .resolve_global_bucket_name(&query.bucket) .await? .ok_or_bad_request("Bucket not found")?; - let key = helper.get_existing_matching_key(&query.key_pattern).await?; + let key = key_helper + .get_existing_matching_key(&query.key_pattern) + .await?; let allow_read = query.read || key.allow_read(&bucket_id); let allow_write = query.write || key.allow_write(&bucket_id); @@ -351,12 +383,15 @@ impl AdminRpcHandler { async fn handle_bucket_deny(&self, query: &PermBucketOpt) -> Result<AdminRpc, Error> { let helper = self.garage.bucket_helper(); + let key_helper = self.garage.key_helper(); let bucket_id = helper .resolve_global_bucket_name(&query.bucket) .await? .ok_or_bad_request("Bucket not found")?; - let key = helper.get_existing_matching_key(&query.key_pattern).await?; + let key = key_helper + .get_existing_matching_key(&query.key_pattern) + .await?; let allow_read = !query.read && key.allow_read(&bucket_id); let allow_write = !query.write && key.allow_write(&bucket_id); @@ -423,6 +458,60 @@ impl AdminRpcHandler { Ok(AdminRpc::Ok(msg)) } + async fn handle_bucket_set_quotas(&self, query: &SetQuotasOpt) -> Result<AdminRpc, Error> { + let bucket_id = self + .garage + .bucket_helper() + .resolve_global_bucket_name(&query.bucket) + .await? + .ok_or_bad_request("Bucket not found")?; + + let mut bucket = self + .garage + .bucket_helper() + .get_existing_bucket(bucket_id) + .await?; + let bucket_state = bucket.state.as_option_mut().unwrap(); + + if query.max_size.is_none() && query.max_objects.is_none() { + return Err(Error::BadRequest( + "You must specify either --max-size or --max-objects (or both) for this command to do something.".to_string(), + )); + } + + let mut quotas = bucket_state.quotas.get().clone(); + + match query.max_size.as_ref().map(String::as_ref) { + Some("none") => quotas.max_size = None, + Some(v) => { + let bs = v + .parse::<bytesize::ByteSize>() + .ok_or_bad_request(format!("Invalid size specified: {}", v))?; + quotas.max_size = Some(bs.as_u64()); + } + _ => (), + } + + match query.max_objects.as_ref().map(String::as_ref) { + Some("none") => quotas.max_objects = None, + Some(v) => { + let mo = v + .parse::<u64>() + .ok_or_bad_request(format!("Invalid number specified: {}", v))?; + quotas.max_objects = Some(mo); + } + _ => (), + } + + bucket_state.quotas.update(quotas); + self.garage.bucket_table.insert(&bucket).await?; + + Ok(AdminRpc::Ok(format!( + "Quotas updated for {}", + &query.bucket + ))) + } + async fn handle_key_cmd(&self, cmd: &KeyOperation) -> Result<AdminRpc, Error> { match cmd { KeyOperation::List => self.handle_list_keys().await, @@ -445,6 +534,7 @@ impl AdminRpcHandler { None, Some(KeyFilter::Deleted(DeletedFilter::NotDeleted)), 10000, + EnumerationOrder::Forward, ) .await? .iter() @@ -456,7 +546,7 @@ impl AdminRpcHandler { async fn handle_key_info(&self, query: &KeyOpt) -> Result<AdminRpc, Error> { let key = self .garage - .bucket_helper() + .key_helper() .get_existing_matching_key(&query.key_pattern) .await?; self.key_info_result(key).await @@ -471,7 +561,7 @@ impl AdminRpcHandler { async fn handle_rename_key(&self, query: &KeyRenameOpt) -> Result<AdminRpc, Error> { let mut key = self .garage - .bucket_helper() + .key_helper() .get_existing_matching_key(&query.key_pattern) .await?; key.params_mut() @@ -483,9 +573,11 @@ impl AdminRpcHandler { } async fn handle_delete_key(&self, query: &KeyDeleteOpt) -> Result<AdminRpc, Error> { - let helper = self.garage.bucket_helper(); + let key_helper = self.garage.key_helper(); - let mut key = helper.get_existing_matching_key(&query.key_pattern).await?; + let mut key = key_helper + .get_existing_matching_key(&query.key_pattern) + .await?; if !query.yes { return Err(Error::BadRequest( @@ -493,32 +585,7 @@ impl AdminRpcHandler { )); } - let state = key.state.as_option_mut().unwrap(); - - // --- done checking, now commit --- - // (the step at unset_local_bucket_alias will fail if a bucket - // does not have another alias, the deletion will be - // interrupted in the middle if that happens) - - // 1. Delete local aliases - for (alias, _, to) in state.local_aliases.items().iter() { - if let Some(bucket_id) = to { - helper - .unset_local_bucket_alias(*bucket_id, &key.key_id, alias) - .await?; - } - } - - // 2. Remove permissions on all authorized buckets - for (ab_id, _auth) in state.authorized_buckets.items().iter() { - helper - .set_bucket_key_permissions(*ab_id, &key.key_id, BucketKeyPerm::NO_PERMISSIONS) - .await?; - } - - // 3. Actually delete key - key.state = Deletable::delete(); - self.garage.key_table.insert(&key).await?; + key_helper.delete_key(&mut key).await?; Ok(AdminRpc::Ok(format!( "Key {} was deleted successfully.", @@ -529,7 +596,7 @@ impl AdminRpcHandler { async fn handle_allow_key(&self, query: &KeyPermOpt) -> Result<AdminRpc, Error> { let mut key = self .garage - .bucket_helper() + .key_helper() .get_existing_matching_key(&query.key_pattern) .await?; if query.create_bucket { @@ -542,7 +609,7 @@ impl AdminRpcHandler { async fn handle_deny_key(&self, query: &KeyPermOpt) -> Result<AdminRpc, Error> { let mut key = self .garage - .bucket_helper() + .key_helper() .get_existing_matching_key(&query.key_pattern) .await?; if query.create_bucket { @@ -616,7 +683,7 @@ impl AdminRpcHandler { .endpoint .call( &node, - &AdminRpc::LaunchRepair(opt_to_send.clone()), + AdminRpc::LaunchRepair(opt_to_send.clone()), PRIO_NORMAL, ) .await; @@ -633,15 +700,7 @@ impl AdminRpcHandler { ))) } } else { - let repair = Repair { - garage: self.garage.clone(), - }; - self.garage - .system - .background - .spawn_worker("Repair worker".into(), move |must_exit| async move { - repair.repair_worker(opt, must_exit).await - }); + launch_online_repair(self.garage.clone(), opt).await; Ok(AdminRpc::Ok(format!( "Repair launched on {:?}", self.garage.system.id @@ -664,7 +723,7 @@ impl AdminRpcHandler { let node_id = (*node).into(); match self .endpoint - .call(&node_id, &AdminRpc::Stats(opt), PRIO_NORMAL) + .call(&node_id, AdminRpc::Stats(opt), PRIO_NORMAL) .await? { Ok(AdminRpc::Ok(s)) => writeln!(&mut ret, "{}", s).unwrap(), @@ -674,22 +733,22 @@ impl AdminRpcHandler { } Ok(AdminRpc::Ok(ret)) } else { - Ok(AdminRpc::Ok(self.gather_stats_local(opt))) + Ok(AdminRpc::Ok(self.gather_stats_local(opt)?)) } } - fn gather_stats_local(&self, opt: StatsOpt) -> String { + fn gather_stats_local(&self, opt: StatsOpt) -> Result<String, Error> { let mut ret = String::new(); writeln!( &mut ret, - "\nGarage version: {}", - option_env!("GIT_VERSION").unwrap_or(git_version::git_version!( - prefix = "git:", - cargo_prefix = "cargo:", - fallback = "unknown" - )) + "\nGarage version: {} [features: {}]", + garage_util::version::garage_version(), + garage_util::version::garage_features() + .map(|list| list.join(", ")) + .unwrap_or_else(|| "(unknown)".into()), ) .unwrap(); + writeln!(&mut ret, "\nDatabase engine: {}", self.garage.db.engine()).unwrap(); // Gather ring statistics let ring = self.garage.system.ring.borrow().clone(); @@ -707,59 +766,108 @@ impl AdminRpcHandler { writeln!(&mut ret, " {:?} {}", n, c).unwrap(); } - self.gather_table_stats(&mut ret, &self.garage.bucket_table, &opt); - self.gather_table_stats(&mut ret, &self.garage.key_table, &opt); - self.gather_table_stats(&mut ret, &self.garage.object_table, &opt); - self.gather_table_stats(&mut ret, &self.garage.version_table, &opt); - self.gather_table_stats(&mut ret, &self.garage.block_ref_table, &opt); + self.gather_table_stats(&mut ret, &self.garage.bucket_table, &opt)?; + self.gather_table_stats(&mut ret, &self.garage.key_table, &opt)?; + self.gather_table_stats(&mut ret, &self.garage.object_table, &opt)?; + self.gather_table_stats(&mut ret, &self.garage.version_table, &opt)?; + self.gather_table_stats(&mut ret, &self.garage.block_ref_table, &opt)?; writeln!(&mut ret, "\nBlock manager stats:").unwrap(); if opt.detailed { writeln!( &mut ret, " number of RC entries (~= number of blocks): {}", - self.garage.block_manager.rc_len() + self.garage.block_manager.rc_len()? ) .unwrap(); } writeln!( &mut ret, " resync queue length: {}", - self.garage.block_manager.resync_queue_len() + self.garage.block_manager.resync.queue_len()? ) .unwrap(); writeln!( &mut ret, " blocks with resync errors: {}", - self.garage.block_manager.resync_errors_len() + self.garage.block_manager.resync.errors_len()? ) .unwrap(); - ret + Ok(ret) } - fn gather_table_stats<F, R>(&self, to: &mut String, t: &Arc<Table<F, R>>, opt: &StatsOpt) + fn gather_table_stats<F, R>( + &self, + to: &mut String, + t: &Arc<Table<F, R>>, + opt: &StatsOpt, + ) -> Result<(), Error> where F: TableSchema + 'static, R: TableReplication + 'static, { writeln!(to, "\nTable stats for {}", F::TABLE_NAME).unwrap(); if opt.detailed { - writeln!(to, " number of items: {}", t.data.store.len()).unwrap(); + writeln!( + to, + " number of items: {}", + t.data.store.len().map_err(GarageError::from)? + ) + .unwrap(); writeln!( to, " Merkle tree size: {}", - t.merkle_updater.merkle_tree_len() + t.merkle_updater.merkle_tree_len()? ) .unwrap(); } writeln!( to, " Merkle updater todo queue length: {}", - t.merkle_updater.todo_len() + t.merkle_updater.todo_len()? ) .unwrap(); - writeln!(to, " GC todo queue length: {}", t.data.gc_todo_len()).unwrap(); + writeln!(to, " GC todo queue length: {}", t.data.gc_todo_len()?).unwrap(); + + Ok(()) + } + + // ---- + + async fn handle_worker_cmd(&self, opt: WorkerOpt) -> Result<AdminRpc, Error> { + match opt.cmd { + WorkerCmd::List { opt } => { + let workers = self.garage.background.get_worker_info(); + Ok(AdminRpc::WorkerList(workers, opt)) + } + WorkerCmd::Set { opt } => match opt { + WorkerSetCmd::ScrubTranquility { tranquility } => { + let scrub_command = ScrubWorkerCommand::SetTranquility(tranquility); + self.garage + .block_manager + .send_scrub_command(scrub_command) + .await; + Ok(AdminRpc::Ok("Scrub tranquility updated".into())) + } + WorkerSetCmd::ResyncNWorkers { n_workers } => { + self.garage + .block_manager + .resync + .set_n_workers(n_workers) + .await?; + Ok(AdminRpc::Ok("Number of resync workers updated".into())) + } + WorkerSetCmd::ResyncTranquility { tranquility } => { + self.garage + .block_manager + .resync + .set_tranquility(tranquility) + .await?; + Ok(AdminRpc::Ok("Resync tranquility updated".into())) + } + }, + } } } @@ -776,6 +884,7 @@ impl EndpointHandler<AdminRpc> for AdminRpcHandler { AdminRpc::Migrate(opt) => self.handle_migrate(opt.clone()).await, AdminRpc::LaunchRepair(opt) => self.handle_launch_repair(opt.clone()).await, AdminRpc::Stats(opt) => self.handle_stats(opt.clone()).await, + AdminRpc::Worker(opt) => self.handle_worker_cmd(opt.clone()).await, m => Err(GarageError::unexpected_rpc_message(m).into()), } } diff --git a/src/garage/cli/cmd.rs b/src/garage/cli/cmd.rs index a90277a0..c8b96489 100644 --- a/src/garage/cli/cmd.rs +++ b/src/garage/cli/cmd.rs @@ -1,6 +1,8 @@ use std::collections::HashSet; +use std::time::Duration; use garage_util::error::*; +use garage_util::formater::format_table; use garage_rpc::layout::*; use garage_rpc::system::*; @@ -38,13 +40,14 @@ pub async fn cli_command_dispatch( cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::LaunchRepair(ro)).await } Command::Stats(so) => cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::Stats(so)).await, + Command::Worker(wo) => cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::Worker(wo)).await, _ => unreachable!(), } } pub async fn cmd_status(rpc_cli: &Endpoint<SystemRpc, ()>, rpc_host: NodeID) -> Result<(), Error> { let status = match rpc_cli - .call(&rpc_host, &SystemRpc::GetKnownNodes, PRIO_NORMAL) + .call(&rpc_host, SystemRpc::GetKnownNodes, PRIO_NORMAL) .await?? { SystemRpc::ReturnKnownNodes(nodes) => nodes, @@ -85,19 +88,21 @@ pub async fn cmd_status(rpc_cli: &Endpoint<SystemRpc, ()>, rpc_host: NodeID) -> format_table(healthy_nodes); let status_keys = status.iter().map(|adv| adv.id).collect::<HashSet<_>>(); - let failure_case_1 = status.iter().any(|adv| !adv.is_up); + let failure_case_1 = status + .iter() + .any(|adv| !adv.is_up && matches!(layout.roles.get(&adv.id), Some(NodeRoleV(Some(_))))); let failure_case_2 = layout .roles .items() .iter() - .filter(|(_, _, v)| v.0.is_some()) - .any(|(id, _, _)| !status_keys.contains(id)); + .any(|(id, _, v)| !status_keys.contains(id) && v.0.is_some()); if failure_case_1 || failure_case_2 { println!("\n==== FAILED NODES ===="); let mut failed_nodes = vec!["ID\tHostname\tAddress\tTags\tZone\tCapacity\tLast seen".to_string()]; for adv in status.iter().filter(|adv| !adv.is_up) { if let Some(NodeRoleV(Some(cfg))) = layout.roles.get(&adv.id) { + let tf = timeago::Formatter::new(); failed_nodes.push(format!( "{id:?}\t{host}\t{addr}\t[{tags}]\t{zone}\t{capacity}\t{last_seen}", id = adv.id, @@ -108,7 +113,7 @@ pub async fn cmd_status(rpc_cli: &Endpoint<SystemRpc, ()>, rpc_host: NodeID) -> capacity = cfg.capacity_string(), last_seen = adv .last_seen_secs_ago - .map(|s| format!("{}s ago", s)) + .map(|s| tf.convert(Duration::from_secs(s))) .unwrap_or_else(|| "never seen".into()), )); } @@ -144,7 +149,7 @@ pub async fn cmd_connect( args: ConnectNodeOpt, ) -> Result<(), Error> { match rpc_cli - .call(&rpc_host, &SystemRpc::Connect(args.node), PRIO_NORMAL) + .call(&rpc_host, SystemRpc::Connect(args.node), PRIO_NORMAL) .await?? { SystemRpc::Ok => { @@ -160,15 +165,19 @@ pub async fn cmd_admin( rpc_host: NodeID, args: AdminRpc, ) -> Result<(), HelperError> { - match rpc_cli.call(&rpc_host, &args, PRIO_NORMAL).await?? { + match rpc_cli.call(&rpc_host, args, PRIO_NORMAL).await?? { AdminRpc::Ok(msg) => { println!("{}", msg); } AdminRpc::BucketList(bl) => { print_bucket_list(bl); } - AdminRpc::BucketInfo(bucket, rk) => { - print_bucket_info(&bucket, &rk); + AdminRpc::BucketInfo { + bucket, + relevant_keys, + counters, + } => { + print_bucket_info(&bucket, &relevant_keys, &counters); } AdminRpc::KeyList(kl) => { print_key_list(kl); @@ -176,6 +185,9 @@ pub async fn cmd_admin( AdminRpc::KeyInfo(key, rb) => { print_key_info(&key, &rb); } + AdminRpc::WorkerList(wi, wlo) => { + print_worker_info(wi, wlo); + } r => { error!("Unexpected response: {:?}", r); } diff --git a/src/garage/cli/layout.rs b/src/garage/cli/layout.rs index e76f7737..3884bb92 100644 --- a/src/garage/cli/layout.rs +++ b/src/garage/cli/layout.rs @@ -1,6 +1,6 @@ use garage_util::crdt::Crdt; -use garage_util::data::*; use garage_util::error::*; +use garage_util::formater::format_table; use garage_rpc::layout::*; use garage_rpc::system::*; @@ -36,21 +36,29 @@ pub async fn cmd_assign_role( args: AssignRoleOpt, ) -> Result<(), Error> { let status = match rpc_cli - .call(&rpc_host, &SystemRpc::GetKnownNodes, PRIO_NORMAL) + .call(&rpc_host, SystemRpc::GetKnownNodes, PRIO_NORMAL) .await?? { SystemRpc::ReturnKnownNodes(nodes) => nodes, resp => return Err(Error::Message(format!("Invalid RPC response: {:?}", resp))), }; + let mut layout = fetch_layout(rpc_cli, rpc_host).await?; + let added_nodes = args .node_ids .iter() - .map(|node_id| find_matching_node(status.iter().map(|adv| adv.id), node_id)) + .map(|node_id| { + find_matching_node( + status + .iter() + .map(|adv| adv.id) + .chain(layout.node_ids().iter().cloned()), + node_id, + ) + }) .collect::<Result<Vec<_>, _>>()?; - let mut layout = fetch_layout(rpc_cli, rpc_host).await?; - let mut roles = layout.roles.clone(); roles.merge(&layout.staging); @@ -203,31 +211,9 @@ pub async fn cmd_apply_layout( rpc_host: NodeID, apply_opt: ApplyLayoutOpt, ) -> Result<(), Error> { - let mut layout = fetch_layout(rpc_cli, rpc_host).await?; - - match apply_opt.version { - None => { - println!("Please pass the --version flag to ensure that you are writing the correct version of the cluster layout."); - println!("To know the correct value of the --version flag, invoke `garage layout show` and review the proposed changes."); - return Err(Error::Message("--version flag is missing".into())); - } - Some(v) => { - if v != layout.version + 1 { - return Err(Error::Message("Invalid value of --version flag".into())); - } - } - } - - layout.roles.merge(&layout.staging); - - if !layout.calculate_partition_assignation() { - return Err(Error::Message("Could not calculate new assignation of partitions to nodes. This can happen if there are less nodes than the desired number of copies of your data (see the replication_mode configuration parameter).".into())); - } + let layout = fetch_layout(rpc_cli, rpc_host).await?; - layout.staging.clear(); - layout.staging_hash = blake2sum(&rmp_to_vec_all_named(&layout.staging).unwrap()[..]); - - layout.version += 1; + let layout = layout.apply_staged_changes(apply_opt.version)?; send_layout(rpc_cli, rpc_host, layout).await?; @@ -242,25 +228,9 @@ pub async fn cmd_revert_layout( rpc_host: NodeID, revert_opt: RevertLayoutOpt, ) -> Result<(), Error> { - let mut layout = fetch_layout(rpc_cli, rpc_host).await?; - - match revert_opt.version { - None => { - println!("Please pass the --version flag to ensure that you are writing the correct version of the cluster layout."); - println!("To know the correct value of the --version flag, invoke `garage layout show` and review the proposed changes."); - return Err(Error::Message("--version flag is missing".into())); - } - Some(v) => { - if v != layout.version + 1 { - return Err(Error::Message("Invalid value of --version flag".into())); - } - } - } - - layout.staging.clear(); - layout.staging_hash = blake2sum(&rmp_to_vec_all_named(&layout.staging).unwrap()[..]); + let layout = fetch_layout(rpc_cli, rpc_host).await?; - layout.version += 1; + let layout = layout.revert_staged_changes(revert_opt.version)?; send_layout(rpc_cli, rpc_host, layout).await?; @@ -275,7 +245,7 @@ pub async fn fetch_layout( rpc_host: NodeID, ) -> Result<ClusterLayout, Error> { match rpc_cli - .call(&rpc_host, &SystemRpc::PullClusterLayout, PRIO_NORMAL) + .call(&rpc_host, SystemRpc::PullClusterLayout, PRIO_NORMAL) .await?? { SystemRpc::AdvertiseClusterLayout(t) => Ok(t), @@ -291,7 +261,7 @@ pub async fn send_layout( rpc_cli .call( &rpc_host, - &SystemRpc::AdvertiseClusterLayout(layout), + SystemRpc::AdvertiseClusterLayout(layout), PRIO_NORMAL, ) .await??; @@ -323,11 +293,20 @@ pub fn print_cluster_layout(layout: &ClusterLayout) -> bool { } pub fn print_staging_role_changes(layout: &ClusterLayout) -> bool { - if !layout.staging.items().is_empty() { + let has_changes = layout + .staging + .items() + .iter() + .any(|(k, _, v)| layout.roles.get(k) != Some(v)); + + if has_changes { println!(); println!("==== STAGED ROLE CHANGES ===="); let mut table = vec!["ID\tTags\tZone\tCapacity".to_string()]; for (id, _, role) in layout.staging.items().iter() { + if layout.roles.get(id) == Some(role) { + continue; + } if let Some(role) = &role.0 { let tags = role.tags.join(","); table.push(format!( diff --git a/src/garage/cli/structs.rs b/src/garage/cli/structs.rs index a0c49aeb..06548e89 100644 --- a/src/garage/cli/structs.rs +++ b/src/garage/cli/structs.rs @@ -1,55 +1,65 @@ use serde::{Deserialize, Serialize}; - use structopt::StructOpt; +use garage_util::version::garage_version; + #[derive(StructOpt, Debug)] pub enum Command { /// Run Garage server - #[structopt(name = "server")] + #[structopt(name = "server", version = garage_version())] Server, /// Get network status - #[structopt(name = "status")] + #[structopt(name = "status", version = garage_version())] Status, /// Operations on individual Garage nodes - #[structopt(name = "node")] + #[structopt(name = "node", version = garage_version())] Node(NodeOperation), /// Operations on the assignation of node roles in the cluster layout - #[structopt(name = "layout")] + #[structopt(name = "layout", version = garage_version())] Layout(LayoutOperation), /// Operations on buckets - #[structopt(name = "bucket")] + #[structopt(name = "bucket", version = garage_version())] Bucket(BucketOperation), /// Operations on S3 access keys - #[structopt(name = "key")] + #[structopt(name = "key", version = garage_version())] Key(KeyOperation), /// Run migrations from previous Garage version /// (DO NOT USE WITHOUT READING FULL DOCUMENTATION) - #[structopt(name = "migrate")] + #[structopt(name = "migrate", version = garage_version())] Migrate(MigrateOpt), - /// Start repair of node data - #[structopt(name = "repair")] + /// Start repair of node data on remote node + #[structopt(name = "repair", version = garage_version())] Repair(RepairOpt), + /// Offline reparation of node data (these repairs must be run offline + /// directly on the server node) + #[structopt(name = "offline-repair", version = garage_version())] + OfflineRepair(OfflineRepairOpt), + /// Gather node statistics - #[structopt(name = "stats")] + #[structopt(name = "stats", version = garage_version())] Stats(StatsOpt), + + /// Manage background workers + #[structopt(name = "worker", version = garage_version())] + Worker(WorkerOpt), } #[derive(StructOpt, Debug)] pub enum NodeOperation { /// Print identifier (public key) of this Garage node - #[structopt(name = "id")] + #[structopt(name = "id", version = garage_version())] NodeId(NodeIdOpt), /// Connect to Garage node that is currently isolated from the system - #[structopt(name = "connect")] + #[structopt(name = "connect", version = garage_version())] Connect(ConnectNodeOpt), } @@ -70,23 +80,23 @@ pub struct ConnectNodeOpt { #[derive(StructOpt, Debug)] pub enum LayoutOperation { /// Assign role to Garage node - #[structopt(name = "assign")] + #[structopt(name = "assign", version = garage_version())] Assign(AssignRoleOpt), /// Remove role from Garage cluster node - #[structopt(name = "remove")] + #[structopt(name = "remove", version = garage_version())] Remove(RemoveRoleOpt), /// Show roles currently assigned to nodes and changes staged for commit - #[structopt(name = "show")] + #[structopt(name = "show", version = garage_version())] Show, /// Apply staged changes to cluster layout - #[structopt(name = "apply")] + #[structopt(name = "apply", version = garage_version())] Apply(ApplyLayoutOpt), /// Revert staged changes to cluster layout - #[structopt(name = "revert")] + #[structopt(name = "revert", version = garage_version())] Revert(RevertLayoutOpt), } @@ -141,40 +151,44 @@ pub struct RevertLayoutOpt { #[derive(Serialize, Deserialize, StructOpt, Debug)] pub enum BucketOperation { /// List buckets - #[structopt(name = "list")] + #[structopt(name = "list", version = garage_version())] List, /// Get bucket info - #[structopt(name = "info")] + #[structopt(name = "info", version = garage_version())] Info(BucketOpt), /// Create bucket - #[structopt(name = "create")] + #[structopt(name = "create", version = garage_version())] Create(BucketOpt), /// Delete bucket - #[structopt(name = "delete")] + #[structopt(name = "delete", version = garage_version())] Delete(DeleteBucketOpt), /// Alias bucket under new name - #[structopt(name = "alias")] + #[structopt(name = "alias", version = garage_version())] Alias(AliasBucketOpt), /// Remove bucket alias - #[structopt(name = "unalias")] + #[structopt(name = "unalias", version = garage_version())] Unalias(UnaliasBucketOpt), /// Allow key to read or write to bucket - #[structopt(name = "allow")] + #[structopt(name = "allow", version = garage_version())] Allow(PermBucketOpt), /// Deny key from reading or writing to bucket - #[structopt(name = "deny")] + #[structopt(name = "deny", version = garage_version())] Deny(PermBucketOpt), /// Expose as website or not - #[structopt(name = "website")] + #[structopt(name = "website", version = garage_version())] Website(WebsiteOpt), + + /// Set the quotas for this bucket + #[structopt(name = "set-quotas", version = garage_version())] + SetQuotas(SetQuotasOpt), } #[derive(Serialize, Deserialize, StructOpt, Debug)] @@ -262,37 +276,52 @@ pub struct PermBucketOpt { } #[derive(Serialize, Deserialize, StructOpt, Debug)] +pub struct SetQuotasOpt { + /// Bucket name + pub bucket: String, + + /// Set a maximum size for the bucket (specify a size e.g. in MiB or GiB, + /// or `none` for no size restriction) + #[structopt(long = "max-size")] + pub max_size: Option<String>, + + /// Set a maximum number of objects for the bucket (or `none` for no restriction) + #[structopt(long = "max-objects")] + pub max_objects: Option<String>, +} + +#[derive(Serialize, Deserialize, StructOpt, Debug)] pub enum KeyOperation { /// List keys - #[structopt(name = "list")] + #[structopt(name = "list", version = garage_version())] List, /// Get key info - #[structopt(name = "info")] + #[structopt(name = "info", version = garage_version())] Info(KeyOpt), /// Create new key - #[structopt(name = "new")] + #[structopt(name = "new", version = garage_version())] New(KeyNewOpt), /// Rename key - #[structopt(name = "rename")] + #[structopt(name = "rename", version = garage_version())] Rename(KeyRenameOpt), /// Delete key - #[structopt(name = "delete")] + #[structopt(name = "delete", version = garage_version())] Delete(KeyDeleteOpt), /// Set permission flags for key - #[structopt(name = "allow")] + #[structopt(name = "allow", version = garage_version())] Allow(KeyPermOpt), /// Unset permission flags for key - #[structopt(name = "deny")] + #[structopt(name = "deny", version = garage_version())] Deny(KeyPermOpt), /// Import key - #[structopt(name = "import")] + #[structopt(name = "import", version = garage_version())] Import(KeyImportOpt), } @@ -364,7 +393,7 @@ pub struct MigrateOpt { #[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] pub enum MigrateWhat { /// Migrate buckets and permissions from v0.5.0 - #[structopt(name = "buckets050")] + #[structopt(name = "buckets050", version = garage_version())] Buckets050, } @@ -385,27 +414,69 @@ pub struct RepairOpt { #[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] pub enum RepairWhat { /// Only do a full sync of metadata tables - #[structopt(name = "tables")] + #[structopt(name = "tables", version = garage_version())] Tables, /// Only repair (resync/rebalance) the set of stored blocks - #[structopt(name = "blocks")] + #[structopt(name = "blocks", version = garage_version())] Blocks, /// Only redo the propagation of object deletions to the version table (slow) - #[structopt(name = "versions")] + #[structopt(name = "versions", version = garage_version())] Versions, /// Only redo the propagation of version deletions to the block ref table (extremely slow) - #[structopt(name = "block_refs")] + #[structopt(name = "block_refs", version = garage_version())] BlockRefs, /// Verify integrity of all blocks on disc (extremely slow, i/o intensive) - #[structopt(name = "scrub")] + #[structopt(name = "scrub", version = garage_version())] Scrub { - /// Tranquility factor (see tranquilizer documentation) - #[structopt(name = "tranquility", default_value = "2")] + #[structopt(subcommand)] + cmd: ScrubCmd, + }, +} + +#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] +pub enum ScrubCmd { + /// Start scrub + #[structopt(name = "start", version = garage_version())] + Start, + /// Pause scrub (it will resume automatically after 24 hours) + #[structopt(name = "pause", version = garage_version())] + Pause, + /// Resume paused scrub + #[structopt(name = "resume", version = garage_version())] + Resume, + /// Cancel scrub in progress + #[structopt(name = "cancel", version = garage_version())] + Cancel, + /// Set tranquility level for in-progress and future scrubs + #[structopt(name = "set-tranquility", version = garage_version())] + SetTranquility { + #[structopt()] tranquility: u32, }, } #[derive(Serialize, Deserialize, StructOpt, Debug, Clone)] +pub struct OfflineRepairOpt { + /// Confirm the launch of the repair operation + #[structopt(long = "yes")] + pub yes: bool, + + #[structopt(subcommand)] + pub what: OfflineRepairWhat, +} + +#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] +pub enum OfflineRepairWhat { + /// Repair K2V item counters + #[cfg(feature = "k2v")] + #[structopt(name = "k2v_item_counters", version = garage_version())] + K2VItemCounters, + /// Repair object counters + #[structopt(name = "object_counters", version = garage_version())] + ObjectCounters, +} + +#[derive(Serialize, Deserialize, StructOpt, Debug, Clone)] pub struct StatsOpt { /// Gather statistics from all nodes #[structopt(short = "a", long = "all-nodes")] @@ -415,3 +486,48 @@ pub struct StatsOpt { #[structopt(short = "d", long = "detailed")] pub detailed: bool, } + +#[derive(Serialize, Deserialize, StructOpt, Debug, Clone)] +pub struct WorkerOpt { + #[structopt(subcommand)] + pub cmd: WorkerCmd, +} + +#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] +pub enum WorkerCmd { + /// List all workers on Garage node + #[structopt(name = "list", version = garage_version())] + List { + #[structopt(flatten)] + opt: WorkerListOpt, + }, + /// Set worker parameter + #[structopt(name = "set", version = garage_version())] + Set { + #[structopt(subcommand)] + opt: WorkerSetCmd, + }, +} + +#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone, Copy)] +pub struct WorkerListOpt { + /// Show only busy workers + #[structopt(short = "b", long = "busy")] + pub busy: bool, + /// Show only workers with errors + #[structopt(short = "e", long = "errors")] + pub errors: bool, +} + +#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] +pub enum WorkerSetCmd { + /// Set tranquility of scrub operations + #[structopt(name = "scrub-tranquility", version = garage_version())] + ScrubTranquility { tranquility: u32 }, + /// Set number of concurrent block resync workers + #[structopt(name = "resync-n-workers", version = garage_version())] + ResyncNWorkers { n_workers: usize }, + /// Set tranquility of block resync operations + #[structopt(name = "resync-tranquility", version = garage_version())] + ResyncTranquility { tranquility: u32 }, +} diff --git a/src/garage/cli/util.rs b/src/garage/cli/util.rs index 7d496507..396938ae 100644 --- a/src/garage/cli/util.rs +++ b/src/garage/cli/util.rs @@ -1,11 +1,18 @@ use std::collections::HashMap; +use std::time::Duration; +use garage_util::background::*; use garage_util::crdt::*; use garage_util::data::Uuid; use garage_util::error::*; +use garage_util::formater::format_table; +use garage_util::time::*; use garage_model::bucket_table::*; use garage_model::key_table::*; +use garage_model::s3::object_table::{BYTES, OBJECTS, UNFINISHED_UPLOADS}; + +use crate::cli::structs::WorkerListOpt; pub fn print_bucket_list(bl: Vec<Bucket>) { println!("List of buckets:"); @@ -28,11 +35,12 @@ pub fn print_bucket_list(bl: Vec<Bucket>) { [((k, n), _, _)] => format!("{}:{}", k, n), s => format!("[{} local aliases]", s.len()), }; + table.push(format!( "\t{}\t{}\t{}", aliases.join(","), local_aliases_n, - hex::encode(bucket.id) + hex::encode(bucket.id), )); } format_table(table); @@ -120,7 +128,11 @@ pub fn print_key_info(key: &Key, relevant_buckets: &HashMap<Uuid, Bucket>) { } } -pub fn print_bucket_info(bucket: &Bucket, relevant_keys: &HashMap<String, Key>) { +pub fn print_bucket_info( + bucket: &Bucket, + relevant_keys: &HashMap<String, Key>, + counters: &HashMap<String, i64>, +) { let key_name = |k| { relevant_keys .get(k) @@ -132,7 +144,42 @@ pub fn print_bucket_info(bucket: &Bucket, relevant_keys: &HashMap<String, Key>) match &bucket.state { Deletable::Deleted => println!("Bucket is deleted."), Deletable::Present(p) => { - println!("Website access: {}", p.website_config.get().is_some()); + let size = + bytesize::ByteSize::b(counters.get(BYTES).cloned().unwrap_or_default() as u64); + println!( + "\nSize: {} ({})", + size.to_string_as(true), + size.to_string_as(false) + ); + println!( + "Objects: {}", + counters.get(OBJECTS).cloned().unwrap_or_default() + ); + println!( + "Unfinished multipart uploads: {}", + counters + .get(UNFINISHED_UPLOADS) + .cloned() + .unwrap_or_default() + ); + + println!("\nWebsite access: {}", p.website_config.get().is_some()); + + let quotas = p.quotas.get(); + if quotas.max_size.is_some() || quotas.max_objects.is_some() { + println!("\nQuotas:"); + if let Some(ms) = quotas.max_size { + let ms = bytesize::ByteSize::b(ms); + println!( + " maximum size: {} ({})", + ms.to_string_as(true), + ms.to_string_as(false) + ); + } + if let Some(mo) = quotas.max_objects { + println!(" maximum number of objects: {}", mo); + } + } println!("\nGlobal aliases:"); for (alias, _, active) in p.aliases.items().iter() { @@ -173,42 +220,13 @@ pub fn print_bucket_info(bucket: &Bucket, relevant_keys: &HashMap<String, Key>) }; } -pub fn format_table(data: Vec<String>) { - let data = data - .iter() - .map(|s| s.split('\t').collect::<Vec<_>>()) - .collect::<Vec<_>>(); - - let columns = data.iter().map(|row| row.len()).fold(0, std::cmp::max); - let mut column_size = vec![0; columns]; - - let mut out = String::new(); - - for row in data.iter() { - for (i, col) in row.iter().enumerate() { - column_size[i] = std::cmp::max(column_size[i], col.chars().count()); - } - } - - for row in data.iter() { - for (col, col_len) in row[..row.len() - 1].iter().zip(column_size.iter()) { - out.push_str(col); - (0..col_len - col.chars().count() + 2).for_each(|_| out.push(' ')); - } - out.push_str(row[row.len() - 1]); - out.push('\n'); - } - - print!("{}", out); -} - pub fn find_matching_node( cand: impl std::iter::Iterator<Item = Uuid>, pattern: &str, ) -> Result<Uuid, Error> { let mut candidates = vec![]; for c in cand { - if hex::encode(&c).starts_with(&pattern) { + if hex::encode(&c).starts_with(&pattern) && !candidates.contains(&c) { candidates.push(c); } } @@ -222,3 +240,56 @@ pub fn find_matching_node( Ok(candidates[0]) } } + +pub fn print_worker_info(wi: HashMap<usize, WorkerInfo>, wlo: WorkerListOpt) { + let mut wi = wi.into_iter().collect::<Vec<_>>(); + wi.sort_by_key(|(tid, info)| { + ( + match info.state { + WorkerState::Busy | WorkerState::Throttled(_) => 0, + WorkerState::Idle => 1, + WorkerState::Done => 2, + }, + *tid, + ) + }); + + let mut table = vec![]; + for (tid, info) in wi.iter() { + if wlo.busy && !matches!(info.state, WorkerState::Busy | WorkerState::Throttled(_)) { + continue; + } + if wlo.errors && info.errors == 0 { + continue; + } + + table.push(format!("{}\t{}\t{}", tid, info.state, info.name)); + if let Some(i) = &info.info { + table.push(format!("\t\t {}", i)); + } + let tf = timeago::Formatter::new(); + let (err_ago, err_msg) = info + .last_error + .as_ref() + .map(|(m, t)| { + ( + tf.convert(Duration::from_millis(now_msec() - t)), + m.as_str(), + ) + }) + .unwrap_or(("(?) ago".into(), "(?)")); + if info.consecutive_errors > 0 { + table.push(format!( + "\t\t {} consecutive errors ({} total), last {}", + info.consecutive_errors, info.errors, err_ago, + )); + table.push(format!("\t\t {}", err_msg)); + } else if info.errors > 0 { + table.push(format!("\t\t ({} errors, last {})", info.errors, err_ago,)); + if wlo.errors { + table.push(format!("\t\t {}", err_msg)); + } + } + } + format_table(table); +} diff --git a/src/garage/main.rs b/src/garage/main.rs index e898e680..e5cba553 100644 --- a/src/garage/main.rs +++ b/src/garage/main.rs @@ -8,6 +8,14 @@ mod admin; mod cli; mod repair; mod server; +#[cfg(feature = "telemetry-otlp")] +mod tracing_setup; + +#[cfg(not(any(feature = "bundled-libs", feature = "system-libs")))] +compile_error!("Either bundled-libs or system-libs Cargo feature must be enabled"); + +#[cfg(all(feature = "bundled-libs", feature = "system-libs"))] +compile_error!("Only one of bundled-libs and system-libs Cargo features must be enabled"); use std::net::SocketAddr; use std::path::PathBuf; @@ -28,7 +36,10 @@ use admin::*; use cli::*; #[derive(StructOpt, Debug)] -#[structopt(name = "garage")] +#[structopt( + name = "garage", + about = "S3-compatible object store for self-hosted geo-distributed deployments" +)] struct Opt { /// Host to connect to for admin operations, in the format: /// <public-key>@<ip>:<port> @@ -57,20 +68,56 @@ async fn main() { if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", "netapp=info,garage=info") } - pretty_env_logger::init(); + tracing_subscriber::fmt() + .with_writer(std::io::stderr) + .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) + .init(); sodiumoxide::init().expect("Unable to init sodiumoxide"); - let opt = Opt::from_args(); + // Abort on panic (same behavior as in Go) + std::panic::set_hook(Box::new(|panic_info| { + error!("{}", panic_info.to_string()); + std::process::abort(); + })); + + // Initialize version and features info + let features = &[ + #[cfg(feature = "k2v")] + "k2v", + #[cfg(feature = "sled")] + "sled", + #[cfg(feature = "lmdb")] + "lmdb", + #[cfg(feature = "sqlite")] + "sqlite", + #[cfg(feature = "kubernetes-discovery")] + "kubernetes-discovery", + #[cfg(feature = "metrics")] + "metrics", + #[cfg(feature = "telemetry-otlp")] + "telemetry-otlp", + #[cfg(feature = "bundled-libs")] + "bundled-libs", + #[cfg(feature = "system-libs")] + "system-libs", + ][..]; + if let Some(git_version) = option_env!("GIT_VERSION") { + garage_util::version::init_version(git_version); + } + garage_util::version::init_features(features); + + // Parse arguments + let version = format!( + "{} [features: {}]", + garage_util::version::garage_version(), + features.join(", ") + ); + let opt = Opt::from_clap(&Opt::clap().version(version.as_str()).get_matches()); let res = match opt.cmd { - Command::Server => { - // Abort on panic (same behavior as in Go) - std::panic::set_hook(Box::new(|panic_info| { - error!("{}", panic_info.to_string()); - std::process::abort(); - })); - - server::run_server(opt.config_file).await + Command::Server => server::run_server(opt.config_file).await, + Command::OfflineRepair(repair_opt) => { + repair::offline::offline_repair(opt.config_file, repair_opt).await } Command::Node(NodeOperation::NodeId(node_id_opt)) => { node_id_command(opt.config_file, node_id_opt.quiet) @@ -115,7 +162,13 @@ async fn cli_command(opt: Opt) -> Result<(), Error> { } else { let node_id = garage_rpc::system::read_node_id(&config.as_ref().unwrap().metadata_dir) .err_context(READ_KEY_ERROR)?; - if let Some(a) = config.as_ref().and_then(|c| c.rpc_public_addr) { + if let Some(a) = config.as_ref().and_then(|c| c.rpc_public_addr.as_ref()) { + use std::net::ToSocketAddrs; + let a = a + .to_socket_addrs() + .ok_or_message("unable to resolve rpc_public_addr specified in config file")? + .next() + .ok_or_message("unable to resolve rpc_public_addr specified in config file")?; (node_id, a) } else { let default_addr = SocketAddr::new( @@ -141,6 +194,7 @@ async fn cli_command(opt: Opt) -> Result<(), Error> { match cli_command_dispatch(opt.cmd, &system_rpc_endpoint, &admin_rpc_endpoint, id).await { Err(HelperError::Internal(i)) => Err(Error::Message(format!("Internal error: {}", i))), Err(HelperError::BadRequest(b)) => Err(Error::Message(b)), + Err(e) => Err(Error::Message(format!("{}", e))), Ok(x) => Ok(x), } } diff --git a/src/garage/repair.rs b/src/garage/repair.rs deleted file mode 100644 index 3666ca8f..00000000 --- a/src/garage/repair.rs +++ /dev/null @@ -1,149 +0,0 @@ -use std::sync::Arc; - -use tokio::sync::watch; - -use garage_model::block_ref_table::*; -use garage_model::garage::Garage; -use garage_model::object_table::*; -use garage_model::version_table::*; -use garage_table::*; -use garage_util::error::Error; - -use crate::*; - -pub struct Repair { - pub garage: Arc<Garage>, -} - -impl Repair { - pub async fn repair_worker(&self, opt: RepairOpt, must_exit: watch::Receiver<bool>) { - if let Err(e) = self.repair_worker_aux(opt, must_exit).await { - warn!("Repair worker failed with error: {}", e); - } - } - - async fn repair_worker_aux( - &self, - opt: RepairOpt, - must_exit: watch::Receiver<bool>, - ) -> Result<(), Error> { - match opt.what { - RepairWhat::Tables => { - info!("Launching a full sync of tables"); - self.garage.bucket_table.syncer.add_full_sync(); - self.garage.object_table.syncer.add_full_sync(); - self.garage.version_table.syncer.add_full_sync(); - self.garage.block_ref_table.syncer.add_full_sync(); - self.garage.key_table.syncer.add_full_sync(); - } - RepairWhat::Versions => { - info!("Repairing the versions table"); - self.repair_versions(&must_exit).await?; - } - RepairWhat::BlockRefs => { - info!("Repairing the block refs table"); - self.repair_block_ref(&must_exit).await?; - } - RepairWhat::Blocks => { - info!("Repairing the stored blocks"); - self.garage - .block_manager - .repair_data_store(&must_exit) - .await?; - } - RepairWhat::Scrub { tranquility } => { - info!("Verifying integrity of stored blocks"); - self.garage - .block_manager - .scrub_data_store(&must_exit, tranquility) - .await?; - } - } - Ok(()) - } - - async fn repair_versions(&self, must_exit: &watch::Receiver<bool>) -> Result<(), Error> { - let mut pos = vec![]; - - while let Some((item_key, item_bytes)) = - self.garage.version_table.data.store.get_gt(&pos)? - { - pos = item_key.to_vec(); - - let version = rmp_serde::decode::from_read_ref::<_, Version>(item_bytes.as_ref())?; - if version.deleted.get() { - continue; - } - let object = self - .garage - .object_table - .get(&version.bucket_id, &version.key) - .await?; - let version_exists = match object { - Some(o) => o - .versions() - .iter() - .any(|x| x.uuid == version.uuid && x.state != ObjectVersionState::Aborted), - None => false, - }; - if !version_exists { - info!("Repair versions: marking version as deleted: {:?}", version); - self.garage - .version_table - .insert(&Version::new( - version.uuid, - version.bucket_id, - version.key, - true, - )) - .await?; - } - - if *must_exit.borrow() { - break; - } - } - Ok(()) - } - - async fn repair_block_ref(&self, must_exit: &watch::Receiver<bool>) -> Result<(), Error> { - let mut pos = vec![]; - - while let Some((item_key, item_bytes)) = - self.garage.block_ref_table.data.store.get_gt(&pos)? - { - pos = item_key.to_vec(); - - let block_ref = rmp_serde::decode::from_read_ref::<_, BlockRef>(item_bytes.as_ref())?; - if block_ref.deleted.get() { - continue; - } - let version = self - .garage - .version_table - .get(&block_ref.version, &EmptyKey) - .await?; - // The version might not exist if it has been GC'ed - let ref_exists = version.map(|v| !v.deleted.get()).unwrap_or(false); - if !ref_exists { - info!( - "Repair block ref: marking block_ref as deleted: {:?}", - block_ref - ); - self.garage - .block_ref_table - .insert(&BlockRef { - block: block_ref.block, - version: block_ref.version, - deleted: true.into(), - }) - .await?; - } - - if *must_exit.borrow() { - break; - } - } - Ok(()) - } -} diff --git a/src/garage/repair/mod.rs b/src/garage/repair/mod.rs new file mode 100644 index 00000000..4699ace5 --- /dev/null +++ b/src/garage/repair/mod.rs @@ -0,0 +1,2 @@ +pub mod offline; +pub mod online; diff --git a/src/garage/repair/offline.rs b/src/garage/repair/offline.rs new file mode 100644 index 00000000..7760a8bd --- /dev/null +++ b/src/garage/repair/offline.rs @@ -0,0 +1,55 @@ +use std::path::PathBuf; + +use tokio::sync::watch; + +use garage_util::background::*; +use garage_util::config::*; +use garage_util::error::*; + +use garage_model::garage::Garage; + +use crate::cli::structs::*; + +pub async fn offline_repair(config_file: PathBuf, opt: OfflineRepairOpt) -> Result<(), Error> { + if !opt.yes { + return Err(Error::Message( + "Please add the --yes flag to launch repair operation".into(), + )); + } + + info!("Loading configuration..."); + let config = read_config(config_file)?; + + info!("Initializing background runner..."); + let (done_tx, done_rx) = watch::channel(false); + let (background, await_background_done) = BackgroundRunner::new(16, done_rx); + + info!("Initializing Garage main data store..."); + let garage = Garage::new(config.clone(), background)?; + + info!("Launching repair operation..."); + match opt.what { + #[cfg(feature = "k2v")] + OfflineRepairWhat::K2VItemCounters => { + garage + .k2v + .counter_table + .offline_recount_all(&garage.k2v.item_table)?; + } + OfflineRepairWhat::ObjectCounters => { + garage + .object_counter_table + .offline_recount_all(&garage.object_table)?; + } + } + + info!("Repair operation finished, shutting down Garage internals..."); + done_tx.send(true).unwrap(); + drop(garage); + + await_background_done.await?; + + info!("Cleaning up..."); + + Ok(()) +} diff --git a/src/garage/repair/online.rs b/src/garage/repair/online.rs new file mode 100644 index 00000000..e33cf097 --- /dev/null +++ b/src/garage/repair/online.rs @@ -0,0 +1,215 @@ +use std::sync::Arc; +use std::time::Duration; + +use async_trait::async_trait; +use tokio::sync::watch; + +use garage_block::repair::ScrubWorkerCommand; +use garage_model::garage::Garage; +use garage_model::s3::block_ref_table::*; +use garage_model::s3::object_table::*; +use garage_model::s3::version_table::*; +use garage_table::*; +use garage_util::background::*; +use garage_util::error::Error; + +use crate::*; + +pub async fn launch_online_repair(garage: Arc<Garage>, opt: RepairOpt) { + match opt.what { + RepairWhat::Tables => { + info!("Launching a full sync of tables"); + garage.bucket_table.syncer.add_full_sync(); + garage.object_table.syncer.add_full_sync(); + garage.version_table.syncer.add_full_sync(); + garage.block_ref_table.syncer.add_full_sync(); + garage.key_table.syncer.add_full_sync(); + } + RepairWhat::Versions => { + info!("Repairing the versions table"); + garage + .background + .spawn_worker(RepairVersionsWorker::new(garage.clone())); + } + RepairWhat::BlockRefs => { + info!("Repairing the block refs table"); + garage + .background + .spawn_worker(RepairBlockrefsWorker::new(garage.clone())); + } + RepairWhat::Blocks => { + info!("Repairing the stored blocks"); + garage + .background + .spawn_worker(garage_block::repair::RepairWorker::new( + garage.block_manager.clone(), + )); + } + RepairWhat::Scrub { cmd } => { + let cmd = match cmd { + ScrubCmd::Start => ScrubWorkerCommand::Start, + ScrubCmd::Pause => ScrubWorkerCommand::Pause(Duration::from_secs(3600 * 24)), + ScrubCmd::Resume => ScrubWorkerCommand::Resume, + ScrubCmd::Cancel => ScrubWorkerCommand::Cancel, + ScrubCmd::SetTranquility { tranquility } => { + ScrubWorkerCommand::SetTranquility(tranquility) + } + }; + info!("Sending command to scrub worker: {:?}", cmd); + garage.block_manager.send_scrub_command(cmd).await; + } + } +} + +// ---- + +struct RepairVersionsWorker { + garage: Arc<Garage>, + pos: Vec<u8>, + counter: usize, +} + +impl RepairVersionsWorker { + fn new(garage: Arc<Garage>) -> Self { + Self { + garage, + pos: vec![], + counter: 0, + } + } +} + +#[async_trait] +impl Worker for RepairVersionsWorker { + fn name(&self) -> String { + "Version repair worker".into() + } + + fn info(&self) -> Option<String> { + Some(format!("{} items done", self.counter)) + } + + async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + let item_bytes = match self.garage.version_table.data.store.get_gt(&self.pos)? { + Some((k, v)) => { + self.pos = k; + v + } + None => { + info!("repair_versions: finished, done {}", self.counter); + return Ok(WorkerState::Done); + } + }; + + self.counter += 1; + + let version = rmp_serde::decode::from_read_ref::<_, Version>(&item_bytes)?; + if !version.deleted.get() { + let object = self + .garage + .object_table + .get(&version.bucket_id, &version.key) + .await?; + let version_exists = match object { + Some(o) => o + .versions() + .iter() + .any(|x| x.uuid == version.uuid && x.state != ObjectVersionState::Aborted), + None => false, + }; + if !version_exists { + info!("Repair versions: marking version as deleted: {:?}", version); + self.garage + .version_table + .insert(&Version::new( + version.uuid, + version.bucket_id, + version.key, + true, + )) + .await?; + } + } + + Ok(WorkerState::Busy) + } + + async fn wait_for_work(&mut self, _must_exit: &watch::Receiver<bool>) -> WorkerState { + unreachable!() + } +} + +// ---- + +struct RepairBlockrefsWorker { + garage: Arc<Garage>, + pos: Vec<u8>, + counter: usize, +} + +impl RepairBlockrefsWorker { + fn new(garage: Arc<Garage>) -> Self { + Self { + garage, + pos: vec![], + counter: 0, + } + } +} + +#[async_trait] +impl Worker for RepairBlockrefsWorker { + fn name(&self) -> String { + "Block refs repair worker".into() + } + + fn info(&self) -> Option<String> { + Some(format!("{} items done", self.counter)) + } + + async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + let item_bytes = match self.garage.block_ref_table.data.store.get_gt(&self.pos)? { + Some((k, v)) => { + self.pos = k; + v + } + None => { + info!("repair_block_ref: finished, done {}", self.counter); + return Ok(WorkerState::Done); + } + }; + + self.counter += 1; + + let block_ref = rmp_serde::decode::from_read_ref::<_, BlockRef>(&item_bytes)?; + if !block_ref.deleted.get() { + let version = self + .garage + .version_table + .get(&block_ref.version, &EmptyKey) + .await?; + // The version might not exist if it has been GC'ed + let ref_exists = version.map(|v| !v.deleted.get()).unwrap_or(false); + if !ref_exists { + info!( + "Repair block ref: marking block_ref as deleted: {:?}", + block_ref + ); + self.garage + .block_ref_table + .insert(&BlockRef { + block: block_ref.block, + version: block_ref.version, + deleted: true.into(), + }) + .await?; + } + } + + Ok(WorkerState::Busy) + } + + async fn wait_for_work(&mut self, _must_exit: &watch::Receiver<bool>) -> WorkerState { + unreachable!() + } +} diff --git a/src/garage/server.rs b/src/garage/server.rs index 58c9e782..d4099a97 100644 --- a/src/garage/server.rs +++ b/src/garage/server.rs @@ -6,13 +6,17 @@ use garage_util::background::*; use garage_util::config::*; use garage_util::error::Error; -use garage_admin::metrics::*; -use garage_admin::tracing_setup::*; -use garage_api::run_api_server; +use garage_api::admin::api_server::AdminApiServer; +use garage_api::s3::api_server::S3ApiServer; use garage_model::garage::Garage; -use garage_web::run_web_server; +use garage_web::WebServer; + +#[cfg(feature = "k2v")] +use garage_api::k2v::api_server::K2VApiServer; use crate::admin::*; +#[cfg(feature = "telemetry-otlp")] +use crate::tracing_setup::*; async fn wait_from(mut chan: watch::Receiver<bool>) { while !*chan.borrow() { @@ -24,79 +28,124 @@ async fn wait_from(mut chan: watch::Receiver<bool>) { pub async fn run_server(config_file: PathBuf) -> Result<(), Error> { info!("Loading configuration..."); - let config = read_config(config_file).expect("Unable to read config file"); + let config = read_config(config_file)?; - info!("Opening database..."); - let mut db_path = config.metadata_dir.clone(); - db_path.push("db"); - let db = sled::Config::default() - .path(&db_path) - .cache_capacity(config.sled_cache_capacity) - .flush_every_ms(Some(config.sled_flush_every_ms)) - .open() - .expect("Unable to open sled DB"); + // ---- Initialize Garage internals ---- - info!("Initialize admin web server and metric backend..."); - let admin_server_init = AdminServer::init(); + #[cfg(feature = "metrics")] + let metrics_exporter = opentelemetry_prometheus::exporter().init(); info!("Initializing background runner..."); - let watch_cancel = netapp::util::watch_ctrl_c(); + let watch_cancel = watch_shutdown_signal(); let (background, await_background_done) = BackgroundRunner::new(16, watch_cancel.clone()); info!("Initializing Garage main data store..."); - let garage = Garage::new(config.clone(), db, background); + let garage = Garage::new(config.clone(), background)?; + + if config.admin.trace_sink.is_some() { + info!("Initialize tracing..."); - info!("Initialize tracing..."); - if let Some(export_to) = config.admin.trace_sink { - init_tracing(&export_to, garage.system.id)?; + #[cfg(feature = "telemetry-otlp")] + init_tracing(config.admin.trace_sink.as_ref().unwrap(), garage.system.id)?; + + #[cfg(not(feature = "telemetry-otlp"))] + error!("Garage was built without OTLP exporter, admin.trace_sink is ignored."); } + info!("Initialize Admin API server and metrics collector..."); + let admin_server = AdminApiServer::new( + garage.clone(), + #[cfg(feature = "metrics")] + metrics_exporter, + ); + + info!("Launching internal Garage cluster communications..."); let run_system = tokio::spawn(garage.system.clone().run(watch_cancel.clone())); info!("Create admin RPC handler..."); AdminRpcHandler::new(garage.clone()); - info!("Initializing API server..."); - let api_server = tokio::spawn(run_api_server( - garage.clone(), - wait_from(watch_cancel.clone()), - )); + // ---- Launch public-facing API servers ---- + + let mut servers = vec![]; + + if let Some(s3_bind_addr) = &config.s3_api.api_bind_addr { + info!("Initializing S3 API server..."); + servers.push(( + "S3 API", + tokio::spawn(S3ApiServer::run( + garage.clone(), + *s3_bind_addr, + config.s3_api.s3_region.clone(), + wait_from(watch_cancel.clone()), + )), + )); + } - info!("Initializing web server..."); - let web_server = tokio::spawn(run_web_server( - garage.clone(), - wait_from(watch_cancel.clone()), - )); - - let admin_server = if let Some(admin_bind_addr) = config.admin.api_bind_addr { - info!("Configure and run admin web server..."); - Some(tokio::spawn( - admin_server_init.run(admin_bind_addr, wait_from(watch_cancel.clone())), - )) - } else { - None - }; + if config.k2v_api.is_some() { + #[cfg(feature = "k2v")] + { + info!("Initializing K2V API server..."); + servers.push(( + "K2V API", + tokio::spawn(K2VApiServer::run( + garage.clone(), + config.k2v_api.as_ref().unwrap().api_bind_addr, + config.s3_api.s3_region.clone(), + wait_from(watch_cancel.clone()), + )), + )); + } + #[cfg(not(feature = "k2v"))] + error!("K2V is not enabled in this build, cannot start K2V API server"); + } - // Stuff runs + if let Some(web_config) = &config.s3_web { + info!("Initializing web server..."); + servers.push(( + "Web", + tokio::spawn(WebServer::run( + garage.clone(), + web_config.bind_addr, + web_config.root_domain.clone(), + wait_from(watch_cancel.clone()), + )), + )); + } - // When a cancel signal is sent, stuff stops - if let Err(e) = api_server.await? { - warn!("API server exited with error: {}", e); + if let Some(admin_bind_addr) = &config.admin.api_bind_addr { + info!("Launching Admin API server..."); + servers.push(( + "Admin", + tokio::spawn(admin_server.run(*admin_bind_addr, wait_from(watch_cancel.clone()))), + )); } - if let Err(e) = web_server.await? { - warn!("Web server exited with error: {}", e); + + #[cfg(not(feature = "metrics"))] + if config.admin.metrics_token.is_some() { + warn!("This Garage version is built without the metrics feature"); } - if let Some(a) = admin_server { - if let Err(e) = a.await? { - warn!("Admin web server exited with error: {}", e); + + // Stuff runs + + // When a cancel signal is sent, stuff stops + + // Collect stuff + for (desc, join_handle) in servers { + if let Err(e) = join_handle.await? { + error!("{} server exited with error: {}", desc, e); + } else { + info!("{} server exited without error.", desc); } } // Remove RPC handlers for system to break reference cycles garage.system.netapp.drop_all_handlers(); + opentelemetry::global::shutdown_tracer_provider(); // Await for netapp RPC system to end run_system.await?; + info!("Netapp exited"); // Drop all references so that stuff can terminate properly drop(garage); @@ -108,3 +157,44 @@ pub async fn run_server(config_file: PathBuf) -> Result<(), Error> { Ok(()) } + +#[cfg(unix)] +fn watch_shutdown_signal() -> watch::Receiver<bool> { + use tokio::signal::unix::*; + + let (send_cancel, watch_cancel) = watch::channel(false); + tokio::spawn(async move { + let mut sigint = signal(SignalKind::interrupt()).expect("Failed to install SIGINT handler"); + let mut sigterm = + signal(SignalKind::terminate()).expect("Failed to install SIGTERM handler"); + let mut sighup = signal(SignalKind::hangup()).expect("Failed to install SIGHUP handler"); + tokio::select! { + _ = sigint.recv() => info!("Received SIGINT, shutting down."), + _ = sigterm.recv() => info!("Received SIGTERM, shutting down."), + _ = sighup.recv() => info!("Received SIGHUP, shutting down."), + } + send_cancel.send(true).unwrap(); + }); + watch_cancel +} + +#[cfg(windows)] +fn watch_shutdown_signal() -> watch::Receiver<bool> { + use tokio::signal::windows::*; + + let (send_cancel, watch_cancel) = watch::channel(false); + tokio::spawn(async move { + let mut sigint = ctrl_c().expect("Failed to install Ctrl-C handler"); + let mut sigclose = ctrl_close().expect("Failed to install Ctrl-Close handler"); + let mut siglogoff = ctrl_logoff().expect("Failed to install Ctrl-Logoff handler"); + let mut sigsdown = ctrl_shutdown().expect("Failed to install Ctrl-Shutdown handler"); + tokio::select! { + _ = sigint.recv() => info!("Received Ctrl-C, shutting down."), + _ = sigclose.recv() => info!("Received Ctrl-Close, shutting down."), + _ = siglogoff.recv() => info!("Received Ctrl-Logoff, shutting down."), + _ = sigsdown.recv() => info!("Received Ctrl-Shutdown, shutting down."), + } + send_cancel.send(true).unwrap(); + }); + watch_cancel +} diff --git a/src/garage/tests/bucket.rs b/src/garage/tests/bucket.rs index ff5cc8da..b32af068 100644 --- a/src/garage/tests/bucket.rs +++ b/src/garage/tests/bucket.rs @@ -29,8 +29,7 @@ async fn test_bucket_all() { .unwrap() .iter() .filter(|x| x.name.as_ref().is_some()) - .find(|x| x.name.as_ref().unwrap() == "hello") - .is_some()); + .any(|x| x.name.as_ref().unwrap() == "hello")); } { // Get its location @@ -75,13 +74,12 @@ async fn test_bucket_all() { { // Check bucket is deleted with List buckets let r = ctx.client.list_buckets().send().await.unwrap(); - assert!(r + assert!(!r .buckets .as_ref() .unwrap() .iter() .filter(|x| x.name.as_ref().is_some()) - .find(|x| x.name.as_ref().unwrap() == "hello") - .is_none()); + .any(|x| x.name.as_ref().unwrap() == "hello")); } } diff --git a/src/garage/tests/common/client.rs b/src/garage/tests/common/client.rs index c5ddc6e5..212588b5 100644 --- a/src/garage/tests/common/client.rs +++ b/src/garage/tests/common/client.rs @@ -10,7 +10,7 @@ pub fn build_client(instance: &Instance) -> Client { None, "garage-integ-test", ); - let endpoint = Endpoint::immutable(instance.uri()); + let endpoint = Endpoint::immutable(instance.s3_uri()); let config = Config::builder() .region(super::REGION) diff --git a/src/garage/tests/common/custom_requester.rs b/src/garage/tests/common/custom_requester.rs index 580691a1..1700cc90 100644 --- a/src/garage/tests/common/custom_requester.rs +++ b/src/garage/tests/common/custom_requester.rs @@ -17,14 +17,25 @@ use garage_api::signature; pub struct CustomRequester { key: Key, uri: Uri, + service: &'static str, client: Client<HttpConnector>, } impl CustomRequester { - pub fn new(instance: &Instance) -> Self { + pub fn new_s3(instance: &Instance) -> Self { CustomRequester { key: instance.key.clone(), - uri: instance.uri(), + uri: instance.s3_uri(), + service: "s3", + client: Client::new(), + } + } + + pub fn new_k2v(instance: &Instance) -> Self { + CustomRequester { + key: instance.key.clone(), + uri: instance.k2v_uri(), + service: "k2v", client: Client::new(), } } @@ -32,6 +43,7 @@ impl CustomRequester { pub fn builder(&self, bucket: String) -> RequestBuilder<'_> { RequestBuilder { requester: self, + service: self.service, bucket, method: Method::GET, path: String::new(), @@ -47,6 +59,7 @@ impl CustomRequester { pub struct RequestBuilder<'a> { requester: &'a CustomRequester, + service: &'static str, bucket: String, method: Method, path: String, @@ -59,13 +72,17 @@ pub struct RequestBuilder<'a> { } impl<'a> RequestBuilder<'a> { + pub fn service(&mut self, service: &'static str) -> &mut Self { + self.service = service; + self + } pub fn method(&mut self, method: Method) -> &mut Self { self.method = method; self } - pub fn path(&mut self, path: String) -> &mut Self { - self.path = path; + pub fn path(&mut self, path: impl ToString) -> &mut Self { + self.path = path.to_string(); self } @@ -74,16 +91,38 @@ impl<'a> RequestBuilder<'a> { self } + pub fn query_param<T, U>(&mut self, param: T, value: Option<U>) -> &mut Self + where + T: ToString, + U: ToString, + { + self.query_params + .insert(param.to_string(), value.as_ref().map(ToString::to_string)); + self + } + pub fn signed_headers(&mut self, signed_headers: HashMap<String, String>) -> &mut Self { self.signed_headers = signed_headers; self } + pub fn signed_header(&mut self, name: impl ToString, value: impl ToString) -> &mut Self { + self.signed_headers + .insert(name.to_string(), value.to_string()); + self + } + pub fn unsigned_headers(&mut self, unsigned_headers: HashMap<String, String>) -> &mut Self { self.unsigned_headers = unsigned_headers; self } + pub fn unsigned_header(&mut self, name: impl ToString, value: impl ToString) -> &mut Self { + self.unsigned_headers + .insert(name.to_string(), value.to_string()); + self + } + pub fn body(&mut self, body: Vec<u8>) -> &mut Self { self.body = body; self @@ -106,24 +145,24 @@ impl<'a> RequestBuilder<'a> { let query = query_param_to_string(&self.query_params); let (host, path) = if self.vhost_style { ( - format!("{}.s3.garage", self.bucket), + format!("{}.{}.garage", self.bucket, self.service), format!("{}{}", self.path, query), ) } else { ( - "s3.garage".to_owned(), + format!("{}.garage", self.service), format!("{}/{}{}", self.bucket, self.path, query), ) }; let uri = format!("{}{}", self.requester.uri, path); let now = Utc::now(); - let scope = signature::compute_scope(&now, super::REGION.as_ref()); + let scope = signature::compute_scope(&now, super::REGION.as_ref(), self.service); let mut signer = signature::signing_hmac( &now, &self.requester.key.secret, super::REGION.as_ref(), - "s3", + self.service, ) .unwrap(); let streaming_signer = signer.clone(); diff --git a/src/garage/tests/common/garage.rs b/src/garage/tests/common/garage.rs index 88c51501..44d727f9 100644 --- a/src/garage/tests/common/garage.rs +++ b/src/garage/tests/common/garage.rs @@ -22,7 +22,9 @@ pub struct Instance { process: process::Child, pub path: PathBuf, pub key: Key, - pub api_port: u16, + pub s3_port: u16, + pub k2v_port: u16, + pub web_port: u16, } impl Instance { @@ -58,9 +60,12 @@ rpc_secret = "{secret}" [s3_api] s3_region = "{region}" -api_bind_addr = "127.0.0.1:{api_port}" +api_bind_addr = "127.0.0.1:{s3_port}" root_domain = ".s3.garage" +[k2v_api] +api_bind_addr = "127.0.0.1:{k2v_port}" + [s3_web] bind_addr = "127.0.0.1:{web_port}" root_domain = ".web.garage" @@ -72,10 +77,11 @@ api_bind_addr = "127.0.0.1:{admin_port}" path = path.display(), secret = GARAGE_TEST_SECRET, region = super::REGION, - api_port = port, - rpc_port = port + 1, - web_port = port + 2, - admin_port = port + 3, + s3_port = port, + k2v_port = port + 1, + rpc_port = port + 2, + web_port = port + 3, + admin_port = port + 4, ); fs::write(path.join("config.toml"), config).expect("Could not write garage config file"); @@ -88,7 +94,7 @@ api_bind_addr = "127.0.0.1:{admin_port}" .arg("server") .stdout(stdout) .stderr(stderr) - .env("RUST_LOG", "garage=info,garage_api=debug") + .env("RUST_LOG", "garage=info,garage_api=trace") .spawn() .expect("Could not start garage"); @@ -96,7 +102,9 @@ api_bind_addr = "127.0.0.1:{admin_port}" process: child, path, key: Key::default(), - api_port: port, + s3_port: port, + k2v_port: port + 1, + web_port: port + 3, } } @@ -147,8 +155,14 @@ api_bind_addr = "127.0.0.1:{admin_port}" String::from_utf8(output.stdout).unwrap() } - pub fn uri(&self) -> http::Uri { - format!("http://127.0.0.1:{api_port}", api_port = self.api_port) + pub fn s3_uri(&self) -> http::Uri { + format!("http://127.0.0.1:{s3_port}", s3_port = self.s3_port) + .parse() + .expect("Could not build garage endpoint URI") + } + + pub fn k2v_uri(&self) -> http::Uri { + format!("http://127.0.0.1:{k2v_port}", k2v_port = self.k2v_port) .parse() .expect("Could not build garage endpoint URI") } diff --git a/src/garage/tests/common/mod.rs b/src/garage/tests/common/mod.rs index 8f88c731..28874b02 100644 --- a/src/garage/tests/common/mod.rs +++ b/src/garage/tests/common/mod.rs @@ -17,18 +17,27 @@ pub struct Context { pub garage: &'static garage::Instance, pub client: Client, pub custom_request: CustomRequester, + pub k2v: K2VContext, +} + +pub struct K2VContext { + pub request: CustomRequester, } impl Context { fn new() -> Self { let garage = garage::instance(); let client = client::build_client(garage); - let custom_request = CustomRequester::new(garage); + let custom_request = CustomRequester::new_s3(garage); + let k2v_request = CustomRequester::new_k2v(garage); Context { garage, client, custom_request, + k2v: K2VContext { + request: k2v_request, + }, } } diff --git a/src/garage/tests/k2v/batch.rs b/src/garage/tests/k2v/batch.rs new file mode 100644 index 00000000..acae1910 --- /dev/null +++ b/src/garage/tests/k2v/batch.rs @@ -0,0 +1,612 @@ +use std::collections::HashMap; + +use crate::common; + +use assert_json_diff::assert_json_eq; +use serde_json::json; + +use super::json_body; +use hyper::Method; + +#[tokio::test] +async fn test_batch() { + let ctx = common::context(); + let bucket = ctx.create_bucket("test-k2v-batch"); + + let mut values = HashMap::new(); + values.insert("a", "initial test 1"); + values.insert("b", "initial test 2"); + values.insert("c", "initial test 3"); + values.insert("d.1", "initial test 4"); + values.insert("d.2", "initial test 5"); + values.insert("e", "initial test 6"); + let mut ct = HashMap::new(); + + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .body( + format!( + r#"[ + {{"pk": "root", "sk": "a", "ct": null, "v": "{}"}}, + {{"pk": "root", "sk": "b", "ct": null, "v": "{}"}}, + {{"pk": "root", "sk": "c", "ct": null, "v": "{}"}}, + {{"pk": "root", "sk": "d.1", "ct": null, "v": "{}"}}, + {{"pk": "root", "sk": "d.2", "ct": null, "v": "{}"}}, + {{"pk": "root", "sk": "e", "ct": null, "v": "{}"}} + ]"#, + base64::encode(values.get(&"a").unwrap()), + base64::encode(values.get(&"b").unwrap()), + base64::encode(values.get(&"c").unwrap()), + base64::encode(values.get(&"d.1").unwrap()), + base64::encode(values.get(&"d.2").unwrap()), + base64::encode(values.get(&"e").unwrap()), + ) + .into_bytes(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + for sk in ["a", "b", "c", "d.1", "d.2", "e"] { + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/octet-stream" + ); + ct.insert( + sk, + res.headers() + .get("x-garage-causality-token") + .unwrap() + .to_str() + .unwrap() + .to_string(), + ); + let res_body = hyper::body::to_bytes(res.into_body()) + .await + .unwrap() + .to_vec(); + assert_eq!(res_body, values.get(sk).unwrap().as_bytes()); + } + + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .query_param("search", Option::<&str>::None) + .body( + br#"[ + {"partitionKey": "root"}, + {"partitionKey": "root", "start": "c"}, + {"partitionKey": "root", "start": "c", "end": "dynamite"}, + {"partitionKey": "root", "start": "c", "reverse": true, "end": "a"}, + {"partitionKey": "root", "start": "c", "reverse": true, "end": "azerty"}, + {"partitionKey": "root", "limit": 1}, + {"partitionKey": "root", "prefix": "d"} + ]"# + .to_vec(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + let json_res = json_body(res).await; + assert_json_eq!( + json_res, + json!([ + { + "partitionKey": "root", + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "a", "ct": ct.get("a").unwrap(), "v": [base64::encode(values.get("a").unwrap())]}, + {"sk": "b", "ct": ct.get("b").unwrap(), "v": [base64::encode(values.get("b").unwrap())]}, + {"sk": "c", "ct": ct.get("c").unwrap(), "v": [base64::encode(values.get("c").unwrap())]}, + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1").unwrap())]}, + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap())]}, + {"sk": "e", "ct": ct.get("e").unwrap(), "v": [base64::encode(values.get("e").unwrap())]} + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": null, + "start": "c", + "end": null, + "limit": null, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "c", "ct": ct.get("c").unwrap(), "v": [base64::encode(values.get("c").unwrap())]}, + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1").unwrap())]}, + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap())]}, + {"sk": "e", "ct": ct.get("e").unwrap(), "v": [base64::encode(values.get("e").unwrap())]} + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": null, + "start": "c", + "end": "dynamite", + "limit": null, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "c", "ct": ct.get("c").unwrap(), "v": [base64::encode(values.get("c").unwrap())]}, + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1").unwrap())]}, + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": null, + "start": "c", + "end": "a", + "limit": null, + "reverse": true, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "c", "ct": ct.get("c").unwrap(), "v": [base64::encode(values.get("c").unwrap())]}, + {"sk": "b", "ct": ct.get("b").unwrap(), "v": [base64::encode(values.get("b").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": null, + "start": "c", + "end": "azerty", + "limit": null, + "reverse": true, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "c", "ct": ct.get("c").unwrap(), "v": [base64::encode(values.get("c").unwrap())]}, + {"sk": "b", "ct": ct.get("b").unwrap(), "v": [base64::encode(values.get("b").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": null, + "start": null, + "end": null, + "limit": 1, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "a", "ct": ct.get("a").unwrap(), "v": [base64::encode(values.get("a").unwrap())]} + ], + "more": true, + "nextStart": "b", + }, + { + "partitionKey": "root", + "prefix": "d", + "start": null, + "end": null, + "limit": null, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1").unwrap())]}, + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap())]} + ], + "more": false, + "nextStart": null, + }, + ]) + ); + + // Insert some new values + values.insert("c'", "new test 3"); + values.insert("d.1'", "new test 4"); + values.insert("d.2'", "new test 5"); + + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .body( + format!( + r#"[ + {{"pk": "root", "sk": "b", "ct": "{}", "v": null}}, + {{"pk": "root", "sk": "c", "ct": null, "v": "{}"}}, + {{"pk": "root", "sk": "d.1", "ct": "{}", "v": "{}"}}, + {{"pk": "root", "sk": "d.2", "ct": null, "v": "{}"}} + ]"#, + ct.get(&"b").unwrap(), + base64::encode(values.get(&"c'").unwrap()), + ct.get(&"d.1").unwrap(), + base64::encode(values.get(&"d.1'").unwrap()), + base64::encode(values.get(&"d.2'").unwrap()), + ) + .into_bytes(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + for sk in ["b", "c", "d.1", "d.2"] { + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + if sk == "b" { + assert_eq!(res.status(), 204); + } else { + assert_eq!(res.status(), 200); + } + ct.insert( + sk, + res.headers() + .get("x-garage-causality-token") + .unwrap() + .to_str() + .unwrap() + .to_string(), + ); + } + + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .query_param("search", Option::<&str>::None) + .body( + br#"[ + {"partitionKey": "root"}, + {"partitionKey": "root", "prefix": "d"}, + {"partitionKey": "root", "prefix": "d.", "end": "d.2"}, + {"partitionKey": "root", "prefix": "d.", "limit": 1}, + {"partitionKey": "root", "prefix": "d.", "start": "d.2", "limit": 1}, + {"partitionKey": "root", "prefix": "d.", "reverse": true}, + {"partitionKey": "root", "prefix": "d.", "start": "d.2", "reverse": true}, + {"partitionKey": "root", "prefix": "d.", "limit": 2} + ]"# + .to_vec(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + let json_res = json_body(res).await; + assert_json_eq!( + json_res, + json!([ + { + "partitionKey": "root", + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "a", "ct": ct.get("a").unwrap(), "v": [base64::encode(values.get("a").unwrap())]}, + {"sk": "c", "ct": ct.get("c").unwrap(), "v": [base64::encode(values.get("c").unwrap()), base64::encode(values.get("c'").unwrap())]}, + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1'").unwrap())]}, + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap()), base64::encode(values.get("d.2'").unwrap())]}, + {"sk": "e", "ct": ct.get("e").unwrap(), "v": [base64::encode(values.get("e").unwrap())]} + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": "d", + "start": null, + "end": null, + "limit": null, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1'").unwrap())]}, + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap()), base64::encode(values.get("d.2'").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": "d.", + "start": null, + "end": "d.2", + "limit": null, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1'").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": "d.", + "start": null, + "end": null, + "limit": 1, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1'").unwrap())]}, + ], + "more": true, + "nextStart": "d.2", + }, + { + "partitionKey": "root", + "prefix": "d.", + "start": "d.2", + "end": null, + "limit": 1, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap()), base64::encode(values.get("d.2'").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": "d.", + "start": null, + "end": null, + "limit": null, + "reverse": true, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap()), base64::encode(values.get("d.2'").unwrap())]}, + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1'").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": "d.", + "start": "d.2", + "end": null, + "limit": null, + "reverse": true, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap()), base64::encode(values.get("d.2'").unwrap())]}, + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1'").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": "d.", + "start": null, + "end": null, + "limit": 2, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [base64::encode(values.get("d.1'").unwrap())]}, + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [base64::encode(values.get("d.2").unwrap()), base64::encode(values.get("d.2'").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + ]) + ); + + // Test DeleteBatch + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .query_param("delete", Option::<&str>::None) + .body( + br#"[ + {"partitionKey": "root", "start": "a", "end": "c"}, + {"partitionKey": "root", "prefix": "d"} + ]"# + .to_vec(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + let json_res = json_body(res).await; + assert_json_eq!( + json_res, + json!([ + { + "partitionKey": "root", + "prefix": null, + "start": "a", + "end": "c", + "singleItem": false, + "deletedItems": 1, + }, + { + "partitionKey": "root", + "prefix": "d", + "start": null, + "end": null, + "singleItem": false, + "deletedItems": 2, + }, + ]) + ); + + // update our known tombstones + for sk in ["a", "b", "d.1", "d.2"] { + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("accept", "application/octet-stream") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 204); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/octet-stream" + ); + ct.insert( + sk, + res.headers() + .get("x-garage-causality-token") + .unwrap() + .to_str() + .unwrap() + .to_string(), + ); + } + + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .query_param("search", Option::<&str>::None) + .body( + br#"[ + {"partitionKey": "root"}, + {"partitionKey": "root", "reverse": true}, + {"partitionKey": "root", "tombstones": true} + ]"# + .to_vec(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + let json_res = json_body(res).await; + assert_json_eq!( + json_res, + json!([ + { + "partitionKey": "root", + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "c", "ct": ct.get("c").unwrap(), "v": [base64::encode(values.get("c").unwrap()), base64::encode(values.get("c'").unwrap())]}, + {"sk": "e", "ct": ct.get("e").unwrap(), "v": [base64::encode(values.get("e").unwrap())]} + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": true, + "conflictsOnly": false, + "tombstones": false, + "singleItem": false, + "items": [ + {"sk": "e", "ct": ct.get("e").unwrap(), "v": [base64::encode(values.get("e").unwrap())]}, + {"sk": "c", "ct": ct.get("c").unwrap(), "v": [base64::encode(values.get("c").unwrap()), base64::encode(values.get("c'").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + { + "partitionKey": "root", + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "conflictsOnly": false, + "tombstones": true, + "singleItem": false, + "items": [ + {"sk": "a", "ct": ct.get("a").unwrap(), "v": [null]}, + {"sk": "b", "ct": ct.get("b").unwrap(), "v": [null]}, + {"sk": "c", "ct": ct.get("c").unwrap(), "v": [base64::encode(values.get("c").unwrap()), base64::encode(values.get("c'").unwrap())]}, + {"sk": "d.1", "ct": ct.get("d.1").unwrap(), "v": [null]}, + {"sk": "d.2", "ct": ct.get("d.2").unwrap(), "v": [null]}, + {"sk": "e", "ct": ct.get("e").unwrap(), "v": [base64::encode(values.get("e").unwrap())]}, + ], + "more": false, + "nextStart": null, + }, + ]) + ); +} diff --git a/src/garage/tests/k2v/errorcodes.rs b/src/garage/tests/k2v/errorcodes.rs new file mode 100644 index 00000000..2fcc45bc --- /dev/null +++ b/src/garage/tests/k2v/errorcodes.rs @@ -0,0 +1,141 @@ +use crate::common; + +use hyper::Method; + +#[tokio::test] +async fn test_error_codes() { + let ctx = common::context(); + let bucket = ctx.create_bucket("test-k2v-error-codes"); + + // Regular insert should work (code 200) + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .method(Method::PUT) + .path("root") + .query_param("sort_key", Some("test1")) + .body(b"Hello, world!".to_vec()) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + // Insert with trash causality token: invalid request + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .method(Method::PUT) + .path("root") + .query_param("sort_key", Some("test1")) + .signed_header("x-garage-causality-token", "tra$sh") + .body(b"Hello, world!".to_vec()) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 400); + + // Search without partition key: invalid request + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .query_param("search", Option::<&str>::None) + .body( + br#"[ + {}, + ]"# + .to_vec(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 400); + + // Search with start that is not in prefix: invalid request + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .query_param("search", Option::<&str>::None) + .body( + br#"[ + {"partition_key": "root", "prefix": "a", "start": "bx"}, + ]"# + .to_vec(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 400); + + // Search with invalid json: 400 + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .query_param("search", Option::<&str>::None) + .body( + br#"[ + {"partition_key": "root" + ]"# + .to_vec(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 400); + + // Batch insert with invalid causality token: 400 + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .body( + br#"[ + {"pk": "root", "sk": "a", "ct": "tra$h", "v": "aGVsbG8sIHdvcmxkCg=="} + ]"# + .to_vec(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 400); + + // Batch insert with invalid data: 400 + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .body( + br#"[ + {"pk": "root", "sk": "a", "ct": null, "v": "aGVsbG8sIHdvcmx$Cg=="} + ]"# + .to_vec(), + ) + .method(Method::POST) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 400); + + // Poll with invalid causality token: 400 + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("test1")) + .query_param("causality_token", Some("tra$h")) + .query_param("timeout", Some("10")) + .signed_header("accept", "application/octet-stream") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 400); +} diff --git a/src/garage/tests/k2v/item.rs b/src/garage/tests/k2v/item.rs new file mode 100644 index 00000000..32537336 --- /dev/null +++ b/src/garage/tests/k2v/item.rs @@ -0,0 +1,725 @@ +use std::time::Duration; + +use crate::common; + +use assert_json_diff::assert_json_eq; +use serde_json::json; + +use super::json_body; +use hyper::Method; + +#[tokio::test] +async fn test_items_and_indices() { + let ctx = common::context(); + let bucket = ctx.create_bucket("test-k2v-item-and-index"); + + // ReadIndex -- there should be nothing + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .send() + .await + .unwrap(); + let res_body = json_body(res).await; + assert_json_eq!( + res_body, + json!({ + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "partitionKeys": [], + "more": false, + "nextStart": null + }) + ); + + let content2_len = "_: hello universe".len(); + let content3_len = "_: concurrent value".len(); + + for (i, sk) in ["a", "b", "c", "d"].iter().enumerate() { + let content = format!("{}: hello world", sk).into_bytes(); + let content2 = format!("{}: hello universe", sk).into_bytes(); + let content3 = format!("{}: concurrent value", sk).into_bytes(); + + // Put initially, no causality token + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .body(content.clone()) + .method(Method::PUT) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + // Get value back + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/octet-stream" + ); + let ct = res + .headers() + .get("x-garage-causality-token") + .unwrap() + .to_str() + .unwrap() + .to_string(); + let res_body = hyper::body::to_bytes(res.into_body()) + .await + .unwrap() + .to_vec(); + assert_eq!(res_body, content); + + // ReadIndex -- now there should be some stuff + tokio::time::sleep(Duration::from_secs(1)).await; + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .send() + .await + .unwrap(); + let res_body = json_body(res).await; + assert_json_eq!( + res_body, + json!({ + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "partitionKeys": [ + { + "pk": "root", + "entries": i+1, + "conflicts": i, + "values": i+i+1, + "bytes": i*(content2.len() + content3.len()) + content.len(), + } + ], + "more": false, + "nextStart": null + }) + ); + + // Put again, this time with causality token + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("x-garage-causality-token", ct.clone()) + .body(content2.clone()) + .method(Method::PUT) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + // Get value back + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/octet-stream" + ); + let res_body = hyper::body::to_bytes(res.into_body()) + .await + .unwrap() + .to_vec(); + assert_eq!(res_body, content2); + + // ReadIndex -- now there should be some stuff + tokio::time::sleep(Duration::from_secs(1)).await; + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .send() + .await + .unwrap(); + let res_body = json_body(res).await; + assert_json_eq!( + res_body, + json!({ + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "partitionKeys": [ + { + "pk": "root", + "entries": i+1, + "conflicts": i, + "values": i+i+1, + "bytes": i*content3.len() + (i+1)*content2.len(), + } + ], + "more": false, + "nextStart": null + }) + ); + + // Put again with same CT, now we have concurrent values + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("x-garage-causality-token", ct.clone()) + .body(content3.clone()) + .method(Method::PUT) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + // Get value back + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_json = json_body(res).await; + assert_json_eq!( + res_json, + [base64::encode(&content2), base64::encode(&content3)] + ); + + // ReadIndex -- now there should be some stuff + tokio::time::sleep(Duration::from_secs(1)).await; + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .send() + .await + .unwrap(); + let res_body = json_body(res).await; + assert_json_eq!( + res_body, + json!({ + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "partitionKeys": [ + { + "pk": "root", + "entries": i+1, + "conflicts": i+1, + "values": 2*(i+1), + "bytes": (i+1)*(content2.len() + content3.len()), + } + ], + "more": false, + "nextStart": null + }) + ); + } + + // Now delete things + for (i, sk) in ["a", "b", "c", "d"].iter().enumerate() { + // Get value back (we just need the CT) + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + let ct = res + .headers() + .get("x-garage-causality-token") + .unwrap() + .to_str() + .unwrap() + .to_string(); + + // Delete it + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .method(Method::DELETE) + .path("root") + .query_param("sort_key", Some(sk)) + .signed_header("x-garage-causality-token", ct) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 204); + + // ReadIndex -- now there should be some stuff + tokio::time::sleep(Duration::from_secs(1)).await; + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .send() + .await + .unwrap(); + let res_body = json_body(res).await; + if i < 3 { + assert_json_eq!( + res_body, + json!({ + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "partitionKeys": [ + { + "pk": "root", + "entries": 3-i, + "conflicts": 3-i, + "values": 2*(3-i), + "bytes": (3-i)*(content2_len + content3_len), + } + ], + "more": false, + "nextStart": null + }) + ); + } else { + assert_json_eq!( + res_body, + json!({ + "prefix": null, + "start": null, + "end": null, + "limit": null, + "reverse": false, + "partitionKeys": [], + "more": false, + "nextStart": null + }) + ); + } + } +} + +#[tokio::test] +async fn test_item_return_format() { + let ctx = common::context(); + let bucket = ctx.create_bucket("test-k2v-item-return-format"); + + let single_value = b"A single value".to_vec(); + let concurrent_value = b"A concurrent value".to_vec(); + + // -- Test with a single value -- + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .body(single_value.clone()) + .method(Method::PUT) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + // f0: either + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/octet-stream" + ); + let ct = res + .headers() + .get("x-garage-causality-token") + .unwrap() + .to_str() + .unwrap() + .to_string(); + let res_body = hyper::body::to_bytes(res.into_body()) + .await + .unwrap() + .to_vec(); + assert_eq!(res_body, single_value); + + // f1: not specified + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_body = json_body(res).await; + assert_json_eq!(res_body, json!([base64::encode(&single_value)])); + + // f2: binary + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "application/octet-stream") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/octet-stream" + ); + let res_body = hyper::body::to_bytes(res.into_body()) + .await + .unwrap() + .to_vec(); + assert_eq!(res_body, single_value); + + // f3: json + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "application/json") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_body = json_body(res).await; + assert_json_eq!(res_body, json!([base64::encode(&single_value)])); + + // -- Test with a second, concurrent value -- + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .body(concurrent_value.clone()) + .method(Method::PUT) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + // f0: either + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_body = json_body(res).await; + assert_json_eq!( + res_body, + json!([ + base64::encode(&single_value), + base64::encode(&concurrent_value) + ]) + ); + + // f1: not specified + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_body = json_body(res).await; + assert_json_eq!( + res_body, + json!([ + base64::encode(&single_value), + base64::encode(&concurrent_value) + ]) + ); + + // f2: binary + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "application/octet-stream") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 409); // CONFLICT + + // f3: json + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "application/json") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_body = json_body(res).await; + assert_json_eq!( + res_body, + json!([ + base64::encode(&single_value), + base64::encode(&concurrent_value) + ]) + ); + + // -- Delete first value, concurrently with second insert -- + // -- (we now have a concurrent value and a deletion) -- + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .method(Method::DELETE) + .signed_header("x-garage-causality-token", ct) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 204); + + // f0: either + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_body = json_body(res).await; + assert_json_eq!(res_body, json!([base64::encode(&concurrent_value), null])); + + // f1: not specified + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let ct = res + .headers() + .get("x-garage-causality-token") + .unwrap() + .to_str() + .unwrap() + .to_string(); + let res_body = json_body(res).await; + assert_json_eq!(res_body, json!([base64::encode(&concurrent_value), null])); + + // f2: binary + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "application/octet-stream") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 409); // CONFLICT + + // f3: json + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "application/json") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_body = json_body(res).await; + assert_json_eq!(res_body, json!([base64::encode(&concurrent_value), null])); + + // -- Delete everything -- + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .method(Method::DELETE) + .signed_header("x-garage-causality-token", ct) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 204); + + // f0: either + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "*/*") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 204); // NO CONTENT + + // f1: not specified + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_body = json_body(res).await; + assert_json_eq!(res_body, json!([null])); + + // f2: binary + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "application/octet-stream") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 204); // NO CONTENT + + // f3: json + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("v1")) + .signed_header("accept", "application/json") + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("content-type").unwrap().to_str().unwrap(), + "application/json" + ); + let res_body = json_body(res).await; + assert_json_eq!(res_body, json!([null])); +} diff --git a/src/garage/tests/k2v/mod.rs b/src/garage/tests/k2v/mod.rs new file mode 100644 index 00000000..a009460e --- /dev/null +++ b/src/garage/tests/k2v/mod.rs @@ -0,0 +1,18 @@ +pub mod batch; +pub mod errorcodes; +pub mod item; +pub mod poll; +pub mod simple; + +use hyper::{Body, Response}; + +pub async fn json_body(res: Response<Body>) -> serde_json::Value { + let res_body: serde_json::Value = serde_json::from_slice( + &hyper::body::to_bytes(res.into_body()) + .await + .unwrap() + .to_vec()[..], + ) + .unwrap(); + res_body +} diff --git a/src/garage/tests/k2v/poll.rs b/src/garage/tests/k2v/poll.rs new file mode 100644 index 00000000..70dc0410 --- /dev/null +++ b/src/garage/tests/k2v/poll.rs @@ -0,0 +1,98 @@ +use hyper::Method; +use std::time::Duration; + +use crate::common; + +#[tokio::test] +async fn test_poll() { + let ctx = common::context(); + let bucket = ctx.create_bucket("test-k2v-poll"); + + // Write initial value + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .method(Method::PUT) + .path("root") + .query_param("sort_key", Some("test1")) + .body(b"Initial value".to_vec()) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + // Retrieve initial value to get its causality token + let res2 = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("test1")) + .signed_header("accept", "application/octet-stream") + .send() + .await + .unwrap(); + assert_eq!(res2.status(), 200); + let ct = res2 + .headers() + .get("x-garage-causality-token") + .unwrap() + .to_str() + .unwrap() + .to_string(); + + let res2_body = hyper::body::to_bytes(res2.into_body()) + .await + .unwrap() + .to_vec(); + assert_eq!(res2_body, b"Initial value"); + + // Start poll operation + let poll = { + let bucket = bucket.clone(); + let ct = ct.clone(); + tokio::spawn(async move { + let ctx = common::context(); + ctx.k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("test1")) + .query_param("causality_token", Some(ct)) + .query_param("timeout", Some("10")) + .signed_header("accept", "application/octet-stream") + .send() + .await + }) + }; + + // Write new value that supersedes initial one + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .method(Method::PUT) + .path("root") + .query_param("sort_key", Some("test1")) + .signed_header("x-garage-causality-token", ct) + .body(b"New value".to_vec()) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + // Check poll finishes with correct value + let poll_res = tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(10)) => panic!("poll did not terminate in time"), + res = poll => res.unwrap().unwrap(), + }; + + assert_eq!(poll_res.status(), 200); + + let poll_res_body = hyper::body::to_bytes(poll_res.into_body()) + .await + .unwrap() + .to_vec(); + assert_eq!(poll_res_body, b"New value"); +} diff --git a/src/garage/tests/k2v/simple.rs b/src/garage/tests/k2v/simple.rs new file mode 100644 index 00000000..ae9a8674 --- /dev/null +++ b/src/garage/tests/k2v/simple.rs @@ -0,0 +1,40 @@ +use crate::common; + +use hyper::Method; + +#[tokio::test] +async fn test_simple() { + let ctx = common::context(); + let bucket = ctx.create_bucket("test-k2v-simple"); + + let res = ctx + .k2v + .request + .builder(bucket.clone()) + .method(Method::PUT) + .path("root") + .query_param("sort_key", Some("test1")) + .body(b"Hello, world!".to_vec()) + .send() + .await + .unwrap(); + assert_eq!(res.status(), 200); + + let res2 = ctx + .k2v + .request + .builder(bucket.clone()) + .path("root") + .query_param("sort_key", Some("test1")) + .signed_header("accept", "application/octet-stream") + .send() + .await + .unwrap(); + assert_eq!(res2.status(), 200); + + let res2_body = hyper::body::to_bytes(res2.into_body()) + .await + .unwrap() + .to_vec(); + assert_eq!(res2_body, b"Hello, world!"); +} diff --git a/src/garage/tests/lib.rs b/src/garage/tests/lib.rs index 8799c395..87be1327 100644 --- a/src/garage/tests/lib.rs +++ b/src/garage/tests/lib.rs @@ -3,9 +3,8 @@ mod common; mod admin; mod bucket; -mod list; -mod multipart; -mod objects; -mod simple; -mod streaming_signature; -mod website; + +mod s3; + +#[cfg(feature = "k2v")] +mod k2v; diff --git a/src/garage/tests/list.rs b/src/garage/tests/s3/list.rs index bb03f250..bb03f250 100644 --- a/src/garage/tests/list.rs +++ b/src/garage/tests/s3/list.rs diff --git a/src/garage/tests/s3/mod.rs b/src/garage/tests/s3/mod.rs new file mode 100644 index 00000000..623eb665 --- /dev/null +++ b/src/garage/tests/s3/mod.rs @@ -0,0 +1,6 @@ +mod list; +mod multipart; +mod objects; +mod simple; +mod streaming_signature; +mod website; diff --git a/src/garage/tests/multipart.rs b/src/garage/tests/s3/multipart.rs index 895a2993..895a2993 100644 --- a/src/garage/tests/multipart.rs +++ b/src/garage/tests/s3/multipart.rs diff --git a/src/garage/tests/objects.rs b/src/garage/tests/s3/objects.rs index e1175b81..65f9e867 100644 --- a/src/garage/tests/objects.rs +++ b/src/garage/tests/s3/objects.rs @@ -263,4 +263,13 @@ async fn test_deleteobject() { .unwrap(); assert!(l.contents.is_none()); + + // Deleting a non-existing object shouldn't be a problem + ctx.client + .delete_object() + .bucket(&bucket) + .key("l-0") + .send() + .await + .unwrap(); } diff --git a/src/garage/tests/simple.rs b/src/garage/tests/s3/simple.rs index f54ae9ac..f54ae9ac 100644 --- a/src/garage/tests/simple.rs +++ b/src/garage/tests/s3/simple.rs diff --git a/src/garage/tests/streaming_signature.rs b/src/garage/tests/s3/streaming_signature.rs index c68f7dfc..c68f7dfc 100644 --- a/src/garage/tests/streaming_signature.rs +++ b/src/garage/tests/s3/streaming_signature.rs diff --git a/src/garage/tests/website.rs b/src/garage/tests/s3/website.rs index 963d11ea..0570ac6a 100644 --- a/src/garage/tests/website.rs +++ b/src/garage/tests/s3/website.rs @@ -35,10 +35,7 @@ async fn test_website() { let req = || { Request::builder() .method("GET") - .uri(format!( - "http://127.0.0.1:{}/", - common::garage::DEFAULT_PORT + 2 - )) + .uri(format!("http://127.0.0.1:{}/", ctx.garage.web_port)) .header("Host", format!("{}.web.garage", BCKT_NAME)) .body(Body::empty()) .unwrap() @@ -170,10 +167,7 @@ async fn test_website_s3_api() { { let req = Request::builder() .method("GET") - .uri(format!( - "http://127.0.0.1:{}/site/", - common::garage::DEFAULT_PORT + 2 - )) + .uri(format!("http://127.0.0.1:{}/site/", ctx.garage.web_port)) .header("Host", format!("{}.web.garage", BCKT_NAME)) .header("Origin", "https://example.com") .body(Body::empty()) @@ -198,7 +192,7 @@ async fn test_website_s3_api() { .method("GET") .uri(format!( "http://127.0.0.1:{}/wrong.html", - common::garage::DEFAULT_PORT + 2 + ctx.garage.web_port )) .header("Host", format!("{}.web.garage", BCKT_NAME)) .body(Body::empty()) @@ -217,10 +211,7 @@ async fn test_website_s3_api() { { let req = Request::builder() .method("OPTIONS") - .uri(format!( - "http://127.0.0.1:{}/site/", - common::garage::DEFAULT_PORT + 2 - )) + .uri(format!("http://127.0.0.1:{}/site/", ctx.garage.web_port)) .header("Host", format!("{}.web.garage", BCKT_NAME)) .header("Origin", "https://example.com") .header("Access-Control-Request-Method", "PUT") @@ -244,10 +235,7 @@ async fn test_website_s3_api() { { let req = Request::builder() .method("OPTIONS") - .uri(format!( - "http://127.0.0.1:{}/site/", - common::garage::DEFAULT_PORT + 2 - )) + .uri(format!("http://127.0.0.1:{}/site/", ctx.garage.web_port)) .header("Host", format!("{}.web.garage", BCKT_NAME)) .header("Origin", "https://example.com") .header("Access-Control-Request-Method", "DELETE") @@ -288,10 +276,7 @@ async fn test_website_s3_api() { { let req = Request::builder() .method("OPTIONS") - .uri(format!( - "http://127.0.0.1:{}/site/", - common::garage::DEFAULT_PORT + 2 - )) + .uri(format!("http://127.0.0.1:{}/site/", ctx.garage.web_port)) .header("Host", format!("{}.web.garage", BCKT_NAME)) .header("Origin", "https://example.com") .header("Access-Control-Request-Method", "PUT") @@ -319,10 +304,7 @@ async fn test_website_s3_api() { { let req = Request::builder() .method("GET") - .uri(format!( - "http://127.0.0.1:{}/site/", - common::garage::DEFAULT_PORT + 2 - )) + .uri(format!("http://127.0.0.1:{}/site/", ctx.garage.web_port)) .header("Host", format!("{}.web.garage", BCKT_NAME)) .body(Body::empty()) .unwrap(); diff --git a/src/admin/tracing_setup.rs b/src/garage/tracing_setup.rs index 55fc4094..55fc4094 100644 --- a/src/admin/tracing_setup.rs +++ b/src/garage/tracing_setup.rs diff --git a/src/k2v-client/Cargo.toml b/src/k2v-client/Cargo.toml new file mode 100644 index 00000000..0f0b76ae --- /dev/null +++ b/src/k2v-client/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "k2v-client" +version = "0.0.1" +authors = ["Trinity Pointard <trinity.pointard@gmail.com>", "Alex Auvolat <alex@adnab.me>"] +edition = "2018" +license = "AGPL-3.0" +description = "Client library for the Garage K2V protocol" +repository = "https://git.deuxfleurs.fr/Deuxfleurs/garage" +readme = "../../README.md" + +[dependencies] +base64 = "0.13.0" +http = "0.2.6" +log = "0.4" +rusoto_core = "0.48.0" +rusoto_credential = "0.48.0" +rusoto_signature = "0.48.0" +serde = "1.0.137" +serde_json = "1.0.81" +thiserror = "1.0.31" +tokio = "1.17.0" + +# cli deps +clap = { version = "3.1.18", optional = true, features = ["derive", "env"] } +garage_util = { version = "0.8.0", path = "../util", optional = true } + + +[features] +cli = ["clap", "tokio/fs", "tokio/io-std", "garage_util"] + +[lib] +path = "lib.rs" + +[[bin]] +name = "k2v-cli" +path = "bin/k2v-cli.rs" +required-features = ["cli"] diff --git a/src/k2v-client/README.md b/src/k2v-client/README.md new file mode 100644 index 00000000..db454805 --- /dev/null +++ b/src/k2v-client/README.md @@ -0,0 +1,25 @@ +Example usage: +```sh +# all these values can be provided on the cli instead +export AWS_ACCESS_KEY_ID=GK123456 +export AWS_SECRET_ACCESS_KEY=0123..789 +export AWS_REGION=garage +export K2V_ENDPOINT=http://172.30.2.1:3903 +export K2V_BUCKET=my-bucket + +cargo run --features=cli -- read-range my-partition-key --all + +cargo run --features=cli -- insert my-partition-key my-sort-key --text "my string1" +cargo run --features=cli -- insert my-partition-key my-sort-key --text "my string2" +cargo run --features=cli -- insert my-partition-key my-sort-key2 --text "my string" + +cargo run --features=cli -- read-range my-partition-key --all + +causality=$(cargo run --features=cli -- read my-partition-key my-sort-key2 -b | head -n1) +cargo run --features=cli -- delete my-partition-key my-sort-key2 -c $causality + +causality=$(cargo run --features=cli -- read my-partition-key my-sort-key -b | head -n1) +cargo run --features=cli -- insert my-partition-key my-sort-key --text "my string3" -c $causality + +cargo run --features=cli -- read-range my-partition-key --all +``` diff --git a/src/k2v-client/bin/k2v-cli.rs b/src/k2v-client/bin/k2v-cli.rs new file mode 100644 index 00000000..925ebeb8 --- /dev/null +++ b/src/k2v-client/bin/k2v-cli.rs @@ -0,0 +1,501 @@ +use std::time::Duration; + +use k2v_client::*; + +use garage_util::formater::format_table; + +use rusoto_core::credential::AwsCredentials; +use rusoto_core::Region; + +use clap::{Parser, Subcommand}; + +/// K2V command line interface +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Name of the region to use + #[clap(short, long, env = "AWS_REGION", default_value = "garage")] + region: String, + /// Url of the endpoint to connect to + #[clap(short, long, env = "K2V_ENDPOINT")] + endpoint: String, + /// Access key ID + #[clap(short, long, env = "AWS_ACCESS_KEY_ID")] + key_id: String, + /// Access key ID + #[clap(short, long, env = "AWS_SECRET_ACCESS_KEY")] + secret: String, + /// Bucket name + #[clap(short, long, env = "K2V_BUCKET")] + bucket: String, + #[clap(subcommand)] + command: Command, +} + +#[derive(Subcommand, Debug)] +enum Command { + /// Insert a single value + Insert { + /// Partition key to insert to + partition_key: String, + /// Sort key to insert to + sort_key: String, + /// Causality of the insertion + #[clap(short, long)] + causality: Option<String>, + /// Value to insert + #[clap(flatten)] + value: Value, + }, + /// Read a single value + Read { + /// Partition key to read from + partition_key: String, + /// Sort key to read from + sort_key: String, + /// Output formating + #[clap(flatten)] + output_kind: ReadOutputKind, + }, + /// Watch changes on a single value + Poll { + /// Partition key to delete from + partition_key: String, + /// Sort key to delete from + sort_key: String, + /// Causality information + #[clap(short, long)] + causality: String, + /// Timeout, in seconds + #[clap(short, long)] + timeout: Option<u64>, + /// Output formating + #[clap(flatten)] + output_kind: ReadOutputKind, + }, + /// Delete a single value + Delete { + /// Partition key to delete from + partition_key: String, + /// Sort key to delete from + sort_key: String, + /// Causality information + #[clap(short, long)] + causality: String, + }, + /// List partition keys + ReadIndex { + /// Output formating + #[clap(flatten)] + output_kind: BatchOutputKind, + /// Output only partition keys matching this filter + #[clap(flatten)] + filter: Filter, + }, + /// Read a range of sort keys + ReadRange { + /// Partition key to read from + partition_key: String, + /// Output formating + #[clap(flatten)] + output_kind: BatchOutputKind, + /// Output only sort keys matching this filter + #[clap(flatten)] + filter: Filter, + }, + /// Delete a range of sort keys + DeleteRange { + /// Partition key to delete from + partition_key: String, + /// Output formating + #[clap(flatten)] + output_kind: BatchOutputKind, + /// Delete only sort keys matching this filter + #[clap(flatten)] + filter: Filter, + }, +} + +/// Where to read a value from +#[derive(Parser, Debug)] +#[clap(group = clap::ArgGroup::new("value").multiple(false).required(true))] +struct Value { + /// Read value from a file. use - to read from stdin + #[clap(short, long, group = "value")] + file: Option<String>, + /// Read a base64 value from commandline + #[clap(short, long, group = "value")] + b64: Option<String>, + /// Read a raw (UTF-8) value from the commandline + #[clap(short, long, group = "value")] + text: Option<String>, +} + +impl Value { + async fn to_data(&self) -> Result<Vec<u8>, Error> { + if let Some(ref text) = self.text { + Ok(text.as_bytes().to_vec()) + } else if let Some(ref b64) = self.b64 { + base64::decode(b64).map_err(|_| Error::Message("invalid base64 input".into())) + } else if let Some(ref path) = self.file { + use tokio::io::AsyncReadExt; + if path == "-" { + let mut file = tokio::io::stdin(); + let mut vec = Vec::new(); + file.read_to_end(&mut vec).await?; + Ok(vec) + } else { + let mut file = tokio::fs::File::open(path).await?; + let mut vec = Vec::new(); + file.read_to_end(&mut vec).await?; + Ok(vec) + } + } else { + unreachable!("Value must have one option set") + } + } +} + +#[derive(Parser, Debug)] +#[clap(group = clap::ArgGroup::new("output-kind").multiple(false).required(false))] +struct ReadOutputKind { + /// Base64 output. Conflicts are line separated, first line is causality token + #[clap(short, long, group = "output-kind")] + b64: bool, + /// Raw output. Conflicts generate error, causality token is not returned + #[clap(short, long, group = "output-kind")] + raw: bool, + /// Human formated output + #[clap(short = 'H', long, group = "output-kind")] + human: bool, + /// JSON formated output + #[clap(short, long, group = "output-kind")] + json: bool, +} + +impl ReadOutputKind { + fn display_output(&self, val: CausalValue) -> ! { + use std::io::Write; + use std::process::exit; + + if self.json { + let stdout = std::io::stdout(); + serde_json::to_writer_pretty(stdout, &val).unwrap(); + exit(0); + } + + if self.raw { + let mut val = val.value; + if val.len() != 1 { + eprintln!( + "Raw mode can only read non-concurent values, found {} values, expected 1", + val.len() + ); + exit(1); + } + let val = val.pop().unwrap(); + match val { + K2vValue::Value(v) => { + std::io::stdout().write_all(&v).unwrap(); + exit(0); + } + K2vValue::Tombstone => { + eprintln!("Expected value, found tombstone"); + exit(2); + } + } + } + + let causality: String = val.causality.into(); + if self.b64 { + println!("{}", causality); + for val in val.value { + match val { + K2vValue::Value(v) => { + println!("{}", base64::encode(&v)) + } + K2vValue::Tombstone => { + println!(); + } + } + } + exit(0); + } + + // human + println!("causality: {}", causality); + println!("values:"); + for val in val.value { + match val { + K2vValue::Value(v) => { + if let Ok(string) = std::str::from_utf8(&v) { + println!(" utf-8: {}", string); + } else { + println!(" base64: {}", base64::encode(&v)); + } + } + K2vValue::Tombstone => { + println!(" tombstone"); + } + } + } + exit(0); + } +} + +#[derive(Parser, Debug)] +#[clap(group = clap::ArgGroup::new("output-kind").multiple(false).required(false))] +struct BatchOutputKind { + /// Human formated output + #[clap(short = 'H', long, group = "output-kind")] + human: bool, + /// JSON formated output + #[clap(short, long, group = "output-kind")] + json: bool, +} + +/// Filter for batch operations +#[derive(Parser, Debug)] +#[clap(group = clap::ArgGroup::new("filter").multiple(true).required(true))] +struct Filter { + /// Match only keys starting with this prefix + #[clap(short, long, group = "filter")] + prefix: Option<String>, + /// Match only keys lexicographically after this key (including this key itself) + #[clap(short, long, group = "filter")] + start: Option<String>, + /// Match only keys lexicographically before this key (excluding this key) + #[clap(short, long, group = "filter")] + end: Option<String>, + /// Only match the first X keys + #[clap(short, long)] + limit: Option<u64>, + /// Return keys in reverse order + #[clap(short, long)] + reverse: bool, + /// Return only keys where conflict happened + #[clap(short, long)] + conflicts_only: bool, + /// Also include keys storing only tombstones + #[clap(short, long)] + tombstones: bool, + /// Return any key + #[clap(short, long, group = "filter")] + all: bool, +} + +impl Filter { + fn k2v_filter(&self) -> k2v_client::Filter<'_> { + k2v_client::Filter { + start: self.start.as_deref(), + end: self.end.as_deref(), + prefix: self.prefix.as_deref(), + limit: self.limit, + reverse: self.reverse, + } + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let args = Args::parse(); + + let region = Region::Custom { + name: args.region, + endpoint: args.endpoint, + }; + + let creds = AwsCredentials::new(args.key_id, args.secret, None, None); + + let client = K2vClient::new(region, args.bucket, creds, None)?; + + match args.command { + Command::Insert { + partition_key, + sort_key, + causality, + value, + } => { + client + .insert_item( + &partition_key, + &sort_key, + value.to_data().await?, + causality.map(Into::into), + ) + .await?; + } + Command::Delete { + partition_key, + sort_key, + causality, + } => { + client + .delete_item(&partition_key, &sort_key, causality.into()) + .await?; + } + Command::Read { + partition_key, + sort_key, + output_kind, + } => { + let res = client.read_item(&partition_key, &sort_key).await?; + output_kind.display_output(res); + } + Command::Poll { + partition_key, + sort_key, + causality, + timeout, + output_kind, + } => { + let timeout = timeout.map(Duration::from_secs); + let res_opt = client + .poll_item(&partition_key, &sort_key, causality.into(), timeout) + .await?; + if let Some(res) = res_opt { + output_kind.display_output(res); + } else { + println!("Delay expired and value didn't change."); + } + } + Command::ReadIndex { + output_kind, + filter, + } => { + if filter.conflicts_only || filter.tombstones { + return Err(Error::Message( + "conlicts-only and tombstones are invalid for read-index".into(), + )); + } + let res = client.read_index(filter.k2v_filter()).await?; + if output_kind.json { + let values = res + .items + .into_iter() + .map(|(k, v)| { + let mut value = serde_json::to_value(v).unwrap(); + value + .as_object_mut() + .unwrap() + .insert("sort_key".to_owned(), k.into()); + value + }) + .collect::<Vec<_>>(); + let json = serde_json::json!({ + "next_key": res.next_start, + "values": values, + }); + + let stdout = std::io::stdout(); + serde_json::to_writer_pretty(stdout, &json).unwrap(); + } else { + if let Some(next) = res.next_start { + println!("next key: {}", next); + } + + let mut to_print = Vec::new(); + to_print.push(format!("key:\tentries\tconflicts\tvalues\tbytes")); + for (k, v) in res.items { + to_print.push(format!( + "{}\t{}\t{}\t{}\t{}", + k, v.entries, v.conflicts, v.values, v.bytes + )); + } + format_table(to_print); + } + } + Command::ReadRange { + partition_key, + output_kind, + filter, + } => { + let op = BatchReadOp { + partition_key: &partition_key, + filter: filter.k2v_filter(), + conflicts_only: filter.conflicts_only, + tombstones: filter.tombstones, + single_item: false, + }; + let mut res = client.read_batch(&[op]).await?; + let res = res.pop().unwrap(); + if output_kind.json { + let values = res + .items + .into_iter() + .map(|(k, v)| { + let mut value = serde_json::to_value(v).unwrap(); + value + .as_object_mut() + .unwrap() + .insert("sort_key".to_owned(), k.into()); + value + }) + .collect::<Vec<_>>(); + let json = serde_json::json!({ + "next_key": res.next_start, + "values": values, + }); + + let stdout = std::io::stdout(); + serde_json::to_writer_pretty(stdout, &json).unwrap(); + } else { + if let Some(next) = res.next_start { + println!("next key: {}", next); + } + for (key, values) in res.items { + println!("key: {}", key); + let causality: String = values.causality.into(); + println!("causality: {}", causality); + for value in values.value { + match value { + K2vValue::Value(v) => { + if let Ok(string) = std::str::from_utf8(&v) { + println!(" value(utf-8): {}", string); + } else { + println!(" value(base64): {}", base64::encode(&v)); + } + } + K2vValue::Tombstone => { + println!(" tombstone"); + } + } + } + } + } + } + Command::DeleteRange { + partition_key, + output_kind, + filter, + } => { + let op = BatchDeleteOp { + partition_key: &partition_key, + prefix: filter.prefix.as_deref(), + start: filter.start.as_deref(), + end: filter.end.as_deref(), + single_item: false, + }; + if filter.reverse + || filter.conflicts_only + || filter.tombstones + || filter.limit.is_some() + { + return Err(Error::Message( + "limit, conlicts-only, reverse and tombstones are invalid for delete-range" + .into(), + )); + } + + let res = client.delete_batch(&[op]).await?; + + if output_kind.json { + println!("{}", res[0]); + } else { + println!("deleted {} keys", res[0]); + } + } + } + + Ok(()) +} diff --git a/src/k2v-client/error.rs b/src/k2v-client/error.rs new file mode 100644 index 00000000..37c221f2 --- /dev/null +++ b/src/k2v-client/error.rs @@ -0,0 +1,29 @@ +use std::borrow::Cow; + +use thiserror::Error; + +/// Errors returned by this crate +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}, {1}: {2} (path = {3})")] + Remote( + http::StatusCode, + Cow<'static, str>, + Cow<'static, str>, + Cow<'static, str>, + ), + #[error("received invalid response: {0}")] + InvalidResponse(Cow<'static, str>), + #[error("not found")] + NotFound, + #[error("io error: {0}")] + IoError(#[from] std::io::Error), + #[error("rusoto tls error: {0}")] + RusotoTls(#[from] rusoto_core::request::TlsError), + #[error("rusoto http error: {0}")] + RusotoHttp(#[from] rusoto_core::HttpDispatchError), + #[error("deserialization error: {0}")] + Deserialization(#[from] serde_json::Error), + #[error("{0}")] + Message(Cow<'static, str>), +} diff --git a/src/k2v-client/lib.rs b/src/k2v-client/lib.rs new file mode 100644 index 00000000..c2606af4 --- /dev/null +++ b/src/k2v-client/lib.rs @@ -0,0 +1,611 @@ +use std::collections::BTreeMap; +use std::time::Duration; + +use http::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE}; +use http::status::StatusCode; +use http::HeaderMap; +use log::{debug, error}; + +use rusoto_core::{ByteStream, DispatchSignedRequest, HttpClient}; +use rusoto_credential::AwsCredentials; +use rusoto_signature::region::Region; +use rusoto_signature::signature::SignedRequest; +use serde::de::Error as DeError; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use tokio::io::AsyncReadExt; + +mod error; + +pub use error::Error; + +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5); +const DEFAULT_POLL_TIMEOUT: Duration = Duration::from_secs(300); +const SERVICE: &str = "k2v"; +const GARAGE_CAUSALITY_TOKEN: &str = "X-Garage-Causality-Token"; + +/// Client used to query a K2V server. +pub struct K2vClient { + region: Region, + bucket: String, + creds: AwsCredentials, + client: HttpClient, +} + +impl K2vClient { + /// Create a new K2V client. + pub fn new( + region: Region, + bucket: String, + creds: AwsCredentials, + user_agent: Option<String>, + ) -> Result<Self, Error> { + let mut client = HttpClient::new()?; + if let Some(ua) = user_agent { + client.local_agent_prepend(ua); + } else { + client.local_agent_prepend(format!("k2v/{}", env!("CARGO_PKG_VERSION"))); + } + Ok(K2vClient { + region, + bucket, + creds, + client, + }) + } + + /// Perform a ReadItem request, reading the value(s) stored for a single pk+sk. + pub async fn read_item( + &self, + partition_key: &str, + sort_key: &str, + ) -> Result<CausalValue, Error> { + let mut req = SignedRequest::new( + "GET", + SERVICE, + &self.region, + &format!("/{}/{}", self.bucket, partition_key), + ); + req.add_param("sort_key", sort_key); + req.add_header(ACCEPT, "application/octet-stream, application/json"); + + let res = self.dispatch(req, None).await?; + + let causality = res + .causality_token + .ok_or_else(|| Error::InvalidResponse("missing causality token".into()))?; + + if res.status == StatusCode::NO_CONTENT { + return Ok(CausalValue { + causality, + value: vec![K2vValue::Tombstone], + }); + } + + match res.content_type.as_deref() { + Some("application/octet-stream") => Ok(CausalValue { + causality, + value: vec![K2vValue::Value(res.body)], + }), + Some("application/json") => { + let value = serde_json::from_slice(&res.body)?; + Ok(CausalValue { causality, value }) + } + Some(ct) => Err(Error::InvalidResponse( + format!("invalid content type: {}", ct).into(), + )), + None => Err(Error::InvalidResponse("missing content type".into())), + } + } + + /// Perform a PollItem request, waiting for the value(s) stored for a single pk+sk to be + /// updated. + pub async fn poll_item( + &self, + partition_key: &str, + sort_key: &str, + causality: CausalityToken, + timeout: Option<Duration>, + ) -> Result<Option<CausalValue>, Error> { + let timeout = timeout.unwrap_or(DEFAULT_POLL_TIMEOUT); + + let mut req = SignedRequest::new( + "GET", + SERVICE, + &self.region, + &format!("/{}/{}", self.bucket, partition_key), + ); + req.add_param("sort_key", sort_key); + req.add_param("causality_token", &causality.0); + req.add_param("timeout", &timeout.as_secs().to_string()); + req.add_header(ACCEPT, "application/octet-stream, application/json"); + + let res = self.dispatch(req, Some(timeout + DEFAULT_TIMEOUT)).await?; + + if res.status == StatusCode::NOT_MODIFIED { + return Ok(None); + } + + let causality = res + .causality_token + .ok_or_else(|| Error::InvalidResponse("missing causality token".into()))?; + + if res.status == StatusCode::NO_CONTENT { + return Ok(Some(CausalValue { + causality, + value: vec![K2vValue::Tombstone], + })); + } + + match res.content_type.as_deref() { + Some("application/octet-stream") => Ok(Some(CausalValue { + causality, + value: vec![K2vValue::Value(res.body)], + })), + Some("application/json") => { + let value = serde_json::from_slice(&res.body)?; + Ok(Some(CausalValue { causality, value })) + } + Some(ct) => Err(Error::InvalidResponse( + format!("invalid content type: {}", ct).into(), + )), + None => Err(Error::InvalidResponse("missing content type".into())), + } + } + + /// Perform an InsertItem request, inserting a value for a single pk+sk. + pub async fn insert_item( + &self, + partition_key: &str, + sort_key: &str, + value: Vec<u8>, + causality: Option<CausalityToken>, + ) -> Result<(), Error> { + let mut req = SignedRequest::new( + "PUT", + SERVICE, + &self.region, + &format!("/{}/{}", self.bucket, partition_key), + ); + req.add_param("sort_key", sort_key); + req.set_payload(Some(value)); + + if let Some(causality) = causality { + req.add_header(GARAGE_CAUSALITY_TOKEN, &causality.0); + } + + self.dispatch(req, None).await?; + Ok(()) + } + + /// Perform a DeleteItem request, deleting the value(s) stored for a single pk+sk. + pub async fn delete_item( + &self, + partition_key: &str, + sort_key: &str, + causality: CausalityToken, + ) -> Result<(), Error> { + let mut req = SignedRequest::new( + "DELETE", + SERVICE, + &self.region, + &format!("/{}/{}", self.bucket, partition_key), + ); + req.add_param("sort_key", sort_key); + req.add_header(GARAGE_CAUSALITY_TOKEN, &causality.0); + + self.dispatch(req, None).await?; + Ok(()) + } + + /// Perform a ReadIndex request, listing partition key which have at least one associated + /// sort key, and which matches the filter. + pub async fn read_index( + &self, + filter: Filter<'_>, + ) -> Result<PaginatedRange<PartitionInfo>, Error> { + let mut req = + SignedRequest::new("GET", SERVICE, &self.region, &format!("/{}", self.bucket)); + filter.insert_params(&mut req); + + let res = self.dispatch(req, None).await?; + + let resp: ReadIndexResponse = serde_json::from_slice(&res.body)?; + + let items = resp + .partition_keys + .into_iter() + .map(|ReadIndexItem { pk, info }| (pk, info)) + .collect(); + + Ok(PaginatedRange { + items, + next_start: resp.next_start, + }) + } + + /// Perform an InsertBatch request, inserting multiple values at once. Note: this operation is + /// *not* atomic: it is possible for some sub-operations to fails and others to success. In + /// that case, failure is reported. + pub async fn insert_batch(&self, operations: &[BatchInsertOp<'_>]) -> Result<(), Error> { + let mut req = + SignedRequest::new("POST", SERVICE, &self.region, &format!("/{}", self.bucket)); + + let payload = serde_json::to_vec(operations)?; + req.set_payload(Some(payload)); + self.dispatch(req, None).await?; + Ok(()) + } + + /// Perform a ReadBatch request, reading multiple values or range of values at once. + pub async fn read_batch( + &self, + operations: &[BatchReadOp<'_>], + ) -> Result<Vec<PaginatedRange<CausalValue>>, Error> { + let mut req = + SignedRequest::new("POST", SERVICE, &self.region, &format!("/{}", self.bucket)); + req.add_param("search", ""); + + let payload = serde_json::to_vec(operations)?; + req.set_payload(Some(payload)); + let res = self.dispatch(req, None).await?; + + let resp: Vec<BatchReadResponse> = serde_json::from_slice(&res.body)?; + + Ok(resp + .into_iter() + .map(|e| PaginatedRange { + items: e + .items + .into_iter() + .map(|BatchReadItem { sk, ct, v }| { + ( + sk, + CausalValue { + causality: ct, + value: v, + }, + ) + }) + .collect(), + next_start: e.next_start, + }) + .collect()) + } + + /// Perform a DeleteBatch request, deleting mutiple values or range of values at once, without + /// providing causality information. + pub async fn delete_batch(&self, operations: &[BatchDeleteOp<'_>]) -> Result<Vec<u64>, Error> { + let mut req = + SignedRequest::new("POST", SERVICE, &self.region, &format!("/{}", self.bucket)); + req.add_param("delete", ""); + + let payload = serde_json::to_vec(operations)?; + req.set_payload(Some(payload)); + let res = self.dispatch(req, None).await?; + + let resp: Vec<BatchDeleteResponse> = serde_json::from_slice(&res.body)?; + + Ok(resp.into_iter().map(|r| r.deleted_items).collect()) + } + + async fn dispatch( + &self, + mut req: SignedRequest, + timeout: Option<Duration>, + ) -> Result<Response, Error> { + req.sign(&self.creds); + let mut res = self + .client + .dispatch(req, Some(timeout.unwrap_or(DEFAULT_TIMEOUT))) + .await?; + + let causality_token = res + .headers + .remove(GARAGE_CAUSALITY_TOKEN) + .map(CausalityToken); + let content_type = res.headers.remove(CONTENT_TYPE); + + let body = match res.status { + StatusCode::OK => read_body(&mut res.headers, res.body).await?, + StatusCode::NO_CONTENT => Vec::new(), + StatusCode::NOT_FOUND => return Err(Error::NotFound), + StatusCode::NOT_MODIFIED => Vec::new(), + s => { + let err_body = read_body(&mut res.headers, res.body) + .await + .unwrap_or_default(); + let err_body_str = std::str::from_utf8(&err_body) + .map(String::from) + .unwrap_or_else(|_| base64::encode(&err_body)); + + if s.is_client_error() || s.is_server_error() { + error!("Error response {}: {}", res.status, err_body_str); + let err = match serde_json::from_slice::<ErrorResponse>(&err_body) { + Ok(err) => Error::Remote( + res.status, + err.code.into(), + err.message.into(), + err.path.into(), + ), + Err(_) => Error::Remote( + res.status, + "unknown".into(), + err_body_str.into(), + "?".into(), + ), + }; + return Err(err); + } else { + let msg = format!( + "Unexpected response code {}. Response body: {}", + res.status, err_body_str + ); + error!("{}", msg); + return Err(Error::InvalidResponse(msg.into())); + } + } + }; + debug!( + "Response body: {}", + std::str::from_utf8(&body) + .map(String::from) + .unwrap_or_else(|_| base64::encode(&body)) + ); + + Ok(Response { + body, + status: res.status, + causality_token, + content_type, + }) + } +} + +async fn read_body(headers: &mut HeaderMap<String>, body: ByteStream) -> Result<Vec<u8>, Error> { + let body_len = headers + .get(CONTENT_LENGTH) + .and_then(|h| h.parse().ok()) + .unwrap_or(0); + let mut res = Vec::with_capacity(body_len); + body.into_async_read().read_to_end(&mut res).await?; + Ok(res) +} + +/// An opaque token used to convey causality between operations. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(transparent)] +pub struct CausalityToken(String); + +impl From<String> for CausalityToken { + fn from(v: String) -> Self { + CausalityToken(v) + } +} + +impl From<CausalityToken> for String { + fn from(v: CausalityToken) -> Self { + v.0 + } +} + +/// A value in K2V. can be either a binary value, or a tombstone. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum K2vValue { + Tombstone, + Value(Vec<u8>), +} + +impl From<Vec<u8>> for K2vValue { + fn from(v: Vec<u8>) -> Self { + K2vValue::Value(v) + } +} + +impl From<Option<Vec<u8>>> for K2vValue { + fn from(v: Option<Vec<u8>>) -> Self { + match v { + Some(v) => K2vValue::Value(v), + None => K2vValue::Tombstone, + } + } +} + +impl<'de> Deserialize<'de> for K2vValue { + fn deserialize<D>(d: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let val: Option<&str> = Option::deserialize(d)?; + Ok(match val { + Some(s) => { + K2vValue::Value(base64::decode(s).map_err(|_| DeError::custom("invalid base64"))?) + } + None => K2vValue::Tombstone, + }) + } +} + +impl Serialize for K2vValue { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + match self { + K2vValue::Tombstone => serializer.serialize_none(), + K2vValue::Value(v) => { + let b64 = base64::encode(v); + serializer.serialize_str(&b64) + } + } + } +} + +/// A set of K2vValue and associated causality information. +#[derive(Debug, Clone, Serialize)] +pub struct CausalValue { + pub causality: CausalityToken, + pub value: Vec<K2vValue>, +} + +/// Result of paginated requests. +#[derive(Debug, Clone)] +pub struct PaginatedRange<V> { + pub items: BTreeMap<String, V>, + pub next_start: Option<String>, +} + +/// Filter for batch operations. +#[derive(Debug, Default, Clone, Deserialize, Serialize)] +pub struct Filter<'a> { + pub start: Option<&'a str>, + pub end: Option<&'a str>, + pub prefix: Option<&'a str>, + pub limit: Option<u64>, + #[serde(default)] + pub reverse: bool, +} + +impl<'a> Filter<'a> { + fn insert_params(&self, req: &mut SignedRequest) { + if let Some(start) = &self.start { + req.add_param("start", start); + } + if let Some(end) = &self.end { + req.add_param("end", end); + } + if let Some(prefix) = &self.prefix { + req.add_param("prefix", prefix); + } + if let Some(limit) = &self.limit { + req.add_param("limit", &limit.to_string()); + } + if self.reverse { + req.add_param("reverse", "true"); + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ReadIndexResponse<'a> { + #[serde(flatten, borrow)] + #[allow(dead_code)] + filter: Filter<'a>, + partition_keys: Vec<ReadIndexItem>, + #[allow(dead_code)] + more: bool, + next_start: Option<String>, +} + +#[derive(Debug, Clone, Deserialize)] +struct ReadIndexItem { + pk: String, + #[serde(flatten)] + info: PartitionInfo, +} + +/// Information about data stored with a given partition key. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PartitionInfo { + pub entries: u64, + pub conflicts: u64, + pub values: u64, + pub bytes: u64, +} + +/// Single sub-operation of an InsertBatch. +#[derive(Debug, Clone, Serialize)] +pub struct BatchInsertOp<'a> { + #[serde(rename = "pk")] + pub partition_key: &'a str, + #[serde(rename = "sk")] + pub sort_key: &'a str, + #[serde(rename = "ct")] + pub causality: Option<CausalityToken>, + #[serde(rename = "v")] + pub value: K2vValue, +} + +/// Single sub-operation of a ReadBatch. +#[derive(Debug, Default, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BatchReadOp<'a> { + pub partition_key: &'a str, + #[serde(flatten, borrow)] + pub filter: Filter<'a>, + #[serde(default)] + pub single_item: bool, + #[serde(default)] + pub conflicts_only: bool, + #[serde(default)] + pub tombstones: bool, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct BatchReadResponse<'a> { + #[serde(flatten, borrow)] + #[allow(dead_code)] + op: BatchReadOp<'a>, + items: Vec<BatchReadItem>, + #[allow(dead_code)] + more: bool, + next_start: Option<String>, +} + +#[derive(Debug, Clone, Deserialize)] +struct BatchReadItem { + sk: String, + ct: CausalityToken, + v: Vec<K2vValue>, +} + +/// Single sub-operation of a DeleteBatch +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BatchDeleteOp<'a> { + pub partition_key: &'a str, + pub prefix: Option<&'a str>, + pub start: Option<&'a str>, + pub end: Option<&'a str>, + #[serde(default)] + pub single_item: bool, +} + +impl<'a> BatchDeleteOp<'a> { + pub fn new(partition_key: &'a str) -> Self { + BatchDeleteOp { + partition_key, + prefix: None, + start: None, + end: None, + single_item: false, + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +struct BatchDeleteResponse<'a> { + #[serde(flatten, borrow)] + #[allow(dead_code)] + filter: BatchDeleteOp<'a>, + deleted_items: u64, +} + +#[derive(Deserialize)] +struct ErrorResponse { + code: String, + message: String, + #[allow(dead_code)] + region: String, + path: String, +} + +struct Response { + body: Vec<u8>, + status: StatusCode, + causality_token: Option<CausalityToken>, + content_type: Option<String>, +} diff --git a/src/model/Cargo.toml b/src/model/Cargo.toml index 007cec89..2c2e2bfe 100644 --- a/src/model/Cargo.toml +++ b/src/model/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "garage_model" -version = "0.7.0" +version = "0.8.0" authors = ["Alex Auvolat <alex@adnab.me>"] edition = "2018" license = "AGPL-3.0" @@ -14,22 +14,22 @@ path = "lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -garage_rpc = { version = "0.7.0", path = "../rpc" } -garage_table = { version = "0.7.0", path = "../table" } -garage_block = { version = "0.7.0", path = "../block" } -garage_util = { version = "0.7.0", path = "../util" } -garage_model_050 = { package = "garage_model", version = "0.5.1" } +garage_db = { version = "0.8.0", path = "../db" } +garage_rpc = { version = "0.8.0", path = "../rpc" } +garage_table = { version = "0.8.0", path = "../table" } +garage_block = { version = "0.8.0", path = "../block" } +garage_util = { version = "0.8.0", path = "../util" } async-trait = "0.1.7" arc-swap = "1.0" +blake2 = "0.9" err-derive = "0.3" hex = "0.4" +base64 = "0.13" tracing = "0.1.30" rand = "0.8" zstd = { version = "0.9", default-features = false } -sled = "0.34" - rmp-serde = "0.15" serde = { version = "1.0", default-features = false, features = ["derive", "rc"] } serde_bytes = "0.11" @@ -39,6 +39,10 @@ futures-util = "0.3" tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] } opentelemetry = "0.17" -#netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" } -#netapp = { version = "0.4", path = "../../../netapp" } -netapp = "0.4" +netapp = "0.5" + +[features] +k2v = [ "garage_util/k2v" ] +lmdb = [ "garage_db/lmdb" ] +sled = [ "garage_db/sled" ] +sqlite = [ "garage_db/sqlite" ] diff --git a/src/model/bucket_alias_table.rs b/src/model/bucket_alias_table.rs index fce03d04..fcd1536e 100644 --- a/src/model/bucket_alias_table.rs +++ b/src/model/bucket_alias_table.rs @@ -7,7 +7,7 @@ use garage_table::*; /// The bucket alias table holds the names given to buckets /// in the global namespace. -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct BucketAlias { name: String, pub state: crdt::Lww<Option<Uuid>>, diff --git a/src/model/bucket_table.rs b/src/model/bucket_table.rs index 7c7b9f30..7be42702 100644 --- a/src/model/bucket_table.rs +++ b/src/model/bucket_table.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use garage_table::crdt::Crdt; +use garage_table::crdt::*; use garage_table::*; use garage_util::data::*; use garage_util::time::*; @@ -12,7 +12,7 @@ use crate::permission::BucketKeyPerm; /// Its parameters are not directly accessible as: /// - It must be possible to merge paramaters, hence the use of a LWW CRDT. /// - A bucket has 2 states, Present or Deleted and parameters make sense only if present. -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct Bucket { /// ID of the bucket pub id: Uuid, @@ -21,7 +21,7 @@ pub struct Bucket { } /// Configuration for a bucket -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct BucketParams { /// Bucket's creation date pub creation_date: u64, @@ -44,6 +44,9 @@ pub struct BucketParams { pub website_config: crdt::Lww<Option<WebsiteConfig>>, /// CORS rules pub cors_config: crdt::Lww<Option<Vec<CorsRule>>>, + /// Bucket quotas + #[serde(default)] + pub quotas: crdt::Lww<BucketQuotas>, } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] @@ -62,6 +65,18 @@ pub struct CorsRule { pub expose_headers: Vec<String>, } +#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)] +pub struct BucketQuotas { + /// Maximum size in bytes (bucket size = sum of sizes of objects in the bucket) + pub max_size: Option<u64>, + /// Maximum number of non-deleted objects in the bucket + pub max_objects: Option<u64>, +} + +impl AutoCrdt for BucketQuotas { + const WARN_IF_DIFFERENT: bool = true; +} + impl BucketParams { /// Create an empty BucketParams with no authorized keys and no website accesss pub fn new() -> Self { @@ -72,6 +87,7 @@ impl BucketParams { local_aliases: crdt::LwwMap::new(), website_config: crdt::Lww::new(None), cors_config: crdt::Lww::new(None), + quotas: crdt::Lww::new(BucketQuotas::default()), } } } @@ -86,6 +102,7 @@ impl Crdt for BucketParams { self.website_config.merge(&o.website_config); self.cors_config.merge(&o.cors_config); + self.quotas.merge(&o.quotas); } } diff --git a/src/model/garage.rs b/src/model/garage.rs index abdb920a..75012952 100644 --- a/src/model/garage.rs +++ b/src/model/garage.rs @@ -2,8 +2,11 @@ use std::sync::Arc; use netapp::NetworkKey; +use garage_db as db; + use garage_util::background::*; use garage_util::config::*; +use garage_util::error::*; use garage_rpc::system::System; @@ -13,13 +16,18 @@ use garage_table::replication::TableFullReplication; use garage_table::replication::TableShardedReplication; use garage_table::*; -use crate::block_ref_table::*; +use crate::s3::block_ref_table::*; +use crate::s3::object_table::*; +use crate::s3::version_table::*; + use crate::bucket_alias_table::*; use crate::bucket_table::*; use crate::helper; +use crate::index_counter::*; use crate::key_table::*; -use crate::object_table::*; -use crate::version_table::*; + +#[cfg(feature = "k2v")] +use crate::k2v::{item_table::*, poll::*, rpc::*}; /// An entire Garage full of data pub struct Garage { @@ -27,7 +35,7 @@ pub struct Garage { pub config: Config, /// The local database - pub db: sled::Db, + pub db: db::Db, /// A background job runner pub background: Arc<BackgroundRunner>, /// The membership manager @@ -35,21 +43,118 @@ pub struct Garage { /// The block manager pub block_manager: Arc<BlockManager>, - /// Table containing informations about buckets + /// Table containing buckets pub bucket_table: Arc<Table<BucketTable, TableFullReplication>>, - /// Table containing informations about bucket aliases + /// Table containing bucket aliases pub bucket_alias_table: Arc<Table<BucketAliasTable, TableFullReplication>>, - /// Table containing informations about api keys + /// Table containing api keys pub key_table: Arc<Table<KeyTable, TableFullReplication>>, + /// Table containing S3 objects pub object_table: Arc<Table<ObjectTable, TableShardedReplication>>, + /// Counting table containing object counters + pub object_counter_table: Arc<IndexCounter<Object>>, + /// Table containing S3 object versions pub version_table: Arc<Table<VersionTable, TableShardedReplication>>, + /// Table containing S3 block references (not blocks themselves) pub block_ref_table: Arc<Table<BlockRefTable, TableShardedReplication>>, + + #[cfg(feature = "k2v")] + pub k2v: GarageK2V, +} + +#[cfg(feature = "k2v")] +pub struct GarageK2V { + /// Table containing K2V items + pub item_table: Arc<Table<K2VItemTable, TableShardedReplication>>, + /// Indexing table containing K2V item counters + pub counter_table: Arc<IndexCounter<K2VItem>>, + /// K2V RPC handler + pub rpc: Arc<K2VRpcHandler>, } impl Garage { /// Create and run garage - pub fn new(config: Config, db: sled::Db, background: Arc<BackgroundRunner>) -> Arc<Self> { + pub fn new(config: Config, background: Arc<BackgroundRunner>) -> Result<Arc<Self>, Error> { + // Create meta dir and data dir if they don't exist already + std::fs::create_dir_all(&config.metadata_dir) + .ok_or_message("Unable to create Garage metadata directory")?; + std::fs::create_dir_all(&config.data_dir) + .ok_or_message("Unable to create Garage data directory")?; + + info!("Opening database..."); + let mut db_path = config.metadata_dir.clone(); + let db = match config.db_engine.as_str() { + // ---- Sled DB ---- + #[cfg(feature = "sled")] + "sled" => { + db_path.push("db"); + info!("Opening Sled database at: {}", db_path.display()); + let db = db::sled_adapter::sled::Config::default() + .path(&db_path) + .cache_capacity(config.sled_cache_capacity) + .flush_every_ms(Some(config.sled_flush_every_ms)) + .open() + .expect("Unable to open sled DB"); + db::sled_adapter::SledDb::init(db) + } + #[cfg(not(feature = "sled"))] + "sled" => return Err(Error::Message("sled db not available in this build".into())), + // ---- Sqlite DB ---- + #[cfg(feature = "sqlite")] + "sqlite" | "sqlite3" | "rusqlite" => { + db_path.push("db.sqlite"); + info!("Opening Sqlite database at: {}", db_path.display()); + let db = db::sqlite_adapter::rusqlite::Connection::open(db_path) + .expect("Unable to open sqlite DB"); + db::sqlite_adapter::SqliteDb::init(db) + } + #[cfg(not(feature = "sqlite"))] + "sqlite" | "sqlite3" | "rusqlite" => { + return Err(Error::Message( + "sqlite db not available in this build".into(), + )) + } + // ---- LMDB DB ---- + #[cfg(feature = "lmdb")] + "lmdb" | "heed" => { + db_path.push("db.lmdb"); + info!("Opening LMDB database at: {}", db_path.display()); + std::fs::create_dir_all(&db_path).expect("Unable to create LMDB data directory"); + let map_size = garage_db::lmdb_adapter::recommended_map_size(); + + use db::lmdb_adapter::heed; + let mut env_builder = heed::EnvOpenOptions::new(); + env_builder.max_dbs(100); + env_builder.max_readers(500); + env_builder.map_size(map_size); + unsafe { + env_builder.flag(heed::flags::Flags::MdbNoSync); + env_builder.flag(heed::flags::Flags::MdbNoMetaSync); + } + let db = env_builder.open(&db_path).expect("Unable to open LMDB DB"); + db::lmdb_adapter::LmdbDb::init(db) + } + #[cfg(not(feature = "lmdb"))] + "lmdb" | "heed" => return Err(Error::Message("lmdb db not available in this build".into())), + // ---- Unavailable DB engine ---- + e => { + return Err(Error::Message(format!( + "Unsupported DB engine: {} (options: {})", + e, + vec![ + #[cfg(feature = "sled")] + "sled", + #[cfg(feature = "sqlite")] + "sqlite", + #[cfg(feature = "lmdb")] + "lmdb", + ] + .join(", ") + ))); + } + }; + let network_key = NetworkKey::from_slice( &hex::decode(&config.rpc_secret).expect("Invalid RPC secret key")[..], ) @@ -64,7 +169,7 @@ impl Garage { background.clone(), replication_mode.replication_factor(), &config, - ); + )?; let data_rep_param = TableShardedReplication { system: system.clone(), @@ -90,11 +195,25 @@ impl Garage { &db, config.data_dir.clone(), config.compression_level, - config.block_manager_background_tranquility, data_rep_param, system.clone(), ); + // ---- admin tables ---- + info!("Initialize bucket_table..."); + let bucket_table = Table::new(BucketTable, control_rep_param.clone(), system.clone(), &db); + + info!("Initialize bucket_alias_table..."); + let bucket_alias_table = Table::new( + BucketAliasTable, + control_rep_param.clone(), + system.clone(), + &db, + ); + info!("Initialize key_table_table..."); + let key_table = Table::new(KeyTable, control_rep_param, system.clone(), &db); + + // ---- S3 tables ---- info!("Initialize block_ref_table..."); let block_ref_table = Table::new( BlockRefTable { @@ -116,34 +235,28 @@ impl Garage { &db, ); + info!("Initialize object counter table..."); + let object_counter_table = IndexCounter::new(system.clone(), meta_rep_param.clone(), &db); + info!("Initialize object_table..."); + #[allow(clippy::redundant_clone)] let object_table = Table::new( ObjectTable { background: background.clone(), version_table: version_table.clone(), + object_counter_table: object_counter_table.clone(), }, - meta_rep_param, - system.clone(), - &db, - ); - - info!("Initialize bucket_table..."); - let bucket_table = Table::new(BucketTable, control_rep_param.clone(), system.clone(), &db); - - info!("Initialize bucket_alias_table..."); - let bucket_alias_table = Table::new( - BucketAliasTable, - control_rep_param.clone(), + meta_rep_param.clone(), system.clone(), &db, ); - info!("Initialize key_table_table..."); - let key_table = Table::new(KeyTable, control_rep_param, system.clone(), &db); - - info!("Initialize Garage..."); + // ---- K2V ---- + #[cfg(feature = "k2v")] + let k2v = GarageK2V::new(system.clone(), &db, meta_rep_param); - Arc::new(Self { + // -- done -- + Ok(Arc::new(Self { config, db, background, @@ -153,12 +266,46 @@ impl Garage { bucket_alias_table, key_table, object_table, + object_counter_table, version_table, block_ref_table, - }) + #[cfg(feature = "k2v")] + k2v, + })) } pub fn bucket_helper(&self) -> helper::bucket::BucketHelper { helper::bucket::BucketHelper(self) } + + pub fn key_helper(&self) -> helper::key::KeyHelper { + helper::key::KeyHelper(self) + } +} + +#[cfg(feature = "k2v")] +impl GarageK2V { + fn new(system: Arc<System>, db: &db::Db, meta_rep_param: TableShardedReplication) -> Self { + info!("Initialize K2V counter table..."); + let counter_table = IndexCounter::new(system.clone(), meta_rep_param.clone(), db); + info!("Initialize K2V subscription manager..."); + let subscriptions = Arc::new(SubscriptionManager::new()); + info!("Initialize K2V item table..."); + let item_table = Table::new( + K2VItemTable { + counter_table: counter_table.clone(), + subscriptions: subscriptions.clone(), + }, + meta_rep_param, + system.clone(), + db, + ); + let rpc = K2VRpcHandler::new(system, item_table.clone(), subscriptions); + + Self { + item_table, + counter_table, + rpc, + } + } } diff --git a/src/model/helper/bucket.rs b/src/model/helper/bucket.rs index 706faf26..130ba5be 100644 --- a/src/model/helper/bucket.rs +++ b/src/model/helper/bucket.rs @@ -1,15 +1,18 @@ -use garage_table::util::EmptyKey; use garage_util::crdt::*; use garage_util::data::*; use garage_util::error::{Error as GarageError, OkOrMessage}; use garage_util::time::*; +use garage_table::util::*; + use crate::bucket_alias_table::*; use crate::bucket_table::*; use crate::garage::Garage; use crate::helper::error::*; -use crate::key_table::{Key, KeyFilter}; +use crate::helper::key::KeyHelper; +use crate::key_table::*; use crate::permission::BucketKeyPerm; +use crate::s3::object_table::ObjectFilter; pub struct BucketHelper<'a>(pub(crate) &'a Garage); @@ -49,6 +52,23 @@ impl<'a> BucketHelper<'a> { } } + #[allow(clippy::ptr_arg)] + pub async fn resolve_bucket(&self, bucket_name: &String, api_key: &Key) -> Result<Uuid, Error> { + let api_key_params = api_key + .state + .as_option() + .ok_or_message("Key should not be deleted at this point")?; + + if let Some(Some(bucket_id)) = api_key_params.local_aliases.get(bucket_name) { + Ok(*bucket_id) + } else { + Ok(self + .resolve_global_bucket_name(bucket_name) + .await? + .ok_or_else(|| Error::NoSuchBucket(bucket_name.to_string()))?) + } + } + /// Returns a Bucket if it is present in bucket table, /// even if it is in deleted state. Querying a non-existing /// bucket ID returns an internal error. @@ -71,63 +91,7 @@ impl<'a> BucketHelper<'a> { .get(&EmptyKey, &bucket_id) .await? .filter(|b| !b.is_deleted()) - .ok_or_bad_request(format!( - "Bucket {:?} does not exist or has been deleted", - bucket_id - )) - } - - /// Returns a Key if it is present in key table, - /// even if it is in deleted state. Querying a non-existing - /// key ID returns an internal error. - pub async fn get_internal_key(&self, key_id: &String) -> Result<Key, Error> { - Ok(self - .0 - .key_table - .get(&EmptyKey, key_id) - .await? - .ok_or_message(format!("Key {} does not exist", key_id))?) - } - - /// Returns a Key if it is present in key table, - /// only if it is in non-deleted state. - /// Querying a non-existing key ID or a deleted key - /// returns a bad request error. - pub async fn get_existing_key(&self, key_id: &String) -> Result<Key, Error> { - self.0 - .key_table - .get(&EmptyKey, key_id) - .await? - .filter(|b| !b.state.is_deleted()) - .ok_or_bad_request(format!("Key {} does not exist or has been deleted", key_id)) - } - - /// Returns a Key if it is present in key table, - /// looking it up by key ID or by a match on its name, - /// only if it is in non-deleted state. - /// Querying a non-existing key ID or a deleted key - /// returns a bad request error. - pub async fn get_existing_matching_key(&self, pattern: &str) -> Result<Key, Error> { - let candidates = self - .0 - .key_table - .get_range( - &EmptyKey, - None, - Some(KeyFilter::MatchesAndNotDeleted(pattern.to_string())), - 10, - ) - .await? - .into_iter() - .collect::<Vec<_>>(); - if candidates.len() != 1 { - Err(Error::BadRequest(format!( - "{} matching keys", - candidates.len() - ))) - } else { - Ok(candidates.into_iter().next().unwrap()) - } + .ok_or_else(|| Error::NoSuchBucket(hex::encode(bucket_id))) } /// Sets a new alias for a bucket in global namespace. @@ -141,10 +105,7 @@ impl<'a> BucketHelper<'a> { alias_name: &String, ) -> Result<(), Error> { if !is_valid_bucket_name(alias_name) { - return Err(Error::BadRequest(format!( - "{}: {}", - alias_name, INVALID_BUCKET_NAME_MESSAGE - ))); + return Err(Error::InvalidBucketName(alias_name.to_string())); } let mut bucket = self.get_existing_bucket(bucket_id).await?; @@ -175,7 +136,7 @@ impl<'a> BucketHelper<'a> { let alias = match alias { None => BucketAlias::new(alias_name.clone(), alias_ts, Some(bucket_id)) - .ok_or_bad_request(format!("{}: {}", alias_name, INVALID_BUCKET_NAME_MESSAGE))?, + .ok_or_else(|| Error::InvalidBucketName(alias_name.clone()))?, Some(mut a) => { a.state = Lww::raw(alias_ts, Some(bucket_id)); a @@ -263,7 +224,7 @@ impl<'a> BucketHelper<'a> { .bucket_alias_table .get(&EmptyKey, alias_name) .await? - .ok_or_message(format!("Alias {} not found", alias_name))?; + .ok_or_else(|| Error::NoSuchBucket(alias_name.to_string()))?; // Checks ok, remove alias let alias_ts = match bucket.state.as_option() { @@ -302,15 +263,14 @@ impl<'a> BucketHelper<'a> { key_id: &String, alias_name: &String, ) -> Result<(), Error> { + let key_helper = KeyHelper(self.0); + if !is_valid_bucket_name(alias_name) { - return Err(Error::BadRequest(format!( - "{}: {}", - alias_name, INVALID_BUCKET_NAME_MESSAGE - ))); + return Err(Error::InvalidBucketName(alias_name.to_string())); } let mut bucket = self.get_existing_bucket(bucket_id).await?; - let mut key = self.get_existing_key(key_id).await?; + let mut key = key_helper.get_existing_key(key_id).await?; let mut key_param = key.state.as_option_mut().unwrap(); @@ -359,8 +319,10 @@ impl<'a> BucketHelper<'a> { key_id: &String, alias_name: &String, ) -> Result<(), Error> { + let key_helper = KeyHelper(self.0); + let mut bucket = self.get_existing_bucket(bucket_id).await?; - let mut key = self.get_existing_key(key_id).await?; + let mut key = key_helper.get_existing_key(key_id).await?; let mut bucket_p = bucket.state.as_option_mut().unwrap(); @@ -428,8 +390,10 @@ impl<'a> BucketHelper<'a> { key_id: &String, mut perm: BucketKeyPerm, ) -> Result<(), Error> { + let key_helper = KeyHelper(self.0); + let mut bucket = self.get_internal_bucket(bucket_id).await?; - let mut key = self.get_internal_key(key_id).await?; + let mut key = key_helper.get_internal_key(key_id).await?; if let Some(bstate) = bucket.state.as_option() { if let Some(kp) = bstate.authorized_keys.get(key_id) { @@ -465,4 +429,47 @@ impl<'a> BucketHelper<'a> { Ok(()) } + + pub async fn is_bucket_empty(&self, bucket_id: Uuid) -> Result<bool, Error> { + let objects = self + .0 + .object_table + .get_range( + &bucket_id, + None, + Some(ObjectFilter::IsData), + 10, + EnumerationOrder::Forward, + ) + .await?; + if !objects.is_empty() { + return Ok(false); + } + + #[cfg(feature = "k2v")] + { + use garage_rpc::ring::Ring; + use std::sync::Arc; + + let ring: Arc<Ring> = self.0.system.ring.borrow().clone(); + let k2vindexes = self + .0 + .k2v + .counter_table + .table + .get_range( + &bucket_id, + None, + Some((DeletedFilter::NotDeleted, ring.layout.node_id_vec.clone())), + 10, + EnumerationOrder::Forward, + ) + .await?; + if !k2vindexes.is_empty() { + return Ok(false); + } + } + + Ok(true) + } } diff --git a/src/model/helper/error.rs b/src/model/helper/error.rs index 30b2ba32..3ca8f55c 100644 --- a/src/model/helper/error.rs +++ b/src/model/helper/error.rs @@ -10,6 +10,16 @@ pub enum Error { #[error(display = "Bad request: {}", _0)] BadRequest(String), + + /// Bucket name is not valid according to AWS S3 specs + #[error(display = "Invalid bucket name: {}", _0)] + InvalidBucketName(String), + + #[error(display = "Access key not found: {}", _0)] + NoSuchAccessKey(String), + + #[error(display = "Bucket not found: {}", _0)] + NoSuchBucket(String), } impl From<netapp::error::Error> for Error { diff --git a/src/model/helper/key.rs b/src/model/helper/key.rs new file mode 100644 index 00000000..c1a8e974 --- /dev/null +++ b/src/model/helper/key.rs @@ -0,0 +1,102 @@ +use garage_table::util::*; +use garage_util::crdt::*; +use garage_util::error::OkOrMessage; + +use crate::garage::Garage; +use crate::helper::bucket::BucketHelper; +use crate::helper::error::*; +use crate::key_table::{Key, KeyFilter}; +use crate::permission::BucketKeyPerm; + +pub struct KeyHelper<'a>(pub(crate) &'a Garage); + +#[allow(clippy::ptr_arg)] +impl<'a> KeyHelper<'a> { + /// Returns a Key if it is present in key table, + /// even if it is in deleted state. Querying a non-existing + /// key ID returns an internal error. + pub async fn get_internal_key(&self, key_id: &String) -> Result<Key, Error> { + Ok(self + .0 + .key_table + .get(&EmptyKey, key_id) + .await? + .ok_or_message(format!("Key {} does not exist", key_id))?) + } + + /// Returns a Key if it is present in key table, + /// only if it is in non-deleted state. + /// Querying a non-existing key ID or a deleted key + /// returns a bad request error. + pub async fn get_existing_key(&self, key_id: &String) -> Result<Key, Error> { + self.0 + .key_table + .get(&EmptyKey, key_id) + .await? + .filter(|b| !b.state.is_deleted()) + .ok_or_else(|| Error::NoSuchAccessKey(key_id.to_string())) + } + + /// Returns a Key if it is present in key table, + /// looking it up by key ID or by a match on its name, + /// only if it is in non-deleted state. + /// Querying a non-existing key ID or a deleted key + /// returns a bad request error. + pub async fn get_existing_matching_key(&self, pattern: &str) -> Result<Key, Error> { + let candidates = self + .0 + .key_table + .get_range( + &EmptyKey, + None, + Some(KeyFilter::MatchesAndNotDeleted(pattern.to_string())), + 10, + EnumerationOrder::Forward, + ) + .await? + .into_iter() + .collect::<Vec<_>>(); + if candidates.len() != 1 { + Err(Error::BadRequest(format!( + "{} matching keys", + candidates.len() + ))) + } else { + Ok(candidates.into_iter().next().unwrap()) + } + } + + /// Deletes an API access key + pub async fn delete_key(&self, key: &mut Key) -> Result<(), Error> { + let bucket_helper = BucketHelper(self.0); + + let state = key.state.as_option_mut().unwrap(); + + // --- done checking, now commit --- + // (the step at unset_local_bucket_alias will fail if a bucket + // does not have another alias, the deletion will be + // interrupted in the middle if that happens) + + // 1. Delete local aliases + for (alias, _, to) in state.local_aliases.items().iter() { + if let Some(bucket_id) = to { + bucket_helper + .unset_local_bucket_alias(*bucket_id, &key.key_id, alias) + .await?; + } + } + + // 2. Remove permissions on all authorized buckets + for (ab_id, _auth) in state.authorized_buckets.items().iter() { + bucket_helper + .set_bucket_key_permissions(*ab_id, &key.key_id, BucketKeyPerm::NO_PERMISSIONS) + .await?; + } + + // 3. Actually delete key + key.state = Deletable::delete(); + self.0.key_table.insert(key).await?; + + Ok(()) + } +} diff --git a/src/model/helper/mod.rs b/src/model/helper/mod.rs index 2f4e8898..dd947c86 100644 --- a/src/model/helper/mod.rs +++ b/src/model/helper/mod.rs @@ -1,2 +1,3 @@ pub mod bucket; pub mod error; +pub mod key; diff --git a/src/model/index_counter.rs b/src/model/index_counter.rs new file mode 100644 index 00000000..e6394f0c --- /dev/null +++ b/src/model/index_counter.rs @@ -0,0 +1,496 @@ +use core::ops::Bound; +use std::collections::{hash_map, BTreeMap, HashMap}; +use std::marker::PhantomData; +use std::sync::Arc; + +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use tokio::sync::{mpsc, watch}; + +use garage_db as db; + +use garage_rpc::ring::Ring; +use garage_rpc::system::System; +use garage_util::background::*; +use garage_util::data::*; +use garage_util::error::*; +use garage_util::time::*; + +use garage_table::crdt::*; +use garage_table::replication::*; +use garage_table::*; + +pub trait CountedItem: Clone + PartialEq + Send + Sync + 'static { + const COUNTER_TABLE_NAME: &'static str; + + type CP: PartitionKey + Clone + PartialEq + Serialize + for<'de> Deserialize<'de> + Send + Sync; + type CS: SortKey + Clone + PartialEq + Serialize + for<'de> Deserialize<'de> + Send + Sync; + + fn counter_partition_key(&self) -> &Self::CP; + fn counter_sort_key(&self) -> &Self::CS; + fn counts(&self) -> Vec<(&'static str, i64)>; +} + +/// A counter entry in the global table +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct CounterEntry<T: CountedItem> { + pub pk: T::CP, + pub sk: T::CS, + pub values: BTreeMap<String, CounterValue>, +} + +impl<T: CountedItem> Entry<T::CP, T::CS> for CounterEntry<T> { + fn partition_key(&self) -> &T::CP { + &self.pk + } + fn sort_key(&self) -> &T::CS { + &self.sk + } + fn is_tombstone(&self) -> bool { + self.values + .iter() + .all(|(_, v)| v.node_values.iter().all(|(_, (_, v))| *v == 0)) + } +} + +impl<T: CountedItem> CounterEntry<T> { + pub fn filtered_values(&self, ring: &Ring) -> HashMap<String, i64> { + let nodes = &ring.layout.node_id_vec[..]; + self.filtered_values_with_nodes(nodes) + } + + pub fn filtered_values_with_nodes(&self, nodes: &[Uuid]) -> HashMap<String, i64> { + let mut ret = HashMap::new(); + for (name, vals) in self.values.iter() { + let new_vals = vals + .node_values + .iter() + .filter(|(n, _)| nodes.contains(n)) + .map(|(_, (_, v))| *v) + .collect::<Vec<_>>(); + if !new_vals.is_empty() { + ret.insert( + name.clone(), + new_vals.iter().fold(i64::MIN, |a, b| std::cmp::max(a, *b)), + ); + } + } + + ret + } +} + +/// A counter entry in the global table +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct CounterValue { + pub node_values: BTreeMap<Uuid, (u64, i64)>, +} + +impl<T: CountedItem> Crdt for CounterEntry<T> { + fn merge(&mut self, other: &Self) { + for (name, e2) in other.values.iter() { + if let Some(e) = self.values.get_mut(name) { + e.merge(e2); + } else { + self.values.insert(name.clone(), e2.clone()); + } + } + } +} + +impl Crdt for CounterValue { + fn merge(&mut self, other: &Self) { + for (node, (t2, e2)) in other.node_values.iter() { + if let Some((t, e)) = self.node_values.get_mut(node) { + if t2 > t { + *e = *e2; + } + } else { + self.node_values.insert(*node, (*t2, *e2)); + } + } + } +} + +pub struct CounterTable<T: CountedItem> { + _phantom_t: PhantomData<T>, +} + +impl<T: CountedItem> TableSchema for CounterTable<T> { + const TABLE_NAME: &'static str = T::COUNTER_TABLE_NAME; + + type P = T::CP; + type S = T::CS; + type E = CounterEntry<T>; + type Filter = (DeletedFilter, Vec<Uuid>); + + fn matches_filter(entry: &Self::E, filter: &Self::Filter) -> bool { + if filter.0 == DeletedFilter::Any { + return true; + } + + let is_tombstone = entry + .filtered_values_with_nodes(&filter.1[..]) + .iter() + .all(|(_, v)| *v == 0); + filter.0.apply(is_tombstone) + } +} + +// ---- + +pub struct IndexCounter<T: CountedItem> { + this_node: Uuid, + local_counter: db::Tree, + propagate_tx: mpsc::UnboundedSender<(T::CP, T::CS, LocalCounterEntry<T>)>, + pub table: Arc<Table<CounterTable<T>, TableShardedReplication>>, +} + +impl<T: CountedItem> IndexCounter<T> { + pub fn new( + system: Arc<System>, + replication: TableShardedReplication, + db: &db::Db, + ) -> Arc<Self> { + let background = system.background.clone(); + + let (propagate_tx, propagate_rx) = mpsc::unbounded_channel(); + + let this = Arc::new(Self { + this_node: system.id, + local_counter: db + .open_tree(format!("local_counter_v2:{}", T::COUNTER_TABLE_NAME)) + .expect("Unable to open local counter tree"), + propagate_tx, + table: Table::new( + CounterTable { + _phantom_t: Default::default(), + }, + replication, + system, + db, + ), + }); + + background.spawn_worker(IndexPropagatorWorker { + index_counter: this.clone(), + propagate_rx, + buf: HashMap::new(), + errors: 0, + }); + + this + } + + pub fn count( + &self, + tx: &mut db::Transaction, + old: Option<&T>, + new: Option<&T>, + ) -> db::TxResult<(), Error> { + let pk = old + .map(|e| e.counter_partition_key()) + .unwrap_or_else(|| new.unwrap().counter_partition_key()); + let sk = old + .map(|e| e.counter_sort_key()) + .unwrap_or_else(|| new.unwrap().counter_sort_key()); + + // calculate counter differences + let mut counts = HashMap::new(); + for (k, v) in old.map(|x| x.counts()).unwrap_or_default() { + *counts.entry(k).or_insert(0) -= v; + } + for (k, v) in new.map(|x| x.counts()).unwrap_or_default() { + *counts.entry(k).or_insert(0) += v; + } + + // update local counter table + let tree_key = self.table.data.tree_key(pk, sk); + + let mut entry = match tx.get(&self.local_counter, &tree_key[..])? { + Some(old_bytes) => { + rmp_serde::decode::from_read_ref::<_, LocalCounterEntry<T>>(&old_bytes) + .map_err(Error::RmpDecode) + .map_err(db::TxError::Abort)? + } + None => LocalCounterEntry { + pk: pk.clone(), + sk: sk.clone(), + values: BTreeMap::new(), + }, + }; + + let now = now_msec(); + for (s, inc) in counts.iter() { + let mut ent = entry.values.entry(s.to_string()).or_insert((0, 0)); + ent.0 = std::cmp::max(ent.0 + 1, now); + ent.1 += *inc; + } + + let new_entry_bytes = rmp_to_vec_all_named(&entry) + .map_err(Error::RmpEncode) + .map_err(db::TxError::Abort)?; + tx.insert(&self.local_counter, &tree_key[..], new_entry_bytes)?; + + if let Err(e) = self.propagate_tx.send((pk.clone(), sk.clone(), entry)) { + error!( + "Could not propagate updated counter values, failed to send to channel: {}", + e + ); + } + + Ok(()) + } + + pub fn offline_recount_all<TS, TR>( + &self, + counted_table: &Arc<Table<TS, TR>>, + ) -> Result<(), Error> + where + TS: TableSchema<E = T>, + TR: TableReplication, + { + let save_counter_entry = |entry: CounterEntry<T>| -> Result<(), Error> { + let entry_k = self + .table + .data + .tree_key(entry.partition_key(), entry.sort_key()); + self.table + .data + .update_entry_with(&entry_k, |ent| match ent { + Some(mut ent) => { + ent.merge(&entry); + ent + } + None => entry.clone(), + })?; + Ok(()) + }; + + // 1. Set all old local counters to zero + let now = now_msec(); + let mut next_start: Option<Vec<u8>> = None; + loop { + let low_bound = match next_start.take() { + Some(v) => Bound::Excluded(v), + None => Bound::Unbounded, + }; + let mut batch = vec![]; + for item in self.local_counter.range((low_bound, Bound::Unbounded))? { + batch.push(item?); + if batch.len() > 1000 { + break; + } + } + + if batch.is_empty() { + break; + } + + info!("zeroing old counters... ({})", hex::encode(&batch[0].0)); + for (local_counter_k, local_counter) in batch { + let mut local_counter = + rmp_serde::decode::from_read_ref::<_, LocalCounterEntry<T>>(&local_counter)?; + + for (_, tv) in local_counter.values.iter_mut() { + tv.0 = std::cmp::max(tv.0 + 1, now); + tv.1 = 0; + } + + let local_counter_bytes = rmp_to_vec_all_named(&local_counter)?; + self.local_counter + .insert(&local_counter_k, &local_counter_bytes)?; + + let counter_entry = local_counter.into_counter_entry(self.this_node); + save_counter_entry(counter_entry)?; + + next_start = Some(local_counter_k); + } + } + + // 2. Recount all table entries + let now = now_msec(); + let mut next_start: Option<Vec<u8>> = None; + loop { + let low_bound = match next_start.take() { + Some(v) => Bound::Excluded(v), + None => Bound::Unbounded, + }; + let mut batch = vec![]; + for item in counted_table + .data + .store + .range((low_bound, Bound::Unbounded))? + { + batch.push(item?); + if batch.len() > 1000 { + break; + } + } + + if batch.is_empty() { + break; + } + + info!("counting entries... ({})", hex::encode(&batch[0].0)); + for (counted_entry_k, counted_entry) in batch { + let counted_entry = counted_table.data.decode_entry(&counted_entry)?; + + let pk = counted_entry.counter_partition_key(); + let sk = counted_entry.counter_sort_key(); + let counts = counted_entry.counts(); + + let local_counter_key = self.table.data.tree_key(pk, sk); + let mut local_counter = match self.local_counter.get(&local_counter_key)? { + Some(old_bytes) => { + let ent = rmp_serde::decode::from_read_ref::<_, LocalCounterEntry<T>>( + &old_bytes, + )?; + assert!(ent.pk == *pk); + assert!(ent.sk == *sk); + ent + } + None => LocalCounterEntry { + pk: pk.clone(), + sk: sk.clone(), + values: BTreeMap::new(), + }, + }; + for (s, v) in counts.iter() { + let mut tv = local_counter.values.entry(s.to_string()).or_insert((0, 0)); + tv.0 = std::cmp::max(tv.0 + 1, now); + tv.1 += v; + } + + let local_counter_bytes = rmp_to_vec_all_named(&local_counter)?; + self.local_counter + .insert(&local_counter_key, local_counter_bytes)?; + + let counter_entry = local_counter.into_counter_entry(self.this_node); + save_counter_entry(counter_entry)?; + + next_start = Some(counted_entry_k); + } + } + + // Done + Ok(()) + } +} + +struct IndexPropagatorWorker<T: CountedItem> { + index_counter: Arc<IndexCounter<T>>, + propagate_rx: mpsc::UnboundedReceiver<(T::CP, T::CS, LocalCounterEntry<T>)>, + + buf: HashMap<Vec<u8>, CounterEntry<T>>, + errors: usize, +} + +impl<T: CountedItem> IndexPropagatorWorker<T> { + fn add_ent(&mut self, pk: T::CP, sk: T::CS, counters: LocalCounterEntry<T>) { + let tree_key = self.index_counter.table.data.tree_key(&pk, &sk); + let dist_entry = counters.into_counter_entry(self.index_counter.this_node); + match self.buf.entry(tree_key) { + hash_map::Entry::Vacant(e) => { + e.insert(dist_entry); + } + hash_map::Entry::Occupied(mut e) => { + e.get_mut().merge(&dist_entry); + } + } + } +} + +#[async_trait] +impl<T: CountedItem> Worker for IndexPropagatorWorker<T> { + fn name(&self) -> String { + format!("{} index counter propagator", T::COUNTER_TABLE_NAME) + } + + fn info(&self) -> Option<String> { + if !self.buf.is_empty() { + Some(format!("{} items in queue", self.buf.len())) + } else { + None + } + } + + async fn work(&mut self, must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + // This loop batches updates to counters to be sent all at once. + // They are sent once the propagate_rx channel has been emptied (or is closed). + let closed = loop { + match self.propagate_rx.try_recv() { + Ok((pk, sk, counters)) => { + self.add_ent(pk, sk, counters); + } + Err(mpsc::error::TryRecvError::Empty) => break false, + Err(mpsc::error::TryRecvError::Disconnected) => break true, + } + }; + + if !self.buf.is_empty() { + let entries_k = self.buf.keys().take(100).cloned().collect::<Vec<_>>(); + let entries = entries_k.iter().map(|k| self.buf.get(k).unwrap()); + if let Err(e) = self.index_counter.table.insert_many(entries).await { + self.errors += 1; + if self.errors >= 2 && *must_exit.borrow() { + error!("({}) Could not propagate {} counter values: {}, these counters will not be updated correctly.", T::COUNTER_TABLE_NAME, self.buf.len(), e); + return Ok(WorkerState::Done); + } + // Propagate error up to worker manager, it will log it, increment a counter, + // and sleep for a certain delay (with exponential backoff), waiting for + // things to go back to normal + return Err(e); + } else { + for k in entries_k { + self.buf.remove(&k); + } + self.errors = 0; + } + + return Ok(WorkerState::Busy); + } else if closed { + return Ok(WorkerState::Done); + } else { + return Ok(WorkerState::Idle); + } + } + + async fn wait_for_work(&mut self, _must_exit: &watch::Receiver<bool>) -> WorkerState { + match self.propagate_rx.recv().await { + Some((pk, sk, counters)) => { + self.add_ent(pk, sk, counters); + WorkerState::Busy + } + None => match self.buf.is_empty() { + false => WorkerState::Busy, + true => WorkerState::Done, + }, + } + } +} + +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +struct LocalCounterEntry<T: CountedItem> { + pk: T::CP, + sk: T::CS, + values: BTreeMap<String, (u64, i64)>, +} + +impl<T: CountedItem> LocalCounterEntry<T> { + fn into_counter_entry(self, this_node: Uuid) -> CounterEntry<T> { + CounterEntry { + pk: self.pk, + sk: self.sk, + values: self + .values + .into_iter() + .map(|(name, (ts, v))| { + let mut node_values = BTreeMap::new(); + node_values.insert(this_node, (ts, v)); + (name, CounterValue { node_values }) + }) + .collect(), + } + } +} diff --git a/src/model/k2v/causality.rs b/src/model/k2v/causality.rs new file mode 100644 index 00000000..9a692870 --- /dev/null +++ b/src/model/k2v/causality.rs @@ -0,0 +1,96 @@ +use std::collections::BTreeMap; +use std::convert::TryInto; + +use serde::{Deserialize, Serialize}; + +use garage_util::data::*; + +/// Node IDs used in K2V are u64 integers that are the abbreviation +/// of full Garage node IDs which are 256-bit UUIDs. +pub type K2VNodeId = u64; + +pub fn make_node_id(node_id: Uuid) -> K2VNodeId { + let mut tmp = [0u8; 8]; + tmp.copy_from_slice(&node_id.as_slice()[..8]); + u64::from_be_bytes(tmp) +} + +#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] +pub struct CausalContext { + pub vector_clock: BTreeMap<K2VNodeId, u64>, +} + +impl CausalContext { + /// Empty causality context + pub fn new_empty() -> Self { + Self { + vector_clock: BTreeMap::new(), + } + } + /// Make binary representation and encode in base64 + pub fn serialize(&self) -> String { + let mut ints = Vec::with_capacity(2 * self.vector_clock.len()); + for (node, time) in self.vector_clock.iter() { + ints.push(*node); + ints.push(*time); + } + let checksum = ints.iter().fold(0, |acc, v| acc ^ *v); + + let mut bytes = u64::to_be_bytes(checksum).to_vec(); + for i in ints { + bytes.extend(u64::to_be_bytes(i)); + } + + base64::encode_config(bytes, base64::URL_SAFE_NO_PAD) + } + /// Parse from base64-encoded binary representation + pub fn parse(s: &str) -> Result<Self, String> { + let bytes = base64::decode_config(s, base64::URL_SAFE_NO_PAD) + .map_err(|e| format!("bad causality token base64: {}", e))?; + if bytes.len() % 16 != 8 || bytes.len() < 8 { + return Err("bad causality token length".into()); + } + + let checksum = u64::from_be_bytes(bytes[..8].try_into().unwrap()); + let mut ret = CausalContext { + vector_clock: BTreeMap::new(), + }; + + for i in 0..(bytes.len() / 16) { + let node_id = u64::from_be_bytes(bytes[8 + i * 16..16 + i * 16].try_into().unwrap()); + let time = u64::from_be_bytes(bytes[16 + i * 16..24 + i * 16].try_into().unwrap()); + ret.vector_clock.insert(node_id, time); + } + + let check = ret.vector_clock.iter().fold(0, |acc, (n, t)| acc ^ *n ^ *t); + + if check != checksum { + return Err("bad causality token checksum".into()); + } + + Ok(ret) + } + /// Check if this causal context contains newer items than another one + pub fn is_newer_than(&self, other: &Self) -> bool { + self.vector_clock + .iter() + .any(|(k, v)| v > other.vector_clock.get(k).unwrap_or(&0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_causality_token_serialization() { + let ct = CausalContext { + vector_clock: [(4, 42), (1928131023, 76), (0xefc0c1c47f9de433, 2)] + .iter() + .cloned() + .collect(), + }; + + assert_eq!(CausalContext::parse(&ct.serialize()).unwrap(), ct); + } +} diff --git a/src/model/k2v/item_table.rs b/src/model/k2v/item_table.rs new file mode 100644 index 00000000..7860cb17 --- /dev/null +++ b/src/model/k2v/item_table.rs @@ -0,0 +1,305 @@ +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::sync::Arc; + +use garage_db as db; +use garage_util::data::*; + +use garage_table::crdt::*; +use garage_table::*; + +use crate::index_counter::*; +use crate::k2v::causality::*; +use crate::k2v::poll::*; + +pub const ENTRIES: &str = "entries"; +pub const CONFLICTS: &str = "conflicts"; +pub const VALUES: &str = "values"; +pub const BYTES: &str = "bytes"; + +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct K2VItem { + pub partition: K2VItemPartition, + pub sort_key: String, + + items: BTreeMap<K2VNodeId, DvvsEntry>, +} + +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)] +pub struct K2VItemPartition { + pub bucket_id: Uuid, + pub partition_key: String, +} + +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +struct DvvsEntry { + t_discard: u64, + values: Vec<(u64, DvvsValue)>, +} + +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub enum DvvsValue { + Value(#[serde(with = "serde_bytes")] Vec<u8>), + Deleted, +} + +impl K2VItem { + /// Creates a new K2VItem when no previous entry existed in the db + pub fn new(bucket_id: Uuid, partition_key: String, sort_key: String) -> Self { + Self { + partition: K2VItemPartition { + bucket_id, + partition_key, + }, + sort_key, + items: BTreeMap::new(), + } + } + /// Updates a K2VItem with a new value or a deletion event + pub fn update( + &mut self, + this_node: Uuid, + context: &Option<CausalContext>, + new_value: DvvsValue, + ) { + if let Some(context) = context { + for (node, t_discard) in context.vector_clock.iter() { + if let Some(e) = self.items.get_mut(node) { + e.t_discard = std::cmp::max(e.t_discard, *t_discard); + } else { + self.items.insert( + *node, + DvvsEntry { + t_discard: *t_discard, + values: vec![], + }, + ); + } + } + } + + self.discard(); + + let node_id = make_node_id(this_node); + let e = self.items.entry(node_id).or_insert(DvvsEntry { + t_discard: 0, + values: vec![], + }); + let t_prev = e.max_time(); + e.values.push((t_prev + 1, new_value)); + } + + /// Extract the causality context of a K2V Item + pub fn causal_context(&self) -> CausalContext { + let mut cc = CausalContext::new_empty(); + for (node, ent) in self.items.iter() { + cc.vector_clock.insert(*node, ent.max_time()); + } + cc + } + + /// Extract the list of values + pub fn values(&'_ self) -> Vec<&'_ DvvsValue> { + let mut ret = vec![]; + for (_, ent) in self.items.iter() { + for (_, v) in ent.values.iter() { + if !ret.contains(&v) { + ret.push(v); + } + } + } + ret + } + + fn discard(&mut self) { + for (_, ent) in self.items.iter_mut() { + ent.discard(); + } + } +} + +impl DvvsEntry { + fn max_time(&self) -> u64 { + self.values + .iter() + .fold(self.t_discard, |acc, (vts, _)| std::cmp::max(acc, *vts)) + } + + fn discard(&mut self) { + self.values = std::mem::take(&mut self.values) + .into_iter() + .filter(|(t, _)| *t > self.t_discard) + .collect::<Vec<_>>(); + } +} + +impl Crdt for K2VItem { + fn merge(&mut self, other: &Self) { + for (node, e2) in other.items.iter() { + if let Some(e) = self.items.get_mut(node) { + e.merge(e2); + } else { + self.items.insert(*node, e2.clone()); + } + } + } +} + +impl Crdt for DvvsEntry { + fn merge(&mut self, other: &Self) { + self.t_discard = std::cmp::max(self.t_discard, other.t_discard); + self.discard(); + + let t_max = self.max_time(); + for (vt, vv) in other.values.iter() { + if *vt > t_max { + self.values.push((*vt, vv.clone())); + } + } + } +} + +impl PartitionKey for K2VItemPartition { + fn hash(&self) -> Hash { + use blake2::{Blake2b, Digest}; + + let mut hasher = Blake2b::new(); + hasher.update(self.bucket_id.as_slice()); + hasher.update(self.partition_key.as_bytes()); + let mut hash = [0u8; 32]; + hash.copy_from_slice(&hasher.finalize()[..32]); + hash.into() + } +} + +impl Entry<K2VItemPartition, String> for K2VItem { + fn partition_key(&self) -> &K2VItemPartition { + &self.partition + } + fn sort_key(&self) -> &String { + &self.sort_key + } + fn is_tombstone(&self) -> bool { + self.values() + .iter() + .all(|v| matches!(v, DvvsValue::Deleted)) + } +} + +pub struct K2VItemTable { + pub(crate) counter_table: Arc<IndexCounter<K2VItem>>, + pub(crate) subscriptions: Arc<SubscriptionManager>, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct ItemFilter { + pub exclude_only_tombstones: bool, + pub conflicts_only: bool, +} + +impl TableSchema for K2VItemTable { + const TABLE_NAME: &'static str = "k2v_item"; + + type P = K2VItemPartition; + type S = String; + type E = K2VItem; + type Filter = ItemFilter; + + fn updated( + &self, + tx: &mut db::Transaction, + old: Option<&Self::E>, + new: Option<&Self::E>, + ) -> db::TxOpResult<()> { + // 1. Count + let counter_res = self.counter_table.count(tx, old, new); + if let Err(e) = db::unabort(counter_res)? { + // This result can be returned by `counter_table.count()` for instance + // if messagepack serialization or deserialization fails at some step. + // Warn admin but ignore this error for now, that's all we can do. + error!( + "Unable to update K2V item counter: {}. Index values will be wrong!", + e + ); + } + + // 2. Notify + if let Some(new_ent) = new { + self.subscriptions.notify(new_ent); + } + + Ok(()) + } + + #[allow(clippy::nonminimal_bool)] + fn matches_filter(entry: &Self::E, filter: &Self::Filter) -> bool { + let v = entry.values(); + !(filter.conflicts_only && v.len() < 2) + && !(filter.exclude_only_tombstones && entry.is_tombstone()) + } +} + +impl CountedItem for K2VItem { + const COUNTER_TABLE_NAME: &'static str = "k2v_index_counter_v2"; + + // Partition key = bucket id + type CP = Uuid; + // Sort key = K2V item's partition key + type CS = String; + + fn counter_partition_key(&self) -> &Uuid { + &self.partition.bucket_id + } + fn counter_sort_key(&self) -> &String { + &self.partition.partition_key + } + + fn counts(&self) -> Vec<(&'static str, i64)> { + let values = self.values(); + + let n_entries = if self.is_tombstone() { 0 } else { 1 }; + let n_conflicts = if values.len() > 1 { 1 } else { 0 }; + let n_values = values + .iter() + .filter(|v| matches!(v, DvvsValue::Value(_))) + .count() as i64; + let n_bytes = values + .iter() + .map(|v| match v { + DvvsValue::Deleted => 0, + DvvsValue::Value(v) => v.len() as i64, + }) + .sum(); + + vec![ + (ENTRIES, n_entries), + (CONFLICTS, n_conflicts), + (VALUES, n_values), + (BYTES, n_bytes), + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dvvsentry_merge_simple() { + let e1 = DvvsEntry { + t_discard: 4, + values: vec![ + (5, DvvsValue::Value(vec![15])), + (6, DvvsValue::Value(vec![16])), + ], + }; + let e2 = DvvsEntry { + t_discard: 5, + values: vec![(6, DvvsValue::Value(vec![16])), (7, DvvsValue::Deleted)], + }; + + let mut e3 = e1.clone(); + e3.merge(&e2); + assert_eq!(e2, e3); + } +} diff --git a/src/model/k2v/mod.rs b/src/model/k2v/mod.rs new file mode 100644 index 00000000..f6a96151 --- /dev/null +++ b/src/model/k2v/mod.rs @@ -0,0 +1,6 @@ +pub mod causality; + +pub mod item_table; + +pub mod poll; +pub mod rpc; diff --git a/src/model/k2v/poll.rs b/src/model/k2v/poll.rs new file mode 100644 index 00000000..93105207 --- /dev/null +++ b/src/model/k2v/poll.rs @@ -0,0 +1,50 @@ +use std::collections::HashMap; +use std::sync::Mutex; + +use serde::{Deserialize, Serialize}; +use tokio::sync::broadcast; + +use crate::k2v::item_table::*; + +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PollKey { + pub partition: K2VItemPartition, + pub sort_key: String, +} + +#[derive(Default)] +pub struct SubscriptionManager { + subscriptions: Mutex<HashMap<PollKey, broadcast::Sender<K2VItem>>>, +} + +impl SubscriptionManager { + pub fn new() -> Self { + Self::default() + } + + pub fn subscribe(&self, key: &PollKey) -> broadcast::Receiver<K2VItem> { + let mut subs = self.subscriptions.lock().unwrap(); + if let Some(s) = subs.get(key) { + s.subscribe() + } else { + let (tx, rx) = broadcast::channel(8); + subs.insert(key.clone(), tx); + rx + } + } + + pub fn notify(&self, item: &K2VItem) { + let key = PollKey { + partition: item.partition.clone(), + sort_key: item.sort_key.clone(), + }; + let mut subs = self.subscriptions.lock().unwrap(); + if let Some(s) = subs.get(&key) { + if s.send(item.clone()).is_err() { + // no more subscribers, remove channel from here + // (we will re-create it later if we need to subscribe again) + subs.remove(&key); + } + } + } +} diff --git a/src/model/k2v/rpc.rs b/src/model/k2v/rpc.rs new file mode 100644 index 00000000..a74df277 --- /dev/null +++ b/src/model/k2v/rpc.rs @@ -0,0 +1,341 @@ +//! Module that implements RPCs specific to K2V. +//! This is necessary for insertions into the K2V store, +//! as they have to be transmitted to one of the nodes responsible +//! for storing the entry to be processed (the API entry +//! node does not process the entry directly, as this would +//! mean the vector clock gets much larger than needed). + +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; + +use async_trait::async_trait; +use futures::stream::FuturesUnordered; +use futures::StreamExt; +use serde::{Deserialize, Serialize}; +use tokio::select; + +use garage_util::crdt::*; +use garage_util::data::*; +use garage_util::error::*; + +use garage_rpc::system::System; +use garage_rpc::*; + +use garage_table::replication::{TableReplication, TableShardedReplication}; +use garage_table::{PartitionKey, Table}; + +use crate::k2v::causality::*; +use crate::k2v::item_table::*; +use crate::k2v::poll::*; + +/// RPC messages for K2V +#[derive(Debug, Serialize, Deserialize)] +enum K2VRpc { + Ok, + InsertItem(InsertedItem), + InsertManyItems(Vec<InsertedItem>), + PollItem { + key: PollKey, + causal_context: CausalContext, + timeout_msec: u64, + }, + PollItemResponse(Option<K2VItem>), +} + +#[derive(Debug, Serialize, Deserialize)] +struct InsertedItem { + partition: K2VItemPartition, + sort_key: String, + causal_context: Option<CausalContext>, + value: DvvsValue, +} + +impl Rpc for K2VRpc { + type Response = Result<K2VRpc, Error>; +} + +/// The block manager, handling block exchange between nodes, and block storage on local node +pub struct K2VRpcHandler { + system: Arc<System>, + item_table: Arc<Table<K2VItemTable, TableShardedReplication>>, + endpoint: Arc<Endpoint<K2VRpc, Self>>, + subscriptions: Arc<SubscriptionManager>, +} + +impl K2VRpcHandler { + pub fn new( + system: Arc<System>, + item_table: Arc<Table<K2VItemTable, TableShardedReplication>>, + subscriptions: Arc<SubscriptionManager>, + ) -> Arc<Self> { + let endpoint = system.netapp.endpoint("garage_model/k2v/Rpc".to_string()); + + let rpc_handler = Arc::new(Self { + system, + item_table, + endpoint, + subscriptions, + }); + rpc_handler.endpoint.set_handler(rpc_handler.clone()); + + rpc_handler + } + + // ---- public interface ---- + + pub async fn insert( + &self, + bucket_id: Uuid, + partition_key: String, + sort_key: String, + causal_context: Option<CausalContext>, + value: DvvsValue, + ) -> Result<(), Error> { + let partition = K2VItemPartition { + bucket_id, + partition_key, + }; + let mut who = self + .item_table + .data + .replication + .write_nodes(&partition.hash()); + who.sort(); + + self.system + .rpc + .try_call_many( + &self.endpoint, + &who[..], + K2VRpc::InsertItem(InsertedItem { + partition, + sort_key, + causal_context, + value, + }), + RequestStrategy::with_priority(PRIO_NORMAL) + .with_quorum(1) + .interrupt_after_quorum(true), + ) + .await?; + + Ok(()) + } + + pub async fn insert_batch( + &self, + bucket_id: Uuid, + items: Vec<(String, String, Option<CausalContext>, DvvsValue)>, + ) -> Result<(), Error> { + let n_items = items.len(); + + let mut call_list: HashMap<_, Vec<_>> = HashMap::new(); + + for (partition_key, sort_key, causal_context, value) in items { + let partition = K2VItemPartition { + bucket_id, + partition_key, + }; + let mut who = self + .item_table + .data + .replication + .write_nodes(&partition.hash()); + who.sort(); + + call_list.entry(who).or_default().push(InsertedItem { + partition, + sort_key, + causal_context, + value, + }); + } + + debug!( + "K2V insert_batch: {} requests to insert {} items", + call_list.len(), + n_items + ); + let call_futures = call_list.into_iter().map(|(nodes, items)| async move { + let resp = self + .system + .rpc + .try_call_many( + &self.endpoint, + &nodes[..], + K2VRpc::InsertManyItems(items), + RequestStrategy::with_priority(PRIO_NORMAL) + .with_quorum(1) + .interrupt_after_quorum(true), + ) + .await?; + Ok::<_, Error>((nodes, resp)) + }); + + let mut resps = call_futures.collect::<FuturesUnordered<_>>(); + while let Some(resp) = resps.next().await { + resp?; + } + + Ok(()) + } + + pub async fn poll( + &self, + bucket_id: Uuid, + partition_key: String, + sort_key: String, + causal_context: CausalContext, + timeout_msec: u64, + ) -> Result<Option<K2VItem>, Error> { + let poll_key = PollKey { + partition: K2VItemPartition { + bucket_id, + partition_key, + }, + sort_key, + }; + let nodes = self + .item_table + .data + .replication + .write_nodes(&poll_key.partition.hash()); + + let rpc = self.system.rpc.try_call_many( + &self.endpoint, + &nodes[..], + K2VRpc::PollItem { + key: poll_key, + causal_context, + timeout_msec, + }, + RequestStrategy::with_priority(PRIO_NORMAL) + .with_quorum(self.item_table.data.replication.read_quorum()) + .without_timeout(), + ); + let timeout_duration = Duration::from_millis(timeout_msec) + self.system.rpc.rpc_timeout(); + let resps = select! { + r = rpc => r?, + _ = tokio::time::sleep(timeout_duration) => return Ok(None), + }; + + let mut resp: Option<K2VItem> = None; + for v in resps { + match v { + K2VRpc::PollItemResponse(Some(x)) => { + if let Some(y) = &mut resp { + y.merge(&x); + } else { + resp = Some(x); + } + } + K2VRpc::PollItemResponse(None) => { + return Ok(None); + } + v => return Err(Error::unexpected_rpc_message(v)), + } + } + + Ok(resp) + } + + // ---- internal handlers ---- + + async fn handle_insert(&self, item: &InsertedItem) -> Result<K2VRpc, Error> { + let new = self.local_insert(item)?; + + // Propagate to rest of network + if let Some(updated) = new { + self.item_table.insert(&updated).await?; + } + + Ok(K2VRpc::Ok) + } + + async fn handle_insert_many(&self, items: &[InsertedItem]) -> Result<K2VRpc, Error> { + let mut updated_vec = vec![]; + + for item in items { + let new = self.local_insert(item)?; + + if let Some(updated) = new { + updated_vec.push(updated); + } + } + + // Propagate to rest of network + if !updated_vec.is_empty() { + self.item_table.insert_many(&updated_vec).await?; + } + + Ok(K2VRpc::Ok) + } + + fn local_insert(&self, item: &InsertedItem) -> Result<Option<K2VItem>, Error> { + let tree_key = self + .item_table + .data + .tree_key(&item.partition, &item.sort_key); + + self.item_table + .data + .update_entry_with(&tree_key[..], |ent| { + let mut ent = ent.unwrap_or_else(|| { + K2VItem::new( + item.partition.bucket_id, + item.partition.partition_key.clone(), + item.sort_key.clone(), + ) + }); + ent.update(self.system.id, &item.causal_context, item.value.clone()); + ent + }) + } + + async fn handle_poll(&self, key: &PollKey, ct: &CausalContext) -> Result<K2VItem, Error> { + let mut chan = self.subscriptions.subscribe(key); + + let mut value = self + .item_table + .data + .read_entry(&key.partition, &key.sort_key)? + .map(|bytes| self.item_table.data.decode_entry(&bytes[..])) + .transpose()? + .unwrap_or_else(|| { + K2VItem::new( + key.partition.bucket_id, + key.partition.partition_key.clone(), + key.sort_key.clone(), + ) + }); + + while !value.causal_context().is_newer_than(ct) { + value = chan.recv().await?; + } + + Ok(value) + } +} + +#[async_trait] +impl EndpointHandler<K2VRpc> for K2VRpcHandler { + async fn handle(self: &Arc<Self>, message: &K2VRpc, _from: NodeID) -> Result<K2VRpc, Error> { + match message { + K2VRpc::InsertItem(item) => self.handle_insert(item).await, + K2VRpc::InsertManyItems(items) => self.handle_insert_many(&items[..]).await, + K2VRpc::PollItem { + key, + causal_context, + timeout_msec, + } => { + let delay = tokio::time::sleep(Duration::from_millis(*timeout_msec)); + select! { + ret = self.handle_poll(key, causal_context) => ret.map(Some).map(K2VRpc::PollItemResponse), + _ = delay => Ok(K2VRpc::PollItemResponse(None)), + } + } + m => Err(Error::unexpected_rpc_message(m)), + } + } +} diff --git a/src/model/key_table.rs b/src/model/key_table.rs index 330e83f0..9d2fc783 100644 --- a/src/model/key_table.rs +++ b/src/model/key_table.rs @@ -6,10 +6,10 @@ use garage_util::data::*; use crate::permission::BucketKeyPerm; -use garage_model_050::key_table as old; +use crate::prev::v051::key_table as old; /// An api key -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct Key { /// The id of the key (immutable), used as partition key pub key_id: String, @@ -19,7 +19,7 @@ pub struct Key { } /// Configuration for a key -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct KeyParams { /// The secret_key associated (immutable) pub secret_key: String, diff --git a/src/model/lib.rs b/src/model/lib.rs index 05a4cdc7..4f20ea46 100644 --- a/src/model/lib.rs +++ b/src/model/lib.rs @@ -1,14 +1,20 @@ #[macro_use] extern crate tracing; +// For migration from previous versions +pub(crate) mod prev; + pub mod permission; -pub mod block_ref_table; +pub mod index_counter; + pub mod bucket_alias_table; pub mod bucket_table; pub mod key_table; -pub mod object_table; -pub mod version_table; + +#[cfg(feature = "k2v")] +pub mod k2v; +pub mod s3; pub mod garage; pub mod helper; diff --git a/src/model/migrate.rs b/src/model/migrate.rs index 7e61957a..cd6ad26a 100644 --- a/src/model/migrate.rs +++ b/src/model/migrate.rs @@ -5,7 +5,7 @@ use garage_util::data::*; use garage_util::error::Error as GarageError; use garage_util::time::*; -use garage_model_050::bucket_table as old_bucket; +use crate::prev::v051::bucket_table as old_bucket; use crate::bucket_alias_table::*; use crate::bucket_table::*; @@ -25,11 +25,15 @@ impl Migrate { .open_tree("bucket:table") .map_err(GarageError::from)?; - for res in tree.iter() { + let mut old_buckets = vec![]; + for res in tree.iter().map_err(GarageError::from)? { let (_k, v) = res.map_err(GarageError::from)?; let bucket = rmp_serde::decode::from_read_ref::<_, old_bucket::Bucket>(&v[..]) .map_err(GarageError::from)?; + old_buckets.push(bucket); + } + for bucket in old_buckets { if let old_bucket::BucketState::Present(p) = bucket.state.get() { self.migrate_buckets050_do_bucket(&bucket, p).await?; } @@ -73,6 +77,7 @@ impl Migrate { local_aliases: LwwMap::new(), website_config: Lww::new(website), cors_config: Lww::new(None), + quotas: Lww::new(Default::default()), }), }) .await?; diff --git a/src/model/prev/mod.rs b/src/model/prev/mod.rs new file mode 100644 index 00000000..68bb1502 --- /dev/null +++ b/src/model/prev/mod.rs @@ -0,0 +1 @@ +pub(crate) mod v051; diff --git a/src/model/prev/v051/bucket_table.rs b/src/model/prev/v051/bucket_table.rs new file mode 100644 index 00000000..628a49dd --- /dev/null +++ b/src/model/prev/v051/bucket_table.rs @@ -0,0 +1,63 @@ +use serde::{Deserialize, Serialize}; + +use garage_table::crdt::Crdt; +use garage_table::*; + +use super::key_table::PermissionSet; + +/// A bucket is a collection of objects +/// +/// Its parameters are not directly accessible as: +/// - It must be possible to merge paramaters, hence the use of a LWW CRDT. +/// - A bucket has 2 states, Present or Deleted and parameters make sense only if present. +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct Bucket { + /// Name of the bucket + pub name: String, + /// State, and configuration if not deleted, of the bucket + pub state: crdt::Lww<BucketState>, +} + +/// State of a bucket +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub enum BucketState { + /// The bucket is deleted + Deleted, + /// The bucket exists + Present(BucketParams), +} + +impl Crdt for BucketState { + fn merge(&mut self, o: &Self) { + match o { + BucketState::Deleted => *self = BucketState::Deleted, + BucketState::Present(other_params) => { + if let BucketState::Present(params) = self { + params.merge(other_params); + } + } + } + } +} + +/// Configuration for a bucket +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct BucketParams { + /// Map of key with access to the bucket, and what kind of access they give + pub authorized_keys: crdt::LwwMap<String, PermissionSet>, + /// Is the bucket served as http + pub website: crdt::Lww<bool>, +} + +impl Crdt for BucketParams { + fn merge(&mut self, o: &Self) { + self.authorized_keys.merge(&o.authorized_keys); + self.website.merge(&o.website); + } +} + +impl Crdt for Bucket { + fn merge(&mut self, other: &Self) { + self.state.merge(&other.state); + } +} diff --git a/src/model/prev/v051/key_table.rs b/src/model/prev/v051/key_table.rs new file mode 100644 index 00000000..37516b1c --- /dev/null +++ b/src/model/prev/v051/key_table.rs @@ -0,0 +1,50 @@ +use serde::{Deserialize, Serialize}; + +use garage_table::crdt::*; +use garage_table::*; + +/// An api key +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct Key { + /// The id of the key (immutable), used as partition key + pub key_id: String, + + /// The secret_key associated + pub secret_key: String, + + /// Name for the key + pub name: crdt::Lww<String>, + + /// Is the key deleted + pub deleted: crdt::Bool, + + /// Buckets in which the key is authorized. Empty if `Key` is deleted + // CRDT interaction: deleted implies authorized_buckets is empty + pub authorized_buckets: crdt::LwwMap<String, PermissionSet>, +} + +/// Permission given to a key in a bucket +#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct PermissionSet { + /// The key can be used to read the bucket + pub allow_read: bool, + /// The key can be used to write in the bucket + pub allow_write: bool, +} + +impl AutoCrdt for PermissionSet { + const WARN_IF_DIFFERENT: bool = true; +} + +impl Crdt for Key { + fn merge(&mut self, other: &Self) { + self.name.merge(&other.name); + self.deleted.merge(&other.deleted); + + if self.deleted.get() { + self.authorized_buckets.clear(); + } else { + self.authorized_buckets.merge(&other.authorized_buckets); + } + } +} diff --git a/src/model/prev/v051/mod.rs b/src/model/prev/v051/mod.rs new file mode 100644 index 00000000..7a954752 --- /dev/null +++ b/src/model/prev/v051/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod bucket_table; +pub(crate) mod key_table; +pub(crate) mod object_table; +pub(crate) mod version_table; diff --git a/src/model/prev/v051/object_table.rs b/src/model/prev/v051/object_table.rs new file mode 100644 index 00000000..e79e5787 --- /dev/null +++ b/src/model/prev/v051/object_table.rs @@ -0,0 +1,149 @@ +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +use garage_util::data::*; + +use garage_table::crdt::*; + +/// An object +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct Object { + /// The bucket in which the object is stored, used as partition key + pub bucket: String, + + /// The key at which the object is stored in its bucket, used as sorting key + pub key: String, + + /// The list of currenty stored versions of the object + versions: Vec<ObjectVersion>, +} + +impl Object { + /// Get a list of currently stored versions of `Object` + pub fn versions(&self) -> &[ObjectVersion] { + &self.versions[..] + } +} + +/// Informations about a version of an object +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct ObjectVersion { + /// Id of the version + pub uuid: Uuid, + /// Timestamp of when the object was created + pub timestamp: u64, + /// State of the version + pub state: ObjectVersionState, +} + +/// State of an object version +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub enum ObjectVersionState { + /// The version is being received + Uploading(ObjectVersionHeaders), + /// The version is fully received + Complete(ObjectVersionData), + /// The version uploaded containded errors or the upload was explicitly aborted + Aborted, +} + +impl Crdt for ObjectVersionState { + fn merge(&mut self, other: &Self) { + use ObjectVersionState::*; + match other { + Aborted => { + *self = Aborted; + } + Complete(b) => match self { + Aborted => {} + Complete(a) => { + a.merge(b); + } + Uploading(_) => { + *self = Complete(b.clone()); + } + }, + Uploading(_) => {} + } + } +} + +/// Data stored in object version +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)] +pub enum ObjectVersionData { + /// The object was deleted, this Version is a tombstone to mark it as such + DeleteMarker, + /// The object is short, it's stored inlined + Inline(ObjectVersionMeta, #[serde(with = "serde_bytes")] Vec<u8>), + /// The object is not short, Hash of first block is stored here, next segments hashes are + /// stored in the version table + FirstBlock(ObjectVersionMeta, Hash), +} + +impl AutoCrdt for ObjectVersionData { + const WARN_IF_DIFFERENT: bool = true; +} + +/// Metadata about the object version +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)] +pub struct ObjectVersionMeta { + /// Headers to send to the client + pub headers: ObjectVersionHeaders, + /// Size of the object + pub size: u64, + /// etag of the object + pub etag: String, +} + +/// Additional headers for an object +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)] +pub struct ObjectVersionHeaders { + /// Content type of the object + pub content_type: String, + /// Any other http headers to send + pub other: BTreeMap<String, String>, +} + +impl ObjectVersion { + fn cmp_key(&self) -> (u64, Uuid) { + (self.timestamp, self.uuid) + } + + /// Is the object version completely received + pub fn is_complete(&self) -> bool { + matches!(self.state, ObjectVersionState::Complete(_)) + } +} + +impl Crdt for Object { + fn merge(&mut self, other: &Self) { + // Merge versions from other into here + for other_v in other.versions.iter() { + match self + .versions + .binary_search_by(|v| v.cmp_key().cmp(&other_v.cmp_key())) + { + Ok(i) => { + self.versions[i].state.merge(&other_v.state); + } + Err(i) => { + self.versions.insert(i, other_v.clone()); + } + } + } + + // Remove versions which are obsolete, i.e. those that come + // before the last version which .is_complete(). + let last_complete = self + .versions + .iter() + .enumerate() + .rev() + .find(|(_, v)| v.is_complete()) + .map(|(vi, _)| vi); + + if let Some(last_vi) = last_complete { + self.versions = self.versions.drain(last_vi..).collect::<Vec<_>>(); + } + } +} diff --git a/src/model/prev/v051/version_table.rs b/src/model/prev/v051/version_table.rs new file mode 100644 index 00000000..c11c62d5 --- /dev/null +++ b/src/model/prev/v051/version_table.rs @@ -0,0 +1,79 @@ +use serde::{Deserialize, Serialize}; + +use garage_util::data::*; + +use garage_table::crdt::*; +use garage_table::*; + +/// A version of an object +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub struct Version { + /// UUID of the version, used as partition key + pub uuid: Uuid, + + // Actual data: the blocks for this version + // In the case of a multipart upload, also store the etags + // of individual parts and check them when doing CompleteMultipartUpload + /// Is this version deleted + pub deleted: crdt::Bool, + /// list of blocks of data composing the version + pub blocks: crdt::Map<VersionBlockKey, VersionBlock>, + /// Etag of each part in case of a multipart upload, empty otherwise + pub parts_etags: crdt::Map<u64, String>, + + // Back link to bucket+key so that we can figure if + // this was deleted later on + /// Bucket in which the related object is stored + pub bucket: String, + /// Key in which the related object is stored + pub key: String, +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] +pub struct VersionBlockKey { + /// Number of the part + pub part_number: u64, + /// Offset of this sub-segment in its part + pub offset: u64, +} + +impl Ord for VersionBlockKey { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.part_number + .cmp(&other.part_number) + .then(self.offset.cmp(&other.offset)) + } +} + +impl PartialOrd for VersionBlockKey { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +/// Informations about a single block +#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize)] +pub struct VersionBlock { + /// Blake2 sum of the block + pub hash: Hash, + /// Size of the block + pub size: u64, +} + +impl AutoCrdt for VersionBlock { + const WARN_IF_DIFFERENT: bool = true; +} + +impl Crdt for Version { + fn merge(&mut self, other: &Self) { + self.deleted.merge(&other.deleted); + + if self.deleted.get() { + self.blocks.clear(); + self.parts_etags.clear(); + } else { + self.blocks.merge(&other.blocks); + self.parts_etags.merge(&other.parts_etags); + } + } +} diff --git a/src/model/block_ref_table.rs b/src/model/s3/block_ref_table.rs index b6945403..c7017409 100644 --- a/src/model/block_ref_table.rs +++ b/src/model/s3/block_ref_table.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; +use garage_db as db; + use garage_util::data::*; use garage_table::crdt::Crdt; @@ -8,7 +10,7 @@ use garage_table::*; use garage_block::manager::*; -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct BlockRef { /// Hash (blake2 sum) of the block, used as partition key pub block: Hash, @@ -51,21 +53,22 @@ impl TableSchema for BlockRefTable { type E = BlockRef; type Filter = DeletedFilter; - fn updated(&self, old: Option<Self::E>, new: Option<Self::E>) { - #[allow(clippy::or_fun_call)] - let block = &old.as_ref().or(new.as_ref()).unwrap().block; - let was_before = old.as_ref().map(|x| !x.deleted.get()).unwrap_or(false); - let is_after = new.as_ref().map(|x| !x.deleted.get()).unwrap_or(false); + fn updated( + &self, + tx: &mut db::Transaction, + old: Option<&Self::E>, + new: Option<&Self::E>, + ) -> db::TxOpResult<()> { + let block = old.or(new).unwrap().block; + let was_before = old.map(|x| !x.deleted.get()).unwrap_or(false); + let is_after = new.map(|x| !x.deleted.get()).unwrap_or(false); if is_after && !was_before { - if let Err(e) = self.block_manager.block_incref(block) { - warn!("block_incref failed for block {:?}: {}", block, e); - } + self.block_manager.block_incref(tx, block)?; } if was_before && !is_after { - if let Err(e) = self.block_manager.block_decref(block) { - warn!("block_decref failed for block {:?}: {}", block, e); - } + self.block_manager.block_decref(tx, block)?; } + Ok(()) } fn matches_filter(entry: &Self::E, filter: &Self::Filter) -> bool { diff --git a/src/model/s3/mod.rs b/src/model/s3/mod.rs new file mode 100644 index 00000000..4e94337d --- /dev/null +++ b/src/model/s3/mod.rs @@ -0,0 +1,3 @@ +pub mod block_ref_table; +pub mod object_table; +pub mod version_table; diff --git a/src/model/object_table.rs b/src/model/s3/object_table.rs index da53878e..26ff57f6 100644 --- a/src/model/object_table.rs +++ b/src/model/s3/object_table.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::sync::Arc; +use garage_db as db; + use garage_util::background::BackgroundRunner; use garage_util::data::*; @@ -9,12 +11,17 @@ use garage_table::crdt::*; use garage_table::replication::TableShardedReplication; use garage_table::*; -use crate::version_table::*; +use crate::index_counter::*; +use crate::s3::version_table::*; + +use crate::prev::v051::object_table as old; -use garage_model_050::object_table as old; +pub const OBJECTS: &str = "objects"; +pub const UNFINISHED_UPLOADS: &str = "unfinished_uploads"; +pub const BYTES: &str = "bytes"; /// An object -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct Object { /// The bucket in which the object is stored, used as partition key pub bucket_id: Uuid, @@ -63,7 +70,7 @@ impl Object { } /// Informations about a version of an object -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct ObjectVersion { /// Id of the version pub uuid: Uuid, @@ -74,7 +81,7 @@ pub struct ObjectVersion { } /// State of an object version -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub enum ObjectVersionState { /// The version is being received Uploading(ObjectVersionHeaders), @@ -216,6 +223,7 @@ impl Crdt for Object { pub struct ObjectTable { pub background: Arc<BackgroundRunner>, pub version_table: Arc<Table<VersionTable, TableShardedReplication>>, + pub object_counter_table: Arc<IndexCounter<Object>>, } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] @@ -232,8 +240,26 @@ impl TableSchema for ObjectTable { type E = Object; type Filter = ObjectFilter; - fn updated(&self, old: Option<Self::E>, new: Option<Self::E>) { + fn updated( + &self, + tx: &mut db::Transaction, + old: Option<&Self::E>, + new: Option<&Self::E>, + ) -> db::TxOpResult<()> { + // 1. Count + let counter_res = self.object_counter_table.count(tx, old, new); + if let Err(e) = db::unabort(counter_res)? { + error!( + "Unable to update object counter: {}. Index values will be wrong!", + e + ); + } + + // 2. Spawn threads that propagates deletions to version table let version_table = self.version_table.clone(); + let old = old.cloned(); + let new = new.cloned(); + self.background.spawn(async move { if let (Some(old_v), Some(new_v)) = (old, new) { // Propagate deletion of old versions @@ -256,7 +282,8 @@ impl TableSchema for ObjectTable { } } Ok(()) - }) + }); + Ok(()) } fn matches_filter(entry: &Self::E, filter: &Self::Filter) -> bool { @@ -272,6 +299,49 @@ impl TableSchema for ObjectTable { } } +impl CountedItem for Object { + const COUNTER_TABLE_NAME: &'static str = "bucket_object_counter"; + + // Partition key = bucket id + type CP = Uuid; + // Sort key = nothing + type CS = EmptyKey; + + fn counter_partition_key(&self) -> &Uuid { + &self.bucket_id + } + fn counter_sort_key(&self) -> &EmptyKey { + &EmptyKey + } + + fn counts(&self) -> Vec<(&'static str, i64)> { + let versions = self.versions(); + let n_objects = if versions.iter().any(|v| v.is_data()) { + 1 + } else { + 0 + }; + let n_unfinished_uploads = versions + .iter() + .filter(|v| matches!(v.state, ObjectVersionState::Uploading(_))) + .count(); + let n_bytes = versions + .iter() + .map(|v| match &v.state { + ObjectVersionState::Complete(ObjectVersionData::Inline(meta, _)) + | ObjectVersionState::Complete(ObjectVersionData::FirstBlock(meta, _)) => meta.size, + _ => 0, + }) + .sum::<u64>(); + + vec![ + (OBJECTS, n_objects), + (UNFINISHED_UPLOADS, n_unfinished_uploads as i64), + (BYTES, n_bytes as i64), + ] + } +} + // vvvvvvvv migration code, stupid stuff vvvvvvvvvvvv // (we just want to change bucket into bucket_id by hashing it) diff --git a/src/model/version_table.rs b/src/model/s3/version_table.rs index 839b1f4f..6bc2ecd1 100644 --- a/src/model/version_table.rs +++ b/src/model/s3/version_table.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; +use garage_db as db; + use garage_util::background::BackgroundRunner; use garage_util::data::*; @@ -8,12 +10,12 @@ use garage_table::crdt::*; use garage_table::replication::TableShardedReplication; use garage_table::*; -use crate::block_ref_table::*; +use crate::s3::block_ref_table::*; -use garage_model_050::version_table as old; +use crate::prev::v051::version_table as old; /// A version of an object -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct Version { /// UUID of the version, used as partition key pub uuid: Uuid, @@ -137,8 +139,16 @@ impl TableSchema for VersionTable { type E = Version; type Filter = DeletedFilter; - fn updated(&self, old: Option<Self::E>, new: Option<Self::E>) { + fn updated( + &self, + _tx: &mut db::Transaction, + old: Option<&Self::E>, + new: Option<&Self::E>, + ) -> db::TxOpResult<()> { let block_ref_table = self.block_ref_table.clone(); + let old = old.cloned(); + let new = new.cloned(); + self.background.spawn(async move { if let (Some(old_v), Some(new_v)) = (old, new) { // Propagate deletion of version blocks @@ -157,7 +167,9 @@ impl TableSchema for VersionTable { } } Ok(()) - }) + }); + + Ok(()) } fn matches_filter(entry: &Self::E, filter: &Self::Filter) -> bool { diff --git a/src/rpc/Cargo.toml b/src/rpc/Cargo.toml index 654c1dc6..5bb6aae0 100644 --- a/src/rpc/Cargo.toml +++ b/src/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "garage_rpc" -version = "0.7.0" +version = "0.8.0" authors = ["Alex Auvolat <alex@adnab.me>"] edition = "2018" license = "AGPL-3.0" @@ -14,8 +14,7 @@ path = "lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -garage_util = { version = "0.7.0", path = "../util" } -garage_admin = { version = "0.7.0", path = "../admin" } +garage_util = { version = "0.8.0", path = "../util" } arc-swap = "1.0" bytes = "1.0" @@ -47,11 +46,11 @@ tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi tokio-stream = { version = "0.1", features = ["net"] } opentelemetry = "0.17" -#netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" } -#netapp = { version = "0.4", path = "../../../netapp", features = ["telemetry"] } -netapp = { version = "0.4.2", features = ["telemetry"] } +netapp = { version = "0.5.2", features = ["telemetry"] } hyper = { version = "0.14", features = ["client", "http1", "runtime", "tcp"] } + [features] kubernetes-discovery = [ "kube", "k8s-openapi", "openssl", "schemars" ] +system-libs = [ "sodiumoxide/use-pkg-config" ] diff --git a/src/rpc/kubernetes.rs b/src/rpc/kubernetes.rs index 939a0eed..197245aa 100644 --- a/src/rpc/kubernetes.rs +++ b/src/rpc/kubernetes.rs @@ -56,7 +56,7 @@ pub async fn get_kubernetes_nodes( let mut ret = Vec::with_capacity(nodes.items.len()); for node in nodes { - println!("Found Pod: {:?}", node.metadata.name); + info!("Found Pod: {:?}", node.metadata.name); let pubkey = &node .metadata diff --git a/src/rpc/layout.rs b/src/rpc/layout.rs index a878f19c..16d573c7 100644 --- a/src/rpc/layout.rs +++ b/src/rpc/layout.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use garage_util::crdt::{AutoCrdt, Crdt, LwwMap}; use garage_util::data::*; +use garage_util::error::*; use crate::graph_algo::*; @@ -144,6 +145,61 @@ impl ClusterLayout { } } + pub fn apply_staged_changes(mut self, version: Option<u64>) -> Result<Self, Error> { + match version { + None => { + let error = r#" +Please pass the new layout version number to ensure that you are writing the correct version of the cluster layout. +To know the correct value of the new layout version, invoke `garage layout show` and review the proposed changes. + "#; + return Err(Error::Message(error.into())); + } + Some(v) => { + if v != self.version + 1 { + return Err(Error::Message("Invalid new layout version".into())); + } + } + } + + self.roles.merge(&self.staging); + self.roles.retain(|(_, _, v)| v.0.is_some()); + + if !self.calculate_partition_assignation() { + return Err(Error::Message("Could not calculate new assignation of partitions to nodes. This can happen if there are less nodes than the desired number of copies of your data (see the replication_mode configuration parameter).".into())); + } + + self.staging.clear(); + self.staging_hash = blake2sum(&rmp_to_vec_all_named(&self.staging).unwrap()[..]); + + self.version += 1; + + Ok(self) + } + + pub fn revert_staged_changes(mut self, version: Option<u64>) -> Result<Self, Error> { + match version { + None => { + let error = r#" +Please pass the new layout version number to ensure that you are writing the correct version of the cluster layout. +To know the correct value of the new layout version, invoke `garage layout show` and review the proposed changes. + "#; + return Err(Error::Message(error.into())); + } + Some(v) => { + if v != self.version + 1 { + return Err(Error::Message("Invalid new layout version".into())); + } + } + } + + self.staging.clear(); + self.staging_hash = blake2sum(&rmp_to_vec_all_named(&self.staging).unwrap()[..]); + + self.version += 1; + + Ok(self) + } + /// Returns a list of IDs of nodes that currently have /// a role in the cluster pub fn node_ids(&self) -> &[Uuid] { diff --git a/src/rpc/metrics.rs b/src/rpc/metrics.rs index c900518c..61f8fa79 100644 --- a/src/rpc/metrics.rs +++ b/src/rpc/metrics.rs @@ -1,31 +1,18 @@ -use std::sync::Arc; - use opentelemetry::{global, metrics::*}; -use tokio::sync::Semaphore; /// TableMetrics reference all counter used for metrics pub struct RpcMetrics { - pub(crate) _rpc_available_permits: ValueObserver<u64>, - pub(crate) rpc_counter: Counter<u64>, pub(crate) rpc_timeout_counter: Counter<u64>, pub(crate) rpc_netapp_error_counter: Counter<u64>, pub(crate) rpc_garage_error_counter: Counter<u64>, pub(crate) rpc_duration: ValueRecorder<f64>, - pub(crate) rpc_queueing_time: ValueRecorder<f64>, } impl RpcMetrics { - pub fn new(sem: Arc<Semaphore>) -> Self { + pub fn new() -> Self { let meter = global::meter("garage_rpc"); RpcMetrics { - _rpc_available_permits: meter - .u64_value_observer("rpc.available_permits", move |observer| { - observer.observe(sem.available_permits() as u64, &[]) - }) - .with_description("Number of available RPC permits") - .init(), - rpc_counter: meter .u64_counter("rpc.request_counter") .with_description("Number of RPC requests emitted") @@ -46,10 +33,6 @@ impl RpcMetrics { .f64_value_recorder("rpc.duration") .with_description("Duration of RPCs") .init(), - rpc_queueing_time: meter - .f64_value_recorder("rpc.queueing_time") - .with_description("Time RPC requests were queued for before being sent") - .init(), } } } diff --git a/src/rpc/rpc_helper.rs b/src/rpc/rpc_helper.rs index 34717d3b..949aced6 100644 --- a/src/rpc/rpc_helper.rs +++ b/src/rpc/rpc_helper.rs @@ -7,7 +7,7 @@ use futures::stream::futures_unordered::FuturesUnordered; use futures::stream::StreamExt; use futures_util::future::FutureExt; use tokio::select; -use tokio::sync::{watch, Semaphore}; +use tokio::sync::watch; use opentelemetry::KeyValue; use opentelemetry::{ @@ -15,10 +15,14 @@ use opentelemetry::{ Context, }; -pub use netapp::endpoint::{Endpoint, EndpointHandler, Message as Rpc}; +pub use netapp::endpoint::{Endpoint, EndpointHandler, StreamingEndpointHandler}; +use netapp::message::IntoReq; +pub use netapp::message::{ + Message as Rpc, OrderTag, Req, RequestPriority, Resp, PRIO_BACKGROUND, PRIO_HIGH, PRIO_NORMAL, + PRIO_SECONDARY, +}; use netapp::peering::fullmesh::FullMeshPeeringStrategy; -pub use netapp::proto::*; -pub use netapp::{NetApp, NodeID}; +pub use netapp::{self, NetApp, NodeID}; use garage_util::background::BackgroundRunner; use garage_util::data::*; @@ -28,34 +32,37 @@ use garage_util::metrics::RecordDuration; use crate::metrics::RpcMetrics; use crate::ring::Ring; -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); - -// Try to never have more than 200MB of outgoing requests -// buffered at the same time. Other requests are queued until -// space is freed. -const REQUEST_BUFFER_SIZE: usize = 200 * 1024 * 1024; +// Default RPC timeout = 5 minutes +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(300); /// Strategy to apply when making RPC #[derive(Copy, Clone)] pub struct RequestStrategy { - /// Max time to wait for reponse - pub rs_timeout: Duration, /// Min number of response to consider the request successful pub rs_quorum: Option<usize>, /// Should requests be dropped after enough response are received pub rs_interrupt_after_quorum: bool, /// Request priority pub rs_priority: RequestPriority, + /// Custom timeout for this request + rs_timeout: Timeout, +} + +#[derive(Copy, Clone)] +enum Timeout { + None, + Default, + Custom(Duration), } impl RequestStrategy { /// Create a RequestStrategy with default timeout and not interrupting when quorum reached pub fn with_priority(prio: RequestPriority) -> Self { RequestStrategy { - rs_timeout: DEFAULT_TIMEOUT, rs_quorum: None, rs_interrupt_after_quorum: false, rs_priority: prio, + rs_timeout: Timeout::Default, } } /// Set quorum to be reached for request @@ -63,17 +70,22 @@ impl RequestStrategy { self.rs_quorum = Some(quorum); self } - /// Set timeout of the strategy - pub fn with_timeout(mut self, timeout: Duration) -> Self { - self.rs_timeout = timeout; - self - } /// Set if requests can be dropped after quorum has been reached /// In general true for read requests, and false for write pub fn interrupt_after_quorum(mut self, interrupt: bool) -> Self { self.rs_interrupt_after_quorum = interrupt; self } + /// Deactivate timeout for this request + pub fn without_timeout(mut self) -> Self { + self.rs_timeout = Timeout::None; + self + } + /// Set custom timeout for this request + pub fn with_custom_timeout(mut self, timeout: Duration) -> Self { + self.rs_timeout = Timeout::Custom(timeout); + self + } } #[derive(Clone)] @@ -84,8 +96,8 @@ struct RpcHelperInner { fullmesh: Arc<FullMeshPeeringStrategy>, background: Arc<BackgroundRunner>, ring: watch::Receiver<Arc<Ring>>, - request_buffer_semaphore: Arc<Semaphore>, metrics: RpcMetrics, + rpc_timeout: Duration, } impl RpcHelper { @@ -94,45 +106,35 @@ impl RpcHelper { fullmesh: Arc<FullMeshPeeringStrategy>, background: Arc<BackgroundRunner>, ring: watch::Receiver<Arc<Ring>>, + rpc_timeout: Option<Duration>, ) -> Self { - let sem = Arc::new(Semaphore::new(REQUEST_BUFFER_SIZE)); - - let metrics = RpcMetrics::new(sem.clone()); + let metrics = RpcMetrics::new(); Self(Arc::new(RpcHelperInner { our_node_id, fullmesh, background, ring, - request_buffer_semaphore: sem, metrics, + rpc_timeout: rpc_timeout.unwrap_or(DEFAULT_TIMEOUT), })) } - pub async fn call<M, H, S>( - &self, - endpoint: &Endpoint<M, H>, - to: Uuid, - msg: M, - strat: RequestStrategy, - ) -> Result<S, Error> - where - M: Rpc<Response = Result<S, Error>>, - H: EndpointHandler<M>, - { - self.call_arc(endpoint, to, Arc::new(msg), strat).await + pub fn rpc_timeout(&self) -> Duration { + self.0.rpc_timeout } - pub async fn call_arc<M, H, S>( + pub async fn call<M, N, H, S>( &self, endpoint: &Endpoint<M, H>, to: Uuid, - msg: Arc<M>, + msg: N, strat: RequestStrategy, ) -> Result<S, Error> where M: Rpc<Response = Result<S, Error>>, - H: EndpointHandler<M>, + N: IntoReq<M> + Send, + H: StreamingEndpointHandler<M>, { let metric_tags = [ KeyValue::new("rpc_endpoint", endpoint.path().to_string()), @@ -140,29 +142,27 @@ impl RpcHelper { KeyValue::new("to", format!("{:?}", to)), ]; - let msg_size = rmp_to_vec_all_named(&msg)?.len() as u32; - let permit = self - .0 - .request_buffer_semaphore - .acquire_many(msg_size) - .record_duration(&self.0.metrics.rpc_queueing_time, &metric_tags) - .await?; - self.0.metrics.rpc_counter.add(1, &metric_tags); let node_id = to.into(); let rpc_call = endpoint - .call(&node_id, msg, strat.rs_priority) + .call_streaming(&node_id, msg, strat.rs_priority) .record_duration(&self.0.metrics.rpc_duration, &metric_tags); + let timeout = async { + match strat.rs_timeout { + Timeout::None => futures::future::pending().await, + Timeout::Default => tokio::time::sleep(self.0.rpc_timeout).await, + Timeout::Custom(t) => tokio::time::sleep(t).await, + } + }; + select! { res = rpc_call => { - drop(permit); - if res.is_err() { self.0.metrics.rpc_netapp_error_counter.add(1, &metric_tags); } - let res = res?; + let res = res?.into_msg(); if res.is_err() { self.0.metrics.rpc_garage_error_counter.add(1, &metric_tags); @@ -170,46 +170,49 @@ impl RpcHelper { Ok(res?) } - _ = tokio::time::sleep(strat.rs_timeout) => { - drop(permit); + () = timeout => { self.0.metrics.rpc_timeout_counter.add(1, &metric_tags); Err(Error::Timeout) } } } - pub async fn call_many<M, H, S>( + pub async fn call_many<M, N, H, S>( &self, endpoint: &Endpoint<M, H>, to: &[Uuid], - msg: M, + msg: N, strat: RequestStrategy, - ) -> Vec<(Uuid, Result<S, Error>)> + ) -> Result<Vec<(Uuid, Result<S, Error>)>, Error> where M: Rpc<Response = Result<S, Error>>, - H: EndpointHandler<M>, + N: IntoReq<M>, + H: StreamingEndpointHandler<M>, { - let msg = Arc::new(msg); + let msg = msg.into_req().map_err(netapp::error::Error::from)?; + let resps = join_all( to.iter() - .map(|to| self.call_arc(endpoint, *to, msg.clone(), strat)), + .map(|to| self.call(endpoint, *to, msg.clone(), strat)), ) .await; - to.iter() + Ok(to + .iter() .cloned() .zip(resps.into_iter()) - .collect::<Vec<_>>() + .collect::<Vec<_>>()) } - pub async fn broadcast<M, H, S>( + pub async fn broadcast<M, N, H, S>( &self, endpoint: &Endpoint<M, H>, - msg: M, + msg: N, strat: RequestStrategy, - ) -> Vec<(Uuid, Result<S, Error>)> + ) -> Result<Vec<(Uuid, Result<S, Error>)>, Error> where M: Rpc<Response = Result<S, Error>>, - H: EndpointHandler<M>, + N: IntoReq<M>, + H: StreamingEndpointHandler<M>, { let to = self .0 @@ -223,16 +226,17 @@ impl RpcHelper { /// Make a RPC call to multiple servers, returning either a Vec of responses, /// or an error if quorum could not be reached due to too many errors - pub async fn try_call_many<M, H, S>( + pub async fn try_call_many<M, N, H, S>( &self, endpoint: &Arc<Endpoint<M, H>>, to: &[Uuid], - msg: M, + msg: N, strategy: RequestStrategy, ) -> Result<Vec<S>, Error> where M: Rpc<Response = Result<S, Error>> + 'static, - H: EndpointHandler<M> + 'static, + N: IntoReq<M>, + H: StreamingEndpointHandler<M> + 'static, S: Send + 'static, { let quorum = strategy.rs_quorum.unwrap_or(to.len()); @@ -262,20 +266,21 @@ impl RpcHelper { .await } - async fn try_call_many_internal<M, H, S>( + async fn try_call_many_internal<M, N, H, S>( &self, endpoint: &Arc<Endpoint<M, H>>, to: &[Uuid], - msg: M, + msg: N, strategy: RequestStrategy, quorum: usize, ) -> Result<Vec<S>, Error> where M: Rpc<Response = Result<S, Error>> + 'static, - H: EndpointHandler<M> + 'static, + N: IntoReq<M>, + H: StreamingEndpointHandler<M> + 'static, S: Send + 'static, { - let msg = Arc::new(msg); + let msg = msg.into_req().map_err(netapp::error::Error::from)?; // Build future for each request // They are not started now: they are added below in a FuturesUnordered @@ -285,7 +290,7 @@ impl RpcHelper { let msg = msg.clone(); let endpoint2 = endpoint.clone(); (to, async move { - self2.call_arc(&endpoint2, to, msg, strategy).await + self2.call(&endpoint2, to, msg, strategy).await }) }); @@ -299,47 +304,19 @@ impl RpcHelper { // to reach a quorum, priorizing nodes with the lowest latency. // When there are errors, we start new requests to compensate. - // Retrieve some status variables that we will use to sort requests - let peer_list = self.0.fullmesh.get_peer_list(); - let ring: Arc<Ring> = self.0.ring.borrow().clone(); - let our_zone = match ring.layout.node_role(&self.0.our_node_id) { - Some(pc) => &pc.zone, - None => "", - }; - - // Augment requests with some information used to sort them. - // The tuples are as follows: - // (is another node?, is another zone?, latency, node ID, request future) - // We store all of these tuples in a vec that we can sort. - // By sorting this vec, we priorize ourself, then nodes in the same zone, - // and within a same zone we priorize nodes with the lowest latency. - let mut requests = requests - .map(|(to, fut)| { - let peer_zone = match ring.layout.node_role(&to) { - Some(pc) => &pc.zone, - None => "", - }; - let peer_avg_ping = peer_list - .iter() - .find(|x| x.id.as_ref() == to.as_slice()) - .and_then(|pi| pi.avg_ping) - .unwrap_or_else(|| Duration::from_secs(1)); - ( - to != self.0.our_node_id, - peer_zone != our_zone, - peer_avg_ping, - to, - fut, - ) - }) + // Reorder requests to priorize closeness / low latency + let request_order = self.request_order(to); + let mut ord_requests = vec![(); request_order.len()] + .into_iter() + .map(|_| None) .collect::<Vec<_>>(); - - // Sort requests by (priorize ourself, priorize same zone, priorize low latency) - requests - .sort_by_key(|(diffnode, diffzone, ping, _to, _fut)| (*diffnode, *diffzone, *ping)); + for (to, fut) in requests { + let i = request_order.iter().position(|x| *x == to).unwrap(); + ord_requests[i] = Some((to, fut)); + } // Make an iterator to take requests in their sorted order - let mut requests = requests.into_iter(); + let mut requests = ord_requests.into_iter().map(Option::unwrap); // resp_stream will contain all of the requests that are currently in flight. // (for the moment none, they will be added in the loop below) @@ -350,7 +327,7 @@ impl RpcHelper { // If the current set of requests that are running is not enough to possibly // reach quorum, start some new requests. while successes.len() + resp_stream.len() < quorum { - if let Some((_, _, _, req_to, fut)) = requests.next() { + if let Some((req_to, fut)) = requests.next() { let tracer = opentelemetry::global::tracer("garage"); let span = tracer.start(format!("RPC to {:?}", req_to)); resp_stream.push(tokio::spawn( @@ -420,4 +397,49 @@ impl RpcHelper { Err(Error::Quorum(quorum, successes.len(), to.len(), errors)) } } + + pub fn request_order(&self, nodes: &[Uuid]) -> Vec<Uuid> { + // Retrieve some status variables that we will use to sort requests + let peer_list = self.0.fullmesh.get_peer_list(); + let ring: Arc<Ring> = self.0.ring.borrow().clone(); + let our_zone = match ring.layout.node_role(&self.0.our_node_id) { + Some(pc) => &pc.zone, + None => "", + }; + + // Augment requests with some information used to sort them. + // The tuples are as follows: + // (is another node?, is another zone?, latency, node ID, request future) + // We store all of these tuples in a vec that we can sort. + // By sorting this vec, we priorize ourself, then nodes in the same zone, + // and within a same zone we priorize nodes with the lowest latency. + let mut nodes = nodes + .iter() + .map(|to| { + let peer_zone = match ring.layout.node_role(to) { + Some(pc) => &pc.zone, + None => "", + }; + let peer_avg_ping = peer_list + .iter() + .find(|x| x.id.as_ref() == to.as_slice()) + .and_then(|pi| pi.avg_ping) + .unwrap_or_else(|| Duration::from_secs(10)); + ( + *to != self.0.our_node_id, + peer_zone != our_zone, + peer_avg_ping, + *to, + ) + }) + .collect::<Vec<_>>(); + + // Sort requests by (priorize ourself, priorize same zone, priorize low latency) + nodes.sort_by_key(|(diffnode, diffzone, ping, _to)| (*diffnode, *diffzone, *ping)); + + nodes + .into_iter() + .map(|(_, _, _, to)| to) + .collect::<Vec<_>>() + } } diff --git a/src/rpc/system.rs b/src/rpc/system.rs index 34031b10..7eb25195 100644 --- a/src/rpc/system.rs +++ b/src/rpc/system.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::io::{Read, Write}; use std::net::{IpAddr, SocketAddr}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; @@ -16,9 +16,9 @@ use tokio::sync::watch; use tokio::sync::Mutex; use netapp::endpoint::{Endpoint, EndpointHandler}; +use netapp::message::*; use netapp::peering::fullmesh::FullMeshPeeringStrategy; -use netapp::proto::*; -use netapp::util::parse_and_resolve_peer_addr; +use netapp::util::parse_and_resolve_peer_addr_async; use netapp::{NetApp, NetworkKey, NodeID, NodeKey}; use garage_util::background::BackgroundRunner; @@ -37,10 +37,11 @@ use crate::rpc_helper::*; const DISCOVERY_INTERVAL: Duration = Duration::from_secs(60); const STATUS_EXCHANGE_INTERVAL: Duration = Duration::from_secs(10); -const PING_TIMEOUT: Duration = Duration::from_secs(2); -/// Version tag used for version check upon Netapp connection -pub const GARAGE_VERSION_TAG: u64 = 0x6761726167650007; // garage 0x0007 +/// Version tag used for version check upon Netapp connection. +/// Cluster nodes with different version tags are deemed +/// incompatible and will refuse to connect. +pub const GARAGE_VERSION_TAG: u64 = 0x6761726167650008; // garage 0x0008 /// RPC endpoint used for calls related to membership pub const SYSTEM_RPC_PATH: &str = "garage_rpc/membership.rs/SystemRpc"; @@ -90,7 +91,7 @@ pub struct System { rpc_listen_addr: SocketAddr, rpc_public_addr: Option<SocketAddr>, - bootstrap_peers: Vec<(NodeID, SocketAddr)>, + bootstrap_peers: Vec<String>, consul_discovery: Option<ConsulDiscoveryParam>, #[cfg(feature = "kubernetes-discovery")] @@ -104,6 +105,9 @@ pub struct System { /// The job runner of this node pub background: Arc<BackgroundRunner>, + + /// Path to metadata directory + pub metadata_dir: PathBuf, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -194,7 +198,7 @@ impl System { replication_factor: usize, zone_redundancy: usize, config: &Config, - ) -> Arc<Self> { + ) -> Result<Arc<Self>, Error> { let node_key = gen_node_key(&config.metadata_dir).expect("Unable to read or generate node ID"); info!( @@ -202,11 +206,21 @@ impl System { hex::encode(&node_key.public_key()[..8]) ); - let persist_cluster_layout = Persister::new(&config.metadata_dir, "cluster_layout"); + let persist_cluster_layout: Persister<ClusterLayout> = + Persister::new(&config.metadata_dir, "cluster_layout"); let persist_peer_list = Persister::new(&config.metadata_dir, "peer_list"); let cluster_layout = match persist_cluster_layout.load() { - Ok(x) => x, + Ok(x) => { + if x.replication_factor != replication_factor { + return Err(Error::Message(format!( + "Prevous cluster layout has replication factor {}, which is different than the one specified in the config file ({}). The previous cluster layout can be purged, if you know what you are doing, simply by deleting the `cluster_layout` file in your metadata directory.", + x.replication_factor, + replication_factor + ))); + } + x + } Err(e) => { info!( "No valid previous cluster layout stored ({}), starting fresh.", @@ -228,8 +242,29 @@ impl System { let ring = Ring::new(cluster_layout, replication_factor); let (update_ring, ring) = watch::channel(Arc::new(ring)); - let rpc_public_addr = match config.rpc_public_addr { - Some(a) => Some(a), + let rpc_public_addr = match &config.rpc_public_addr { + Some(a_str) => { + use std::net::ToSocketAddrs; + match a_str.to_socket_addrs() { + Err(e) => { + error!( + "Cannot resolve rpc_public_addr {} from config file: {}.", + a_str, e + ); + None + } + Ok(a) => { + let a = a.collect::<Vec<_>>(); + if a.is_empty() { + error!("rpc_public_addr {} resolve to no known IP address", a_str); + } + if a.len() > 1 { + warn!("Multiple possible resolutions for rpc_public_addr: {:?}. Taking the first one.", a); + } + a.into_iter().next() + } + } + } None => { let addr = get_default_ip().map(|ip| SocketAddr::new(ip, config.rpc_bind_addr.port())); @@ -239,13 +274,15 @@ impl System { addr } }; + if rpc_public_addr.is_none() { + warn!("This Garage node does not know its publicly reachable RPC address, this might hamper intra-cluster communication."); + } let netapp = NetApp::new(GARAGE_VERSION_TAG, network_key, node_key); - let fullmesh = FullMeshPeeringStrategy::new( - netapp.clone(), - config.bootstrap_peers.clone(), - rpc_public_addr, - ); + let fullmesh = FullMeshPeeringStrategy::new(netapp.clone(), vec![], rpc_public_addr); + if let Some(ping_timeout) = config.rpc_ping_timeout_msec { + fullmesh.set_ping_timeout_millis(ping_timeout); + } let system_endpoint = netapp.endpoint(SYSTEM_RPC_PATH.into()); @@ -283,7 +320,13 @@ impl System { node_status: RwLock::new(HashMap::new()), netapp: netapp.clone(), fullmesh: fullmesh.clone(), - rpc: RpcHelper::new(netapp.id.into(), fullmesh, background.clone(), ring.clone()), + rpc: RpcHelper::new( + netapp.id.into(), + fullmesh, + background.clone(), + ring.clone(), + config.rpc_timeout_msec.map(Duration::from_millis), + ), system_endpoint, replication_factor, rpc_listen_addr: config.rpc_bind_addr, @@ -296,9 +339,10 @@ impl System { ring, update_ring: Mutex::new(update_ring), background, + metadata_dir: config.metadata_dir.clone(), }); sys.system_endpoint.set_handler(sys.clone()); - sys + Ok(sys) } /// Perform bootstraping, starting the ping loop @@ -313,6 +357,80 @@ impl System { ); } + // ---- Administrative operations (directly available and + // also available through RPC) ---- + + pub fn get_known_nodes(&self) -> Vec<KnownNodeInfo> { + let node_status = self.node_status.read().unwrap(); + let known_nodes = self + .fullmesh + .get_peer_list() + .iter() + .map(|n| KnownNodeInfo { + id: n.id.into(), + addr: n.addr, + is_up: n.is_up(), + last_seen_secs_ago: n + .last_seen + .map(|t| (Instant::now().saturating_duration_since(t)).as_secs()), + status: node_status + .get(&n.id.into()) + .cloned() + .map(|(_, st)| st) + .unwrap_or(NodeStatus { + hostname: "?".to_string(), + replication_factor: 0, + cluster_layout_version: 0, + cluster_layout_staging_hash: Hash::from([0u8; 32]), + }), + }) + .collect::<Vec<_>>(); + known_nodes + } + + pub fn get_cluster_layout(&self) -> ClusterLayout { + self.ring.borrow().layout.clone() + } + + pub async fn update_cluster_layout( + self: &Arc<Self>, + layout: &ClusterLayout, + ) -> Result<(), Error> { + self.handle_advertise_cluster_layout(layout).await?; + Ok(()) + } + + pub async fn connect(&self, node: &str) -> Result<(), Error> { + let (pubkey, addrs) = parse_and_resolve_peer_addr_async(node) + .await + .ok_or_else(|| { + Error::Message(format!( + "Unable to parse or resolve node specification: {}", + node + )) + })?; + let mut errors = vec![]; + for ip in addrs.iter() { + match self + .netapp + .clone() + .try_connect(*ip, pubkey) + .await + .err_context(CONNECT_ERROR_MESSAGE) + { + Ok(()) => return Ok(()), + Err(e) => { + errors.push((*ip, e)); + } + } + } + if errors.len() == 1 { + Err(Error::Message(errors[0].1.to_string())) + } else { + Err(Error::Message(format!("{:?}", errors))) + } + } + // ---- INTERNALS ---- async fn advertise_to_consul(self: Arc<Self>) -> Result<(), Error> { @@ -385,32 +503,11 @@ impl System { self.local_status.swap(Arc::new(new_si)); } + // --- RPC HANDLERS --- + async fn handle_connect(&self, node: &str) -> Result<SystemRpc, Error> { - let (pubkey, addrs) = parse_and_resolve_peer_addr(node).ok_or_else(|| { - Error::Message(format!( - "Unable to parse or resolve node specification: {}", - node - )) - })?; - let mut errors = vec![]; - for ip in addrs.iter() { - match self - .netapp - .clone() - .try_connect(*ip, pubkey) - .await - .err_context(CONNECT_ERROR_MESSAGE) - { - Ok(()) => return Ok(SystemRpc::Ok), - Err(e) => { - errors.push((*ip, e)); - } - } - } - return Err(Error::Message(format!( - "Could not connect to specified peers. Errors: {:?}", - errors - ))); + self.connect(node).await?; + Ok(SystemRpc::Ok) } fn handle_pull_cluster_layout(&self) -> SystemRpc { @@ -419,28 +516,7 @@ impl System { } fn handle_get_known_nodes(&self) -> SystemRpc { - let node_status = self.node_status.read().unwrap(); - let known_nodes = self - .fullmesh - .get_peer_list() - .iter() - .map(|n| KnownNodeInfo { - id: n.id.into(), - addr: n.addr, - is_up: n.is_up(), - last_seen_secs_ago: n.last_seen.map(|t| (Instant::now() - t).as_secs()), - status: node_status - .get(&n.id.into()) - .cloned() - .map(|(_, st)| st) - .unwrap_or(NodeStatus { - hostname: "?".to_string(), - replication_factor: 0, - cluster_layout_version: 0, - cluster_layout_staging_hash: Hash::from([0u8; 32]), - }), - }) - .collect::<Vec<_>>(); + let known_nodes = self.get_known_nodes(); SystemRpc::ReturnKnownNodes(known_nodes) } @@ -452,7 +528,7 @@ impl System { let local_info = self.local_status.load(); if local_info.replication_factor < info.replication_factor { - error!("Some node have a higher replication factor ({}) than this one ({}). This is not supported and might lead to bugs", + error!("Some node have a higher replication factor ({}) than this one ({}). This is not supported and will lead to data corruption. Shutting down for safety.", info.replication_factor, local_info.replication_factor); std::process::exit(1); @@ -477,9 +553,19 @@ impl System { } async fn handle_advertise_cluster_layout( - self: Arc<Self>, + self: &Arc<Self>, adv: &ClusterLayout, ) -> Result<SystemRpc, Error> { + if adv.replication_factor != self.replication_factor { + let msg = format!( + "Received a cluster layout from another node with replication factor {}, which is different from what we have in our configuration ({}). Discarding the cluster layout we received.", + adv.replication_factor, + self.replication_factor + ); + error!("{}", msg); + return Err(Error::Message(msg)); + } + let update_ring = self.update_ring.lock().await; let mut layout: ClusterLayout = self.ring.borrow().layout.clone(); @@ -505,7 +591,7 @@ impl System { SystemRpc::AdvertiseClusterLayout(layout), RequestStrategy::with_priority(PRIO_HIGH), ) - .await; + .await?; Ok(()) }); self.background.spawn(self.clone().save_cluster_layout()); @@ -520,11 +606,12 @@ impl System { self.update_local_status(); let local_status: NodeStatus = self.local_status.load().as_ref().clone(); - self.rpc + let _ = self + .rpc .broadcast( &self.system_endpoint, SystemRpc::AdvertiseStatus(local_status), - RequestStrategy::with_priority(PRIO_HIGH).with_timeout(PING_TIMEOUT), + RequestStrategy::with_priority(PRIO_HIGH), ) .await; @@ -550,7 +637,7 @@ impl System { if not_configured || no_peers || bad_peers { info!("Doing a bootstrap/discovery step (not_configured: {}, no_peers: {}, bad_peers: {})", not_configured, no_peers, bad_peers); - let mut ping_list = self.bootstrap_peers.clone(); + let mut ping_list = resolve_peers(&self.bootstrap_peers).await; // Add peer list from list stored on disk if let Ok(peers) = self.persist_peer_list.load_async().await { @@ -648,7 +735,7 @@ impl System { &self.system_endpoint, peer, SystemRpc::PullClusterLayout, - RequestStrategy::with_priority(PRIO_HIGH).with_timeout(PING_TIMEOUT), + RequestStrategy::with_priority(PRIO_HIGH), ) .await; if let Ok(SystemRpc::AdvertiseClusterLayout(layout)) = resp { @@ -681,6 +768,25 @@ fn get_default_ip() -> Option<IpAddr> { .map(|a| a.ip()) } +async fn resolve_peers(peers: &[String]) -> Vec<(NodeID, SocketAddr)> { + let mut ret = vec![]; + + for peer in peers.iter() { + match parse_and_resolve_peer_addr_async(peer).await { + Some((pubkey, addrs)) => { + for ip in addrs { + ret.push((pubkey, ip)); + } + } + None => { + warn!("Unable to parse and/or resolve peer hostname {}", peer); + } + } + } + + ret +} + struct ConsulDiscoveryParam { consul_host: String, service_name: String, diff --git a/src/table/Cargo.toml b/src/table/Cargo.toml index ed1a213f..38c6b41c 100644 --- a/src/table/Cargo.toml +++ b/src/table/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "garage_table" -version = "0.7.0" +version = "0.8.0" authors = ["Alex Auvolat <alex@adnab.me>"] edition = "2018" license = "AGPL-3.0" @@ -14,19 +14,19 @@ path = "lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -garage_rpc = { version = "0.7.0", path = "../rpc" } -garage_util = { version = "0.7.0", path = "../util" } +garage_db = { version = "0.8.0", path = "../db" } +garage_rpc = { version = "0.8.0", path = "../rpc" } +garage_util = { version = "0.8.0", path = "../util" } opentelemetry = "0.17" async-trait = "0.1.7" bytes = "1.0" +hex = "0.4" hexdump = "0.1" tracing = "0.1.30" rand = "0.8" -sled = "0.34" - rmp-serde = "0.15" serde = { version = "1.0", default-features = false, features = ["derive", "rc"] } serde_bytes = "0.11" diff --git a/src/table/data.rs b/src/table/data.rs index ff7965f5..3212e82b 100644 --- a/src/table/data.rs +++ b/src/table/data.rs @@ -1,13 +1,15 @@ use core::borrow::Borrow; +use std::convert::TryInto; use std::sync::Arc; use serde_bytes::ByteBuf; -use sled::Transactional; use tokio::sync::Notify; +use garage_db as db; +use garage_db::counted_tree_hack::CountedTree; + use garage_util::data::*; use garage_util::error::*; -use garage_util::sled_counter::SledCountedTree; use garage_rpc::system::System; @@ -16,19 +18,20 @@ use crate::gc::GcTodoEntry; use crate::metrics::*; use crate::replication::*; use crate::schema::*; +use crate::util::*; pub struct TableData<F: TableSchema, R: TableReplication> { system: Arc<System>, - pub(crate) instance: F, - pub(crate) replication: R, + pub instance: F, + pub replication: R, - pub store: sled::Tree, + pub store: db::Tree, - pub(crate) merkle_tree: sled::Tree, - pub(crate) merkle_todo: sled::Tree, + pub(crate) merkle_tree: db::Tree, + pub(crate) merkle_todo: db::Tree, pub(crate) merkle_todo_notify: Notify, - pub(crate) gc_todo: SledCountedTree, + pub(crate) gc_todo: CountedTree, pub(crate) metrics: TableMetrics, } @@ -38,7 +41,7 @@ where F: TableSchema, R: TableReplication, { - pub fn new(system: Arc<System>, instance: F, replication: R, db: &sled::Db) -> Arc<Self> { + pub fn new(system: Arc<System>, instance: F, replication: R, db: &db::Db) -> Arc<Self> { let store = db .open_tree(&format!("{}:table", F::TABLE_NAME)) .expect("Unable to open DB tree"); @@ -53,7 +56,7 @@ where let gc_todo = db .open_tree(&format!("{}:gc_todo_v2", F::TABLE_NAME)) .expect("Unable to open DB tree"); - let gc_todo = SledCountedTree::new(gc_todo); + let gc_todo = CountedTree::new(gc_todo).expect("Cannot count gc_todo_v2"); let metrics = TableMetrics::new(F::TABLE_NAME, merkle_todo.clone(), gc_todo.clone()); @@ -83,18 +86,48 @@ where pub fn read_range( &self, - p: &F::P, - s: &Option<F::S>, + partition_key: &F::P, + start: &Option<F::S>, + filter: &Option<F::Filter>, + limit: usize, + enumeration_order: EnumerationOrder, + ) -> Result<Vec<Arc<ByteBuf>>, Error> { + let partition_hash = partition_key.hash(); + match enumeration_order { + EnumerationOrder::Forward => { + let first_key = match start { + None => partition_hash.to_vec(), + Some(sk) => self.tree_key(partition_key, sk), + }; + let range = self.store.range(first_key..)?; + self.read_range_aux(partition_hash, range, filter, limit) + } + EnumerationOrder::Reverse => match start { + Some(sk) => { + let last_key = self.tree_key(partition_key, sk); + let range = self.store.range_rev(..=last_key)?; + self.read_range_aux(partition_hash, range, filter, limit) + } + None => { + let mut last_key = partition_hash.to_vec(); + let lower = u128::from_be_bytes(last_key[16..32].try_into().unwrap()); + last_key[16..32].copy_from_slice(&u128::to_be_bytes(lower + 1)); + let range = self.store.range_rev(..last_key)?; + self.read_range_aux(partition_hash, range, filter, limit) + } + }, + } + } + + fn read_range_aux<'a>( + &self, + partition_hash: Hash, + range: db::ValueIter<'a>, filter: &Option<F::Filter>, limit: usize, ) -> Result<Vec<Arc<ByteBuf>>, Error> { - let partition_hash = p.hash(); - let first_key = match s { - None => partition_hash.to_vec(), - Some(sk) => self.tree_key(p, sk), - }; let mut ret = vec![]; - for item in self.store.range(first_key..) { + for item in range { let (key, value) = item?; if &key[..32] != partition_hash.as_slice() { break; @@ -107,7 +140,7 @@ where } }; if keep { - ret.push(Arc::new(ByteBuf::from(value.as_ref()))); + ret.push(Arc::new(ByteBuf::from(value))); } if ret.len() >= limit { break; @@ -136,17 +169,29 @@ where let update = self.decode_entry(update_bytes)?; let tree_key = self.tree_key(update.partition_key(), update.sort_key()); - let changed = (&self.store, &self.merkle_todo).transaction(|(store, mkl_todo)| { - let (old_entry, old_bytes, new_entry) = match store.get(&tree_key)? { + self.update_entry_with(&tree_key[..], |ent| match ent { + Some(mut ent) => { + ent.merge(&update); + ent + } + None => update.clone(), + })?; + Ok(()) + } + + pub fn update_entry_with( + &self, + tree_key: &[u8], + f: impl Fn(Option<F::E>) -> F::E, + ) -> Result<Option<F::E>, Error> { + let changed = self.store.db().transaction(|mut tx| { + let (old_entry, old_bytes, new_entry) = match tx.get(&self.store, tree_key)? { Some(old_bytes) => { - let old_entry = self - .decode_entry(&old_bytes) - .map_err(sled::transaction::ConflictableTransactionError::Abort)?; - let mut new_entry = old_entry.clone(); - new_entry.merge(&update); + let old_entry = self.decode_entry(&old_bytes).map_err(db::TxError::Abort)?; + let new_entry = f(Some(old_entry.clone())); (Some(old_entry), Some(old_bytes), new_entry) } - None => (None, None, update.clone()), + None => (None, None, f(None)), }; // Scenario 1: the value changed, so of course there is a change @@ -158,24 +203,28 @@ where // the associated Merkle tree entry. let new_bytes = rmp_to_vec_all_named(&new_entry) .map_err(Error::RmpEncode) - .map_err(sled::transaction::ConflictableTransactionError::Abort)?; + .map_err(db::TxError::Abort)?; let encoding_changed = Some(&new_bytes[..]) != old_bytes.as_ref().map(|x| &x[..]); + drop(old_bytes); if value_changed || encoding_changed { let new_bytes_hash = blake2sum(&new_bytes[..]); - mkl_todo.insert(tree_key.clone(), new_bytes_hash.as_slice())?; - store.insert(tree_key.clone(), new_bytes)?; - Ok(Some((old_entry, new_entry, new_bytes_hash))) + tx.insert(&self.merkle_todo, tree_key, new_bytes_hash.as_slice())?; + tx.insert(&self.store, tree_key, new_bytes)?; + + self.instance + .updated(&mut tx, old_entry.as_ref(), Some(&new_entry))?; + + Ok(Some((new_entry, new_bytes_hash))) } else { Ok(None) } })?; - if let Some((old_entry, new_entry, new_bytes_hash)) = changed { + if let Some((new_entry, new_bytes_hash)) = changed { self.metrics.internal_update_counter.add(1); let is_tombstone = new_entry.is_tombstone(); - self.instance.updated(old_entry, Some(new_entry)); self.merkle_todo_notify.notify_one(); if is_tombstone { // We are only responsible for GC'ing this item if we are the @@ -187,31 +236,34 @@ where let pk_hash = Hash::try_from(&tree_key[..32]).unwrap(); let nodes = self.replication.write_nodes(&pk_hash); if nodes.first() == Some(&self.system.id) { - GcTodoEntry::new(tree_key, new_bytes_hash).save(&self.gc_todo)?; + GcTodoEntry::new(tree_key.to_vec(), new_bytes_hash).save(&self.gc_todo)?; } } - } - Ok(()) + Ok(Some(new_entry)) + } else { + Ok(None) + } } pub(crate) fn delete_if_equal(self: &Arc<Self>, k: &[u8], v: &[u8]) -> Result<bool, Error> { - let removed = (&self.store, &self.merkle_todo).transaction(|(store, mkl_todo)| { - if let Some(cur_v) = store.get(k)? { - if cur_v == v { - store.remove(k)?; - mkl_todo.insert(k, vec![])?; - return Ok(true); + let removed = self + .store + .db() + .transaction(|mut tx| match tx.get(&self.store, k)? { + Some(cur_v) if cur_v == v => { + tx.remove(&self.store, k)?; + tx.insert(&self.merkle_todo, k, vec![])?; + + let old_entry = self.decode_entry(v).map_err(db::TxError::Abort)?; + self.instance.updated(&mut tx, Some(&old_entry), None)?; + Ok(true) } - } - Ok(false) - })?; + _ => Ok(false), + })?; if removed { self.metrics.internal_delete_counter.add(1); - - let old_entry = self.decode_entry(v)?; - self.instance.updated(Some(old_entry), None); self.merkle_todo_notify.notify_one(); } Ok(removed) @@ -222,36 +274,37 @@ where k: &[u8], vhash: Hash, ) -> Result<bool, Error> { - let removed = (&self.store, &self.merkle_todo).transaction(|(store, mkl_todo)| { - if let Some(cur_v) = store.get(k)? { - if blake2sum(&cur_v[..]) == vhash { - store.remove(k)?; - mkl_todo.insert(k, vec![])?; - return Ok(Some(cur_v)); + let removed = self + .store + .db() + .transaction(|mut tx| match tx.get(&self.store, k)? { + Some(cur_v) if blake2sum(&cur_v[..]) == vhash => { + tx.remove(&self.store, k)?; + tx.insert(&self.merkle_todo, k, vec![])?; + + let old_entry = self.decode_entry(&cur_v[..]).map_err(db::TxError::Abort)?; + self.instance.updated(&mut tx, Some(&old_entry), None)?; + Ok(true) } - } - Ok(None) - })?; + _ => Ok(false), + })?; - if let Some(old_v) = removed { - let old_entry = self.decode_entry(&old_v[..])?; - self.instance.updated(Some(old_entry), None); + if removed { + self.metrics.internal_delete_counter.add(1); self.merkle_todo_notify.notify_one(); - Ok(true) - } else { - Ok(false) } + Ok(removed) } // ---- Utility functions ---- - pub(crate) fn tree_key(&self, p: &F::P, s: &F::S) -> Vec<u8> { + pub fn tree_key(&self, p: &F::P, s: &F::S) -> Vec<u8> { let mut ret = p.hash().to_vec(); ret.extend(s.sort_key()); ret } - pub(crate) fn decode_entry(&self, bytes: &[u8]) -> Result<F::E, Error> { + pub fn decode_entry(&self, bytes: &[u8]) -> Result<F::E, Error> { match rmp_serde::decode::from_read_ref::<_, F::E>(bytes) { Ok(x) => Ok(x), Err(e) => match F::try_migrate(bytes) { @@ -267,7 +320,7 @@ where } } - pub fn gc_todo_len(&self) -> usize { - self.gc_todo.len() + pub fn gc_todo_len(&self) -> Result<usize, Error> { + Ok(self.gc_todo.len()) } } diff --git a/src/table/gc.rs b/src/table/gc.rs index 2a05b6ae..83e7eeff 100644 --- a/src/table/gc.rs +++ b/src/table/gc.rs @@ -8,13 +8,13 @@ use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; use futures::future::join_all; -use futures::select; -use futures_util::future::*; use tokio::sync::watch; +use garage_db::counted_tree_hack::CountedTree; + +use garage_util::background::*; use garage_util::data::*; use garage_util::error::*; -use garage_util::sled_counter::SledCountedTree; use garage_util::time::*; use garage_rpc::system::System; @@ -25,7 +25,6 @@ use crate::replication::*; use crate::schema::*; const TABLE_GC_BATCH_SIZE: usize = 1024; -const TABLE_GC_RPC_TIMEOUT: Duration = Duration::from_secs(30); // GC delay for table entries: 1 day (24 hours) // (the delay before the entry is added in the GC todo list @@ -68,50 +67,24 @@ where gc.endpoint.set_handler(gc.clone()); - let gc1 = gc.clone(); - system.background.spawn_worker( - format!("GC loop for {}", F::TABLE_NAME), - move |must_exit: watch::Receiver<bool>| gc1.gc_loop(must_exit), - ); + system.background.spawn_worker(GcWorker::new(gc.clone())); gc } - async fn gc_loop(self: Arc<Self>, mut must_exit: watch::Receiver<bool>) { - while !*must_exit.borrow() { - match self.gc_loop_iter().await { - Ok(None) => { - // Stuff was done, loop immediately - } - Ok(Some(wait_delay)) => { - // Nothing was done, wait specified delay. - select! { - _ = tokio::time::sleep(wait_delay).fuse() => {}, - _ = must_exit.changed().fuse() => {}, - } - } - Err(e) => { - warn!("({}) Error doing GC: {}", F::TABLE_NAME, e); - } - } - } - } - async fn gc_loop_iter(&self) -> Result<Option<Duration>, Error> { let now = now_msec(); - let mut entries = vec![]; - let mut excluded = vec![]; - // List entries in the GC todo list // These entries are put there when a tombstone is inserted in the table // (see update_entry in data.rs) - for entry_kv in self.data.gc_todo.iter() { + let mut candidates = vec![]; + for entry_kv in self.data.gc_todo.iter()? { let (k, vhash) = entry_kv?; - let mut todo_entry = GcTodoEntry::parse(&k, &vhash); + let todo_entry = GcTodoEntry::parse(&k, &vhash); if todo_entry.deletion_time() > now { - if entries.is_empty() && excluded.is_empty() { + if candidates.is_empty() { // If the earliest entry in the todo list shouldn't yet be processed, // return a duration to wait in the loop return Ok(Some(Duration::from_millis( @@ -123,15 +96,23 @@ where } } - let vhash = Hash::try_from(&vhash[..]).unwrap(); + candidates.push(todo_entry); + if candidates.len() >= 2 * TABLE_GC_BATCH_SIZE { + break; + } + } + let mut entries = vec![]; + let mut excluded = vec![]; + for mut todo_entry in candidates { // Check if the tombstone is still the current value of the entry. // If not, we don't actually want to GC it, and we will remove it // from the gc_todo table later (below). + let vhash = todo_entry.value_hash; todo_entry.value = self .data .store - .get(&k[..])? + .get(&todo_entry.key[..])? .filter(|v| blake2sum(&v[..]) == vhash) .map(|v| v.to_vec()); @@ -254,9 +235,7 @@ where &self.endpoint, &nodes[..], GcRpc::Update(updates), - RequestStrategy::with_priority(PRIO_BACKGROUND) - .with_quorum(nodes.len()) - .with_timeout(TABLE_GC_RPC_TIMEOUT), + RequestStrategy::with_priority(PRIO_BACKGROUND).with_quorum(nodes.len()), ) .await .err_context("GC: send tombstones")?; @@ -277,9 +256,7 @@ where &self.endpoint, &nodes[..], GcRpc::DeleteIfEqualHash(deletes), - RequestStrategy::with_priority(PRIO_BACKGROUND) - .with_quorum(nodes.len()) - .with_timeout(TABLE_GC_RPC_TIMEOUT), + RequestStrategy::with_priority(PRIO_BACKGROUND).with_quorum(nodes.len()), ) .await .err_context("GC: remote delete tombstones")?; @@ -321,6 +298,66 @@ where } } +struct GcWorker<F, R> +where + F: TableSchema + 'static, + R: TableReplication + 'static, +{ + gc: Arc<TableGc<F, R>>, + wait_delay: Duration, +} + +impl<F, R> GcWorker<F, R> +where + F: TableSchema + 'static, + R: TableReplication + 'static, +{ + fn new(gc: Arc<TableGc<F, R>>) -> Self { + Self { + gc, + wait_delay: Duration::from_secs(0), + } + } +} + +#[async_trait] +impl<F, R> Worker for GcWorker<F, R> +where + F: TableSchema + 'static, + R: TableReplication + 'static, +{ + fn name(&self) -> String { + format!("{} GC", F::TABLE_NAME) + } + + fn info(&self) -> Option<String> { + let l = self.gc.data.gc_todo_len().unwrap_or(0); + if l > 0 { + Some(format!("{} items in queue", l)) + } else { + None + } + } + + async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + match self.gc.gc_loop_iter().await? { + None => Ok(WorkerState::Busy), + Some(delay) => { + self.wait_delay = delay; + Ok(WorkerState::Idle) + } + } + } + + async fn wait_for_work(&mut self, must_exit: &watch::Receiver<bool>) -> WorkerState { + if *must_exit.borrow() { + return WorkerState::Done; + } + tokio::time::sleep(self.wait_delay).await; + WorkerState::Busy + } +} + /// An entry stored in the gc_todo Sled tree associated with the table /// Contains helper function for parsing, saving, and removing /// such entry in Sled @@ -353,17 +390,17 @@ impl GcTodoEntry { } /// Parses a GcTodoEntry from a (k, v) pair stored in the gc_todo tree - pub(crate) fn parse(sled_k: &[u8], sled_v: &[u8]) -> Self { + pub(crate) fn parse(db_k: &[u8], db_v: &[u8]) -> Self { Self { - tombstone_timestamp: u64::from_be_bytes(sled_k[0..8].try_into().unwrap()), - key: sled_k[8..].to_vec(), - value_hash: Hash::try_from(sled_v).unwrap(), + tombstone_timestamp: u64::from_be_bytes(db_k[0..8].try_into().unwrap()), + key: db_k[8..].to_vec(), + value_hash: Hash::try_from(db_v).unwrap(), value: None, } } /// Saves the GcTodoEntry in the gc_todo tree - pub(crate) fn save(&self, gc_todo_tree: &SledCountedTree) -> Result<(), Error> { + pub(crate) fn save(&self, gc_todo_tree: &CountedTree) -> Result<(), Error> { gc_todo_tree.insert(self.todo_table_key(), self.value_hash.as_slice())?; Ok(()) } @@ -373,9 +410,9 @@ impl GcTodoEntry { /// This is usefull to remove a todo entry only under the condition /// that it has not changed since the time it was read, i.e. /// what we have to do is still the same - pub(crate) fn remove_if_equal(&self, gc_todo_tree: &SledCountedTree) -> Result<(), Error> { - let _ = gc_todo_tree.compare_and_swap::<_, _, Vec<u8>>( - &self.todo_table_key()[..], + pub(crate) fn remove_if_equal(&self, gc_todo_tree: &CountedTree) -> Result<(), Error> { + gc_todo_tree.compare_and_swap::<_, _, &[u8]>( + &self.todo_table_key(), Some(self.value_hash), None, )?; diff --git a/src/table/merkle.rs b/src/table/merkle.rs index 93bf7e47..a5c29723 100644 --- a/src/table/merkle.rs +++ b/src/table/merkle.rs @@ -1,15 +1,13 @@ use std::sync::Arc; use std::time::Duration; -use futures::select; -use futures_util::future::*; +use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use sled::transaction::{ - ConflictableTransactionError, ConflictableTransactionResult, TransactionalTree, -}; use tokio::sync::watch; -use garage_util::background::BackgroundRunner; +use garage_db as db; + +use garage_util::background::*; use garage_util::data::*; use garage_util::error::Error; @@ -79,43 +77,17 @@ where empty_node_hash, }); - let ret2 = ret.clone(); - background.spawn_worker( - format!("Merkle tree updater for {}", F::TABLE_NAME), - |must_exit: watch::Receiver<bool>| ret2.updater_loop(must_exit), - ); + background.spawn_worker(MerkleWorker(ret.clone())); ret } - async fn updater_loop(self: Arc<Self>, mut must_exit: watch::Receiver<bool>) { - while !*must_exit.borrow() { - if let Some(x) = self.data.merkle_todo.iter().next() { - match x { - Ok((key, valhash)) => { - if let Err(e) = self.update_item(&key[..], &valhash[..]) { - warn!( - "({}) Error while updating Merkle tree item: {}", - F::TABLE_NAME, - e - ); - } - } - Err(e) => { - warn!( - "({}) Error while iterating on Merkle todo tree: {}", - F::TABLE_NAME, - e - ); - tokio::time::sleep(Duration::from_secs(10)).await; - } - } - } else { - select! { - _ = self.data.merkle_todo_notify.notified().fuse() => {}, - _ = must_exit.changed().fuse() => {}, - } - } + fn updater_loop_iter(&self) -> Result<WorkerState, Error> { + if let Some((key, valhash)) = self.data.merkle_todo.first()? { + self.update_item(&key, &valhash)?; + Ok(WorkerState::Busy) + } else { + Ok(WorkerState::Idle) } } @@ -137,13 +109,16 @@ where }; self.data .merkle_tree - .transaction(|tx| self.update_item_rec(tx, k, &khash, &key, new_vhash))?; + .db() + .transaction(|mut tx| self.update_item_rec(&mut tx, k, &khash, &key, new_vhash))?; - let deleted = self - .data - .merkle_todo - .compare_and_swap::<_, _, Vec<u8>>(k, Some(vhash_by), None)? - .is_ok(); + let deleted = self.data.merkle_todo.db().transaction(|mut tx| { + let remove = matches!(tx.get(&self.data.merkle_todo, k)?, Some(ov) if ov == vhash_by); + if remove { + tx.remove(&self.data.merkle_todo, k)?; + } + Ok(remove) + })?; if !deleted { debug!( @@ -157,12 +132,12 @@ where fn update_item_rec( &self, - tx: &TransactionalTree, + tx: &mut db::Transaction<'_>, k: &[u8], khash: &Hash, key: &MerkleNodeKey, new_vhash: Option<Hash>, - ) -> ConflictableTransactionResult<Option<Hash>, Error> { + ) -> db::TxResult<Option<Hash>, Error> { let i = key.prefix.len(); // Read node at current position (defined by the prefix stored in key) @@ -203,7 +178,7 @@ where } MerkleNode::Intermediate(_) => Some(MerkleNode::Intermediate(children)), x @ MerkleNode::Leaf(_, _) => { - tx.remove(key_sub.encode())?; + tx.remove(&self.data.merkle_tree, key_sub.encode())?; Some(x) } } @@ -283,28 +258,27 @@ where fn read_node_txn( &self, - tx: &TransactionalTree, + tx: &mut db::Transaction<'_>, k: &MerkleNodeKey, - ) -> ConflictableTransactionResult<MerkleNode, Error> { - let ent = tx.get(k.encode())?; - MerkleNode::decode_opt(ent).map_err(ConflictableTransactionError::Abort) + ) -> db::TxResult<MerkleNode, Error> { + let ent = tx.get(&self.data.merkle_tree, k.encode())?; + MerkleNode::decode_opt(&ent).map_err(db::TxError::Abort) } fn put_node_txn( &self, - tx: &TransactionalTree, + tx: &mut db::Transaction<'_>, k: &MerkleNodeKey, v: &MerkleNode, - ) -> ConflictableTransactionResult<Hash, Error> { + ) -> db::TxResult<Hash, Error> { trace!("Put Merkle node: {:?} => {:?}", k, v); if *v == MerkleNode::Empty { - tx.remove(k.encode())?; + tx.remove(&self.data.merkle_tree, k.encode())?; Ok(self.empty_node_hash) } else { - let vby = rmp_to_vec_all_named(v) - .map_err(|e| ConflictableTransactionError::Abort(e.into()))?; + let vby = rmp_to_vec_all_named(v).map_err(|e| db::TxError::Abort(e.into()))?; let rethash = blake2sum(&vby[..]); - tx.insert(k.encode(), vby)?; + tx.insert(&self.data.merkle_tree, k.encode(), vby)?; Ok(rethash) } } @@ -312,15 +286,63 @@ where // Access a node in the Merkle tree, used by the sync protocol pub(crate) fn read_node(&self, k: &MerkleNodeKey) -> Result<MerkleNode, Error> { let ent = self.data.merkle_tree.get(k.encode())?; - MerkleNode::decode_opt(ent) + MerkleNode::decode_opt(&ent) + } + + pub fn merkle_tree_len(&self) -> Result<usize, Error> { + Ok(self.data.merkle_tree.len()?) } - pub fn merkle_tree_len(&self) -> usize { - self.data.merkle_tree.len() + pub fn todo_len(&self) -> Result<usize, Error> { + Ok(self.data.merkle_todo.len()?) } +} + +struct MerkleWorker<F, R>(Arc<MerkleUpdater<F, R>>) +where + F: TableSchema + 'static, + R: TableReplication + 'static; - pub fn todo_len(&self) -> usize { - self.data.merkle_todo.len() +#[async_trait] +impl<F, R> Worker for MerkleWorker<F, R> +where + F: TableSchema + 'static, + R: TableReplication + 'static, +{ + fn name(&self) -> String { + format!("{} Merkle tree updater", F::TABLE_NAME) + } + + fn info(&self) -> Option<String> { + let l = self.0.todo_len().unwrap_or(0); + if l > 0 { + Some(format!("{} items in queue", l)) + } else { + None + } + } + + async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + let updater = self.0.clone(); + tokio::task::spawn_blocking(move || { + for _i in 0..100 { + let s = updater.updater_loop_iter(); + if !matches!(s, Ok(WorkerState::Busy)) { + return s; + } + } + Ok(WorkerState::Busy) + }) + .await + .unwrap() + } + + async fn wait_for_work(&mut self, must_exit: &watch::Receiver<bool>) -> WorkerState { + if *must_exit.borrow() { + return WorkerState::Done; + } + tokio::time::sleep(Duration::from_secs(10)).await; + WorkerState::Busy } } @@ -347,7 +369,7 @@ impl MerkleNodeKey { } impl MerkleNode { - fn decode_opt(ent: Option<sled::IVec>) -> Result<Self, Error> { + fn decode_opt(ent: &Option<db::Value>) -> Result<Self, Error> { match ent { None => Ok(MerkleNode::Empty), Some(v) => Ok(rmp_serde::decode::from_read_ref::<_, MerkleNode>(&v[..])?), diff --git a/src/table/metrics.rs b/src/table/metrics.rs index 752a2a6d..3a1783e0 100644 --- a/src/table/metrics.rs +++ b/src/table/metrics.rs @@ -1,6 +1,7 @@ use opentelemetry::{global, metrics::*, KeyValue}; -use garage_util::sled_counter::SledCountedTree; +use garage_db as db; +use garage_db::counted_tree_hack::CountedTree; /// TableMetrics reference all counter used for metrics pub struct TableMetrics { @@ -19,21 +20,19 @@ pub struct TableMetrics { pub(crate) sync_items_received: Counter<u64>, } impl TableMetrics { - pub fn new( - table_name: &'static str, - merkle_todo: sled::Tree, - gc_todo: SledCountedTree, - ) -> Self { + pub fn new(table_name: &'static str, merkle_todo: db::Tree, gc_todo: CountedTree) -> Self { let meter = global::meter(table_name); TableMetrics { _merkle_todo_len: meter .u64_value_observer( "table.merkle_updater_todo_queue_length", move |observer| { - observer.observe( - merkle_todo.len() as u64, - &[KeyValue::new("table_name", table_name)], - ) + if let Ok(v) = merkle_todo.len() { + observer.observe( + v as u64, + &[KeyValue::new("table_name", table_name)], + ); + } }, ) .with_description("Merkle tree updater TODO queue length") @@ -45,7 +44,7 @@ impl TableMetrics { observer.observe( gc_todo.len() as u64, &[KeyValue::new("table_name", table_name)], - ) + ); }, ) .with_description("Table garbage collector TODO queue length") diff --git a/src/table/schema.rs b/src/table/schema.rs index eba918a2..f37e98d8 100644 --- a/src/table/schema.rs +++ b/src/table/schema.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; +use garage_db as db; use garage_util::data::*; use crate::crdt::Crdt; @@ -59,7 +60,7 @@ pub trait Entry<P: PartitionKey, S: SortKey>: } /// Trait for the schema used in a table -pub trait TableSchema: Send + Sync { +pub trait TableSchema: Send + Sync + 'static { /// The name of the table in the database const TABLE_NAME: &'static str; @@ -82,11 +83,19 @@ pub trait TableSchema: Send + Sync { None } - // Updated triggers some stuff downstream, but it is not supposed to block or fail, - // as the update itself is an unchangeable fact that will never go back - // due to CRDT logic. Typically errors in propagation of info should be logged - // to stderr. - fn updated(&self, _old: Option<Self::E>, _new: Option<Self::E>) {} + /// Actions triggered by data changing in a table. If such actions + /// include updates to the local database that should be applied + /// atomically with the item update itself, a db transaction is + /// provided on which these changes should be done. + /// This function can return a DB error but that's all. + fn updated( + &self, + _tx: &mut db::Transaction, + _old: Option<&Self::E>, + _new: Option<&Self::E>, + ) -> db::TxOpResult<()> { + Ok(()) + } fn matches_filter(entry: &Self::E, filter: &Self::Filter) -> bool; } diff --git a/src/table/sync.rs b/src/table/sync.rs index 08069ad0..9d79d856 100644 --- a/src/table/sync.rs +++ b/src/table/sync.rs @@ -1,17 +1,17 @@ use std::collections::VecDeque; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::time::{Duration, Instant}; use async_trait::async_trait; -use futures::select; -use futures_util::future::*; use futures_util::stream::*; use opentelemetry::KeyValue; use rand::Rng; use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; +use tokio::select; use tokio::sync::{mpsc, watch}; +use garage_util::background::*; use garage_util::data::*; use garage_util::error::Error; @@ -24,8 +24,6 @@ use crate::merkle::*; use crate::replication::*; use crate::*; -const TABLE_SYNC_RPC_TIMEOUT: Duration = Duration::from_secs(30); - // Do anti-entropy every 10 minutes const ANTI_ENTROPY_INTERVAL: Duration = Duration::from_secs(10 * 60); @@ -34,7 +32,7 @@ pub struct TableSyncer<F: TableSchema + 'static, R: TableReplication + 'static> data: Arc<TableData<F, R>>, merkle: Arc<MerkleUpdater<F, R>>, - todo: Mutex<SyncTodo>, + add_full_sync_tx: mpsc::UnboundedSender<()>, endpoint: Arc<Endpoint<SyncRpc, Self>>, } @@ -52,10 +50,6 @@ impl Rpc for SyncRpc { type Response = Result<SyncRpc, Error>; } -struct SyncTodo { - todo: Vec<TodoPartition>, -} - #[derive(Debug, Clone)] struct TodoPartition { partition: Partition, @@ -80,118 +74,40 @@ where .netapp .endpoint(format!("garage_table/sync.rs/Rpc:{}", F::TABLE_NAME)); - let todo = SyncTodo { todo: vec![] }; + let (add_full_sync_tx, add_full_sync_rx) = mpsc::unbounded_channel(); let syncer = Arc::new(Self { system: system.clone(), data, merkle, - todo: Mutex::new(todo), + add_full_sync_tx, endpoint, }); syncer.endpoint.set_handler(syncer.clone()); - let (busy_tx, busy_rx) = mpsc::unbounded_channel(); - - let s1 = syncer.clone(); - system.background.spawn_worker( - format!("table sync watcher for {}", F::TABLE_NAME), - move |must_exit: watch::Receiver<bool>| s1.watcher_task(must_exit, busy_rx), - ); - - let s2 = syncer.clone(); - system.background.spawn_worker( - format!("table syncer for {}", F::TABLE_NAME), - move |must_exit: watch::Receiver<bool>| s2.syncer_task(must_exit, busy_tx), - ); - - let s3 = syncer.clone(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_secs(20)).await; - s3.add_full_sync(); + system.background.spawn_worker(SyncWorker { + syncer: syncer.clone(), + ring_recv: system.ring.clone(), + ring: system.ring.borrow().clone(), + add_full_sync_rx, + todo: vec![], + next_full_sync: Instant::now() + Duration::from_secs(20), }); syncer } - async fn watcher_task( - self: Arc<Self>, - mut must_exit: watch::Receiver<bool>, - mut busy_rx: mpsc::UnboundedReceiver<bool>, - ) { - let mut prev_ring: Arc<Ring> = self.system.ring.borrow().clone(); - let mut ring_recv: watch::Receiver<Arc<Ring>> = self.system.ring.clone(); - let mut nothing_to_do_since = Some(Instant::now()); - - while !*must_exit.borrow() { - select! { - _ = ring_recv.changed().fuse() => { - let new_ring = ring_recv.borrow(); - if !Arc::ptr_eq(&new_ring, &prev_ring) { - debug!("({}) Ring changed, adding full sync to syncer todo list", F::TABLE_NAME); - self.add_full_sync(); - prev_ring = new_ring.clone(); - } - } - busy_opt = busy_rx.recv().fuse() => { - if let Some(busy) = busy_opt { - if busy { - nothing_to_do_since = None; - } else if nothing_to_do_since.is_none() { - nothing_to_do_since = Some(Instant::now()); - } - } - } - _ = must_exit.changed().fuse() => {}, - _ = tokio::time::sleep(Duration::from_secs(1)).fuse() => { - if nothing_to_do_since.map(|t| Instant::now() - t >= ANTI_ENTROPY_INTERVAL).unwrap_or(false) { - nothing_to_do_since = None; - debug!("({}) Interval passed, adding full sync to syncer todo list", F::TABLE_NAME); - self.add_full_sync(); - } - } - } - } - } - pub fn add_full_sync(&self) { - self.todo - .lock() - .unwrap() - .add_full_sync(&self.data, &self.system); - } - - async fn syncer_task( - self: Arc<Self>, - mut must_exit: watch::Receiver<bool>, - busy_tx: mpsc::UnboundedSender<bool>, - ) { - while !*must_exit.borrow() { - let task = self.todo.lock().unwrap().pop_task(); - if let Some(partition) = task { - busy_tx.send(true).unwrap(); - let res = self - .clone() - .sync_partition(&partition, &mut must_exit) - .await; - if let Err(e) = res { - warn!( - "({}) Error while syncing {:?}: {}", - F::TABLE_NAME, - partition, - e - ); - } - } else { - busy_tx.send(false).unwrap(); - tokio::time::sleep(Duration::from_secs(1)).await; - } + if self.add_full_sync_tx.send(()).is_err() { + error!("({}) Could not add full sync", F::TABLE_NAME); } } + // ---- + async fn sync_partition( - self: Arc<Self>, + self: &Arc<Self>, partition: &TodoPartition, must_exit: &mut watch::Receiver<bool>, ) -> Result<(), Error> { @@ -258,9 +174,9 @@ where while !*must_exit.borrow() { let mut items = Vec::new(); - for item in self.data.store.range(begin.to_vec()..end.to_vec()) { + for item in self.data.store.range(begin.to_vec()..end.to_vec())? { let (key, value) = item?; - items.push((key.to_vec(), Arc::new(ByteBuf::from(value.as_ref())))); + items.push((key.to_vec(), Arc::new(ByteBuf::from(value)))); if items.len() >= 1024 { break; @@ -329,9 +245,7 @@ where &self.endpoint, nodes, SyncRpc::Items(values), - RequestStrategy::with_priority(PRIO_BACKGROUND) - .with_quorum(nodes.len()) - .with_timeout(TABLE_SYNC_RPC_TIMEOUT), + RequestStrategy::with_priority(PRIO_BACKGROUND).with_quorum(nodes.len()), ) .await?; @@ -392,8 +306,7 @@ where &self.endpoint, who, SyncRpc::RootCkHash(partition.partition, root_ck_hash), - RequestStrategy::with_priority(PRIO_BACKGROUND) - .with_timeout(TABLE_SYNC_RPC_TIMEOUT), + RequestStrategy::with_priority(PRIO_BACKGROUND), ) .await?; @@ -432,11 +345,11 @@ where // Just send that item directly if let Some(val) = self.data.store.get(&ik[..])? { if blake2sum(&val[..]) != ivhash { - warn!("({}) Hashes differ between stored value and Merkle tree, key: {:?} (if your server is very busy, don't worry, this happens when the Merkle tree can't be updated fast enough)", F::TABLE_NAME, ik); + debug!("({}) Hashes differ between stored value and Merkle tree, key: {} (if your server is very busy, don't worry, this happens when the Merkle tree can't be updated fast enough)", F::TABLE_NAME, hex::encode(ik)); } todo_items.push(val.to_vec()); } else { - warn!("({}) Item from Merkle tree not found in store: {:?} (if your server is very busy, don't worry, this happens when the Merkle tree can't be updated fast enough)", F::TABLE_NAME, ik); + debug!("({}) Item from Merkle tree not found in store: {} (if your server is very busy, don't worry, this happens when the Merkle tree can't be updated fast enough)", F::TABLE_NAME, hex::encode(ik)); } } MerkleNode::Intermediate(l) => { @@ -449,8 +362,7 @@ where &self.endpoint, who, SyncRpc::GetNode(key.clone()), - RequestStrategy::with_priority(PRIO_BACKGROUND) - .with_timeout(TABLE_SYNC_RPC_TIMEOUT), + RequestStrategy::with_priority(PRIO_BACKGROUND), ) .await? { @@ -526,8 +438,7 @@ where &self.endpoint, who, SyncRpc::Items(values), - RequestStrategy::with_priority(PRIO_BACKGROUND) - .with_timeout(TABLE_SYNC_RPC_TIMEOUT), + RequestStrategy::with_priority(PRIO_BACKGROUND), ) .await?; if let SyncRpc::Ok = rpc_resp { @@ -577,12 +488,22 @@ where } } -impl SyncTodo { - fn add_full_sync<F: TableSchema, R: TableReplication>( - &mut self, - data: &TableData<F, R>, - system: &System, - ) { +// -------- Sync Worker --------- + +struct SyncWorker<F: TableSchema + 'static, R: TableReplication + 'static> { + syncer: Arc<TableSyncer<F, R>>, + ring_recv: watch::Receiver<Arc<Ring>>, + ring: Arc<Ring>, + add_full_sync_rx: mpsc::UnboundedReceiver<()>, + todo: Vec<TodoPartition>, + next_full_sync: Instant, +} + +impl<F: TableSchema + 'static, R: TableReplication + 'static> SyncWorker<F, R> { + fn add_full_sync(&mut self) { + let system = &self.syncer.system; + let data = &self.syncer.data; + let my_id = system.id; self.todo.clear(); @@ -603,8 +524,16 @@ impl SyncTodo { let retain = nodes.contains(&my_id); if !retain { // Check if we have some data to send, otherwise skip - if data.store.range(begin..end).next().is_none() { - continue; + match data.store.range(begin..end) { + Ok(mut iter) => { + if iter.next().is_none() { + continue; + } + } + Err(e) => { + warn!("DB error in add_full_sync: {}", e); + continue; + } } } @@ -615,6 +544,8 @@ impl SyncTodo { retain, }); } + + self.next_full_sync = Instant::now() + ANTI_ENTROPY_INTERVAL; } fn pop_task(&mut self) -> Option<TodoPartition> { @@ -633,6 +564,62 @@ impl SyncTodo { } } +#[async_trait] +impl<F: TableSchema + 'static, R: TableReplication + 'static> Worker for SyncWorker<F, R> { + fn name(&self) -> String { + format!("{} sync", F::TABLE_NAME) + } + + fn info(&self) -> Option<String> { + let l = self.todo.len(); + if l > 0 { + Some(format!("{} partitions remaining", l)) + } else { + None + } + } + + async fn work(&mut self, must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + if let Some(partition) = self.pop_task() { + self.syncer.sync_partition(&partition, must_exit).await?; + Ok(WorkerState::Busy) + } else { + Ok(WorkerState::Idle) + } + } + + async fn wait_for_work(&mut self, must_exit: &watch::Receiver<bool>) -> WorkerState { + if *must_exit.borrow() { + return WorkerState::Done; + } + select! { + s = self.add_full_sync_rx.recv() => { + if let Some(()) = s { + self.add_full_sync(); + } + }, + _ = self.ring_recv.changed() => { + let new_ring = self.ring_recv.borrow(); + if !Arc::ptr_eq(&new_ring, &self.ring) { + self.ring = new_ring.clone(); + drop(new_ring); + debug!("({}) Ring changed, adding full sync to syncer todo list", F::TABLE_NAME); + self.add_full_sync(); + } + }, + _ = tokio::time::sleep_until(self.next_full_sync.into()) => { + self.add_full_sync(); + } + } + match self.todo.is_empty() { + false => WorkerState::Busy, + true => WorkerState::Idle, + } + } +} + +// ---- UTIL ---- + fn hash_of<T: Serialize>(x: &T) -> Result<Hash, Error> { Ok(blake2sum(&rmp_to_vec_all_named(x)?[..])) } diff --git a/src/table/table.rs b/src/table/table.rs index 7f87a449..8a66c420 100644 --- a/src/table/table.rs +++ b/src/table/table.rs @@ -1,6 +1,6 @@ -use std::collections::{BTreeMap, HashMap}; +use std::borrow::Borrow; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::sync::Arc; -use std::time::Duration; use async_trait::async_trait; use futures::stream::*; @@ -12,6 +12,8 @@ use opentelemetry::{ Context, }; +use garage_db as db; + use garage_util::data::*; use garage_util::error::Error; use garage_util::metrics::RecordDuration; @@ -26,8 +28,7 @@ use crate::merkle::*; use crate::replication::*; use crate::schema::*; use crate::sync::*; - -const TABLE_RPC_TIMEOUT: Duration = Duration::from_secs(10); +use crate::util::*; pub struct Table<F: TableSchema + 'static, R: TableReplication + 'static> { pub system: Arc<System>, @@ -45,7 +46,13 @@ pub(crate) enum TableRpc<F: TableSchema> { ReadEntryResponse(Option<ByteBuf>), // Read range: read all keys in partition P, possibly starting at a certain sort key offset - ReadRange(F::P, Option<F::S>, Option<F::Filter>, usize), + ReadRange { + partition: F::P, + begin_sort_key: Option<F::S>, + filter: Option<F::Filter>, + limit: usize, + enumeration_order: EnumerationOrder, + }, Update(Vec<Arc<ByteBuf>>), } @@ -61,7 +68,7 @@ where { // =============== PUBLIC INTERFACE FUNCTIONS (new, insert, get, etc) =============== - pub fn new(instance: F, replication: R, system: Arc<System>, db: &sled::Db) -> Arc<Self> { + pub fn new(instance: F, replication: R, system: Arc<System>, db: &db::Db) -> Arc<Self> { let endpoint = system .netapp .endpoint(format!("garage_table/table.rs/Rpc:{}", F::TABLE_NAME)); @@ -103,7 +110,6 @@ where async fn insert_internal(&self, e: &F::E) -> Result<(), Error> { let hash = e.partition_key().hash(); let who = self.data.replication.write_nodes(&hash); - //eprintln!("insert who: {:?}", who); let e_enc = Arc::new(ByteBuf::from(rmp_to_vec_all_named(e)?)); let rpc = TableRpc::<F>::Update(vec![e_enc]); @@ -115,17 +121,20 @@ where &who[..], rpc, RequestStrategy::with_priority(PRIO_NORMAL) - .with_quorum(self.data.replication.write_quorum()) - .with_timeout(TABLE_RPC_TIMEOUT), + .with_quorum(self.data.replication.write_quorum()), ) .await?; Ok(()) } - pub async fn insert_many(&self, entries: &[F::E]) -> Result<(), Error> { + pub async fn insert_many<I, IE>(&self, entries: I) -> Result<(), Error> + where + I: IntoIterator<Item = IE> + Send + Sync, + IE: Borrow<F::E> + Send + Sync, + { let tracer = opentelemetry::global::tracer("garage_table"); - let span = tracer.start(format!("{} insert_many {}", F::TABLE_NAME, entries.len())); + let span = tracer.start(format!("{} insert_many", F::TABLE_NAME)); self.insert_many_internal(entries) .bound_record_duration(&self.data.metrics.put_request_duration) @@ -137,10 +146,15 @@ where Ok(()) } - async fn insert_many_internal(&self, entries: &[F::E]) -> Result<(), Error> { + async fn insert_many_internal<I, IE>(&self, entries: I) -> Result<(), Error> + where + I: IntoIterator<Item = IE> + Send + Sync, + IE: Borrow<F::E> + Send + Sync, + { let mut call_list: HashMap<_, Vec<_>> = HashMap::new(); - for entry in entries.iter() { + for entry in entries.into_iter() { + let entry = entry.borrow(); let hash = entry.partition_key().hash(); let who = self.data.replication.write_nodes(&hash); let e_enc = Arc::new(ByteBuf::from(rmp_to_vec_all_named(entry)?)); @@ -159,7 +173,7 @@ where &self.endpoint, node, rpc, - RequestStrategy::with_priority(PRIO_NORMAL).with_timeout(TABLE_RPC_TIMEOUT), + RequestStrategy::with_priority(PRIO_NORMAL), ) .await?; Ok::<_, Error>((node, resp)) @@ -216,7 +230,6 @@ where rpc, RequestStrategy::with_priority(PRIO_NORMAL) .with_quorum(self.data.replication.read_quorum()) - .with_timeout(TABLE_RPC_TIMEOUT) .interrupt_after_quorum(true), ) .await?; @@ -261,12 +274,19 @@ where begin_sort_key: Option<F::S>, filter: Option<F::Filter>, limit: usize, + enumeration_order: EnumerationOrder, ) -> Result<Vec<F::E>, Error> { let tracer = opentelemetry::global::tracer("garage_table"); let span = tracer.start(format!("{} get_range", F::TABLE_NAME)); let res = self - .get_range_internal(partition_key, begin_sort_key, filter, limit) + .get_range_internal( + partition_key, + begin_sort_key, + filter, + limit, + enumeration_order, + ) .bound_record_duration(&self.data.metrics.get_request_duration) .with_context(Context::current_with_span(span)) .await?; @@ -282,11 +302,18 @@ where begin_sort_key: Option<F::S>, filter: Option<F::Filter>, limit: usize, + enumeration_order: EnumerationOrder, ) -> Result<Vec<F::E>, Error> { let hash = partition_key.hash(); let who = self.data.replication.read_nodes(&hash); - let rpc = TableRpc::<F>::ReadRange(partition_key.clone(), begin_sort_key, filter, limit); + let rpc = TableRpc::<F>::ReadRange { + partition: partition_key.clone(), + begin_sort_key, + filter, + limit, + enumeration_order, + }; let resps = self .system @@ -297,49 +324,69 @@ where rpc, RequestStrategy::with_priority(PRIO_NORMAL) .with_quorum(self.data.replication.read_quorum()) - .with_timeout(TABLE_RPC_TIMEOUT) .interrupt_after_quorum(true), ) .await?; - let mut ret = BTreeMap::new(); - let mut to_repair = BTreeMap::new(); + let mut ret: BTreeMap<Vec<u8>, F::E> = BTreeMap::new(); + let mut to_repair = BTreeSet::new(); for resp in resps { if let TableRpc::Update(entries) = resp { for entry_bytes in entries.iter() { let entry = self.data.decode_entry(entry_bytes.as_slice())?; let entry_key = self.data.tree_key(entry.partition_key(), entry.sort_key()); - match ret.remove(&entry_key) { - None => { - ret.insert(entry_key, Some(entry)); - } - Some(Some(mut prev)) => { - let must_repair = prev != entry; - prev.merge(&entry); - if must_repair { - to_repair.insert(entry_key.clone(), Some(prev.clone())); + match ret.get_mut(&entry_key) { + Some(e) => { + if *e != entry { + e.merge(&entry); + to_repair.insert(entry_key.clone()); } - ret.insert(entry_key, Some(prev)); } - Some(None) => unreachable!(), + None => { + ret.insert(entry_key, entry); + } } } + } else { + return Err(Error::unexpected_rpc_message(resp)); } } + if !to_repair.is_empty() { let self2 = self.clone(); + let to_repair = to_repair + .into_iter() + .map(|k| ret.get(&k).unwrap().clone()) + .collect::<Vec<_>>(); self.system.background.spawn_cancellable(async move { - for (_, v) in to_repair.iter_mut() { - self2.repair_on_read(&who[..], v.take().unwrap()).await?; + for v in to_repair { + self2.repair_on_read(&who[..], v).await?; } Ok(()) }); } - let ret_vec = ret - .iter_mut() - .take(limit) - .map(|(_k, v)| v.take().unwrap()) - .collect::<Vec<_>>(); + + // At this point, the `ret` btreemap might contain more than `limit` + // items, because nodes might have returned us each `limit` items + // but for different keys. We have to take only the first `limit` items + // in this map, in the specified enumeration order, for two reasons: + // 1. To return to the user no more than the number of items that they requested + // 2. To return only items for which we have a read quorum: we do not know + // that we have a read quorum for the items after the first `limit` + // of them + let ret_vec = match enumeration_order { + EnumerationOrder::Forward => ret + .into_iter() + .take(limit) + .map(|(_k, v)| v) + .collect::<Vec<_>>(), + EnumerationOrder::Reverse => ret + .into_iter() + .rev() + .take(limit) + .map(|(_k, v)| v) + .collect::<Vec<_>>(), + }; Ok(ret_vec) } @@ -353,9 +400,7 @@ where &self.endpoint, who, TableRpc::<F>::Update(vec![what_enc]), - RequestStrategy::with_priority(PRIO_NORMAL) - .with_quorum(who.len()) - .with_timeout(TABLE_RPC_TIMEOUT), + RequestStrategy::with_priority(PRIO_NORMAL).with_quorum(who.len()), ) .await?; Ok(()) @@ -378,8 +423,20 @@ where let value = self.data.read_entry(key, sort_key)?; Ok(TableRpc::ReadEntryResponse(value)) } - TableRpc::ReadRange(key, begin_sort_key, filter, limit) => { - let values = self.data.read_range(key, begin_sort_key, filter, *limit)?; + TableRpc::ReadRange { + partition, + begin_sort_key, + filter, + limit, + enumeration_order, + } => { + let values = self.data.read_range( + partition, + begin_sort_key, + filter, + *limit, + *enumeration_order, + )?; Ok(TableRpc::Update(values)) } TableRpc::Update(pairs) => { diff --git a/src/table/util.rs b/src/table/util.rs index 2a5c3afe..20595a94 100644 --- a/src/table/util.rs +++ b/src/table/util.rs @@ -17,7 +17,7 @@ impl PartitionKey for EmptyKey { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum DeletedFilter { Any, Deleted, @@ -33,3 +33,19 @@ impl DeletedFilter { } } } + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum EnumerationOrder { + Forward, + Reverse, +} + +impl EnumerationOrder { + pub fn from_reverse(reverse: bool) -> Self { + if reverse { + Self::Reverse + } else { + Self::Forward + } + } +} diff --git a/src/util/Cargo.toml b/src/util/Cargo.toml index f13c1589..8e978fc2 100644 --- a/src/util/Cargo.toml +++ b/src/util/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "garage_util" -version = "0.7.0" +version = "0.8.0" authors = ["Alex Auvolat <alex@adnab.me>"] edition = "2018" license = "AGPL-3.0" @@ -14,15 +14,21 @@ path = "lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +garage_db = { version = "0.8.0", path = "../db" } + +arc-swap = "1.0" +async-trait = "0.1" blake2 = "0.9" +bytes = "1.0" +digest = "0.10" err-derive = "0.3" +git-version = "0.3.4" xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] } hex = "0.4" +lazy_static = "1.4" tracing = "0.1.30" rand = "0.8" -sha2 = "0.9" - -sled = "0.34" +sha2 = "0.10" chrono = "0.4" rmp-serde = "0.15" @@ -33,11 +39,13 @@ toml = "0.5" futures = "0.3" tokio = { version = "1.0", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "time", "macros", "sync", "signal", "fs"] } -#netapp = { version = "0.3.0", git = "https://git.deuxfleurs.fr/lx/netapp" } -#netapp = { version = "0.4", path = "../../../netapp" } -netapp = "0.4" +netapp = "0.5" http = "0.2" hyper = "0.14" opentelemetry = { version = "0.17", features = [ "rt-tokio", "metrics", "trace" ] } + + +[features] +k2v = [] diff --git a/src/util/async_hash.rs b/src/util/async_hash.rs new file mode 100644 index 00000000..5631ea6b --- /dev/null +++ b/src/util/async_hash.rs @@ -0,0 +1,61 @@ +use bytes::Bytes; +use digest::Digest; + +use tokio::sync::mpsc; +use tokio::task::JoinHandle; + +use crate::data::*; + +/// Compute the sha256 of a slice, +/// spawning on a tokio thread for CPU-intensive processing +/// The argument has to be an owned Bytes, as it is moved out to a new thread. +pub async fn async_sha256sum(data: Bytes) -> Hash { + tokio::task::spawn_blocking(move || sha256sum(&data)) + .await + .unwrap() +} + +/// Compute the blake2sum of a slice, +/// spawning on a tokio thread for CPU-intensive processing. +/// The argument has to be an owned Bytes, as it is moved out to a new thread. +pub async fn async_blake2sum(data: Bytes) -> Hash { + tokio::task::spawn_blocking(move || blake2sum(&data)) + .await + .unwrap() +} + +// ---- + +pub struct AsyncHasher<D: Digest> { + sendblk: mpsc::Sender<Bytes>, + task: JoinHandle<digest::Output<D>>, +} + +impl<D: Digest> AsyncHasher<D> { + pub fn new() -> Self { + let (sendblk, mut recvblk) = mpsc::channel::<Bytes>(1); + let task = tokio::task::spawn_blocking(move || { + let mut digest = D::new(); + while let Some(blk) = recvblk.blocking_recv() { + digest.update(&blk[..]); + } + digest.finalize() + }); + Self { sendblk, task } + } + + pub async fn update(&self, b: Bytes) { + self.sendblk.send(b).await.unwrap(); + } + + pub async fn finalize(self) -> digest::Output<D> { + drop(self.sendblk); + self.task.await.unwrap() + } +} + +impl<D: Digest> Default for AsyncHasher<D> { + fn default() -> Self { + Self::new() + } +} diff --git a/src/util/background.rs b/src/util/background.rs deleted file mode 100644 index bfdaaf1e..00000000 --- a/src/util/background.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! Job runner for futures and async functions -use core::future::Future; -use std::pin::Pin; -use std::sync::Arc; -use std::time::Duration; - -use futures::future::*; -use futures::select; -use tokio::sync::{mpsc, watch, Mutex}; - -use crate::error::Error; - -type JobOutput = Result<(), Error>; -type Job = Pin<Box<dyn Future<Output = JobOutput> + Send>>; - -/// Job runner for futures and async functions -pub struct BackgroundRunner { - stop_signal: watch::Receiver<bool>, - queue_in: mpsc::UnboundedSender<(Job, bool)>, - worker_in: mpsc::UnboundedSender<tokio::task::JoinHandle<()>>, -} - -impl BackgroundRunner { - /// Create a new BackgroundRunner - pub fn new( - n_runners: usize, - stop_signal: watch::Receiver<bool>, - ) -> (Arc<Self>, tokio::task::JoinHandle<()>) { - let (worker_in, mut worker_out) = mpsc::unbounded_channel(); - - let stop_signal_2 = stop_signal.clone(); - let await_all_done = tokio::spawn(async move { - loop { - let wkr = { - select! { - item = worker_out.recv().fuse() => { - match item { - Some(x) => x, - None => break, - } - } - _ = tokio::time::sleep(Duration::from_secs(5)).fuse() => { - if *stop_signal_2.borrow() { - break; - } else { - continue; - } - } - } - }; - if let Err(e) = wkr.await { - error!("Error while awaiting for worker: {}", e); - } - } - }); - - let (queue_in, queue_out) = mpsc::unbounded_channel(); - let queue_out = Arc::new(Mutex::new(queue_out)); - - for i in 0..n_runners { - let queue_out = queue_out.clone(); - let stop_signal = stop_signal.clone(); - - worker_in - .send(tokio::spawn(async move { - loop { - let (job, cancellable) = { - select! { - item = wait_job(&queue_out).fuse() => match item { - // We received a task, process it - Some(x) => x, - // We received a signal that no more tasks will ever be sent - // because the sending side was dropped. Exit now. - None => break, - }, - _ = tokio::time::sleep(Duration::from_secs(5)).fuse() => { - if *stop_signal.borrow() { - // Nothing has been going on for 5 secs, and we are shutting - // down. Exit now. - break; - } else { - // Nothing is going on but we don't want to exit. - continue; - } - } - } - }; - if cancellable && *stop_signal.borrow() { - continue; - } - if let Err(e) = job.await { - error!("Job failed: {}", e) - } - } - info!("Background worker {} exiting", i); - })) - .unwrap(); - } - - let bgrunner = Arc::new(Self { - stop_signal, - queue_in, - worker_in, - }); - (bgrunner, await_all_done) - } - - /// Spawn a task to be run in background - pub fn spawn<T>(&self, job: T) - where - T: Future<Output = JobOutput> + Send + 'static, - { - let boxed: Job = Box::pin(job); - self.queue_in - .send((boxed, false)) - .map_err(|_| "could not put job in queue") - .unwrap(); - } - - /// Spawn a task to be run in background. It may get discarded before running if spawned while - /// the runner is stopping - pub fn spawn_cancellable<T>(&self, job: T) - where - T: Future<Output = JobOutput> + Send + 'static, - { - let boxed: Job = Box::pin(job); - self.queue_in - .send((boxed, true)) - .map_err(|_| "could not put job in queue") - .unwrap(); - } - - pub fn spawn_worker<F, T>(&self, name: String, worker: F) - where - F: FnOnce(watch::Receiver<bool>) -> T + Send + 'static, - T: Future<Output = ()> + Send + 'static, - { - let stop_signal = self.stop_signal.clone(); - let task = tokio::spawn(async move { - info!("Worker started: {}", name); - worker(stop_signal).await; - info!("Worker exited: {}", name); - }); - self.worker_in - .send(task) - .map_err(|_| "could not put job in queue") - .unwrap(); - } -} - -async fn wait_job(q: &Mutex<mpsc::UnboundedReceiver<(Job, bool)>>) -> Option<(Job, bool)> { - q.lock().await.recv().await -} diff --git a/src/util/background/job_worker.rs b/src/util/background/job_worker.rs new file mode 100644 index 00000000..2568ea11 --- /dev/null +++ b/src/util/background/job_worker.rs @@ -0,0 +1,48 @@ +//! Job worker: a generic worker that just processes incoming +//! jobs one by one + +use std::sync::Arc; + +use async_trait::async_trait; +use tokio::sync::{mpsc, Mutex}; + +use crate::background::worker::*; +use crate::background::*; + +pub(crate) struct JobWorker { + pub(crate) index: usize, + pub(crate) job_chan: Arc<Mutex<mpsc::UnboundedReceiver<(Job, bool)>>>, + pub(crate) next_job: Option<Job>, +} + +#[async_trait] +impl Worker for JobWorker { + fn name(&self) -> String { + format!("Job worker #{}", self.index) + } + + async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> { + match self.next_job.take() { + None => return Ok(WorkerState::Idle), + Some(job) => { + job.await?; + Ok(WorkerState::Busy) + } + } + } + + async fn wait_for_work(&mut self, must_exit: &watch::Receiver<bool>) -> WorkerState { + loop { + match self.job_chan.lock().await.recv().await { + Some((job, cancellable)) => { + if cancellable && *must_exit.borrow() { + continue; + } + self.next_job = Some(job); + return WorkerState::Busy; + } + None => return WorkerState::Done, + } + } + } +} diff --git a/src/util/background/mod.rs b/src/util/background/mod.rs new file mode 100644 index 00000000..619f5068 --- /dev/null +++ b/src/util/background/mod.rs @@ -0,0 +1,117 @@ +//! Job runner for futures and async functions + +pub mod job_worker; +pub mod worker; + +use core::future::Future; + +use std::collections::HashMap; +use std::pin::Pin; +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; +use tokio::sync::{mpsc, watch, Mutex}; + +use crate::error::Error; +use worker::WorkerProcessor; +pub use worker::{Worker, WorkerState}; + +pub(crate) type JobOutput = Result<(), Error>; +pub(crate) type Job = Pin<Box<dyn Future<Output = JobOutput> + Send>>; + +/// Job runner for futures and async functions +pub struct BackgroundRunner { + send_job: mpsc::UnboundedSender<(Job, bool)>, + send_worker: mpsc::UnboundedSender<Box<dyn Worker>>, + worker_info: Arc<std::sync::Mutex<HashMap<usize, WorkerInfo>>>, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct WorkerInfo { + pub name: String, + pub info: Option<String>, + pub state: WorkerState, + pub errors: usize, + pub consecutive_errors: usize, + pub last_error: Option<(String, u64)>, +} + +impl BackgroundRunner { + /// Create a new BackgroundRunner + pub fn new( + n_runners: usize, + stop_signal: watch::Receiver<bool>, + ) -> (Arc<Self>, tokio::task::JoinHandle<()>) { + let (send_worker, worker_out) = mpsc::unbounded_channel::<Box<dyn Worker>>(); + + let worker_info = Arc::new(std::sync::Mutex::new(HashMap::new())); + let mut worker_processor = + WorkerProcessor::new(worker_out, stop_signal, worker_info.clone()); + + let await_all_done = tokio::spawn(async move { + worker_processor.run().await; + }); + + let (send_job, queue_out) = mpsc::unbounded_channel(); + let queue_out = Arc::new(Mutex::new(queue_out)); + + for i in 0..n_runners { + let queue_out = queue_out.clone(); + + send_worker + .send(Box::new(job_worker::JobWorker { + index: i, + job_chan: queue_out.clone(), + next_job: None, + })) + .ok() + .unwrap(); + } + + let bgrunner = Arc::new(Self { + send_job, + send_worker, + worker_info, + }); + (bgrunner, await_all_done) + } + + pub fn get_worker_info(&self) -> HashMap<usize, WorkerInfo> { + self.worker_info.lock().unwrap().clone() + } + + /// Spawn a task to be run in background + pub fn spawn<T>(&self, job: T) + where + T: Future<Output = JobOutput> + Send + 'static, + { + let boxed: Job = Box::pin(job); + self.send_job + .send((boxed, false)) + .ok() + .expect("Could not put job in queue"); + } + + /// Spawn a task to be run in background. It may get discarded before running if spawned while + /// the runner is stopping + pub fn spawn_cancellable<T>(&self, job: T) + where + T: Future<Output = JobOutput> + Send + 'static, + { + let boxed: Job = Box::pin(job); + self.send_job + .send((boxed, true)) + .ok() + .expect("Could not put job in queue"); + } + + pub fn spawn_worker<W>(&self, worker: W) + where + W: Worker + 'static, + { + self.send_worker + .send(Box::new(worker)) + .ok() + .expect("Could not put worker in queue"); + } +} diff --git a/src/util/background/worker.rs b/src/util/background/worker.rs new file mode 100644 index 00000000..f5e3addb --- /dev/null +++ b/src/util/background/worker.rs @@ -0,0 +1,260 @@ +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use async_trait::async_trait; +use futures::future::*; +use futures::stream::FuturesUnordered; +use futures::StreamExt; +use serde::{Deserialize, Serialize}; +use tokio::select; +use tokio::sync::{mpsc, watch}; + +use crate::background::WorkerInfo; +use crate::error::Error; +use crate::time::now_msec; + +#[derive(PartialEq, Copy, Clone, Serialize, Deserialize, Debug)] +pub enum WorkerState { + Busy, + Throttled(f32), + Idle, + Done, +} + +impl std::fmt::Display for WorkerState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WorkerState::Busy => write!(f, "Busy"), + WorkerState::Throttled(t) => write!(f, "Thr:{:.3}", t), + WorkerState::Idle => write!(f, "Idle"), + WorkerState::Done => write!(f, "Done"), + } + } +} + +#[async_trait] +pub trait Worker: Send { + fn name(&self) -> String; + + fn info(&self) -> Option<String> { + None + } + + /// Work: do a basic unit of work, if one is available (otherwise, should return + /// WorkerState::Idle immediately). We will do our best to not interrupt this future in the + /// middle of processing, it will only be interrupted at the last minute when Garage is trying + /// to exit and this hasn't returned yet. This function may return an error to indicate that + /// its unit of work could not be processed due to an error: the error will be logged and + /// .work() will be called again after a short delay. + async fn work(&mut self, must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error>; + + /// Wait for work: await for some task to become available. This future can be interrupted in + /// the middle for any reason. This future doesn't have to await on must_exit.changed(), we + /// are doing it for you. Therefore it only receives a read refernce to must_exit which allows + /// it to check if we are exiting. + async fn wait_for_work(&mut self, must_exit: &watch::Receiver<bool>) -> WorkerState; +} + +pub(crate) struct WorkerProcessor { + stop_signal: watch::Receiver<bool>, + worker_chan: mpsc::UnboundedReceiver<Box<dyn Worker>>, + worker_info: Arc<std::sync::Mutex<HashMap<usize, WorkerInfo>>>, +} + +impl WorkerProcessor { + pub(crate) fn new( + worker_chan: mpsc::UnboundedReceiver<Box<dyn Worker>>, + stop_signal: watch::Receiver<bool>, + worker_info: Arc<std::sync::Mutex<HashMap<usize, WorkerInfo>>>, + ) -> Self { + Self { + stop_signal, + worker_chan, + worker_info, + } + } + + pub(crate) async fn run(&mut self) { + let mut workers = FuturesUnordered::new(); + let mut next_task_id = 1; + + while !*self.stop_signal.borrow() { + let await_next_worker = async { + if workers.is_empty() { + futures::future::pending().await + } else { + workers.next().await + } + }; + select! { + new_worker_opt = self.worker_chan.recv() => { + if let Some(new_worker) = new_worker_opt { + let task_id = next_task_id; + next_task_id += 1; + let stop_signal = self.stop_signal.clone(); + let stop_signal_worker = self.stop_signal.clone(); + let mut worker = WorkerHandler { + task_id, + stop_signal, + stop_signal_worker, + worker: new_worker, + state: WorkerState::Busy, + errors: 0, + consecutive_errors: 0, + last_error: None, + }; + workers.push(async move { + worker.step().await; + worker + }.boxed()); + } + } + worker = await_next_worker => { + if let Some(mut worker) = worker { + trace!("{} (TID {}): {:?}", worker.worker.name(), worker.task_id, worker.state); + + // Save worker info + let mut wi = self.worker_info.lock().unwrap(); + match wi.get_mut(&worker.task_id) { + Some(i) => { + i.state = worker.state; + i.info = worker.worker.info(); + i.errors = worker.errors; + i.consecutive_errors = worker.consecutive_errors; + if worker.last_error.is_some() { + i.last_error = worker.last_error.take(); + } + } + None => { + wi.insert(worker.task_id, WorkerInfo { + name: worker.worker.name(), + state: worker.state, + info: worker.worker.info(), + errors: worker.errors, + consecutive_errors: worker.consecutive_errors, + last_error: worker.last_error.take(), + }); + } + } + + if worker.state == WorkerState::Done { + info!("Worker {} (TID {}) exited", worker.worker.name(), worker.task_id); + } else { + workers.push(async move { + worker.step().await; + worker + }.boxed()); + } + } + } + _ = self.stop_signal.changed() => (), + } + } + + // We are exiting, drain everything + let drain_half_time = Instant::now() + Duration::from_secs(5); + let drain_everything = async move { + while let Some(mut worker) = workers.next().await { + if worker.state == WorkerState::Done { + info!( + "Worker {} (TID {}) exited", + worker.worker.name(), + worker.task_id + ); + } else if Instant::now() > drain_half_time { + warn!("Worker {} (TID {}) interrupted between two iterations in state {:?} (this should be fine)", worker.worker.name(), worker.task_id, worker.state); + } else { + workers.push( + async move { + worker.step().await; + worker + } + .boxed(), + ); + } + } + }; + + select! { + _ = drain_everything => { + info!("All workers exited peacefully \\o/"); + } + _ = tokio::time::sleep(Duration::from_secs(9)) => { + error!("Some workers could not exit in time, we are cancelling some things in the middle"); + } + } + } +} + +struct WorkerHandler { + task_id: usize, + stop_signal: watch::Receiver<bool>, + stop_signal_worker: watch::Receiver<bool>, + worker: Box<dyn Worker>, + state: WorkerState, + errors: usize, + consecutive_errors: usize, + last_error: Option<(String, u64)>, +} + +impl WorkerHandler { + async fn step(&mut self) { + match self.state { + WorkerState::Busy => match self.worker.work(&mut self.stop_signal).await { + Ok(s) => { + self.state = s; + self.consecutive_errors = 0; + } + Err(e) => { + error!( + "Error in worker {} (TID {}): {}", + self.worker.name(), + self.task_id, + e + ); + self.errors += 1; + self.consecutive_errors += 1; + self.last_error = Some((format!("{}", e), now_msec())); + // Sleep a bit so that error won't repeat immediately, exponential backoff + // strategy (min 1sec, max ~60sec) + self.state = WorkerState::Throttled( + (1.5f32).powf(std::cmp::min(10, self.consecutive_errors - 1) as f32), + ); + } + }, + WorkerState::Throttled(delay) => { + // Sleep for given delay and go back to busy state + if !*self.stop_signal.borrow() { + select! { + _ = tokio::time::sleep(Duration::from_secs_f32(delay)) => (), + _ = self.stop_signal.changed() => (), + } + } + self.state = WorkerState::Busy; + } + WorkerState::Idle => { + if *self.stop_signal.borrow() { + select! { + new_st = self.worker.wait_for_work(&self.stop_signal_worker) => { + self.state = new_st; + } + _ = tokio::time::sleep(Duration::from_secs(1)) => { + // stay in Idle state + } + } + } else { + select! { + new_st = self.worker.wait_for_work(&self.stop_signal_worker) => { + self.state = new_st; + } + _ = self.stop_signal.changed() => { + // stay in Idle state + } + } + } + } + WorkerState::Done => unreachable!(), + } + } +} diff --git a/src/util/config.rs b/src/util/config.rs index e4d96476..2d4b4f57 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -3,12 +3,8 @@ use std::io::Read; use std::net::SocketAddr; use std::path::PathBuf; -use serde::de::Error as SerdeError; use serde::{de, Deserialize}; -use netapp::util::parse_and_resolve_peer_addr; -use netapp::NodeID; - use crate::error::Error; /// Represent the whole configuration @@ -23,10 +19,6 @@ pub struct Config { #[serde(default = "default_block_size")] pub block_size: usize, - /// Size of data blocks to save to disk - #[serde(default = "default_block_manager_background_tranquility")] - pub block_manager_background_tranquility: u32, - /// Replication mode. Supported values: /// - none, 1 -> no replication /// - 2 -> 2-way replication @@ -47,11 +39,16 @@ pub struct Config { /// Address to bind for RPC pub rpc_bind_addr: SocketAddr, /// Public IP address of this node - pub rpc_public_addr: Option<SocketAddr>, + pub rpc_public_addr: Option<String>, + + /// Timeout for Netapp's ping messagess + pub rpc_ping_timeout_msec: Option<u64>, + /// Timeout for Netapp RPC calls + pub rpc_timeout_msec: Option<u64>, /// Bootstrap peers RPC address - #[serde(deserialize_with = "deserialize_vec_addr", default)] - pub bootstrap_peers: Vec<(NodeID, SocketAddr)>, + #[serde(default)] + pub bootstrap_peers: Vec<String>, /// Consul host to connect to to discover more peers pub consul_host: Option<String>, /// Consul service name to use @@ -64,19 +61,27 @@ pub struct Config { #[serde(default)] pub kubernetes_skip_crd: bool, + // -- DB + /// Database engine to use for metadata (options: sled, sqlite, lmdb) + #[serde(default = "default_db_engine")] + pub db_engine: String, + /// Sled cache size, in bytes #[serde(default = "default_sled_cache_capacity")] pub sled_cache_capacity: u64, - /// Sled flush interval in milliseconds #[serde(default = "default_sled_flush_every_ms")] pub sled_flush_every_ms: u64, + // -- APIs /// Configuration for S3 api - pub s3_api: ApiConfig, + pub s3_api: S3ApiConfig, + + /// Configuration for K2V api + pub k2v_api: Option<K2VApiConfig>, /// Configuration for serving files as normal web server - pub s3_web: WebConfig, + pub s3_web: Option<WebConfig>, /// Configuration for the admin API endpoint #[serde(default = "Default::default")] @@ -85,9 +90,9 @@ pub struct Config { /// Configuration for S3 api #[derive(Deserialize, Debug, Clone)] -pub struct ApiConfig { +pub struct S3ApiConfig { /// Address and port to bind for api serving - pub api_bind_addr: SocketAddr, + pub api_bind_addr: Option<SocketAddr>, /// S3 region to use pub s3_region: String, /// Suffix to remove from domain name to find bucket. If None, @@ -95,6 +100,13 @@ pub struct ApiConfig { pub root_domain: Option<String>, } +/// Configuration for K2V api +#[derive(Deserialize, Debug, Clone)] +pub struct K2VApiConfig { + /// Address and port to bind for api serving + pub api_bind_addr: SocketAddr, +} + /// Configuration for serving files as normal web server #[derive(Deserialize, Debug, Clone)] pub struct WebConfig { @@ -109,10 +121,18 @@ pub struct WebConfig { pub struct AdminConfig { /// Address and port to bind for admin API serving pub api_bind_addr: Option<SocketAddr>, + /// Bearer token to use to scrape metrics + pub metrics_token: Option<String>, + /// Bearer token to use to access Admin API endpoints + pub admin_token: Option<String>, /// OTLP server to where to export traces pub trace_sink: Option<String>, } +fn default_db_engine() -> String { + "sled".into() +} + fn default_sled_cache_capacity() -> u64 { 128 * 1024 * 1024 } @@ -122,9 +142,6 @@ fn default_sled_flush_every_ms() -> u64 { fn default_block_size() -> usize { 1048576 } -fn default_block_manager_background_tranquility() -> u32 { - 2 -} /// Read and parse configuration pub fn read_config(config_file: PathBuf) -> Result<Config, Error> { @@ -138,24 +155,6 @@ pub fn read_config(config_file: PathBuf) -> Result<Config, Error> { Ok(toml::from_str(&config)?) } -fn deserialize_vec_addr<'de, D>(deserializer: D) -> Result<Vec<(NodeID, SocketAddr)>, D::Error> -where - D: de::Deserializer<'de>, -{ - let mut ret = vec![]; - - for peer in <Vec<&str>>::deserialize(deserializer)? { - let (pubkey, addrs) = parse_and_resolve_peer_addr(peer).ok_or_else(|| { - D::Error::custom(format!("Unable to parse or resolve peer: {}", peer)) - })?; - for ip in addrs { - ret.push((pubkey, ip)); - } - } - - Ok(ret) -} - fn default_compression() -> Option<i32> { Some(1) } diff --git a/src/util/crdt/bool.rs b/src/util/crdt/bool.rs index 53af8f82..111eb5f1 100644 --- a/src/util/crdt/bool.rs +++ b/src/util/crdt/bool.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::crdt::crdt::*; /// Boolean, where `true` is an absorbing state -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Bool(bool); impl Bool { diff --git a/src/util/crdt/deletable.rs b/src/util/crdt/deletable.rs index c76f5cbb..e771aceb 100644 --- a/src/util/crdt/deletable.rs +++ b/src/util/crdt/deletable.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::crdt::crdt::*; /// Deletable object (once deleted, cannot go back) -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum Deletable<T> { Present(T), Deleted, diff --git a/src/util/crdt/lww.rs b/src/util/crdt/lww.rs index 254abe8e..958844c9 100644 --- a/src/util/crdt/lww.rs +++ b/src/util/crdt/lww.rs @@ -37,7 +37,7 @@ use crate::crdt::crdt::*; /// /// This scheme is used by AWS S3 or Soundcloud and often without knowing /// in enterprise when reconciliating databases with ad-hoc scripts. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Lww<T> { ts: u64, v: T, diff --git a/src/util/crdt/lww_map.rs b/src/util/crdt/lww_map.rs index c155c3a8..88113856 100644 --- a/src/util/crdt/lww_map.rs +++ b/src/util/crdt/lww_map.rs @@ -23,7 +23,7 @@ use crate::crdt::crdt::*; /// However, note that even if we were using a more efficient data structure such as a `BTreeMap`, /// the serialization cost `O(n)` would still have to be paid at each modification, so we are /// actually not losing anything here. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct LwwMap<K, V> { vals: Vec<(K, u64, V)>, } @@ -140,6 +140,11 @@ where self.vals.clear(); } + /// Retain only values that match a certain predicate + pub fn retain(&mut self, pred: impl FnMut(&(K, u64, V)) -> bool) { + self.vals.retain(pred); + } + /// Get a reference to the value assigned to a key pub fn get(&self, k: &K) -> Option<&V> { match self.vals.binary_search_by(|(k2, _, _)| k2.cmp(k)) { diff --git a/src/util/crdt/map.rs b/src/util/crdt/map.rs index f9ed19b6..5d1e1520 100644 --- a/src/util/crdt/map.rs +++ b/src/util/crdt/map.rs @@ -16,7 +16,7 @@ use crate::crdt::crdt::*; /// However, note that even if we were using a more efficient data structure such as a `BTreeMap`, /// the serialization cost `O(n)` would still have to be paid at each modification, so we are /// actually not losing anything here. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Map<K, V> { vals: Vec<(K, V)>, } diff --git a/src/util/error.rs b/src/util/error.rs index bdb3a69b..9995c746 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -26,8 +26,8 @@ pub enum Error { #[error(display = "Netapp error: {}", _0)] Netapp(#[error(source)] netapp::error::Error), - #[error(display = "Sled error: {}", _0)] - Sled(#[error(source)] sled::Error), + #[error(display = "DB error: {}", _0)] + Db(#[error(source)] garage_db::Error), #[error(display = "Messagepack encode error: {}", _0)] RmpEncode(#[error(source)] rmp_serde::encode::Error), @@ -44,6 +44,9 @@ pub enum Error { #[error(display = "Tokio semaphore acquire error: {}", _0)] TokioSemAcquire(#[error(source)] tokio::sync::AcquireError), + #[error(display = "Tokio broadcast receive error: {}", _0)] + TokioBcastRecv(#[error(source)] tokio::sync::broadcast::error::RecvError), + #[error(display = "Remote error: {}", _0)] RemoteError(String), @@ -75,11 +78,11 @@ impl Error { } } -impl From<sled::transaction::TransactionError<Error>> for Error { - fn from(e: sled::transaction::TransactionError<Error>) -> Error { +impl From<garage_db::TxError<Error>> for Error { + fn from(e: garage_db::TxError<Error>) -> Error { match e { - sled::transaction::TransactionError::Abort(x) => x, - sled::transaction::TransactionError::Storage(x) => Error::Sled(x), + garage_db::TxError::Abort(x) => x, + garage_db::TxError::Db(x) => Error::Db(x), } } } diff --git a/src/util/formater.rs b/src/util/formater.rs new file mode 100644 index 00000000..95324f9a --- /dev/null +++ b/src/util/formater.rs @@ -0,0 +1,28 @@ +pub fn format_table(data: Vec<String>) { + let data = data + .iter() + .map(|s| s.split('\t').collect::<Vec<_>>()) + .collect::<Vec<_>>(); + + let columns = data.iter().map(|row| row.len()).fold(0, std::cmp::max); + let mut column_size = vec![0; columns]; + + let mut out = String::new(); + + for row in data.iter() { + for (i, col) in row.iter().enumerate() { + column_size[i] = std::cmp::max(column_size[i], col.chars().count()); + } + } + + for row in data.iter() { + for (col, col_len) in row[..row.len() - 1].iter().zip(column_size.iter()) { + out.push_str(col); + (0..col_len - col.chars().count() + 2).for_each(|_| out.push(' ')); + } + out.push_str(row[row.len() - 1]); + out.push('\n'); + } + + print!("{}", out); +} diff --git a/src/util/lib.rs b/src/util/lib.rs index e83fc2e6..264cc192 100644 --- a/src/util/lib.rs +++ b/src/util/lib.rs @@ -3,14 +3,16 @@ #[macro_use] extern crate tracing; +pub mod async_hash; pub mod background; pub mod config; pub mod crdt; pub mod data; pub mod error; +pub mod formater; pub mod metrics; pub mod persister; -pub mod sled_counter; pub mod time; pub mod token_bucket; pub mod tranquilizer; +pub mod version; diff --git a/src/util/metrics.rs b/src/util/metrics.rs index 1b05eabe..b882a886 100644 --- a/src/util/metrics.rs +++ b/src/util/metrics.rs @@ -1,4 +1,4 @@ -use std::time::SystemTime; +use std::time::Instant; use futures::{future::BoxFuture, Future, FutureExt}; use rand::Rng; @@ -28,10 +28,12 @@ where attributes: &'a [KeyValue], ) -> BoxFuture<'a, Self::Output> { async move { - let request_start = SystemTime::now(); + let request_start = Instant::now(); let res = self.await; r.record( - request_start.elapsed().map_or(0.0, |d| d.as_secs_f64()), + Instant::now() + .saturating_duration_since(request_start) + .as_secs_f64(), attributes, ); res @@ -41,9 +43,13 @@ where fn bound_record_duration(self, r: &'a BoundValueRecorder<f64>) -> BoxFuture<'a, Self::Output> { async move { - let request_start = SystemTime::now(); + let request_start = Instant::now(); let res = self.await; - r.record(request_start.elapsed().map_or(0.0, |d| d.as_secs_f64())); + r.record( + Instant::now() + .saturating_duration_since(request_start) + .as_secs_f64(), + ); res } .boxed() diff --git a/src/util/sled_counter.rs b/src/util/sled_counter.rs deleted file mode 100644 index bc54cea0..00000000 --- a/src/util/sled_counter.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, -}; - -use sled::{CompareAndSwapError, IVec, Iter, Result, Tree}; - -#[derive(Clone)] -pub struct SledCountedTree(Arc<SledCountedTreeInternal>); - -struct SledCountedTreeInternal { - tree: Tree, - len: AtomicUsize, -} - -impl SledCountedTree { - pub fn new(tree: Tree) -> Self { - let len = tree.len(); - Self(Arc::new(SledCountedTreeInternal { - tree, - len: AtomicUsize::new(len), - })) - } - - pub fn len(&self) -> usize { - self.0.len.load(Ordering::Relaxed) - } - - pub fn is_empty(&self) -> bool { - self.0.tree.is_empty() - } - - pub fn get<K: AsRef<[u8]>>(&self, key: K) -> Result<Option<IVec>> { - self.0.tree.get(key) - } - - pub fn iter(&self) -> Iter { - self.0.tree.iter() - } - - // ---- writing functions ---- - - pub fn insert<K, V>(&self, key: K, value: V) -> Result<Option<IVec>> - where - K: AsRef<[u8]>, - V: Into<IVec>, - { - let res = self.0.tree.insert(key, value); - if res == Ok(None) { - self.0.len.fetch_add(1, Ordering::Relaxed); - } - res - } - - pub fn remove<K: AsRef<[u8]>>(&self, key: K) -> Result<Option<IVec>> { - let res = self.0.tree.remove(key); - if matches!(res, Ok(Some(_))) { - self.0.len.fetch_sub(1, Ordering::Relaxed); - } - res - } - - pub fn pop_min(&self) -> Result<Option<(IVec, IVec)>> { - let res = self.0.tree.pop_min(); - if let Ok(Some(_)) = &res { - self.0.len.fetch_sub(1, Ordering::Relaxed); - }; - res - } - - pub fn compare_and_swap<K, OV, NV>( - &self, - key: K, - old: Option<OV>, - new: Option<NV>, - ) -> Result<std::result::Result<(), CompareAndSwapError>> - where - K: AsRef<[u8]>, - OV: AsRef<[u8]>, - NV: Into<IVec>, - { - let old_some = old.is_some(); - let new_some = new.is_some(); - - let res = self.0.tree.compare_and_swap(key, old, new); - - if res == Ok(Ok(())) { - match (old_some, new_some) { - (false, true) => { - self.0.len.fetch_add(1, Ordering::Relaxed); - } - (true, false) => { - self.0.len.fetch_sub(1, Ordering::Relaxed); - } - _ => (), - } - } - res - } -} diff --git a/src/util/tranquilizer.rs b/src/util/tranquilizer.rs index 28711387..8a96cbb3 100644 --- a/src/util/tranquilizer.rs +++ b/src/util/tranquilizer.rs @@ -3,6 +3,8 @@ use std::time::{Duration, Instant}; use tokio::time::sleep; +use crate::background::WorkerState; + /// A tranquilizer is a helper object that is used to make /// background operations not take up too much time. /// @@ -33,8 +35,8 @@ impl Tranquilizer { } } - pub async fn tranquilize(&mut self, tranquility: u32) { - let observation = Instant::now() - self.last_step_begin; + fn tranquilize_internal(&mut self, tranquility: u32) -> Option<Duration> { + let observation = Instant::now().saturating_duration_since(self.last_step_begin); self.observations.push_back(observation); self.sum_observations += observation; @@ -45,13 +47,32 @@ impl Tranquilizer { if !self.observations.is_empty() { let delay = (tranquility * self.sum_observations) / (self.observations.len() as u32); + Some(delay) + } else { + None + } + } + + pub async fn tranquilize(&mut self, tranquility: u32) { + if let Some(delay) = self.tranquilize_internal(tranquility) { sleep(delay).await; + self.reset(); } + } - self.reset(); + #[must_use] + pub fn tranquilize_worker(&mut self, tranquility: u32) -> WorkerState { + match self.tranquilize_internal(tranquility) { + Some(delay) => WorkerState::Throttled(delay.as_secs_f32()), + None => WorkerState::Busy, + } } pub fn reset(&mut self) { self.last_step_begin = Instant::now(); } + + pub fn clear(&mut self) { + self.observations.clear(); + } } diff --git a/src/util/version.rs b/src/util/version.rs new file mode 100644 index 00000000..b515dccc --- /dev/null +++ b/src/util/version.rs @@ -0,0 +1,28 @@ +use std::sync::Arc; + +use arc_swap::{ArcSwap, ArcSwapOption}; + +lazy_static::lazy_static! { + static ref VERSION: ArcSwap<&'static str> = ArcSwap::new(Arc::new(git_version::git_version!( + prefix = "git:", + cargo_prefix = "cargo:", + fallback = "unknown" + ))); + static ref FEATURES: ArcSwapOption<&'static [&'static str]> = ArcSwapOption::new(None); +} + +pub fn garage_version() -> &'static str { + &VERSION.load() +} + +pub fn garage_features() -> Option<&'static [&'static str]> { + FEATURES.load().as_ref().map(|f| &f[..]) +} + +pub fn init_version(version: &'static str) { + VERSION.store(Arc::new(version)); +} + +pub fn init_features(features: &'static [&'static str]) { + FEATURES.store(Some(Arc::new(features))); +} diff --git a/src/web/Cargo.toml b/src/web/Cargo.toml index 59a1231d..7bf70c55 100644 --- a/src/web/Cargo.toml +++ b/src/web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "garage_web" -version = "0.7.0" +version = "0.8.0" authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"] edition = "2018" license = "AGPL-3.0" @@ -14,10 +14,10 @@ path = "lib.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -garage_api = { version = "0.7.0", path = "../api" } -garage_model = { version = "0.7.0", path = "../model" } -garage_util = { version = "0.7.0", path = "../util" } -garage_table = { version = "0.7.0", path = "../table" } +garage_api = { version = "0.8.0", path = "../api" } +garage_model = { version = "0.8.0", path = "../model" } +garage_util = { version = "0.8.0", path = "../util" } +garage_table = { version = "0.8.0", path = "../table" } err-derive = "0.3" tracing = "0.1.30" diff --git a/src/web/error.rs b/src/web/error.rs index 55990e9d..bd8f17b5 100644 --- a/src/web/error.rs +++ b/src/web/error.rs @@ -2,57 +2,47 @@ use err_derive::Error; use hyper::header::HeaderValue; use hyper::{HeaderMap, StatusCode}; -use garage_util::error::Error as GarageError; +use garage_api::generic_server::ApiError; /// Errors of this crate #[derive(Debug, Error)] pub enum Error { /// An error received from the API crate #[error(display = "API error: {}", _0)] - ApiError(#[error(source)] garage_api::Error), - - // Category: internal error - /// Error internal to garage - #[error(display = "Internal error: {}", _0)] - InternalError(#[error(source)] GarageError), + ApiError(garage_api::s3::error::Error), /// The file does not exist #[error(display = "Not found")] NotFound, - /// The request contained an invalid UTF-8 sequence in its path or in other parameters - #[error(display = "Invalid UTF-8: {}", _0)] - InvalidUtf8(#[error(source)] std::str::Utf8Error), - - /// The client send a header with invalid value - #[error(display = "Invalid header value: {}", _0)] - InvalidHeader(#[error(source)] hyper::header::ToStrError), - /// The client sent a request without host, or with unsupported method #[error(display = "Bad request: {}", _0)] BadRequest(String), } +impl<T> From<T> for Error +where + garage_api::s3::error::Error: From<T>, +{ + fn from(err: T) -> Self { + Error::ApiError(garage_api::s3::error::Error::from(err)) + } +} + impl Error { /// Transform errors into http status code pub fn http_status_code(&self) -> StatusCode { match self { Error::NotFound => StatusCode::NOT_FOUND, Error::ApiError(e) => e.http_status_code(), - Error::InternalError( - GarageError::Timeout - | GarageError::RemoteError(_) - | GarageError::Quorum(_, _, _, _), - ) => StatusCode::SERVICE_UNAVAILABLE, - Error::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, - _ => StatusCode::BAD_REQUEST, + Error::BadRequest(_) => StatusCode::BAD_REQUEST, } } pub fn add_headers(&self, header_map: &mut HeaderMap<HeaderValue>) { #[allow(clippy::single_match)] match self { - Error::ApiError(e) => e.add_headers(header_map), + Error::ApiError(e) => e.add_http_headers(header_map), _ => (), } } diff --git a/src/web/lib.rs b/src/web/lib.rs index 9b7c8573..7207c365 100644 --- a/src/web/lib.rs +++ b/src/web/lib.rs @@ -6,4 +6,4 @@ mod error; pub use error::Error; mod web_server; -pub use web_server::run_web_server; +pub use web_server::WebServer; diff --git a/src/web/web_server.rs b/src/web/web_server.rs index c3d691d0..c2322073 100644 --- a/src/web/web_server.rs +++ b/src/web/web_server.rs @@ -18,10 +18,12 @@ use opentelemetry::{ use crate::error::*; -use garage_api::error::{Error as ApiError, OkOrBadRequest, OkOrInternalError}; use garage_api::helpers::{authority_to_host, host_to_bucket}; -use garage_api::s3_cors::{add_cors_headers, find_matching_cors_rule, handle_options_for_bucket}; -use garage_api::s3_get::{handle_get, handle_head}; +use garage_api::s3::cors::{add_cors_headers, find_matching_cors_rule, handle_options_for_bucket}; +use garage_api::s3::error::{ + CommonErrorDerivative, Error as ApiError, OkOrBadRequest, OkOrInternalError, +}; +use garage_api::s3::get::{handle_get, handle_head}; use garage_model::garage::Garage; @@ -55,90 +57,226 @@ impl WebMetrics { } } -/// Run a web server -pub async fn run_web_server( +pub struct WebServer { garage: Arc<Garage>, - shutdown_signal: impl Future<Output = ()>, -) -> Result<(), GarageError> { - let addr = &garage.config.s3_web.bind_addr; + metrics: Arc<WebMetrics>, + root_domain: String, +} - let metrics = Arc::new(WebMetrics::new()); +impl WebServer { + /// Run a web server + pub async fn run( + garage: Arc<Garage>, + addr: SocketAddr, + root_domain: String, + shutdown_signal: impl Future<Output = ()>, + ) -> Result<(), GarageError> { + let metrics = Arc::new(WebMetrics::new()); + let web_server = Arc::new(WebServer { + garage, + metrics, + root_domain, + }); + + let service = make_service_fn(|conn: &AddrStream| { + let web_server = web_server.clone(); + + let client_addr = conn.remote_addr(); + async move { + Ok::<_, Error>(service_fn(move |req: Request<Body>| { + let web_server = web_server.clone(); + + web_server.handle_request(req, client_addr) + })) + } + }); - let service = make_service_fn(|conn: &AddrStream| { - let garage = garage.clone(); - let metrics = metrics.clone(); + let server = Server::bind(&addr).serve(service); + let graceful = server.with_graceful_shutdown(shutdown_signal); + info!("Web server listening on http://{}", addr); - let client_addr = conn.remote_addr(); - async move { - Ok::<_, Error>(service_fn(move |req: Request<Body>| { - let garage = garage.clone(); - let metrics = metrics.clone(); + graceful.await?; + Ok(()) + } - handle_request(garage, metrics, req, client_addr) - })) + async fn handle_request( + self: Arc<Self>, + req: Request<Body>, + addr: SocketAddr, + ) -> Result<Response<Body>, Infallible> { + info!("{} {} {}", addr, req.method(), req.uri()); + + // Lots of instrumentation + let tracer = opentelemetry::global::tracer("garage"); + let span = tracer + .span_builder(format!("Web {} request", req.method())) + .with_trace_id(gen_trace_id()) + .with_attributes(vec![ + KeyValue::new("method", format!("{}", req.method())), + KeyValue::new("uri", req.uri().to_string()), + ]) + .start(&tracer); + + let metrics_tags = &[KeyValue::new("method", req.method().to_string())]; + + // The actual handler + let res = self + .serve_file(&req) + .with_context(Context::current_with_span(span)) + .record_duration(&self.metrics.request_duration, &metrics_tags[..]) + .await; + + // More instrumentation + self.metrics.request_counter.add(1, &metrics_tags[..]); + + // Returning the result + match res { + Ok(res) => { + debug!("{} {} {}", req.method(), res.status(), req.uri()); + Ok(res) + } + Err(error) => { + info!( + "{} {} {} {}", + req.method(), + error.http_status_code(), + req.uri(), + error + ); + self.metrics.error_counter.add( + 1, + &[ + metrics_tags[0].clone(), + KeyValue::new("status_code", error.http_status_code().to_string()), + ], + ); + Ok(error_to_res(error)) + } } - }); + } - let server = Server::bind(addr).serve(service); - let graceful = server.with_graceful_shutdown(shutdown_signal); - info!("Web server listening on http://{}", addr); + async fn serve_file(self: &Arc<Self>, req: &Request<Body>) -> Result<Response<Body>, Error> { + // Get http authority string (eg. [::1]:3902 or garage.tld:80) + let authority = req + .headers() + .get(HOST) + .ok_or_bad_request("HOST header required")? + .to_str()?; + + // Get bucket + let host = authority_to_host(authority)?; + + let bucket_name = host_to_bucket(&host, &self.root_domain).unwrap_or(&host); + let bucket_id = self + .garage + .bucket_alias_table + .get(&EmptyKey, &bucket_name.to_string()) + .await? + .and_then(|x| x.state.take()) + .ok_or(Error::NotFound)?; + + // Check bucket isn't deleted and has website access enabled + let bucket = self + .garage + .bucket_table + .get(&EmptyKey, &bucket_id) + .await? + .ok_or(Error::NotFound)?; + + let website_config = bucket + .params() + .ok_or(Error::NotFound)? + .website_config + .get() + .as_ref() + .ok_or(Error::NotFound)?; + + // Get path + let path = req.uri().path().to_string(); + let index = &website_config.index_document; + let key = path_to_key(&path, index)?; + + debug!( + "Selected bucket: \"{}\" {:?}, selected key: \"{}\"", + bucket_name, bucket_id, key + ); + + let ret_doc = match *req.method() { + Method::OPTIONS => handle_options_for_bucket(req, &bucket), + Method::HEAD => handle_head(self.garage.clone(), req, bucket_id, &key, None).await, + Method::GET => handle_get(self.garage.clone(), req, bucket_id, &key, None).await, + _ => Err(ApiError::bad_request("HTTP method not supported")), + } + .map_err(Error::from); + + match ret_doc { + Err(error) => { + // For a HEAD or OPTIONS method, and for non-4xx errors, + // we don't return the error document as content, + // we return above and just return the error message + // by relying on err_to_res that is called when we return an Err. + if *req.method() == Method::HEAD + || *req.method() == Method::OPTIONS + || !error.http_status_code().is_client_error() + { + return Err(error); + } - graceful.await?; - Ok(()) -} + // If no error document is set: just return the error directly + let error_document = match &website_config.error_document { + Some(ed) => ed.trim_start_matches('/').to_owned(), + None => return Err(error), + }; + + // We want to return the error document + // Create a fake HTTP request with path = the error document + let req2 = Request::builder() + .uri(format!("http://{}/{}", host, &error_document)) + .body(Body::empty()) + .unwrap(); + + match handle_get(self.garage.clone(), &req2, bucket_id, &error_document, None).await + { + Ok(mut error_doc) => { + // The error won't be logged back in handle_request, + // so log it here + info!( + "{} {} {} {}", + req.method(), + req.uri(), + error.http_status_code(), + error + ); + + *error_doc.status_mut() = error.http_status_code(); + error.add_headers(error_doc.headers_mut()); + + // Preserve error message in a special header + for error_line in error.to_string().split('\n') { + if let Ok(v) = HeaderValue::from_bytes(error_line.as_bytes()) { + error_doc.headers_mut().append("X-Garage-Error", v); + } + } -async fn handle_request( - garage: Arc<Garage>, - metrics: Arc<WebMetrics>, - req: Request<Body>, - addr: SocketAddr, -) -> Result<Response<Body>, Infallible> { - info!("{} {} {}", addr, req.method(), req.uri()); - - // Lots of instrumentation - let tracer = opentelemetry::global::tracer("garage"); - let span = tracer - .span_builder(format!("Web {} request", req.method())) - .with_trace_id(gen_trace_id()) - .with_attributes(vec![ - KeyValue::new("method", format!("{}", req.method())), - KeyValue::new("uri", req.uri().to_string()), - ]) - .start(&tracer); - - let metrics_tags = &[KeyValue::new("method", req.method().to_string())]; - - // The actual handler - let res = serve_file(garage, &req) - .with_context(Context::current_with_span(span)) - .record_duration(&metrics.request_duration, &metrics_tags[..]) - .await; - - // More instrumentation - metrics.request_counter.add(1, &metrics_tags[..]); - - // Returning the result - match res { - Ok(res) => { - debug!("{} {} {}", req.method(), res.status(), req.uri()); - Ok(res) - } - Err(error) => { - info!( - "{} {} {} {}", - req.method(), - error.http_status_code(), - req.uri(), - error - ); - metrics.error_counter.add( - 1, - &[ - metrics_tags[0].clone(), - KeyValue::new("status_code", error.http_status_code().to_string()), - ], - ); - Ok(error_to_res(error)) + Ok(error_doc) + } + Err(error_doc_error) => { + warn!( + "Couldn't get error document {} for bucket {:?}: {}", + error_document, bucket_id, error_doc_error + ); + Err(error) + } + } + } + Ok(mut resp) => { + // Maybe add CORS headers + if let Some(rule) = find_matching_cors_rule(&bucket, req)? { + add_cors_headers(&mut resp, rule) + .ok_or_internal_error("Invalid bucket CORS configuration")?; + } + Ok(resp) + } } } } @@ -158,129 +296,6 @@ fn error_to_res(e: Error) -> Response<Body> { http_error } -async fn serve_file(garage: Arc<Garage>, req: &Request<Body>) -> Result<Response<Body>, Error> { - // Get http authority string (eg. [::1]:3902 or garage.tld:80) - let authority = req - .headers() - .get(HOST) - .ok_or_bad_request("HOST header required")? - .to_str()?; - - // Get bucket - let host = authority_to_host(authority)?; - let root = &garage.config.s3_web.root_domain; - - let bucket_name = host_to_bucket(&host, root).unwrap_or(&host); - let bucket_id = garage - .bucket_alias_table - .get(&EmptyKey, &bucket_name.to_string()) - .await? - .and_then(|x| x.state.take()) - .ok_or(Error::NotFound)?; - - // Check bucket isn't deleted and has website access enabled - let bucket = garage - .bucket_table - .get(&EmptyKey, &bucket_id) - .await? - .ok_or(Error::NotFound)?; - - let website_config = bucket - .params() - .ok_or(Error::NotFound)? - .website_config - .get() - .as_ref() - .ok_or(Error::NotFound)?; - - // Get path - let path = req.uri().path().to_string(); - let index = &website_config.index_document; - let key = path_to_key(&path, index)?; - - debug!( - "Selected bucket: \"{}\" {:?}, selected key: \"{}\"", - bucket_name, bucket_id, key - ); - - let ret_doc = match *req.method() { - Method::OPTIONS => handle_options_for_bucket(req, &bucket), - Method::HEAD => handle_head(garage.clone(), req, bucket_id, &key, None).await, - Method::GET => handle_get(garage.clone(), req, bucket_id, &key, None).await, - _ => Err(ApiError::BadRequest("HTTP method not supported".into())), - } - .map_err(Error::from); - - match ret_doc { - Err(error) => { - // For a HEAD or OPTIONS method, and for non-4xx errors, - // we don't return the error document as content, - // we return above and just return the error message - // by relying on err_to_res that is called when we return an Err. - if *req.method() == Method::HEAD - || *req.method() == Method::OPTIONS - || !error.http_status_code().is_client_error() - { - return Err(error); - } - - // If no error document is set: just return the error directly - let error_document = match &website_config.error_document { - Some(ed) => ed.trim_start_matches('/').to_owned(), - None => return Err(error), - }; - - // We want to return the error document - // Create a fake HTTP request with path = the error document - let req2 = Request::builder() - .uri(format!("http://{}/{}", host, &error_document)) - .body(Body::empty()) - .unwrap(); - - match handle_get(garage, &req2, bucket_id, &error_document, None).await { - Ok(mut error_doc) => { - // The error won't be logged back in handle_request, - // so log it here - info!( - "{} {} {} {}", - req.method(), - req.uri(), - error.http_status_code(), - error - ); - - *error_doc.status_mut() = error.http_status_code(); - error.add_headers(error_doc.headers_mut()); - - // Preserve error message in a special header - for error_line in error.to_string().split('\n') { - if let Ok(v) = HeaderValue::from_bytes(error_line.as_bytes()) { - error_doc.headers_mut().append("X-Garage-Error", v); - } - } - - Ok(error_doc) - } - Err(error_doc_error) => { - warn!( - "Couldn't get error document {} for bucket {:?}: {}", - error_document, bucket_id, error_doc_error - ); - Err(error) - } - } - } - Ok(mut resp) => { - // Maybe add CORS headers - if let Some(rule) = find_matching_cors_rule(&bucket, req)? { - add_cors_headers(&mut resp, rule) - .ok_or_internal_error("Invalid bucket CORS configuration")?; - } - Ok(resp) - } - } -} - /// Path to key /// /// Convert the provided path to the internal key @@ -290,9 +305,7 @@ fn path_to_key<'a>(path: &'a str, index: &str) -> Result<Cow<'a, str>, Error> { let path_utf8 = percent_encoding::percent_decode_str(path).decode_utf8()?; if !path_utf8.starts_with('/') { - return Err(Error::BadRequest( - "Path must start with a / (slash)".to_string(), - )); + return Err(Error::BadRequest("Path must start with a / (slash)".into())); } match path_utf8.chars().last() { |