Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4756db4d29 | |||
| 1fcd0b371b | |||
| 13c8662126 | |||
| e6f14ab5cf | |||
| 510b620108 | |||
| dfc131850a | |||
| d4af27f920 | |||
| 0d6b05bb6c | |||
| a19bfef508 | |||
| d56c472712 | |||
| 2183518edc | |||
| 83c8467e23 | |||
| f8e528c15d | |||
| d1279e04f3 | |||
| 041b60ed1d | |||
| f8d5409894 | |||
| d6040e32a6 | |||
| d7f90cabb0 | |||
| 687660b27f | |||
| 9d82196945 | |||
| a51e8d94c6 | |||
| de9d6cddf7 | |||
| f7c65e830e | |||
| 0e61e3b6fb | |||
| 2ac75018a1 | |||
| 980572a887 | |||
| 7a0014b6f7 | |||
| edb0b9c1ee | |||
| f58a813a36 | |||
| defd7d9e63 | |||
| 533afcf4e1 | |||
| 5ea5fd2130 | |||
| 35f8e8e2fb | |||
| d5a2502b09 | |||
| d7868c48a4 | |||
| 280d1be7b1 | |||
| 2065f011ca | |||
| 243b7c9a1c | |||
| a3afc761b6 | |||
| 19bdd1c799 | |||
| 448dcc5cf4 | |||
| 26121bb619 | |||
| 280330ac72 | |||
| 4d7b4d9d20 | |||
| fc450ec13a | |||
| 379b2049f5 | |||
| 293139a94a | |||
| 54e800ef8d | |||
| 1e40c93fd0 | |||
| 0cfb56d33e | |||
| c1fb65194c | |||
| 67941000ee | |||
| 60c26fbc62 | |||
| e76dba9561 | |||
| 7fafd14a25 | |||
| 555a54ec40 | |||
| fc8f795bba | |||
| a7af0c8af9 | |||
| bcc9772470 | |||
| c4e4cc1156 | |||
| 05547f2ba6 | |||
| 39ac295eb7 | |||
| cf23aee183 | |||
| 74ea449f4b | |||
| eabb37b53f | |||
| e7824faa17 | |||
| 8dfc909759 | |||
| 485109ea60 | |||
| ebe8a41f2d | |||
| dc50fa3b34 | |||
| a976c9190c | |||
| 72a0f90070 | |||
| d814deb806 | |||
| 6a09f16da7 | |||
| 23207d18a0 | |||
| 3024405a65 | |||
| 5f0928f89c | |||
| 0a01b34e81 | |||
| 66f2daa025 | |||
| 26b3295aaa | |||
| 0d279918b7 | |||
| e03d9062f7 | |||
| 8d3bbf5703 | |||
| 5b18fd8201 | |||
| 043246c575 | |||
| d6c77ea327 |
@@ -1 +0,0 @@
|
|||||||
*.pdf filter=lfs diff=lfs merge=lfs -text
|
|
||||||
@@ -2,6 +2,21 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -389,6 +404,21 @@ dependencies = [
|
|||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.66"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cc",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -1023,6 +1053,7 @@ dependencies = [
|
|||||||
"assert-json-diff",
|
"assert-json-diff",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"aws-sdk-s3",
|
"aws-sdk-s3",
|
||||||
|
"backtrace",
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"bytesize",
|
"bytesize",
|
||||||
@@ -1046,6 +1077,7 @@ dependencies = [
|
|||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"opentelemetry-otlp",
|
"opentelemetry-otlp",
|
||||||
"opentelemetry-prometheus",
|
"opentelemetry-prometheus",
|
||||||
|
"parse_duration",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rmp-serde",
|
"rmp-serde",
|
||||||
@@ -1140,10 +1172,15 @@ dependencies = [
|
|||||||
"clap 3.1.18",
|
"clap 3.1.18",
|
||||||
"err-derive",
|
"err-derive",
|
||||||
"heed",
|
"heed",
|
||||||
|
"hex",
|
||||||
"hexdump",
|
"hexdump",
|
||||||
"mktemp",
|
"mktemp",
|
||||||
|
"nettext",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"rmp-serde",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
"serde",
|
||||||
|
"serde-transcode",
|
||||||
"sled",
|
"sled",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
@@ -1211,6 +1248,7 @@ dependencies = [
|
|||||||
name = "garage_table"
|
name = "garage_table"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures",
|
"futures",
|
||||||
@@ -1308,6 +1346,12 @@ dependencies = [
|
|||||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.26.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git-version"
|
name = "git-version"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@@ -2010,6 +2054,15 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -2093,6 +2146,17 @@ dependencies = [
|
|||||||
"tokio-util 0.7.0",
|
"tokio-util 0.7.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nettext"
|
||||||
|
version = "0.3.2"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"err-derive",
|
||||||
|
"hex",
|
||||||
|
"nom",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.1"
|
version = "7.1.1"
|
||||||
@@ -2112,6 +2176,41 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.1.0",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.1.0",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
@@ -2122,6 +2221,29 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.43"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.1.0",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.1.0",
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@@ -2150,6 +2272,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
@@ -2303,6 +2434,17 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parse_duration"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"num",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem"
|
name = "pem"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -2954,6 +3096,12 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -3150,6 +3298,15 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-transcode"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde-value"
|
name = "serde-value"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ args@{
|
|||||||
ignoreLockHash,
|
ignoreLockHash,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
nixifiedLockHash = "45b04ec226341a714068633cf7a3a6b421aa70ac3f78c72931d43ab8301b3c7b";
|
nixifiedLockHash = "a1d84930f23d3d8abc8abbed59b8ce3c9adf9f25d06bc1f39cbdf5bd90aceead";
|
||||||
workspaceSrc = if args.workspaceSrc == null then ./. else args.workspaceSrc;
|
workspaceSrc = if args.workspaceSrc == null then ./. else args.workspaceSrc;
|
||||||
currentLockHash = builtins.hashFile "sha256" (workspaceSrc + /Cargo.lock);
|
currentLockHash = builtins.hashFile "sha256" (workspaceSrc + /Cargo.lock);
|
||||||
lockHashIgnored = if ignoreLockHash
|
lockHashIgnored = if ignoreLockHash
|
||||||
@@ -67,6 +67,23 @@ in
|
|||||||
garage = rustPackages.unknown.garage."0.8.0";
|
garage = rustPackages.unknown.garage."0.8.0";
|
||||||
k2v-client = rustPackages.unknown.k2v-client."0.0.1";
|
k2v-client = rustPackages.unknown.k2v-client."0.0.1";
|
||||||
};
|
};
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".addr2line."0.17.0" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "addr2line";
|
||||||
|
version = "0.17.0";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"; };
|
||||||
|
dependencies = {
|
||||||
|
gimli = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".gimli."0.26.2" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".adler."1.0.2" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "adler";
|
||||||
|
version = "1.0.2";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"; };
|
||||||
|
});
|
||||||
|
|
||||||
"registry+https://github.com/rust-lang/crates.io-index".ahash."0.7.6" = overridableMkRustCrate (profileName: rec {
|
"registry+https://github.com/rust-lang/crates.io-index".ahash."0.7.6" = overridableMkRustCrate (profileName: rec {
|
||||||
name = "ahash";
|
name = "ahash";
|
||||||
version = "0.7.6";
|
version = "0.7.6";
|
||||||
@@ -555,6 +572,28 @@ in
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".backtrace."0.3.66" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "backtrace";
|
||||||
|
version = "0.3.66";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"; };
|
||||||
|
features = builtins.concatLists [
|
||||||
|
[ "default" ]
|
||||||
|
[ "std" ]
|
||||||
|
];
|
||||||
|
dependencies = {
|
||||||
|
addr2line = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".addr2line."0.17.0" { inherit profileName; }).out;
|
||||||
|
cfg_if = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out;
|
||||||
|
libc = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }).out;
|
||||||
|
miniz_oxide = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".miniz_oxide."0.5.4" { inherit profileName; }).out;
|
||||||
|
object = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".object."0.29.0" { inherit profileName; }).out;
|
||||||
|
rustc_demangle = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rustc-demangle."0.1.21" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
buildDependencies = {
|
||||||
|
cc = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".cc."1.0.73" { profileName = "__noProfile"; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
"registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" = overridableMkRustCrate (profileName: rec {
|
"registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" = overridableMkRustCrate (profileName: rec {
|
||||||
name = "base64";
|
name = "base64";
|
||||||
version = "0.13.0";
|
version = "0.13.0";
|
||||||
@@ -907,20 +946,20 @@ in
|
|||||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
src = fetchCratesIo { inherit name version; sha256 = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"; };
|
src = fetchCratesIo { inherit name version; sha256 = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"; };
|
||||||
features = builtins.concatLists [
|
features = builtins.concatLists [
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "alloc")
|
[ "alloc" ]
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "default")
|
[ "default" ]
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "lazy_static")
|
[ "lazy_static" ]
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "std")
|
[ "std" ]
|
||||||
];
|
];
|
||||||
dependencies = {
|
dependencies = {
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "cfg_if" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out;
|
cfg_if = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "crossbeam_utils" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-utils."0.8.8" { inherit profileName; }).out;
|
crossbeam_utils = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-utils."0.8.8" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "lazy_static" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }).out;
|
lazy_static = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "memoffset" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".memoffset."0.6.5" { inherit profileName; }).out;
|
memoffset = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".memoffset."0.6.5" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "scopeguard" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".scopeguard."1.1.0" { inherit profileName; }).out;
|
scopeguard = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".scopeguard."1.1.0" { inherit profileName; }).out;
|
||||||
};
|
};
|
||||||
buildDependencies = {
|
buildDependencies = {
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "autocfg" else null } = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out;
|
autocfg = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -956,7 +995,7 @@ in
|
|||||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
src = fetchCratesIo { inherit name version; sha256 = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"; };
|
src = fetchCratesIo { inherit name version; sha256 = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"; };
|
||||||
features = builtins.concatLists [
|
features = builtins.concatLists [
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "default")
|
[ "default" ]
|
||||||
[ "lazy_static" ]
|
[ "lazy_static" ]
|
||||||
[ "std" ]
|
[ "std" ]
|
||||||
];
|
];
|
||||||
@@ -1282,8 +1321,8 @@ in
|
|||||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
src = fetchCratesIo { inherit name version; sha256 = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"; };
|
src = fetchCratesIo { inherit name version; sha256 = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"; };
|
||||||
dependencies = {
|
dependencies = {
|
||||||
${ if (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") && hostPlatform.isUnix then "libc" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }).out;
|
${ if hostPlatform.isUnix then "libc" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }).out;
|
||||||
${ if (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") && hostPlatform.isWindows then "winapi" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }).out;
|
${ if hostPlatform.isWindows then "winapi" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }).out;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1451,7 +1490,7 @@ in
|
|||||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
src = fetchCratesIo { inherit name version; sha256 = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"; };
|
src = fetchCratesIo { inherit name version; sha256 = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"; };
|
||||||
dependencies = {
|
dependencies = {
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "byteorder" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".byteorder."1.4.3" { inherit profileName; }).out;
|
byteorder = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".byteorder."1.4.3" { inherit profileName; }).out;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1478,6 +1517,7 @@ in
|
|||||||
];
|
];
|
||||||
dependencies = {
|
dependencies = {
|
||||||
async_trait = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }).out;
|
async_trait = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }).out;
|
||||||
|
backtrace = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".backtrace."0.3.66" { inherit profileName; }).out;
|
||||||
bytes = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }).out;
|
bytes = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }).out;
|
||||||
bytesize = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytesize."1.1.0" { inherit profileName; }).out;
|
bytesize = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytesize."1.1.0" { inherit profileName; }).out;
|
||||||
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }).out;
|
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }).out;
|
||||||
@@ -1496,6 +1536,7 @@ in
|
|||||||
opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out;
|
opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/opentelemetry-otlp" || rootFeatures' ? "garage/telemetry-otlp" then "opentelemetry_otlp" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-otlp."0.10.0" { inherit profileName; }).out;
|
${ if rootFeatures' ? "garage/opentelemetry-otlp" || rootFeatures' ? "garage/telemetry-otlp" then "opentelemetry_otlp" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-otlp."0.10.0" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/metrics" || rootFeatures' ? "garage/opentelemetry-prometheus" then "opentelemetry_prometheus" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-prometheus."0.10.0" { inherit profileName; }).out;
|
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/metrics" || rootFeatures' ? "garage/opentelemetry-prometheus" then "opentelemetry_prometheus" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry-prometheus."0.10.0" { inherit profileName; }).out;
|
||||||
|
parse_duration = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".parse_duration."2.1.1" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/metrics" || rootFeatures' ? "garage/prometheus" then "prometheus" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".prometheus."0.13.0" { inherit profileName; }).out;
|
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/metrics" || rootFeatures' ? "garage/prometheus" then "prometheus" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".prometheus."0.13.0" { inherit profileName; }).out;
|
||||||
rand = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }).out;
|
rand = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }).out;
|
||||||
rmp_serde = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }).out;
|
rmp_serde = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }).out;
|
||||||
@@ -1617,11 +1658,12 @@ in
|
|||||||
(lib.optional (rootFeatures' ? "garage/bundled-libs" || rootFeatures' ? "garage/default" || rootFeatures' ? "garage_db/bundled-libs") "bundled-libs")
|
(lib.optional (rootFeatures' ? "garage/bundled-libs" || rootFeatures' ? "garage/default" || rootFeatures' ? "garage_db/bundled-libs") "bundled-libs")
|
||||||
(lib.optional (rootFeatures' ? "garage_db/clap" || rootFeatures' ? "garage_db/cli") "clap")
|
(lib.optional (rootFeatures' ? "garage_db/clap" || rootFeatures' ? "garage_db/cli") "clap")
|
||||||
(lib.optional (rootFeatures' ? "garage_db/cli") "cli")
|
(lib.optional (rootFeatures' ? "garage_db/cli") "cli")
|
||||||
|
[ "default" ]
|
||||||
(lib.optional (rootFeatures' ? "garage/lmdb" || rootFeatures' ? "garage_db/heed" || rootFeatures' ? "garage_db/lmdb" || rootFeatures' ? "garage_model/lmdb") "heed")
|
(lib.optional (rootFeatures' ? "garage/lmdb" || rootFeatures' ? "garage_db/heed" || rootFeatures' ? "garage_db/lmdb" || rootFeatures' ? "garage_model/lmdb") "heed")
|
||||||
(lib.optional (rootFeatures' ? "garage/lmdb" || rootFeatures' ? "garage_db/lmdb" || rootFeatures' ? "garage_model/lmdb") "lmdb")
|
(lib.optional (rootFeatures' ? "garage/lmdb" || rootFeatures' ? "garage_db/lmdb" || rootFeatures' ? "garage_model/lmdb") "lmdb")
|
||||||
(lib.optional (rootFeatures' ? "garage_db/cli" || rootFeatures' ? "garage_db/pretty_env_logger") "pretty_env_logger")
|
(lib.optional (rootFeatures' ? "garage_db/cli" || rootFeatures' ? "garage_db/pretty_env_logger") "pretty_env_logger")
|
||||||
(lib.optional (rootFeatures' ? "garage/bundled-libs" || rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sqlite" || rootFeatures' ? "garage_db/bundled-libs" || rootFeatures' ? "garage_db/rusqlite" || rootFeatures' ? "garage_db/sqlite" || rootFeatures' ? "garage_model/sqlite") "rusqlite")
|
(lib.optional (rootFeatures' ? "garage/bundled-libs" || rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sqlite" || rootFeatures' ? "garage_db/bundled-libs" || rootFeatures' ? "garage_db/rusqlite" || rootFeatures' ? "garage_db/sqlite" || rootFeatures' ? "garage_model/sqlite") "rusqlite")
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "sled")
|
[ "sled" ]
|
||||||
(lib.optional (rootFeatures' ? "garage/sqlite" || rootFeatures' ? "garage_db/sqlite" || rootFeatures' ? "garage_model/sqlite") "sqlite")
|
(lib.optional (rootFeatures' ? "garage/sqlite" || rootFeatures' ? "garage_db/sqlite" || rootFeatures' ? "garage_model/sqlite") "sqlite")
|
||||||
];
|
];
|
||||||
dependencies = {
|
dependencies = {
|
||||||
@@ -1631,7 +1673,7 @@ in
|
|||||||
hexdump = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hexdump."0.1.1" { inherit profileName; }).out;
|
hexdump = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hexdump."0.1.1" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage_db/cli" || rootFeatures' ? "garage_db/pretty_env_logger" then "pretty_env_logger" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".pretty_env_logger."0.4.0" { inherit profileName; }).out;
|
${ if rootFeatures' ? "garage_db/cli" || rootFeatures' ? "garage_db/pretty_env_logger" then "pretty_env_logger" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".pretty_env_logger."0.4.0" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/bundled-libs" || rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sqlite" || rootFeatures' ? "garage_db/bundled-libs" || rootFeatures' ? "garage_db/rusqlite" || rootFeatures' ? "garage_db/sqlite" || rootFeatures' ? "garage_model/sqlite" then "rusqlite" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rusqlite."0.27.0" { inherit profileName; }).out;
|
${ if rootFeatures' ? "garage/bundled-libs" || rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sqlite" || rootFeatures' ? "garage_db/bundled-libs" || rootFeatures' ? "garage_db/rusqlite" || rootFeatures' ? "garage_db/sqlite" || rootFeatures' ? "garage_model/sqlite" then "rusqlite" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rusqlite."0.27.0" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "sled" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }).out;
|
sled = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".sled."0.34.7" { inherit profileName; }).out;
|
||||||
tracing = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }).out;
|
tracing = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.32" { inherit profileName; }).out;
|
||||||
};
|
};
|
||||||
devDependencies = {
|
devDependencies = {
|
||||||
@@ -1645,9 +1687,10 @@ in
|
|||||||
registry = "unknown";
|
registry = "unknown";
|
||||||
src = fetchCrateLocal (workspaceSrc + "/src/model");
|
src = fetchCrateLocal (workspaceSrc + "/src/model");
|
||||||
features = builtins.concatLists [
|
features = builtins.concatLists [
|
||||||
|
[ "default" ]
|
||||||
(lib.optional (rootFeatures' ? "garage/k2v" || rootFeatures' ? "garage_api/k2v" || rootFeatures' ? "garage_model/k2v") "k2v")
|
(lib.optional (rootFeatures' ? "garage/k2v" || rootFeatures' ? "garage_api/k2v" || rootFeatures' ? "garage_model/k2v") "k2v")
|
||||||
(lib.optional (rootFeatures' ? "garage/lmdb" || rootFeatures' ? "garage_model/lmdb") "lmdb")
|
(lib.optional (rootFeatures' ? "garage/lmdb" || rootFeatures' ? "garage_model/lmdb") "lmdb")
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_model/sled") "sled")
|
[ "sled" ]
|
||||||
(lib.optional (rootFeatures' ? "garage/sqlite" || rootFeatures' ? "garage_model/sqlite") "sqlite")
|
(lib.optional (rootFeatures' ? "garage/sqlite" || rootFeatures' ? "garage_model/sqlite") "sqlite")
|
||||||
];
|
];
|
||||||
dependencies = {
|
dependencies = {
|
||||||
@@ -1726,6 +1769,7 @@ in
|
|||||||
registry = "unknown";
|
registry = "unknown";
|
||||||
src = fetchCrateLocal (workspaceSrc + "/src/table");
|
src = fetchCrateLocal (workspaceSrc + "/src/table");
|
||||||
dependencies = {
|
dependencies = {
|
||||||
|
arc_swap = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".arc-swap."1.5.0" { inherit profileName; }).out;
|
||||||
async_trait = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }).out;
|
async_trait = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".async-trait."0.1.52" { profileName = "__noProfile"; }).out;
|
||||||
bytes = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }).out;
|
bytes = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }).out;
|
||||||
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }).out;
|
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }).out;
|
||||||
@@ -1844,6 +1888,17 @@ in
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".gimli."0.26.2" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "gimli";
|
||||||
|
version = "0.26.2";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"; };
|
||||||
|
features = builtins.concatLists [
|
||||||
|
[ "read" ]
|
||||||
|
[ "read-core" ]
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
"registry+https://github.com/rust-lang/crates.io-index".git-version."0.3.5" = overridableMkRustCrate (profileName: rec {
|
"registry+https://github.com/rust-lang/crates.io-index".git-version."0.3.5" = overridableMkRustCrate (profileName: rec {
|
||||||
name = "git-version";
|
name = "git-version";
|
||||||
version = "0.3.5";
|
version = "0.3.5";
|
||||||
@@ -2794,10 +2849,10 @@ in
|
|||||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
src = fetchCratesIo { inherit name version; sha256 = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"; };
|
src = fetchCratesIo { inherit name version; sha256 = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"; };
|
||||||
features = builtins.concatLists [
|
features = builtins.concatLists [
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "default")
|
[ "default" ]
|
||||||
];
|
];
|
||||||
buildDependencies = {
|
buildDependencies = {
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "autocfg" else null } = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out;
|
autocfg = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2818,6 +2873,16 @@ in
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".miniz_oxide."0.5.4" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "miniz_oxide";
|
||||||
|
version = "0.5.4";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"; };
|
||||||
|
dependencies = {
|
||||||
|
adler = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".adler."1.0.2" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
"registry+https://github.com/rust-lang/crates.io-index".mio."0.8.2" = overridableMkRustCrate (profileName: rec {
|
"registry+https://github.com/rust-lang/crates.io-index".mio."0.8.2" = overridableMkRustCrate (profileName: rec {
|
||||||
name = "mio";
|
name = "mio";
|
||||||
version = "0.8.2";
|
version = "0.8.2";
|
||||||
@@ -2954,6 +3019,59 @@ in
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".num."0.2.1" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "num";
|
||||||
|
version = "0.2.1";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"; };
|
||||||
|
features = builtins.concatLists [
|
||||||
|
[ "default" ]
|
||||||
|
[ "num-bigint" ]
|
||||||
|
[ "std" ]
|
||||||
|
];
|
||||||
|
dependencies = {
|
||||||
|
num_bigint = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-bigint."0.2.6" { inherit profileName; }).out;
|
||||||
|
num_complex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-complex."0.2.4" { inherit profileName; }).out;
|
||||||
|
num_integer = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-integer."0.1.44" { inherit profileName; }).out;
|
||||||
|
num_iter = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-iter."0.1.43" { inherit profileName; }).out;
|
||||||
|
num_rational = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-rational."0.2.4" { inherit profileName; }).out;
|
||||||
|
num_traits = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".num-bigint."0.2.6" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "num-bigint";
|
||||||
|
version = "0.2.6";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"; };
|
||||||
|
features = builtins.concatLists [
|
||||||
|
[ "std" ]
|
||||||
|
];
|
||||||
|
dependencies = {
|
||||||
|
num_integer = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-integer."0.1.44" { inherit profileName; }).out;
|
||||||
|
num_traits = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
buildDependencies = {
|
||||||
|
autocfg = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".num-complex."0.2.4" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "num-complex";
|
||||||
|
version = "0.2.4";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"; };
|
||||||
|
features = builtins.concatLists [
|
||||||
|
[ "std" ]
|
||||||
|
];
|
||||||
|
dependencies = {
|
||||||
|
num_traits = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
buildDependencies = {
|
||||||
|
autocfg = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
"registry+https://github.com/rust-lang/crates.io-index".num-integer."0.1.44" = overridableMkRustCrate (profileName: rec {
|
"registry+https://github.com/rust-lang/crates.io-index".num-integer."0.1.44" = overridableMkRustCrate (profileName: rec {
|
||||||
name = "num-integer";
|
name = "num-integer";
|
||||||
version = "0.1.44";
|
version = "0.1.44";
|
||||||
@@ -2971,6 +3089,43 @@ in
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".num-iter."0.1.43" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "num-iter";
|
||||||
|
version = "0.1.43";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"; };
|
||||||
|
features = builtins.concatLists [
|
||||||
|
[ "std" ]
|
||||||
|
];
|
||||||
|
dependencies = {
|
||||||
|
num_integer = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-integer."0.1.44" { inherit profileName; }).out;
|
||||||
|
num_traits = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
buildDependencies = {
|
||||||
|
autocfg = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".num-rational."0.2.4" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "num-rational";
|
||||||
|
version = "0.2.4";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"; };
|
||||||
|
features = builtins.concatLists [
|
||||||
|
[ "bigint" ]
|
||||||
|
[ "num-bigint" ]
|
||||||
|
[ "std" ]
|
||||||
|
];
|
||||||
|
dependencies = {
|
||||||
|
num_bigint = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-bigint."0.2.6" { inherit profileName; }).out;
|
||||||
|
num_integer = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-integer."0.1.44" { inherit profileName; }).out;
|
||||||
|
num_traits = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
buildDependencies = {
|
||||||
|
autocfg = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".autocfg."1.1.0" { profileName = "__noProfile"; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
"registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" = overridableMkRustCrate (profileName: rec {
|
"registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.14" = overridableMkRustCrate (profileName: rec {
|
||||||
name = "num-traits";
|
name = "num-traits";
|
||||||
version = "0.2.14";
|
version = "0.2.14";
|
||||||
@@ -3006,6 +3161,25 @@ in
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".object."0.29.0" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "object";
|
||||||
|
version = "0.29.0";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"; };
|
||||||
|
features = builtins.concatLists [
|
||||||
|
[ "archive" ]
|
||||||
|
[ "coff" ]
|
||||||
|
[ "elf" ]
|
||||||
|
[ "macho" ]
|
||||||
|
[ "pe" ]
|
||||||
|
[ "read_core" ]
|
||||||
|
[ "unaligned" ]
|
||||||
|
];
|
||||||
|
dependencies = {
|
||||||
|
memchr = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".memchr."2.4.1" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
"registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" = overridableMkRustCrate (profileName: rec {
|
"registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" = overridableMkRustCrate (profileName: rec {
|
||||||
name = "once_cell";
|
name = "once_cell";
|
||||||
version = "1.10.0";
|
version = "1.10.0";
|
||||||
@@ -3220,6 +3394,18 @@ in
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".parse_duration."2.1.1" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "parse_duration";
|
||||||
|
version = "2.1.1";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d"; };
|
||||||
|
dependencies = {
|
||||||
|
lazy_static = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".lazy_static."1.4.0" { inherit profileName; }).out;
|
||||||
|
num = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num."0.2.1" { inherit profileName; }).out;
|
||||||
|
regex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".regex."1.5.5" { inherit profileName; }).out;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
"registry+https://github.com/rust-lang/crates.io-index".pem."1.1.0" = overridableMkRustCrate (profileName: rec {
|
"registry+https://github.com/rust-lang/crates.io-index".pem."1.1.0" = overridableMkRustCrate (profileName: rec {
|
||||||
name = "pem";
|
name = "pem";
|
||||||
version = "1.1.0";
|
version = "1.1.0";
|
||||||
@@ -4078,6 +4264,13 @@ in
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
"registry+https://github.com/rust-lang/crates.io-index".rustc-demangle."0.1.21" = overridableMkRustCrate (profileName: rec {
|
||||||
|
name = "rustc-demangle";
|
||||||
|
version = "0.1.21";
|
||||||
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
|
src = fetchCratesIo { inherit name version; sha256 = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"; };
|
||||||
|
});
|
||||||
|
|
||||||
"registry+https://github.com/rust-lang/crates.io-index".rustc_version."0.4.0" = overridableMkRustCrate (profileName: rec {
|
"registry+https://github.com/rust-lang/crates.io-index".rustc_version."0.4.0" = overridableMkRustCrate (profileName: rec {
|
||||||
name = "rustc_version";
|
name = "rustc_version";
|
||||||
version = "0.4.0";
|
version = "0.4.0";
|
||||||
@@ -4542,18 +4735,18 @@ in
|
|||||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||||
src = fetchCratesIo { inherit name version; sha256 = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"; };
|
src = fetchCratesIo { inherit name version; sha256 = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"; };
|
||||||
features = builtins.concatLists [
|
features = builtins.concatLists [
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "default")
|
[ "default" ]
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "no_metrics")
|
[ "no_metrics" ]
|
||||||
];
|
];
|
||||||
dependencies = {
|
dependencies = {
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "crc32fast" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crc32fast."1.3.2" { inherit profileName; }).out;
|
crc32fast = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crc32fast."1.3.2" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "crossbeam_epoch" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-epoch."0.9.8" { inherit profileName; }).out;
|
crossbeam_epoch = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-epoch."0.9.8" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "crossbeam_utils" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-utils."0.8.8" { inherit profileName; }).out;
|
crossbeam_utils = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".crossbeam-utils."0.8.8" { inherit profileName; }).out;
|
||||||
${ if (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") && (hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "darwin" || hostPlatform.parsed.kernel.name == "windows") then "fs2" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".fs2."0.4.3" { inherit profileName; }).out;
|
${ if hostPlatform.parsed.kernel.name == "linux" || hostPlatform.parsed.kernel.name == "darwin" || hostPlatform.parsed.kernel.name == "windows" then "fs2" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".fs2."0.4.3" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "fxhash" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".fxhash."0.2.1" { inherit profileName; }).out;
|
fxhash = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".fxhash."0.2.1" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "libc" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }).out;
|
libc = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libc."0.2.121" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "log" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }).out;
|
log = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }).out;
|
||||||
${ if rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled" then "parking_lot" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.11.2" { inherit profileName; }).out;
|
parking_lot = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".parking_lot."0.11.2" { inherit profileName; }).out;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -5206,7 +5399,6 @@ in
|
|||||||
[ "attributes" ]
|
[ "attributes" ]
|
||||||
[ "default" ]
|
[ "default" ]
|
||||||
[ "log" ]
|
[ "log" ]
|
||||||
[ "log-always" ]
|
|
||||||
[ "std" ]
|
[ "std" ]
|
||||||
[ "tracing-attributes" ]
|
[ "tracing-attributes" ]
|
||||||
];
|
];
|
||||||
@@ -5700,7 +5892,7 @@ in
|
|||||||
[ "ntstatus" ]
|
[ "ntstatus" ]
|
||||||
[ "objbase" ]
|
[ "objbase" ]
|
||||||
[ "processenv" ]
|
[ "processenv" ]
|
||||||
(lib.optional (rootFeatures' ? "garage/default" || rootFeatures' ? "garage/sled" || rootFeatures' ? "garage_db/sled" || rootFeatures' ? "garage_model/sled") "processthreadsapi")
|
[ "processthreadsapi" ]
|
||||||
[ "profileapi" ]
|
[ "profileapi" ]
|
||||||
[ "schannel" ]
|
[ "schannel" ]
|
||||||
[ "securitybaseapi" ]
|
[ "securitybaseapi" ]
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ let
|
|||||||
|
|
||||||
build_debug_and_release = (target: {
|
build_debug_and_release = (target: {
|
||||||
debug = (compile {
|
debug = (compile {
|
||||||
inherit target git_version;
|
inherit system target git_version pkgsSrc cargo2nixOverlay;
|
||||||
release = false;
|
release = false;
|
||||||
}).workspace.garage {
|
}).workspace.garage {
|
||||||
compileMode = "build";
|
compileMode = "build";
|
||||||
};
|
};
|
||||||
|
|
||||||
release = (compile {
|
release = (compile {
|
||||||
inherit target git_version;
|
inherit system target git_version pkgsSrc cargo2nixOverlay;
|
||||||
release = true;
|
release = true;
|
||||||
}).workspace.garage {
|
}).workspace.garage {
|
||||||
compileMode = "build";
|
compileMode = "build";
|
||||||
@@ -39,7 +39,7 @@ in {
|
|||||||
};
|
};
|
||||||
test = {
|
test = {
|
||||||
amd64 = test (compile {
|
amd64 = test (compile {
|
||||||
inherit git_version;
|
inherit system git_version pkgsSrc cargo2nixOverlay;
|
||||||
target = "x86_64-unknown-linux-musl";
|
target = "x86_64-unknown-linux-musl";
|
||||||
features = [
|
features = [
|
||||||
"garage/bundled-libs"
|
"garage/bundled-libs"
|
||||||
@@ -52,7 +52,7 @@ in {
|
|||||||
};
|
};
|
||||||
clippy = {
|
clippy = {
|
||||||
amd64 = (compile {
|
amd64 = (compile {
|
||||||
inherit git_version;
|
inherit system git_version pkgsSrc cargo2nixOverlay;
|
||||||
target = "x86_64-unknown-linux-musl";
|
target = "x86_64-unknown-linux-musl";
|
||||||
compiler = "clippy";
|
compiler = "clippy";
|
||||||
}).workspace.garage {
|
}).workspace.garage {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Browse doc
|
||||||
|
|
||||||
|
Run in this directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 -m http.server
|
||||||
|
```
|
||||||
|
|
||||||
|
And open in your browser:
|
||||||
|
- http://localhost:8000/garage-admin-v0.html
|
||||||
|
|
||||||
|
# Validate doc
|
||||||
|
|
||||||
|
```
|
||||||
|
wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.1.0/openapi-generator-cli-6.1.0.jar -O openapi-generator-cli.jar
|
||||||
|
java -jar openapi-generator-cli.jar validate -i garage-admin-v0.yml
|
||||||
|
```
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/* montserrat-300 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Montserrat';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local(''),
|
||||||
|
url('../fonts/montserrat-v25-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('../fonts/montserrat-v25-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* montserrat-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Montserrat';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local(''),
|
||||||
|
url('../fonts/montserrat-v25-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('../fonts/montserrat-v25-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* montserrat-700 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Montserrat';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local(''),
|
||||||
|
url('../fonts/montserrat-v25-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('../fonts/montserrat-v25-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
/* roboto-300 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local(''),
|
||||||
|
url('../fonts/roboto-v30-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('../fonts/roboto-v30-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local(''),
|
||||||
|
url('../fonts/roboto-v30-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('../fonts/roboto-v30-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roboto-700 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local(''),
|
||||||
|
url('../fonts/roboto-v30-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('../fonts/roboto-v30-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Garage Adminstration API v0</title>
|
||||||
|
<!-- needed for adaptive design -->
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link href="./css/redoc.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Redoc doesn't change outer page styles
|
||||||
|
-->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<redoc spec-url='./garage-admin-v0.yml'></redoc>
|
||||||
|
<script src="./redoc.standalone.js"> </script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
+++
|
||||||
|
title = "Build your own app"
|
||||||
|
weight = 4
|
||||||
|
sort_by = "weight"
|
||||||
|
template = "documentation.html"
|
||||||
|
+++
|
||||||
|
|
||||||
|
Garage has many API that you can rely on to build complex applications.
|
||||||
|
In this section, we reference the existing SDKs and give some code examples.
|
||||||
|
|
||||||
|
|
||||||
|
## ⚠️ DISCLAIMER
|
||||||
|
|
||||||
|
**K2V AND ADMIN SDK ARE TECHNICAL PREVIEWS**. The following limitations apply:
|
||||||
|
- The API is not complete, some actions are possible only through the `garage` binary
|
||||||
|
- The underlying admin API is not yet stable nor complete, it can breaks at any time
|
||||||
|
- The generator configuration is currently tweaked, the library might break at any time due to a generator change
|
||||||
|
- Because the API and the library are not stable, none of them are published in a package manager (npm, pypi, etc.)
|
||||||
|
- This code has not been extensively tested, some things might not work (please report!)
|
||||||
|
|
||||||
|
To have the best experience possible, please consider:
|
||||||
|
- Make sure that the version of the library you are using is pinned (`go.sum`, `package-lock.json`, `requirements.txt`).
|
||||||
|
- Before upgrading your Garage cluster, make sure that you can find a version of this SDK that works with your targeted version and that you are able to update your own code to work with this new version of the library.
|
||||||
|
- Join our Matrix channel at `#garage:deuxfleurs.fr`, say that you are interested by this SDK, and report any friction.
|
||||||
|
- If stability is critical, mirror this repository on your own infrastructure, regenerate the SDKs and upgrade them at your own pace.
|
||||||
|
|
||||||
|
|
||||||
|
## About the APIs
|
||||||
|
|
||||||
|
Code can interact with Garage through 3 different APIs: S3, K2V, and Admin.
|
||||||
|
Each of them has a specific scope.
|
||||||
|
|
||||||
|
### S3
|
||||||
|
|
||||||
|
De-facto standard, introduced by Amazon, designed to store blobs of data.
|
||||||
|
|
||||||
|
### K2V
|
||||||
|
|
||||||
|
A simple database API similar to RiakKV or DynamoDB.
|
||||||
|
Think a key value store with some additional operations.
|
||||||
|
Its design is inspired by Distributed Hash Tables (DHT).
|
||||||
|
|
||||||
|
More information:
|
||||||
|
- [In the reference manual](@/documentation/reference-manual/k2v.md)
|
||||||
|
|
||||||
|
|
||||||
|
### Administration
|
||||||
|
|
||||||
|
Garage operations can also be automated through a REST API.
|
||||||
|
We are currently building this SDK for [Python](@/documentation/build/python.md#admin-api), [Javascript](@/documentation/build/javascript.md#administration) and [Golang](@/documentation/build/golang.md#administration).
|
||||||
|
|
||||||
|
More information:
|
||||||
|
- [In the reference manual](@/documentation/reference-manual/admin-api.md)
|
||||||
|
- [Full specifiction](https://garagehq.deuxfleurs.fr/api/garage-admin-v0.html)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
+++
|
||||||
|
title = "Golang"
|
||||||
|
weight = 30
|
||||||
|
+++
|
||||||
|
|
||||||
|
## S3
|
||||||
|
|
||||||
|
*Coming soon*
|
||||||
|
|
||||||
|
Some refs:
|
||||||
|
- Minio minio-go-sdk
|
||||||
|
- [Reference](https://docs.min.io/docs/golang-client-api-reference.html)
|
||||||
|
|
||||||
|
- Amazon aws-sdk-go-v2
|
||||||
|
- [Installation](https://aws.github.io/aws-sdk-go-v2/docs/getting-started/)
|
||||||
|
- [Reference](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/s3)
|
||||||
|
- [Example](https://aws.github.io/aws-sdk-go-v2/docs/code-examples/s3/putobject/)
|
||||||
|
|
||||||
|
## K2V
|
||||||
|
|
||||||
|
*Coming soon*
|
||||||
|
|
||||||
|
## Administration
|
||||||
|
|
||||||
|
Install the SDK with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang
|
||||||
|
```
|
||||||
|
|
||||||
|
A short example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Set Host and other parameters
|
||||||
|
configuration := garage.NewConfiguration()
|
||||||
|
configuration.Host = "127.0.0.1:3903"
|
||||||
|
|
||||||
|
|
||||||
|
// We can now generate a client
|
||||||
|
client := garage.NewAPIClient(configuration)
|
||||||
|
|
||||||
|
// Authentication is handled through the context pattern
|
||||||
|
ctx := context.WithValue(context.Background(), garage.ContextAccessToken, "s3cr3t")
|
||||||
|
|
||||||
|
// Send a request
|
||||||
|
resp, r, err := client.NodesApi.GetNodes(ctx).Execute()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error when calling `NodesApi.GetNodes``: %v\n", err)
|
||||||
|
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the response
|
||||||
|
fmt.Fprintf(os.Stdout, "Target hostname: %v\n", resp.KnownNodes[resp.Node].Hostname)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See also:
|
||||||
|
- [generated doc](https://git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang)
|
||||||
|
- [examples](https://git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-generator/src/branch/main/example/golang)
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
+++
|
||||||
|
title = "Javascript"
|
||||||
|
weight = 10
|
||||||
|
+++
|
||||||
|
|
||||||
|
## S3
|
||||||
|
|
||||||
|
*Coming soon*.
|
||||||
|
|
||||||
|
Some refs:
|
||||||
|
- Minio SDK
|
||||||
|
- [Reference](https://docs.min.io/docs/javascript-client-api-reference.html)
|
||||||
|
|
||||||
|
- Amazon aws-sdk-js
|
||||||
|
- [Installation](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-started.html)
|
||||||
|
- [Reference](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html)
|
||||||
|
- [Example](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/s3-example-creating-buckets.html)
|
||||||
|
|
||||||
|
## K2V
|
||||||
|
|
||||||
|
*Coming soon*
|
||||||
|
|
||||||
|
## Administration
|
||||||
|
|
||||||
|
Install the SDK with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save git+https://git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-js.git
|
||||||
|
```
|
||||||
|
|
||||||
|
A short example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const garage = require('garage_administration_api_v0garage_v0_8_0');
|
||||||
|
|
||||||
|
const api = new garage.ApiClient("http://127.0.0.1:3903/v0");
|
||||||
|
api.authentications['bearerAuth'].accessToken = "s3cr3t";
|
||||||
|
|
||||||
|
const [node, layout, key, bucket] = [
|
||||||
|
new garage.NodesApi(api),
|
||||||
|
new garage.LayoutApi(api),
|
||||||
|
new garage.KeyApi(api),
|
||||||
|
new garage.BucketApi(api),
|
||||||
|
];
|
||||||
|
|
||||||
|
node.getNodes().then((data) => {
|
||||||
|
console.log(`nodes: ${Object.values(data.knownNodes).map(n => n.hostname)}`)
|
||||||
|
}, (error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
See also:
|
||||||
|
- [sdk repository](https://git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-js)
|
||||||
|
- [examples](https://git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-generator/src/branch/main/example/javascript)
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
+++
|
+++
|
||||||
title = "Your code (PHP, JS, Go...)"
|
title = "Others"
|
||||||
weight = 30
|
weight = 99
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
## S3
|
||||||
|
|
||||||
If you are developping a new application, you may want to use Garage to store your user's media.
|
If you are developping a new application, you may want to use Garage to store your user's media.
|
||||||
|
|
||||||
The S3 API that Garage uses is a standard REST API, so as long as you can make HTTP requests,
|
The S3 API that Garage uses is a standard REST API, so as long as you can make HTTP requests,
|
||||||
@@ -13,44 +15,14 @@ Instead, there are some libraries already avalaible.
|
|||||||
|
|
||||||
Some of them are maintained by Amazon, some by Minio, others by the community.
|
Some of them are maintained by Amazon, some by Minio, others by the community.
|
||||||
|
|
||||||
## PHP
|
### PHP
|
||||||
|
|
||||||
- Amazon aws-sdk-php
|
- Amazon aws-sdk-php
|
||||||
- [Installation](https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/getting-started_installation.html)
|
- [Installation](https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/getting-started_installation.html)
|
||||||
- [Reference](https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html)
|
- [Reference](https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html)
|
||||||
- [Example](https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-examples-creating-buckets.html)
|
- [Example](https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-examples-creating-buckets.html)
|
||||||
|
|
||||||
## Javascript
|
### Java
|
||||||
|
|
||||||
- Minio SDK
|
|
||||||
- [Reference](https://docs.min.io/docs/javascript-client-api-reference.html)
|
|
||||||
|
|
||||||
- Amazon aws-sdk-js
|
|
||||||
- [Installation](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-started.html)
|
|
||||||
- [Reference](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html)
|
|
||||||
- [Example](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/s3-example-creating-buckets.html)
|
|
||||||
|
|
||||||
## Golang
|
|
||||||
|
|
||||||
- Minio minio-go-sdk
|
|
||||||
- [Reference](https://docs.min.io/docs/golang-client-api-reference.html)
|
|
||||||
|
|
||||||
- Amazon aws-sdk-go-v2
|
|
||||||
- [Installation](https://aws.github.io/aws-sdk-go-v2/docs/getting-started/)
|
|
||||||
- [Reference](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/s3)
|
|
||||||
- [Example](https://aws.github.io/aws-sdk-go-v2/docs/code-examples/s3/putobject/)
|
|
||||||
|
|
||||||
## Python
|
|
||||||
|
|
||||||
- Minio SDK
|
|
||||||
- [Reference](https://docs.min.io/docs/python-client-api-reference.html)
|
|
||||||
|
|
||||||
- Amazon boto3
|
|
||||||
- [Installation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html)
|
|
||||||
- [Reference](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html)
|
|
||||||
- [Example](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-uploading-files.html)
|
|
||||||
|
|
||||||
## Java
|
|
||||||
|
|
||||||
- Minio SDK
|
- Minio SDK
|
||||||
- [Reference](https://docs.min.io/docs/java-client-api-reference.html)
|
- [Reference](https://docs.min.io/docs/java-client-api-reference.html)
|
||||||
@@ -60,23 +32,18 @@ Some of them are maintained by Amazon, some by Minio, others by the community.
|
|||||||
- [Reference](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html)
|
- [Reference](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html)
|
||||||
- [Example](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/examples-s3-objects.html)
|
- [Example](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/examples-s3-objects.html)
|
||||||
|
|
||||||
## Rust
|
### .NET
|
||||||
|
|
||||||
- Amazon aws-rust-sdk
|
|
||||||
- [Github](https://github.com/awslabs/aws-sdk-rust)
|
|
||||||
|
|
||||||
## .NET
|
|
||||||
|
|
||||||
- Minio SDK
|
- Minio SDK
|
||||||
- [Reference](https://docs.min.io/docs/dotnet-client-api-reference.html)
|
- [Reference](https://docs.min.io/docs/dotnet-client-api-reference.html)
|
||||||
|
|
||||||
- Amazon aws-dotnet-sdk
|
- Amazon aws-dotnet-sdk
|
||||||
|
|
||||||
## C++
|
### C++
|
||||||
|
|
||||||
- Amazon aws-cpp-sdk
|
- Amazon aws-cpp-sdk
|
||||||
|
|
||||||
## Haskell
|
### Haskell
|
||||||
|
|
||||||
- Minio SDK
|
- Minio SDK
|
||||||
- [Reference](https://docs.min.io/docs/haskell-client-api-reference.html)
|
- [Reference](https://docs.min.io/docs/haskell-client-api-reference.html)
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
+++
|
||||||
|
title = "Python"
|
||||||
|
weight = 20
|
||||||
|
+++
|
||||||
|
|
||||||
|
## S3
|
||||||
|
|
||||||
|
*Coming soon*
|
||||||
|
|
||||||
|
Some refs:
|
||||||
|
- Minio SDK
|
||||||
|
- [Reference](https://docs.min.io/docs/python-client-api-reference.html)
|
||||||
|
|
||||||
|
- Amazon boto3
|
||||||
|
- [Installation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html)
|
||||||
|
- [Reference](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html)
|
||||||
|
- [Example](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-uploading-files.html)
|
||||||
|
|
||||||
|
## K2V
|
||||||
|
|
||||||
|
*Coming soon*
|
||||||
|
|
||||||
|
## Admin API
|
||||||
|
|
||||||
|
You need at least Python 3.6, pip, and setuptools.
|
||||||
|
Because the python package is in a subfolder, the command is a bit more complicated than usual:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip3 install --user 'git+https://git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-python'
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, let imagine you have a fresh Garage instance running on localhost, with the admin API configured on port 3903 with the bearer `s3cr3t`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import garage_admin_sdk
|
||||||
|
from garage_admin_sdk.apis import *
|
||||||
|
from garage_admin_sdk.models import *
|
||||||
|
|
||||||
|
configuration = garage_admin_sdk.Configuration(
|
||||||
|
host = "http://localhost:3903/v0",
|
||||||
|
access_token = "s3cr3t"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Init APIs
|
||||||
|
api = garage_admin_sdk.ApiClient(configuration)
|
||||||
|
nodes, layout, keys, buckets = NodesApi(api), LayoutApi(api), KeyApi(api), BucketApi(api)
|
||||||
|
|
||||||
|
# Display some info on the node
|
||||||
|
status = nodes.get_nodes()
|
||||||
|
print(f"running garage {status.garage_version}, node_id {status.node}")
|
||||||
|
|
||||||
|
# Change layout of this node
|
||||||
|
current = layout.get_layout()
|
||||||
|
layout.add_layout({
|
||||||
|
status.node: NodeClusterInfo(
|
||||||
|
zone = "dc1",
|
||||||
|
capacity = 1,
|
||||||
|
tags = [ "dev" ],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
layout.apply_layout(LayoutVersion(
|
||||||
|
version = current.version + 1
|
||||||
|
))
|
||||||
|
|
||||||
|
# Create key, allow it to create buckets
|
||||||
|
kinfo = keys.add_key(AddKeyRequest(name="openapi"))
|
||||||
|
|
||||||
|
allow_create = UpdateKeyRequestAllow(create_bucket=True)
|
||||||
|
keys.update_key(kinfo.access_key_id, UpdateKeyRequest(allow=allow_create))
|
||||||
|
|
||||||
|
# Create a bucket, allow key, set quotas
|
||||||
|
binfo = buckets.create_bucket(CreateBucketRequest(global_alias="documentation"))
|
||||||
|
binfo = buckets.allow_bucket_key(AllowBucketKeyRequest(
|
||||||
|
bucket_id=binfo.id,
|
||||||
|
access_key_id=kinfo.access_key_id,
|
||||||
|
permissions=AllowBucketKeyRequestPermissions(read=True, write=True, owner=True),
|
||||||
|
))
|
||||||
|
binfo = buckets.update_bucket(binfo.id, UpdateBucketRequest(
|
||||||
|
quotas=UpdateBucketRequestQuotas(max_size=19029801,max_objects=1500)))
|
||||||
|
|
||||||
|
# Display key
|
||||||
|
print(f"""
|
||||||
|
cluster ready
|
||||||
|
key id is {kinfo.access_key_id}
|
||||||
|
secret key is {kinfo.secret_access_key}
|
||||||
|
bucket {binfo.global_aliases[0]} contains {binfo.objects}/{binfo.quotas.max_objects} objects
|
||||||
|
""")
|
||||||
|
```
|
||||||
|
|
||||||
|
*This example is named `short.py` in the example folder. Other python examples are also available.*
|
||||||
|
|
||||||
|
See also:
|
||||||
|
- [sdk repo](https://git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-python)
|
||||||
|
- [examples](https://git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-generator/src/branch/main/example/python)
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
+++
|
||||||
|
title = "Rust"
|
||||||
|
weight = 40
|
||||||
|
+++
|
||||||
|
|
||||||
|
## S3
|
||||||
|
|
||||||
|
*Coming soon*
|
||||||
|
|
||||||
|
Some refs:
|
||||||
|
- Amazon aws-rust-sdk
|
||||||
|
- [Github](https://github.com/awslabs/aws-sdk-rust)
|
||||||
|
|
||||||
|
## K2V
|
||||||
|
|
||||||
|
*Coming soon*
|
||||||
|
|
||||||
|
Some refs: https://git.deuxfleurs.fr/Deuxfleurs/garage/src/branch/main/src/k2v-client
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Admin API
|
||||||
|
|
||||||
|
*Coming soon*
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
+++
|
+++
|
||||||
title = "Integrations"
|
title = "Existing integrations"
|
||||||
weight = 3
|
weight = 3
|
||||||
sort_by = "weight"
|
sort_by = "weight"
|
||||||
template = "documentation.html"
|
template = "documentation.html"
|
||||||
@@ -14,7 +14,6 @@ In particular, you will find here instructions to connect it with:
|
|||||||
- [Applications](@/documentation/connect/apps/index.md)
|
- [Applications](@/documentation/connect/apps/index.md)
|
||||||
- [Website hosting](@/documentation/connect/websites.md)
|
- [Website hosting](@/documentation/connect/websites.md)
|
||||||
- [Software repositories](@/documentation/connect/repositories.md)
|
- [Software repositories](@/documentation/connect/repositories.md)
|
||||||
- [Your own code](@/documentation/connect/code.md)
|
|
||||||
- [FUSE](@/documentation/connect/fs.md)
|
- [FUSE](@/documentation/connect/fs.md)
|
||||||
|
|
||||||
### Generic instructions
|
### Generic instructions
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ In this section, we cover the following web applications:
|
|||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| [Nextcloud](#nextcloud) | ✅ | Both Primary Storage and External Storage are supported |
|
| [Nextcloud](#nextcloud) | ✅ | Both Primary Storage and External Storage are supported |
|
||||||
| [Peertube](#peertube) | ✅ | Must be configured with the website endpoint |
|
| [Peertube](#peertube) | ✅ | Must be configured with the website endpoint |
|
||||||
| [Mastodon](#mastodon) | ❓ | Not yet tested |
|
| [Mastodon](#mastodon) | ✅ | Natively supported |
|
||||||
| [Matrix](#matrix) | ✅ | Tested with `synapse-s3-storage-provider` |
|
| [Matrix](#matrix) | ✅ | Tested with `synapse-s3-storage-provider` |
|
||||||
| [Pixelfed](#pixelfed) | ❓ | Not yet tested |
|
| [Pixelfed](#pixelfed) | ❓ | Not yet tested |
|
||||||
| [Pleroma](#pleroma) | ❓ | Not yet tested |
|
| [Pleroma](#pleroma) | ❓ | Not yet tested |
|
||||||
@@ -224,7 +224,135 @@ You can now reload the page and see in your browser console that data are fetche
|
|||||||
|
|
||||||
## Mastodon
|
## Mastodon
|
||||||
|
|
||||||
https://docs.joinmastodon.org/admin/config/#cdn
|
Mastodon natively supports the S3 protocol to store media files, and it works out-of-the-box with Garage.
|
||||||
|
You will need to expose your Garage bucket as a website: that way, media files will be served directly from Garage.
|
||||||
|
|
||||||
|
### Performance considerations
|
||||||
|
|
||||||
|
Mastodon tends to store many small objects over time: expect hundreds of thousands of objects,
|
||||||
|
with average object size ranging from 50 KB to 150 KB.
|
||||||
|
|
||||||
|
As such, your Garage cluster should be configured appropriately for good performance:
|
||||||
|
|
||||||
|
- use Garage v0.8.0 or higher with the [LMDB database engine](@documentation/reference-manual/configuration.md#db-engine-since-v0-8-0).
|
||||||
|
With the default Sled database engine, your database could quickly end up taking tens of GB of disk space.
|
||||||
|
- the Garage database should be stored on a SSD
|
||||||
|
|
||||||
|
### Creating your bucket
|
||||||
|
|
||||||
|
This is the usual Garage setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
garage key new --name mastodon-key
|
||||||
|
garage bucket create mastodon-data
|
||||||
|
garage bucket allow mastodon-data --read --write --key mastodon-key
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the Key ID and Secret Key.
|
||||||
|
|
||||||
|
### Exposing your bucket as a website
|
||||||
|
|
||||||
|
Create a DNS name to serve your media files, such as `my-social-media.mydomain.tld`.
|
||||||
|
This name will be publicly exposed to the users of your Mastodon instance: they
|
||||||
|
will load images directly from this DNS name.
|
||||||
|
|
||||||
|
As [documented here](@/documentation/cookbook/exposing-websites.md),
|
||||||
|
add this DNS name as alias to your bucket, and expose it as a website:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
garage bucket alias mastodon-data my-social-media.mydomain.tld
|
||||||
|
garage bucket website --allow mastodon-data
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you will likely need to [setup a reverse proxy](@/documentation/cookbook/reverse-proxy.md)
|
||||||
|
in front of it to serve your media files over HTTPS.
|
||||||
|
|
||||||
|
### Cleaning up old media files before migration
|
||||||
|
|
||||||
|
Mastodon instance quickly accumulate a lot of media files from the federation.
|
||||||
|
Most of them are not strictly necessary because they can be fetched again from
|
||||||
|
other servers. As such, it is highly recommended to clean them up before
|
||||||
|
migration, this will greatly reduce the migration time.
|
||||||
|
|
||||||
|
From the [official Mastodon documentation](https://docs.joinmastodon.org/admin/tootctl/#media):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ RAILS_ENV=production bin/tootctl media remove --days 3
|
||||||
|
$ RAILS_ENV=production bin/tootctl media remove-orphans
|
||||||
|
$ RAILS_ENV=production bin/tootctl preview_cards remove --days 15
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is a typical disk usage for a small but multi-year instance after cleanup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ RAILS_ENV=production bin/tootctl media usage
|
||||||
|
Attachments: 5.67 GB (1.14 GB local)
|
||||||
|
Custom emoji: 295 MB (0 Bytes local)
|
||||||
|
Preview cards: 154 MB
|
||||||
|
Avatars: 3.77 GB (127 KB local)
|
||||||
|
Headers: 8.72 GB (242 KB local)
|
||||||
|
Backups: 0 Bytes
|
||||||
|
Imports: 1.7 KB
|
||||||
|
Settings: 0 Bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
Unfortunately, [old avatars and headers cannot currently be cleaned up](https://github.com/mastodon/mastodon/issues/9567).
|
||||||
|
|
||||||
|
### Migrating your data
|
||||||
|
|
||||||
|
Data migration should be done with an efficient S3 client.
|
||||||
|
The [minio client](@documentation/connect/cli.md#minio-client) is a good choice
|
||||||
|
thanks to its mirror mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mc mirror ./public/system/ garage/mastodon-data
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is a typical bucket usage after all data has been migrated:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ garage bucket info mastodon-data
|
||||||
|
|
||||||
|
Size: 20.3 GiB (21.8 GB)
|
||||||
|
Objects: 175968
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuring Mastodon
|
||||||
|
|
||||||
|
In your `.env.production` configuration file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
S3_ENABLED=true
|
||||||
|
# Internal access to Garage
|
||||||
|
S3_ENDPOINT=http://my-garage-instance.mydomain.tld:3900
|
||||||
|
S3_REGION=garage
|
||||||
|
S3_BUCKET=mastodon-data
|
||||||
|
# Change this (Key ID and Secret Key of your Garage key)
|
||||||
|
AWS_ACCESS_KEY_ID=GKe88df__CHANGETHIS__c5145
|
||||||
|
AWS_SECRET_ACCESS_KEY=a2f7__CHANGETHIS__77fcfcf7a58f47a4aa4431f2e675c56da37821a1070000
|
||||||
|
# What name gets exposed to users (HTTPS is implicit)
|
||||||
|
S3_ALIAS_HOST=my-social-media.mydomain.tld
|
||||||
|
```
|
||||||
|
|
||||||
|
For more details, see the [reference Mastodon documentation](https://docs.joinmastodon.org/admin/config/#cdn).
|
||||||
|
|
||||||
|
Restart all Mastodon services and everything should now be using Garage!
|
||||||
|
You can check the URLs of images in the Mastodon web client, they should start
|
||||||
|
with `https://my-social-media.mydomain.tld`.
|
||||||
|
|
||||||
|
### Last migration sync
|
||||||
|
|
||||||
|
After Mastodon is successfully using Garage, you can run a last sync from the local filesystem to Garage:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mc mirror --newer-than "3h" ./public/system/ garage/mastodon-data
|
||||||
|
```
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
[cybrespace's guide to migrate to S3](https://github.com/cybrespace/cybrespace-meta/blob/master/s3.md)
|
||||||
|
(the guide is for Amazon S3, so the configuration is a bit different, but the rest is similar)
|
||||||
|
|
||||||
|
|
||||||
## Matrix
|
## Matrix
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,306 @@
|
|||||||
|
+++
|
||||||
|
title = "Monitoring Garage"
|
||||||
|
weight = 40
|
||||||
|
+++
|
||||||
|
|
||||||
|
Garage exposes some internal metrics in the Prometheus data format.
|
||||||
|
This page explains how to exploit these metrics.
|
||||||
|
|
||||||
|
## Setting up monitoring
|
||||||
|
|
||||||
|
### Enabling the Admin API endpoint
|
||||||
|
|
||||||
|
If you have not already enabled the [administration API endpoint](@/documentation/reference-manual/admin-api.md), do so by adding the following lines to your configuration file:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[admin]
|
||||||
|
api_bind_addr = "0.0.0.0:3903"
|
||||||
|
```
|
||||||
|
|
||||||
|
This will allow anyone to scrape Prometheus metrics by fetching
|
||||||
|
`http://localhost:3093/metrics`. If you want to restrict access
|
||||||
|
to the exported metrics, set the `metrics_token` configuration value
|
||||||
|
to a bearer token to be used when fetching the metrics endpoint.
|
||||||
|
|
||||||
|
### Setting up Prometheus and Grafana
|
||||||
|
|
||||||
|
Add a scrape config to your Prometheus daemon to scrape metrics from
|
||||||
|
all of your nodes:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'garage'
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- 'node1.mycluster:3903'
|
||||||
|
- 'node2.mycluster:3903'
|
||||||
|
- 'node3.mycluster:3903'
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have set a metrics token in your Garage configuration file,
|
||||||
|
add the following lines in your Prometheus scrape config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
authorization:
|
||||||
|
type: Bearer
|
||||||
|
credentials: 'your metrics token'
|
||||||
|
```
|
||||||
|
|
||||||
|
To visualize the scraped data in Grafana,
|
||||||
|
you can either import our [Grafana dashboard for Garage](https://git.deuxfleurs.fr/Deuxfleurs/garage/raw/branch/main/script/telemetry/grafana-garage-dashboard-prometheus.json)
|
||||||
|
or make your own.
|
||||||
|
We detail below the list of exposed metrics and their meaning.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## List of exported metrics
|
||||||
|
|
||||||
|
|
||||||
|
### Metrics of the API endpoints
|
||||||
|
|
||||||
|
#### `api_admin_request_counter` (counter)
|
||||||
|
|
||||||
|
Counts the number of requests to a given endpoint of the administration API. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
api_admin_request_counter{api_endpoint="Metrics"} 127041
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `api_admin_request_duration` (histogram)
|
||||||
|
|
||||||
|
Evaluates the duration of API calls to the various administration API endpoint. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
api_admin_request_duration_bucket{api_endpoint="Metrics",le="0.5"} 127041
|
||||||
|
api_admin_request_duration_sum{api_endpoint="Metrics"} 605.250344830999
|
||||||
|
api_admin_request_duration_count{api_endpoint="Metrics"} 127041
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `api_s3_request_counter` (counter)
|
||||||
|
|
||||||
|
Counts the number of requests to a given endpoint of the S3 API. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
api_s3_request_counter{api_endpoint="CreateMultipartUpload"} 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `api_s3_error_counter` (counter)
|
||||||
|
|
||||||
|
Counts the number of requests to a given endpoint of the S3 API that returned an error. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
api_s3_error_counter{api_endpoint="GetObject",status_code="404"} 39
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `api_s3_request_duration` (histogram)
|
||||||
|
|
||||||
|
Evaluates the duration of API calls to the various S3 API endpoints. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
api_s3_request_duration_bucket{api_endpoint="CreateMultipartUpload",le="0.5"} 1
|
||||||
|
api_s3_request_duration_sum{api_endpoint="CreateMultipartUpload"} 0.046340762
|
||||||
|
api_s3_request_duration_count{api_endpoint="CreateMultipartUpload"} 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `api_k2v_request_counter` (counter), `api_k2v_error_counter` (counter), `api_k2v_error_duration` (histogram)
|
||||||
|
|
||||||
|
Same as for S3, for the K2V API.
|
||||||
|
|
||||||
|
|
||||||
|
### Metrics of the Web endpoint
|
||||||
|
|
||||||
|
|
||||||
|
#### `web_request_counter` (counter)
|
||||||
|
|
||||||
|
Number of requests to the web endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
web_request_counter{method="GET"} 80
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `web_request_duration` (histogram)
|
||||||
|
|
||||||
|
Duration of requests to the web endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
web_request_duration_bucket{method="GET",le="0.5"} 80
|
||||||
|
web_request_duration_sum{method="GET"} 1.0528433229999998
|
||||||
|
web_request_duration_count{method="GET"} 80
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `web_error_counter` (counter)
|
||||||
|
|
||||||
|
Number of requests to the web endpoint resulting in errors
|
||||||
|
|
||||||
|
```
|
||||||
|
web_error_counter{method="GET",status_code="404 Not Found"} 64
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Metrics of the data block manager
|
||||||
|
|
||||||
|
#### `block_bytes_read`, `block_bytes_written` (counter)
|
||||||
|
|
||||||
|
Number of bytes read/written to/from disk in the data storage directory.
|
||||||
|
|
||||||
|
```
|
||||||
|
block_bytes_read 120586322022
|
||||||
|
block_bytes_written 3386618077
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `block_read_duration`, `block_write_duration` (histograms)
|
||||||
|
|
||||||
|
Evaluates the duration of the reading/writing of individual data blocks in the data storage directory.
|
||||||
|
|
||||||
|
```
|
||||||
|
block_read_duration_bucket{le="0.5"} 169229
|
||||||
|
block_read_duration_sum 2761.6902550310056
|
||||||
|
block_read_duration_count 169240
|
||||||
|
block_write_duration_bucket{le="0.5"} 3559
|
||||||
|
block_write_duration_sum 195.59170078500006
|
||||||
|
block_write_duration_count 3571
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `block_delete_counter` (counter)
|
||||||
|
|
||||||
|
Counts the number of data blocks that have been deleted from storage.
|
||||||
|
|
||||||
|
```
|
||||||
|
block_delete_counter 122
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `block_resync_counter` (counter), `block_resync_duration` (histogram)
|
||||||
|
|
||||||
|
Counts the number of resync operations the node has executed, and evaluates their duration.
|
||||||
|
|
||||||
|
```
|
||||||
|
block_resync_counter 308897
|
||||||
|
block_resync_duration_bucket{le="0.5"} 308892
|
||||||
|
block_resync_duration_sum 139.64204196100016
|
||||||
|
block_resync_duration_count 308897
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `block_resync_queue_length` (gauge)
|
||||||
|
|
||||||
|
The number of block hashes currently queued for a resync.
|
||||||
|
This is normal to be nonzero for long periods of time.
|
||||||
|
|
||||||
|
```
|
||||||
|
block_resync_queue_length 0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `block_resync_errored_blocks` (gauge)
|
||||||
|
|
||||||
|
The number of block hashes that we were unable to resync last time we tried.
|
||||||
|
**THIS SHOULD BE ZERO, OR FALL BACK TO ZERO RAPIDLY, IN A HEALTHY CLUSTER.**
|
||||||
|
Persistent nonzero values indicate that some data is likely to be lost.
|
||||||
|
|
||||||
|
```
|
||||||
|
block_resync_errored_blocks 0
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Metrics related to RPCs (remote procedure calls) between nodes
|
||||||
|
|
||||||
|
#### `rpc_netapp_request_counter` (counter)
|
||||||
|
|
||||||
|
Number of RPC requests emitted
|
||||||
|
|
||||||
|
```
|
||||||
|
rpc_request_counter{from="<this node>",rpc_endpoint="garage_block/manager.rs/Rpc",to="<remote node>"} 176
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `rpc_netapp_error_counter` (counter)
|
||||||
|
|
||||||
|
Number of communication errors (errors in the Netapp library, generally due to disconnected nodes)
|
||||||
|
|
||||||
|
```
|
||||||
|
rpc_netapp_error_counter{from="<this node>",rpc_endpoint="garage_block/manager.rs/Rpc",to="<remote node>"} 354
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `rpc_timeout_counter` (counter)
|
||||||
|
|
||||||
|
Number of RPC timeouts, should be close to zero in a healthy cluster.
|
||||||
|
|
||||||
|
```
|
||||||
|
rpc_timeout_counter{from="<this node>",rpc_endpoint="garage_rpc/membership.rs/SystemRpc",to="<remote node>"} 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `rpc_duration` (histogram)
|
||||||
|
|
||||||
|
The duration of internal RPC calls between Garage nodes.
|
||||||
|
|
||||||
|
```
|
||||||
|
rpc_duration_bucket{from="<this node>",rpc_endpoint="garage_block/manager.rs/Rpc",to="<remote node>",le="0.5"} 166
|
||||||
|
rpc_duration_sum{from="<this node>",rpc_endpoint="garage_block/manager.rs/Rpc",to="<remote node>"} 35.172253716
|
||||||
|
rpc_duration_count{from="<this node>",rpc_endpoint="garage_block/manager.rs/Rpc",to="<remote node>"} 174
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Metrics of the metadata table manager
|
||||||
|
|
||||||
|
#### `table_gc_todo_queue_length` (gauge)
|
||||||
|
|
||||||
|
Table garbage collector TODO queue length
|
||||||
|
|
||||||
|
```
|
||||||
|
table_gc_todo_queue_length{table_name="block_ref"} 0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `table_get_request_counter` (counter), `table_get_request_duration` (histogram)
|
||||||
|
|
||||||
|
Number of get/get_range requests internally made on each table, and their duration.
|
||||||
|
|
||||||
|
```
|
||||||
|
table_get_request_counter{table_name="bucket_alias"} 315
|
||||||
|
table_get_request_duration_bucket{table_name="bucket_alias",le="0.5"} 315
|
||||||
|
table_get_request_duration_sum{table_name="bucket_alias"} 0.048509778000000024
|
||||||
|
table_get_request_duration_count{table_name="bucket_alias"} 315
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### `table_put_request_counter` (counter), `table_put_request_duration` (histogram)
|
||||||
|
|
||||||
|
Number of insert/insert_many requests internally made on this table, and their duration
|
||||||
|
|
||||||
|
```
|
||||||
|
table_put_request_counter{table_name="block_ref"} 677
|
||||||
|
table_put_request_duration_bucket{table_name="block_ref",le="0.5"} 677
|
||||||
|
table_put_request_duration_sum{table_name="block_ref"} 61.617528636
|
||||||
|
table_put_request_duration_count{table_name="block_ref"} 677
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `table_internal_delete_counter` (counter)
|
||||||
|
|
||||||
|
Number of value deletions in the tree (due to GC or repartitioning)
|
||||||
|
|
||||||
|
```
|
||||||
|
table_internal_delete_counter{table_name="block_ref"} 2296
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `table_internal_update_counter` (counter)
|
||||||
|
|
||||||
|
Number of value updates where the value actually changes (includes creation of new key and update of existing key)
|
||||||
|
|
||||||
|
```
|
||||||
|
table_internal_update_counter{table_name="block_ref"} 5996
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `table_merkle_updater_todo_queue_length` (gauge)
|
||||||
|
|
||||||
|
Merkle tree updater TODO queue length (should fall to zero rapidly)
|
||||||
|
|
||||||
|
```
|
||||||
|
table_merkle_updater_todo_queue_length{table_name="block_ref"} 0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `table_sync_items_received`, `table_sync_items_sent` (counters)
|
||||||
|
|
||||||
|
Number of data items sent to/recieved from other nodes during resync procedures
|
||||||
|
|
||||||
|
```
|
||||||
|
table_sync_items_received{from="<remote node>",table_name="bucket_v2"} 3
|
||||||
|
table_sync_items_sent{table_name="block_ref",to="<remote node>"} 2
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -11,8 +11,9 @@ We recommend first following the [quick start guide](@/documentation/quick-start
|
|||||||
to get familiar with Garage's command line and usage patterns.
|
to get familiar with Garage's command line and usage patterns.
|
||||||
|
|
||||||
|
|
||||||
|
## Preparing your environment
|
||||||
|
|
||||||
## Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
To run a real-world deployment, make sure the following conditions are met:
|
To run a real-world deployment, make sure the following conditions are met:
|
||||||
|
|
||||||
@@ -21,10 +22,6 @@ To run a real-world deployment, make sure the following conditions are met:
|
|||||||
- Each machine has a public IP address which is reachable by other machines.
|
- Each machine has a public IP address which is reachable by other machines.
|
||||||
Running behind a NAT is likely to be possible but hasn't been tested for the latest version (TODO).
|
Running behind a NAT is likely to be possible but hasn't been tested for the latest version (TODO).
|
||||||
|
|
||||||
- Ideally, each machine should have a SSD available in addition to the HDD you are dedicating
|
|
||||||
to Garage. This will allow for faster access to metadata and has the potential
|
|
||||||
to significantly reduce Garage's response times.
|
|
||||||
|
|
||||||
- This guide will assume you are using Docker containers to deploy Garage on each node.
|
- This guide will assume you are using Docker containers to deploy Garage on each node.
|
||||||
Garage can also be run independently, for instance as a [Systemd service](@/documentation/cookbook/systemd.md).
|
Garage can also be run independently, for instance as a [Systemd service](@/documentation/cookbook/systemd.md).
|
||||||
You can also use an orchestrator such as Nomad or Kubernetes to automatically manage
|
You can also use an orchestrator such as Nomad or Kubernetes to automatically manage
|
||||||
@@ -49,6 +46,42 @@ available in the different locations of your cluster is roughly the same.
|
|||||||
For instance, here, the Mercury node could be moved to Brussels; this would allow the cluster
|
For instance, here, the Mercury node could be moved to Brussels; this would allow the cluster
|
||||||
to store 2 TB of data in total.
|
to store 2 TB of data in total.
|
||||||
|
|
||||||
|
### Best practices
|
||||||
|
|
||||||
|
- If you have fast dedicated networking between all your nodes, and are planing to store
|
||||||
|
very large files, bump the `block_size` configuration parameter to 10 MB
|
||||||
|
(`block_size = 10485760`).
|
||||||
|
|
||||||
|
- Garage stores its files in two locations: it uses a metadata directory to store frequently-accessed
|
||||||
|
small metadata items, and a data directory to store data blocks of uploaded objects.
|
||||||
|
Ideally, the metadata directory would be stored on an SSD (smaller but faster),
|
||||||
|
and the data directory would be stored on an HDD (larger but slower).
|
||||||
|
|
||||||
|
- For the data directory, Garage already does checksumming and integrity verification,
|
||||||
|
so there is no need to use a filesystem such as BTRFS or ZFS that does it.
|
||||||
|
We recommend using XFS for the data partition, as it has the best performance.
|
||||||
|
EXT4 is not recommended as it has more strict limitations on the number of inodes,
|
||||||
|
which might cause issues with Garage when large numbers of objects are stored.
|
||||||
|
|
||||||
|
- If you only have an HDD and no SSD, it's fine to put your metadata alongside the data
|
||||||
|
on the same drive. Having lots of RAM for your kernel to cache the metadata will
|
||||||
|
help a lot with performance. Make sure to use the LMDB database engine,
|
||||||
|
instead of Sled, which suffers from quite bad performance degradation on HDDs.
|
||||||
|
Sled is still the default for legacy reasons, but is not recommended anymore.
|
||||||
|
|
||||||
|
- For the metadata storage, Garage does not do checksumming and integrity
|
||||||
|
verification on its own. If you are afraid of bitrot/data corruption,
|
||||||
|
put your metadata directory on a BTRFS partition. Otherwise, just use regular
|
||||||
|
EXT4 or XFS.
|
||||||
|
|
||||||
|
- Having a single server with several storage drives is currently not very well
|
||||||
|
supported in Garage ([#218](https://git.deuxfleurs.fr/Deuxfleurs/garage/issues/218)).
|
||||||
|
For an easy setup, just put all your drives in a RAID0 or a ZFS RAIDZ array.
|
||||||
|
If you're adventurous, you can try to format each of your disk as
|
||||||
|
a separate XFS partition, and then run one `garage` daemon per disk drive,
|
||||||
|
or use something like [`mergerfs`](https://github.com/trapexit/mergerfs) to merge
|
||||||
|
all your disks in a single union filesystem that spreads load over them.
|
||||||
|
|
||||||
## Get a Docker image
|
## Get a Docker image
|
||||||
|
|
||||||
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).
|
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).
|
||||||
@@ -76,11 +109,12 @@ especially you must consider the following folders/files:
|
|||||||
this folder will be your main data storage and must be on a large storage (e.g. large HDD)
|
this folder will be your main data storage and must be on a large storage (e.g. large HDD)
|
||||||
|
|
||||||
|
|
||||||
A valid `/etc/garage/garage.toml` for our cluster would look as follows:
|
A valid `/etc/garage.toml` for our cluster would look as follows:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
metadata_dir = "/var/lib/garage/meta"
|
metadata_dir = "/var/lib/garage/meta"
|
||||||
data_dir = "/var/lib/garage/data"
|
data_dir = "/var/lib/garage/data"
|
||||||
|
db_engine = "lmdb"
|
||||||
|
|
||||||
replication_mode = "3"
|
replication_mode = "3"
|
||||||
|
|
||||||
@@ -90,8 +124,6 @@ rpc_bind_addr = "[::]:3901"
|
|||||||
rpc_public_addr = "<this node's public IP>:3901"
|
rpc_public_addr = "<this node's public IP>:3901"
|
||||||
rpc_secret = "<RPC secret>"
|
rpc_secret = "<RPC secret>"
|
||||||
|
|
||||||
bootstrap_peers = []
|
|
||||||
|
|
||||||
[s3_api]
|
[s3_api]
|
||||||
s3_region = "garage"
|
s3_region = "garage"
|
||||||
api_bind_addr = "[::]:3900"
|
api_bind_addr = "[::]:3900"
|
||||||
@@ -132,6 +164,21 @@ It should be restarted automatically at each reboot.
|
|||||||
Please note that we use host networking as otherwise Docker containers
|
Please note that we use host networking as otherwise Docker containers
|
||||||
can not communicate with IPv6.
|
can not communicate with IPv6.
|
||||||
|
|
||||||
|
If you want to use `docker-compose`, you may use the following `docker-compose.yml` file as a reference:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
garage:
|
||||||
|
image: dxflrs/garage:v0.8.0
|
||||||
|
network_mode: "host"
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /etc/garage.toml:/etc/garage.toml
|
||||||
|
- /var/lib/garage/meta:/var/lib/garage/meta
|
||||||
|
- /var/lib/garage/data:/var/lib/garage/data
|
||||||
|
```
|
||||||
|
|
||||||
Upgrading between Garage versions should be supported transparently,
|
Upgrading between Garage versions should be supported transparently,
|
||||||
but please check the relase notes before doing so!
|
but please check the relase notes before doing so!
|
||||||
To upgrade, simply stop and remove this container and
|
To upgrade, simply stop and remove this container and
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Recovering from failures"
|
title = "Recovering from failures"
|
||||||
weight = 35
|
weight = 50
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Garage is meant to work on old, second-hand hardware.
|
Garage is meant to work on old, second-hand hardware.
|
||||||
|
|||||||
@@ -70,14 +70,16 @@ A possible configuration:
|
|||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
upstream s3_backend {
|
upstream s3_backend {
|
||||||
# if you have a garage instance locally
|
# If you have a garage instance locally.
|
||||||
server 127.0.0.1:3900;
|
server 127.0.0.1:3900;
|
||||||
# you can also put your other instances
|
# You can also put your other instances.
|
||||||
server 192.168.1.3:3900;
|
server 192.168.1.3:3900;
|
||||||
# domain names also work
|
# Domain names also work.
|
||||||
server garage1.example.com:3900;
|
server garage1.example.com:3900;
|
||||||
# you can assign weights if you have some servers
|
# A "backup" server is only used if all others have failed.
|
||||||
# that are more powerful than others
|
server garage-remote.example.com:3900 backup;
|
||||||
|
# You can assign weights if you have some servers
|
||||||
|
# that can serve more requests than others.
|
||||||
server garage2.example.com:3900 weight=2;
|
server garage2.example.com:3900 weight=2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +98,8 @@ server {
|
|||||||
proxy_pass http://s3_backend;
|
proxy_pass http://s3_backend;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
# Disable buffering to a temporary file.
|
||||||
|
proxy_max_temp_file_size 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Upgrading Garage"
|
title = "Upgrading Garage"
|
||||||
weight = 40
|
weight = 60
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Garage is a stateful clustered application, where all nodes are communicating together and share data structures.
|
Garage is a stateful clustered application, where all nodes are communicating together and share data structures.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Design"
|
title = "Design"
|
||||||
weight = 5
|
weight = 6
|
||||||
sort_by = "weight"
|
sort_by = "weight"
|
||||||
template = "documentation.html"
|
template = "documentation.html"
|
||||||
+++
|
+++
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ as pictures, video, images, documents, etc., in a redundant multi-node
|
|||||||
setting. S3 is versatile enough to also be used to publish a static
|
setting. S3 is versatile enough to also be used to publish a static
|
||||||
website.
|
website.
|
||||||
|
|
||||||
Garage is an opinionated object storage solutoin, we focus on the following **desirable properties**:
|
Garage is an opinionated object storage solution, we focus on the following **desirable properties**:
|
||||||
|
|
||||||
- **Internet enabled**: made for multi-sites (eg. datacenters, offices, households, etc.) interconnected through regular Internet connections.
|
- **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).
|
- **Self-contained & lightweight**: works everywhere and integrates well in existing environments to target [hyperconverged infrastructures](https://en.wikipedia.org/wiki/Hyper-converged_infrastructure).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Development"
|
title = "Development"
|
||||||
weight = 6
|
weight = 7
|
||||||
sort_by = "weight"
|
sort_by = "weight"
|
||||||
template = "documentation.html"
|
template = "documentation.html"
|
||||||
+++
|
+++
|
||||||
|
|||||||
@@ -42,25 +42,25 @@ you can [build Garage from source](@/documentation/cookbook/from-source.md).
|
|||||||
|
|
||||||
## Configuring and starting Garage
|
## Configuring and starting Garage
|
||||||
|
|
||||||
### Writing a first configuration file
|
### Generating a first configuration file
|
||||||
|
|
||||||
This first configuration file should allow you to get started easily with the simplest
|
This first configuration file should allow you to get started easily with the simplest
|
||||||
possible Garage deployment.
|
possible Garage deployment.
|
||||||
**Save it as `/etc/garage.toml`.**
|
|
||||||
You can also store it somewhere else, but you will have to specify `-c path/to/garage.toml`
|
|
||||||
at each invocation of the `garage` binary (for example: `garage -c ./garage.toml server`, `garage -c ./garage.toml status`).
|
|
||||||
|
|
||||||
```toml
|
We will create it with the following command line
|
||||||
|
to generate unique and private secrets for security reasons:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > garage.toml <<EOF
|
||||||
metadata_dir = "/tmp/meta"
|
metadata_dir = "/tmp/meta"
|
||||||
data_dir = "/tmp/data"
|
data_dir = "/tmp/data"
|
||||||
|
db_engine = "lmdb"
|
||||||
|
|
||||||
replication_mode = "none"
|
replication_mode = "none"
|
||||||
|
|
||||||
rpc_bind_addr = "[::]:3901"
|
rpc_bind_addr = "[::]:3901"
|
||||||
rpc_public_addr = "127.0.0.1:3901"
|
rpc_public_addr = "127.0.0.1:3901"
|
||||||
rpc_secret = "1799bccfd7411eddcf9ebd316bc1f5287ad12a68094e1c6ac6abde7e6feae1ec"
|
rpc_secret = "$(openssl rand -hex 32)"
|
||||||
|
|
||||||
bootstrap_peers = []
|
|
||||||
|
|
||||||
[s3_api]
|
[s3_api]
|
||||||
s3_region = "garage"
|
s3_region = "garage"
|
||||||
@@ -71,12 +71,26 @@ root_domain = ".s3.garage.localhost"
|
|||||||
bind_addr = "[::]:3902"
|
bind_addr = "[::]:3902"
|
||||||
root_domain = ".web.garage.localhost"
|
root_domain = ".web.garage.localhost"
|
||||||
index = "index.html"
|
index = "index.html"
|
||||||
|
|
||||||
|
[k2v_api]
|
||||||
|
api_bind_addr = "[::]:3904"
|
||||||
|
|
||||||
|
[admin]
|
||||||
|
api_bind_addr = "0.0.0.0:3903"
|
||||||
|
admin_token = "$(openssl rand -base64 32)"
|
||||||
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
The `rpc_secret` value provided above is just an example. It will work, but in
|
Now that your configuration file has been created, you can put
|
||||||
order to secure your cluster you will need to use another one. You can generate
|
it in the right place. By default, garage looks at **`/etc/garage.toml`.**
|
||||||
such a value with `openssl rand -hex 32`.
|
|
||||||
|
|
||||||
|
You can also store it somewhere else, but you will have to specify `-c path/to/garage.toml`
|
||||||
|
at each invocation of the `garage` binary (for example: `garage -c ./garage.toml server`, `garage -c ./garage.toml status`).
|
||||||
|
|
||||||
|
As you can see, the `rpc_secret` is a 32 bytes hexadecimal string.
|
||||||
|
You can regenerate it with `openssl rand -hex 32`.
|
||||||
|
If you target a cluster deployment with multiple nodes, make sure that
|
||||||
|
you use the same value for all nodes.
|
||||||
|
|
||||||
As you can see in the `metadata_dir` and `data_dir` parameters, we are saving Garage's data
|
As you can see in the `metadata_dir` and `data_dir` parameters, we are saving Garage's data
|
||||||
in `/tmp` which gets erased when your system reboots. This means that data stored on this
|
in `/tmp` which gets erased when your system reboots. This means that data stored on this
|
||||||
@@ -219,6 +233,7 @@ Now that we have a bucket and a key, we need to give permissions to the key on t
|
|||||||
garage bucket allow \
|
garage bucket allow \
|
||||||
--read \
|
--read \
|
||||||
--write \
|
--write \
|
||||||
|
--owner \
|
||||||
nextcloud-bucket \
|
nextcloud-bucket \
|
||||||
--key nextcloud-app-key
|
--key nextcloud-app-key
|
||||||
```
|
```
|
||||||
@@ -232,54 +247,73 @@ garage bucket info nextcloud-bucket
|
|||||||
|
|
||||||
## Uploading and downlading from Garage
|
## Uploading and downlading from Garage
|
||||||
|
|
||||||
We recommend the use of MinIO Client to interact with Garage files (`mc`).
|
To download and upload files on garage, we can use a third-party tool named `awscli`.
|
||||||
Instructions to install it and use it are provided on the
|
|
||||||
[MinIO website](https://docs.min.io/docs/minio-client-quickstart-guide.html).
|
|
||||||
Before reading the following, you need a working `mc` command on your path.
|
|
||||||
|
|
||||||
Note that on certain Linux distributions such as Arch Linux, the Minio client binary
|
|
||||||
is called `mcli` instead of `mc` (to avoid name clashes with the Midnight Commander).
|
|
||||||
|
|
||||||
### Configure `mc`
|
### Install and configure `awscli`
|
||||||
|
|
||||||
You need your access key and secret key created above.
|
If you have python on your system, you can install it with:
|
||||||
We will assume you are invoking `mc` on the same machine as the Garage server,
|
|
||||||
your S3 API endpoint is therefore `http://127.0.0.1:3900`.
|
|
||||||
For this whole configuration, you must set an alias name: we chose `my-garage`, that you will used for all commands.
|
|
||||||
|
|
||||||
Adapt the following command accordingly and run it:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mc alias set \
|
python -m pip install --user awscli
|
||||||
my-garage \
|
|
||||||
http://127.0.0.1:3900 \
|
|
||||||
<access key> \
|
|
||||||
<secret key> \
|
|
||||||
--api S3v4
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use `mc`
|
Now that `awscli` is installed, you must configure it to talk to your Garage instance,
|
||||||
|
with your key. There are multiple ways to do that, the simplest one is to create a file
|
||||||
You can not list buckets from `mc` currently.
|
named `~/.awsrc` with this content:
|
||||||
|
|
||||||
But the following commands and many more should work:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mc cp image.png my-garage/nextcloud-bucket
|
export AWS_ACCESS_KEY_ID=xxxx # put your Key ID here
|
||||||
mc cp my-garage/nextcloud-bucket/image.png .
|
export AWS_SECRET_ACCESS_KEY=xxxx # put your Secret key here
|
||||||
mc ls my-garage/nextcloud-bucket
|
export AWS_DEFAULT_REGION='garage'
|
||||||
mc mirror localdir/ my-garage/another-bucket
|
export AWS_ENDPOINT='http://localhost:3900'
|
||||||
|
|
||||||
|
function aws { command aws --endpoint-url $AWS_ENDPOINT $@ ; }
|
||||||
|
aws --version
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Now, each time you want to use `awscli` on this target, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source ~/.awsrc
|
||||||
|
```
|
||||||
|
|
||||||
|
*You can create multiple files with different names if you
|
||||||
|
have multiple Garage clusters or different keys.
|
||||||
|
Switching from one cluster to another is as simple as
|
||||||
|
sourcing the right file.*
|
||||||
|
|
||||||
|
### Example usage of `awscli`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# list buckets
|
||||||
|
aws s3 ls
|
||||||
|
|
||||||
|
# list objects of a bucket
|
||||||
|
aws s3 ls s3://my_files
|
||||||
|
|
||||||
|
# copy from your filesystem to garage
|
||||||
|
aws s3 cp /proc/cpuinfo s3://my_files/cpuinfo.txt
|
||||||
|
|
||||||
|
# copy from garage to your filesystem
|
||||||
|
aws s3 cp s3/my_files/cpuinfo.txt /tmp/cpuinfo.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that you can use `awscli` for more advanced operations like
|
||||||
|
creating a bucket, pre-signing a request or managing your website.
|
||||||
|
[Read the full documentation to know more](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/index.html).
|
||||||
|
|
||||||
|
Some features are however not implemented like ACL or policy.
|
||||||
|
Check [our s3 compatibility list](@/documentation/reference-manual/s3-compatibility.md).
|
||||||
|
|
||||||
### Other tools for interacting with Garage
|
### Other tools for interacting with Garage
|
||||||
|
|
||||||
The following tools can also be used to send and recieve files from/to Garage:
|
The following tools can also be used to send and recieve files from/to Garage:
|
||||||
|
|
||||||
- the [AWS CLI](https://aws.amazon.com/cli/)
|
- [minio-client](@/documentation/connect/cli.md#minio-client)
|
||||||
- [`rclone`](https://rclone.org/)
|
- [s3cmd](@/documentation/connect/cli.md#s3cmd)
|
||||||
- [Cyberduck](https://cyberduck.io/)
|
- [rclone](@/documentation/connect/cli.md#rclone)
|
||||||
- [`s3cmd`](https://s3tools.org/s3cmd)
|
- [Cyberduck](@/documentation/connect/cli.md#cyberduck)
|
||||||
|
- [WinSCP](@/documentation/connect/cli.md#winscp)
|
||||||
|
|
||||||
Refer to the ["Integrations" section](@/documentation/connect/_index.md) to learn how to
|
An exhaustive list is maintained in the ["Integrations" > "Browsing tools" section](@/documentation/connect/_index.md).
|
||||||
configure application and command line utilities to integrate with Garage.
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Reference Manual"
|
title = "Reference Manual"
|
||||||
weight = 4
|
weight = 5
|
||||||
sort_by = "weight"
|
sort_by = "weight"
|
||||||
template = "documentation.html"
|
template = "documentation.html"
|
||||||
+++
|
+++
|
||||||
|
|||||||
@@ -47,598 +47,13 @@ Returns internal Garage metrics in Prometheus format.
|
|||||||
|
|
||||||
### Cluster operations
|
### Cluster operations
|
||||||
|
|
||||||
#### GetClusterStatus `GET /v0/status`
|
These endpoints are defined on a dedicated [Redocly page](https://garagehq.deuxfleurs.fr/api/garage-admin-v0.html). You can also download its [OpenAPI specification](https://garagehq.deuxfleurs.fr/api/garage-admin-v0.yml).
|
||||||
|
|
||||||
Returns the cluster's current status in JSON, including:
|
Requesting the API from the command line can be as simple as running:
|
||||||
|
|
||||||
- ID of the node being queried and its version of the Garage daemon
|
```bash
|
||||||
- Live nodes
|
curl -H 'Authorization: Bearer s3cr3t' http://localhost:3903/v0/status | jq
|
||||||
- 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`
|
For more advanced use cases, we recommend using a SDK.
|
||||||
|
[Go to the "Build your own app" section to know how to use our SDKs](@/documentation/build/_index.md)
|
||||||
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.
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ This feature is totally invisible to S3 clients and does not break compatibility
|
|||||||
### Cluster administration API
|
### Cluster administration API
|
||||||
|
|
||||||
Garage provides a fully-fledged REST API to administer your cluster programatically.
|
Garage provides a fully-fledged REST API to administer your cluster programatically.
|
||||||
Functionnality included in the admin API include: setting up and monitoring
|
Functionality included in the admin API include: setting up and monitoring
|
||||||
cluster nodes, managing access credentials, and managing storage buckets and bucket aliases.
|
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).
|
A full reference of the administration API is available [here](@/documentation/reference-manual/admin-api.md).
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Working Documents"
|
title = "Working Documents"
|
||||||
weight = 7
|
weight = 8
|
||||||
sort_by = "weight"
|
sort_by = "weight"
|
||||||
template = "documentation.html"
|
template = "documentation.html"
|
||||||
+++
|
+++
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Design draft (obsolete)"
|
title = "Design draft (obsolete)"
|
||||||
weight = 50
|
weight = 900
|
||||||
+++
|
+++
|
||||||
|
|
||||||
**WARNING: this documentation is a design draft which was written before Garage's actual implementation.
|
**WARNING: this documentation is a design draft which was written before Garage's actual implementation.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Load balancing data (obsolete)"
|
title = "Load balancing data (obsolete)"
|
||||||
weight = 60
|
weight = 910
|
||||||
+++
|
+++
|
||||||
|
|
||||||
**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.**
|
**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.**
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
+++
|
||||||
|
title = "Testing strategy"
|
||||||
|
weight = 30
|
||||||
|
+++
|
||||||
|
|
||||||
|
|
||||||
|
## Testing Garage
|
||||||
|
|
||||||
|
Currently, we have the following tests:
|
||||||
|
|
||||||
|
- some unit tests spread around the codebase
|
||||||
|
- integration tests written in Rust (`src/garage/test`) to check that Garage operations perform correctly
|
||||||
|
- integration test for compatibility with external tools (`script/test-smoke.sh`)
|
||||||
|
|
||||||
|
We have also tried `minio/mint` but it fails a lot and for now we haven't gotten a lot from it.
|
||||||
|
|
||||||
|
In the future:
|
||||||
|
|
||||||
|
1. We'd like to have a systematic way of testing with `minio/mint`,
|
||||||
|
it would add value to Garage by providing a compatibility score and reference that can be trusted.
|
||||||
|
2. We'd also like to do testing with Jepsen in some way.
|
||||||
|
|
||||||
|
## How to instrument Garagae
|
||||||
|
|
||||||
|
We should try to test in least invasive ways, i.e. minimize the impact of the testing framework on Garage's source code. This means for example:
|
||||||
|
|
||||||
|
- Not abstracting IO/nondeterminism in the source code
|
||||||
|
- Not making `garage` a shared library (launch using `execve`, it's perfectly fine)
|
||||||
|
|
||||||
|
Instead, we should focus on building a clean outer interface for the `garage` binary,
|
||||||
|
for example loading configuration using environnement variables instead of the configuration file if that's helpfull for writing the tests.
|
||||||
|
|
||||||
|
There are two reasons for this:
|
||||||
|
|
||||||
|
- Keep the soure code clean and focused
|
||||||
|
- Test something that is as close as possible as the true garage that will actually be running
|
||||||
|
|
||||||
|
Reminder: rules of simplicity, concerning changes to Garage's source code.
|
||||||
|
Always question what we are doing.
|
||||||
|
Never do anything just because it looks nice or because we "think" it might be usefull at some later point but without knowing precisely why/when.
|
||||||
|
Only do things that make perfect sense in the context of what we currently know.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
Testing is a research field on its own.
|
||||||
|
About testing distributed systems:
|
||||||
|
|
||||||
|
- [Jepsen](https://jepsen.io/) is a testing framework designed to test distributed systems. It can mock some part of the system like the time and the network.
|
||||||
|
- [FoundationDB Testing Approach](https://www.micahlerner.com/2021/06/12/foundationdb-a-distributed-unbundled-transactional-key-value-store.html#what-is-unique-about-foundationdbs-testing-framework). They chose to abstract "all sources of nondeterminism and communication are abstracted, including network, disk, time, and pseudo random number generator" to be able to run tests by simulating faults.
|
||||||
|
- [Testing Distributed Systems](https://asatarin.github.io/testing-distributed-systems/) - Curated list of resources on testing distributed systems
|
||||||
|
|
||||||
|
About S3 compatibility:
|
||||||
|
- [ceph/s3-tests](https://github.com/ceph/s3-tests)
|
||||||
|
- (deprecated) [minio/s3verify](https://blog.min.io/s3verify-a-simple-tool-to-verify-aws-s3-api-compatibility/)
|
||||||
|
- [minio/mint](https://github.com/minio/mint)
|
||||||
|
|
||||||
|
About benchmarking S3 (I think it is not necessarily very relevant for this iteration):
|
||||||
|
- [minio/warp](https://github.com/minio/warp)
|
||||||
|
- [wasabi-tech/s3-benchmark](https://github.com/wasabi-tech/s3-benchmark)
|
||||||
|
- [dvassallo/s3-benchmark](https://github.com/dvassallo/s3-benchmark)
|
||||||
|
- [intel-cloud/cosbench](https://github.com/intel-cloud/cosbench) - used by Ceph
|
||||||
|
|
||||||
|
Engineering blog posts:
|
||||||
|
- [Quincy @ Scale: A Tale of Three Large-Scale Clusters](https://ceph.io/en/news/blog/2022/three-large-scale-clusters/)
|
||||||
|
|
||||||
|
Interesting blog posts on the blog of the Sled database:
|
||||||
|
|
||||||
|
- <https://sled.rs/simulation.html>
|
||||||
|
- <https://sled.rs/perf.html>
|
||||||
|
|
||||||
|
Misc:
|
||||||
|
- [mutagen](https://github.com/llogiq/mutagen) - mutation testing is a way to assert our test quality by mutating the code and see if the mutation makes the tests fail
|
||||||
|
- [fuzzing](https://rust-fuzz.github.io/book/) - cargo supports fuzzing, it could be a way to test our software reliability in presence of garbage data.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,686 @@
|
|||||||
|
+++
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Health `GET /health`
|
||||||
|
|
||||||
|
Used for simple health checks in a cluster setting with an orchestrator.
|
||||||
|
Returns an HTTP status 200 if the node is ready to answer user's requests,
|
||||||
|
and an HTTP status 503 (Service Unavailable) if there are some partitions
|
||||||
|
for which a quorum of nodes is not available.
|
||||||
|
A simple textual message is also returned in a body with content-type `text/plain`.
|
||||||
|
See `/v0/health` for an API that also returns JSON output.
|
||||||
|
|
||||||
|
### 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GetClusterHealth `GET /v0/health`
|
||||||
|
|
||||||
|
Returns the cluster's current health in JSON format, with the following variables:
|
||||||
|
|
||||||
|
- `status`: one of `Healthy`, `Degraded` or `Unavailable`:
|
||||||
|
- Healthy: Garage node is connected to all storage nodes
|
||||||
|
- Degraded: Garage node is not connected to all storage nodes, but a quorum of write nodes is available for all partitions
|
||||||
|
- Unavailable: a quorum of write nodes is not available for some partitions
|
||||||
|
- `known_nodes`: the number of nodes this Garage node has had a TCP connection to since the daemon started
|
||||||
|
- `connected_nodes`: the nubmer of nodes this Garage node currently has an open connection to
|
||||||
|
- `storage_nodes`: the number of storage nodes currently registered in the cluster layout
|
||||||
|
- `storage_nodes_ok`: the number of storage nodes to which a connection is currently open
|
||||||
|
- `partitions`: the total number of partitions of the data (currently always 256)
|
||||||
|
- `partitions_quorum`: the number of partitions for which a quorum of write nodes is available
|
||||||
|
- `partitions_all_ok`: the number of partitions for which we are connected to all storage nodes responsible of storing it
|
||||||
|
|
||||||
|
Contrarily to `GET /health`, this endpoint always returns a 200 OK HTTP response code.
|
||||||
|
|
||||||
|
Example response body:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "Degraded",
|
||||||
|
"known_nodes": 3,
|
||||||
|
"connected_nodes": 2,
|
||||||
|
"storage_nodes": 3,
|
||||||
|
"storage_nodes_ok": 2,
|
||||||
|
"partitions": 256,
|
||||||
|
"partitions_quorum": 256,
|
||||||
|
"partitions_all_ok": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
*.aux
|
||||||
|
*.bbl
|
||||||
|
*.blg
|
||||||
|
*.log
|
||||||
|
*.nav
|
||||||
|
*.out
|
||||||
|
*.snm
|
||||||
|
*.synctex.gz
|
||||||
|
*.toc
|
||||||
|
*.dvi
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
all:
|
||||||
|
pdflatex présentation.tex
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.aux *.bbl *.blg *.log *.nav *.out *.snm *.synctex.gz *.toc *.dvi présentation.pdf
|
||||||
|
|
||||||
|
clean_sauf_pdf:
|
||||||
|
rm -f *.aux *.bbl *.blg *.log *.nav *.out *.snm *.synctex.gz *.toc *.dvi
|
||||||
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 199 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,340 @@
|
|||||||
|
\documentclass[11pt, aspectratio=1610]{beamer}
|
||||||
|
\usetheme{Warsaw}
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[french]{babel}
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{amsfonts}
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{tikz}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{xcolor}
|
||||||
|
\usepackage{setspace}
|
||||||
|
\usepackage{todonotes}
|
||||||
|
\presetkeys{todonotes}{inline}{}
|
||||||
|
\renewcommand{\baselinestretch}{1.25}
|
||||||
|
|
||||||
|
\definecolor{orange_garage}{RGB}{255,147,41}
|
||||||
|
\definecolor{gris_garage}{RGB}{78,78,78}
|
||||||
|
|
||||||
|
\author[Association Deuxfleurs]{~\linebreak Vincent Giraud}
|
||||||
|
\title[De l'auto-hébergement à l'entre-hébergement avec Garage]{De l'auto-hébergement à l'entre-hébergement :\\Garage, pour conserver ses données ensemble}
|
||||||
|
%\setbeamercovered{transparent}
|
||||||
|
%\setbeamertemplate{navigation symbols}{}
|
||||||
|
\date{Capitole du Libre 2022\linebreak
|
||||||
|
|
||||||
|
\scriptsize Samedi 19 novembre 2022\linebreak
|
||||||
|
}
|
||||||
|
|
||||||
|
\setbeamercolor{palette primary}{fg=gris_garage,bg=orange_garage}
|
||||||
|
\setbeamercolor{palette secondary}{fg=gris_garage,bg=gris_garage}
|
||||||
|
\setbeamercolor{palette tiertary}{fg=white,bg=gris_garage}
|
||||||
|
\setbeamercolor{palette quaternary}{fg=white,bg=gris_garage}
|
||||||
|
\setbeamercolor{navigation symbols}{fg=black, bg=white}
|
||||||
|
\setbeamercolor{navigation symbols dimmed}{fg=darkgray, bg=white}
|
||||||
|
\setbeamercolor{itemize item}{fg=gris_garage}
|
||||||
|
\setbeamertemplate{itemize item}[circle]
|
||||||
|
|
||||||
|
\addtobeamertemplate{navigation symbols}{}{%
|
||||||
|
\usebeamerfont{footline}%
|
||||||
|
\usebeamercolor[fg]{footline}%
|
||||||
|
\hspace{1em}%
|
||||||
|
\insertframenumber/\inserttotalframenumber
|
||||||
|
}
|
||||||
|
|
||||||
|
\setbeamertemplate{headline}
|
||||||
|
{%
|
||||||
|
\leavevmode%
|
||||||
|
\begin{beamercolorbox}[wd=.5\paperwidth,ht=2.5ex,dp=1.125ex]{section in head/foot}%
|
||||||
|
\hbox to .5\paperwidth{\hfil\insertsectionhead\hfil}
|
||||||
|
\end{beamercolorbox}%
|
||||||
|
\begin{beamercolorbox}[wd=.5\paperwidth,ht=2.5ex,dp=1.125ex]{subsection in head/foot}%
|
||||||
|
\hbox to .5\paperwidth{\hfil\insertsubsectionhead\hfil}
|
||||||
|
\end{beamercolorbox}%
|
||||||
|
}
|
||||||
|
\addtobeamertemplate{footnote}{}{\vspace{2ex}}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\begin{frame}
|
||||||
|
\titlepage
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\section{Introduction}
|
||||||
|
\subsection{Présentation}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{columns}
|
||||||
|
\column{0.5 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=3.5cm]{deuxfleurs-logo.png}\linebreak
|
||||||
|
|
||||||
|
\texttt{https://deuxfleurs.fr}
|
||||||
|
\end{center}
|
||||||
|
\column{0.4 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
Deuxfleurs est une association militant en faveur d'un internet plus convivial, avec une organisation et des rapports de force repensés.\linebreak
|
||||||
|
|
||||||
|
Nous faisons partie du CHATONS\footnote[frame]{Collectif des Hébergeurs Alternatifs, Transparents, Ouverts, Neutres et Solidaires} depuis avril 2022.
|
||||||
|
|
||||||
|
\includegraphics[width=2cm]{logo_chatons.png}
|
||||||
|
\end{center}
|
||||||
|
\end{columns}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Héberger à la maison}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{columns}
|
||||||
|
\begin{column}{0.5 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
Pour échapper au contrôle et au giron des opérateurs de clouds, héberger ses données à la maison présente de nombreux avantages...
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\vspace{0.5cm}
|
||||||
|
|
||||||
|
\begin{itemize}[<+(1)->]
|
||||||
|
\item On récupère la souveraineté sur ses données
|
||||||
|
\item On gagne en vie privée
|
||||||
|
\item On gagne en libertés
|
||||||
|
\item On est responsabilisé face à ses besoins
|
||||||
|
\end{itemize}
|
||||||
|
\end{column}
|
||||||
|
\vrule{}
|
||||||
|
\begin{column}{0.5 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
\onslide<6->{... mais aussi bien des contraintes...}
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\vspace{0.5cm}
|
||||||
|
|
||||||
|
\begin{itemize}[<+(2)->]
|
||||||
|
\item On repose sur une connexion internet pour particulier
|
||||||
|
\item Un certain savoir-faire et moultes compétences sont requis
|
||||||
|
\item Assurer la résilience de ses services est difficile
|
||||||
|
\item Bien sauvegarder ses données, et ceci au-delà de son site géographique, n'est pas évident
|
||||||
|
\end{itemize}
|
||||||
|
\end{column}
|
||||||
|
\end{columns}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Sauvegarder pour se parer à tout imprévu}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
Sauvegarder pour se parer contre les pannes matérielles est une chose...
|
||||||
|
|
||||||
|
Sauvegarder pour se parer contre les cambriolages et les incendies en est une autre !\linebreak
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
\onslide<2->{Répartir géographiquement ses données devient alors nécessaire.}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\section{Les solutions à explorer}
|
||||||
|
\subsection{L'entre-hébergement}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
On a vu récemment se développer au sein du CHATONS la notion d'entre-hébergement : en plus de renforcer l'intégrité des sauvegardes, on va améliorer la disponibilité pendant les coupures de liaison internet, de courant, ou pendant les déménagements d'administrateurs par exemple.\linebreak
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
\onslide<2->
|
||||||
|
{
|
||||||
|
Dans le cadre du collectif, il s'agit de partager ses volumes de données entre hébergeurs.\linebreak
|
||||||
|
|
||||||
|
Pour assurer la confidentialité, on peut chiffrer les données au niveau applicatif.
|
||||||
|
}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{S3 contre les systèmes de fichiers}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
Dans le cadre de l'administration de services en ligne, les systèmes de fichiers recèlent certaines difficultés.\linebreak
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
Le standard S3 apporte des facilités; on réduit le stockage à un paradigme de clé-valeur basé essentiellement sur deux opérations seulement: lire ou écrire une clé.
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\section{Garage}
|
||||||
|
\subsection{Présentation}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{columns}
|
||||||
|
\column{0.5 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
Garage essaye de répondre à l'ensemble de ces besoins.\linebreak
|
||||||
|
|
||||||
|
\vspace{0.5cm}
|
||||||
|
Il s'agit d'un logiciel libre permettant de distribuer un service S3 sur diverses machines éloignées.
|
||||||
|
\end{center}
|
||||||
|
\column{0.5 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=4cm]{garage-logo.png}\linebreak
|
||||||
|
|
||||||
|
\texttt{https://garagehq.deuxfleurs.fr/}
|
||||||
|
\end{center}
|
||||||
|
\end{columns}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Gestion des zones}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
Garage va prendre en compte les zones géographiques au moment de répliquer les données.\linebreak
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
\includegraphics[width=13.25cm]{zones.png}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Comment ça marche ?}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{columns}
|
||||||
|
\column{0.5 \linewidth}
|
||||||
|
\input{schéma europe}
|
||||||
|
\column{0.5 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
Chaque objet est dupliqué sur plusieurs zones différentes.\linebreak
|
||||||
|
|
||||||
|
\onslide<5->{Lorsqu'un nouvel hébergeur rejoint le réseau, la charge se voit équilibrée.}\linebreak
|
||||||
|
|
||||||
|
\onslide<12->{Si une zone devient indisponible, les autres continuent d'assurer le service.}\linebreak
|
||||||
|
\end{center}
|
||||||
|
\end{columns}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Financement}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
Dans le cadre du programme \textit{Horizon 2021} de l'Union Européenne, nous avons reçu une subvention de la part de l'initiative NGI Pointer\footnote[frame]{Next Generation Internet Program for Open Internet Renovation}.\linebreak
|
||||||
|
|
||||||
|
\includegraphics[width=3cm]{drapeau_européen.png}\hspace{1cm}
|
||||||
|
\includegraphics[width=3cm]{NGI.png}\linebreak
|
||||||
|
|
||||||
|
Nous avons ainsi pu financer le développement de Garage pendant 1 an.
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Licence}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
De par nos valeurs, nous avons attribué la licence AGPL version 3 à Garage, notamment afin qu'il reste parmi les biens communs.\linebreak
|
||||||
|
|
||||||
|
\vspace{0.5cm}
|
||||||
|
\includegraphics[width=5cm]{agpl-v3-logo.png}\linebreak
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Langage utilisé}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
Nous avons décidé d'écrire Garage à l'aide du langage Rust, afin d'obtenir une compilation vers des binaires natifs et efficaces.\linebreak
|
||||||
|
|
||||||
|
\includegraphics[width=3.5cm]{rust-logo.png}\linebreak
|
||||||
|
|
||||||
|
Ce choix permet également de bénéficier des avantages reconnus de Rust en termes de sécurité.
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Matériel requis}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
Garage peut ainsi être performant sur des machines limitées. Les prérequis sont minimes : n'importe quelle machine avec un processeur qui a moins d'une décennie, 1~gigaoctet de mémoire vive, et 16~gigaoctets de stockage suffit.\linebreak
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
|
||||||
|
Cet aspect est déterminant : il permet en effet d'héberger sur du matériel acheté d'occasion, pour réduire l'impact écologique de nos infrastructures.
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Performances}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=13.25cm]{rpc-amplification.png}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=11cm]{rpc-complexity.png}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Services}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
Puisqu'il suit le standard S3, beaucoup de services populaires sont par conséquence compatibles avec Garage :\linebreak
|
||||||
|
|
||||||
|
\begin{columns}
|
||||||
|
\column{0.2 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=2.5cm]{nextcloud-logo.png}
|
||||||
|
\end{center}
|
||||||
|
\column{0.2 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=2.5cm]{peertube-logo.png}
|
||||||
|
\end{center}
|
||||||
|
\column{0.2 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=2.5cm]{matrix-logo.png}
|
||||||
|
\end{center}
|
||||||
|
\column{0.2 \linewidth}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=2.5cm]{mastodon-logo.png}
|
||||||
|
\end{center}
|
||||||
|
\end{columns}
|
||||||
|
~\linebreak
|
||||||
|
|
||||||
|
Et comme souvent avec S3, on peut assimiler un bucket à un site, et utiliser le serveur pour héberger des sites web statiques.
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\section{Intégration chez Deuxfleurs}
|
||||||
|
\subsection{Matériel}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=13cm]{neptune.jpg}\linebreak
|
||||||
|
|
||||||
|
En pratique, nos serveurs ne sont effectivement que des machines achetées d'occasion (très souvent des anciens ordinateurs destinés à la bureautique en entreprise).
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Environnement logiciel}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
Pour faciliter la reproduction d'un environnement connu, NixOS est installé sur nos machines.\linebreak
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
Pour s’accommoder des réseaux qu'on trouve derrière des routeurs pour particuliers, on s'aide de notre logiciel Diplonat\footnote[frame]{\texttt{https://git.deuxfleurs.fr/Deuxfleurs/diplonat}}.
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\section{Au-delà...}
|
||||||
|
\subsection{... de Deuxfleurs}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=10cm]{tedomum.png}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{... de Garage}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
Nous avons récemment lancé le développement d'Aérogramme\footnote[frame]{\texttt{https://git.deuxfleurs.fr/Deuxfleurs/aerogramme}}.\linebreak
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
Il s'agit d'un serveur de stockage de courriels chiffrés.\linebreak
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
Il est conçu pour pouvoir travailler avec Garage.
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\section{Fin}
|
||||||
|
\subsection{Contacts}
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
\begin{tikzpicture}
|
||||||
|
\node (ronce) {\includegraphics[width=0.95\textwidth]{ronce.jpg}};
|
||||||
|
\node[white] at (-5.1,3.6) {Intéressé(e) ?};
|
||||||
|
\node[white, align=center] at (4.2,-2.6) {Contactez-nous !\\\texttt{coucou@deuxfleurs.fr}\\\texttt{\#forum:deuxfleurs.fr}};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
\end{document}
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
@@ -0,0 +1,52 @@
|
|||||||
|
\begin{tikzpicture}
|
||||||
|
\node (carte) {\includegraphics[width=\textwidth]{carte-Europe.pdf}};
|
||||||
|
|
||||||
|
% \personnage{position X}{position Y}{facteur d'échelle}
|
||||||
|
\newcommand{\personnage}[4]
|
||||||
|
{
|
||||||
|
\fill[#4] ({#1-(0.4 * #3)},{#2-(0.9 * #3)}) .. controls ({#1-(0.4 * #3)},#2) and ({#1+(0.4 * #3)},#2) .. ({#1+(0.4 * #3)},{#2-(0.9 * #3)}) -- ({#1-(0.4 * #3)},{#2-(0.9 * #3)});
|
||||||
|
\fill[#4] (#1,#2) circle ({0.25 * #3});
|
||||||
|
}
|
||||||
|
|
||||||
|
\onslide<1-11>{\personnage{-2.25}{-0.75}{0.75}{green}}
|
||||||
|
\onslide<1-11>{\draw (-1.9,-1.6) rectangle ++(1,1.2);}
|
||||||
|
\onslide<2-11>{\draw[fill=green] (-1.8,-1.525) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 1};}
|
||||||
|
\onslide<4-5>{\draw[fill=red] (-1.8,-1.15) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 3};}
|
||||||
|
\onslide<7-11>{\draw[fill=yellow] (-1.8,-1.15) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 4};}
|
||||||
|
\onslide<9-11>{\draw[fill=red] (-1.8,-0.775) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 6};}
|
||||||
|
\onslide<3-11>{\draw[fill=blue] (-1.35,-1.525) rectangle ++(0.35,0.3) node[pos=0.5, white] {\tiny 2};}
|
||||||
|
\onslide<8-11>{\draw[fill=blue] (-1.35,-1.15) rectangle ++(0.35,0.3) node[pos=0.5, white] {\tiny 5};}
|
||||||
|
\onslide<11-11>{\draw[fill=yellow] (-1.35,-0.775) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 8};}
|
||||||
|
|
||||||
|
\personnage{1.65}{1.5}{0.75}{blue}
|
||||||
|
\draw (0.3,0.7) rectangle ++(1,1.2);
|
||||||
|
\onslide<2->{\draw[fill=green] (0.4,0.775) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 1};}
|
||||||
|
\onslide<4->{\draw[fill=red] (0.4,1.15) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 3};}
|
||||||
|
\onslide<10->{\draw[fill=green] (0.4,1.525) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 7};}
|
||||||
|
\onslide<3->{\draw[fill=blue] (0.85,0.775) rectangle ++(0.35,0.3) node[pos=0.5, white] {\tiny 2};}
|
||||||
|
\onslide<9->{\draw[fill=red] (0.85,1.15) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 6};}
|
||||||
|
\onslide<11->{\draw[fill=yellow] (0.85,1.525) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 8};}
|
||||||
|
|
||||||
|
\personnage{1.85}{-2.3}{0.75}{red}
|
||||||
|
\draw (0.5,-3.15) rectangle ++(1,1.2);
|
||||||
|
\onslide<2->{\draw[fill=green] (0.6,-3.075) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 1};}
|
||||||
|
\onslide<4-5>{\draw[fill=red] (0.6,-2.7) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 3};}
|
||||||
|
\onslide<7->{\draw[fill=yellow] (0.6,-2.7) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 4};}
|
||||||
|
\onslide<9->{\draw[fill=red] (0.6,-2.325) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 6};}
|
||||||
|
\onslide<3-5>{\draw[fill=blue] (1.05,-3.075) rectangle ++(0.35,0.3) node[pos=0.5, white] {\tiny 2};}
|
||||||
|
\onslide<6->{\draw[fill=red] (1.05,-3.075) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 3};}
|
||||||
|
\onslide<8->{\draw[fill=blue] (1.05,-2.7) rectangle ++(0.35,0.3) node[pos=0.5, white] {\tiny 5};}
|
||||||
|
\onslide<10->{\draw[fill=green] (1.05,-2.325) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 7};}
|
||||||
|
|
||||||
|
\onslide<5->{\personnage{1.05}{-0.15}{0.75}{yellow}}
|
||||||
|
\onslide<5->{\draw (-0.35,-1) rectangle ++(1,1.2);}
|
||||||
|
\onslide<6->{\draw[fill=blue] (-0.25,-0.925) rectangle ++(0.35,0.3) node[pos=0.5, white] {\tiny 2};}
|
||||||
|
\onslide<7->{\draw[fill=yellow] (-0.25,-0.55) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 4};}
|
||||||
|
\onslide<10->{\draw[fill=green] (-0.25,-0.175) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 7};}
|
||||||
|
\onslide<6->{\draw[fill=red] (0.2,-0.925) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 3};}
|
||||||
|
\onslide<8->{\draw[fill=blue] (0.2,-0.55) rectangle ++(0.35,0.3) node[pos=0.5,white] {\tiny 5};}
|
||||||
|
\onslide<11->{\draw[fill=yellow] (0.2,-0.175) rectangle ++(0.35,0.3) node[pos=0.5] {\tiny 8};}
|
||||||
|
|
||||||
|
\onslide<12->{\draw[line width=0.25cm] (-2.15,-0.5) -- ++(1,-1);}
|
||||||
|
\onslide<12->{\draw[line width=0.25cm] (-2.15,-1.5) -- ++(1,1);}
|
||||||
|
\end{tikzpicture}
|
||||||
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 97 KiB |
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"cargo2nix": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1666087781,
|
||||||
|
"narHash": "sha256-trKVdjMZ8mNkGfLcY5LsJJGtdV3xJDZnMVrkFjErlcs=",
|
||||||
|
"owner": "Alexis211",
|
||||||
|
"repo": "cargo2nix",
|
||||||
|
"rev": "a7a61179b66054904ef6a195d8da736eaaa06c36",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "Alexis211",
|
||||||
|
"repo": "cargo2nix",
|
||||||
|
"rev": "a7a61179b66054904ef6a195d8da736eaaa06c36",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1650374568,
|
||||||
|
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659877975,
|
||||||
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1665657542,
|
||||||
|
"narHash": "sha256-mojxNyzbvmp8NtVtxqiHGhRfjCALLfk9i/Uup68Y5q8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a3073c49bc0163fea6a121c276f526837672b555",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a3073c49bc0163fea6a121c276f526837672b555",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"cargo2nix": "cargo2nix",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": [
|
||||||
|
"cargo2nix",
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"cargo2nix",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1664247556,
|
||||||
|
"narHash": "sha256-J4vazHU3609ekn7dr+3wfqPo5WGlZVAgV7jfux352L0=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "524db9c9ea7bc7743bb74cdd45b6d46ea3fcc2ab",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
description = "Garage, an S3-compatible distributed object store for self-hosted deployments";
|
||||||
|
|
||||||
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs/a3073c49bc0163fea6a121c276f526837672b555";
|
||||||
|
inputs.cargo2nix = {
|
||||||
|
# As of 2022-10-18: two small patches over unstable branch, one for clippy and one to fix feature detection
|
||||||
|
url = "github:Alexis211/cargo2nix/a7a61179b66054904ef6a195d8da736eaaa06c36";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, cargo2nix }: let
|
||||||
|
git_version = self.lastModifiedDate;
|
||||||
|
compile = import ./nix/compile.nix;
|
||||||
|
forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages = forAllSystems (system: {
|
||||||
|
default = (compile {
|
||||||
|
inherit system git_version;
|
||||||
|
pkgsSrc = nixpkgs;
|
||||||
|
cargo2nixOverlay = cargo2nix.overlays.default;
|
||||||
|
release = true;
|
||||||
|
}).workspace.garage {
|
||||||
|
compileMode = "build";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,25 +1,32 @@
|
|||||||
{
|
{
|
||||||
system ? builtins.currentSystem,
|
system,
|
||||||
target,
|
target ? null,
|
||||||
|
pkgsSrc,
|
||||||
|
cargo2nixOverlay,
|
||||||
compiler ? "rustc",
|
compiler ? "rustc",
|
||||||
release ? false,
|
release ? false,
|
||||||
git_version ? null,
|
git_version ? null,
|
||||||
features ? null,
|
features ? null,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
with import ./common.nix;
|
|
||||||
|
|
||||||
let
|
let
|
||||||
log = v: builtins.trace v v;
|
log = v: builtins.trace v v;
|
||||||
|
|
||||||
pkgs = import pkgsSrc {
|
pkgs =
|
||||||
inherit system;
|
if target != null then
|
||||||
crossSystem = {
|
import pkgsSrc {
|
||||||
config = target;
|
inherit system;
|
||||||
isStatic = true;
|
crossSystem = {
|
||||||
};
|
config = target;
|
||||||
overlays = [ cargo2nixOverlay ];
|
isStatic = true;
|
||||||
};
|
};
|
||||||
|
overlays = [ cargo2nixOverlay ];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
import pkgsSrc {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ cargo2nixOverlay ];
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Cargo2nix is built for rustOverlay which installs Rust from Mozilla releases.
|
Cargo2nix is built for rustOverlay which installs Rust from Mozilla releases.
|
||||||
@@ -34,7 +41,7 @@ let
|
|||||||
NixOS ships them in separate ones. We reunite them with symlinkJoin.
|
NixOS ships them in separate ones. We reunite them with symlinkJoin.
|
||||||
*/
|
*/
|
||||||
toolchainOptions =
|
toolchainOptions =
|
||||||
if target == "x86_64-unknown-linux-musl" || target == "aarch64-unknown-linux-musl" then {
|
if target == null || target == "x86_64-unknown-linux-musl" || target == "aarch64-unknown-linux-musl" then {
|
||||||
rustVersion = "1.63.0";
|
rustVersion = "1.63.0";
|
||||||
extraRustComponents = [ "clippy" ];
|
extraRustComponents = [ "clippy" ];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# 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.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 0.1.0
|
version: 0.2.0
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# 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
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ data:
|
|||||||
|
|
||||||
bootstrap_peers = {{ .Values.garage.bootstrapPeers }}
|
bootstrap_peers = {{ .Values.garage.bootstrapPeers }}
|
||||||
|
|
||||||
kubernetes_namespace = "{{ .Release.Namespace }}"
|
[kubernetes_discovery]
|
||||||
kubernetes_service_name = "{{ include "garage.fullname" . }}"
|
namespace = "{{ .Release.Namespace }}"
|
||||||
kubernetes_skip_crd = {{ .Values.garage.kubernetesSkipCrd }}
|
service_name = "{{ include "garage.fullname" . }}"
|
||||||
|
skip_crd = {{ .Values.garage.kubernetesSkipCrd }}
|
||||||
|
|
||||||
[s3_api]
|
[s3_api]
|
||||||
s3_region = "{{ .Values.garage.s3.api.region }}"
|
s3_region = "{{ .Values.garage.s3.api.region }}"
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ metadata:
|
|||||||
name: {{ $fullName }}-s3-api
|
name: {{ $fullName }}-s3-api
|
||||||
labels:
|
labels:
|
||||||
{{- include "garage.labels" . | nindent 4 }}
|
{{- include "garage.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.s3.api.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
{{- with .Values.ingress.s3.api.annotations }}
|
{{- with .Values.ingress.s3.api.annotations }}
|
||||||
annotations:
|
annotations:
|
||||||
{{- toYaml . | nindent 4 }}
|
{{- toYaml . | nindent 4 }}
|
||||||
@@ -80,6 +83,9 @@ metadata:
|
|||||||
name: {{ $fullName }}-s3-web
|
name: {{ $fullName }}-s3-web
|
||||||
labels:
|
labels:
|
||||||
{{- include "garage.labels" . | nindent 4 }}
|
{{- include "garage.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.s3.web.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
{{- with .Values.ingress.s3.web.annotations }}
|
{{- with .Values.ingress.s3.web.annotations }}
|
||||||
annotations:
|
annotations:
|
||||||
{{- toYaml . | nindent 4 }}
|
{{- toYaml . | nindent 4 }}
|
||||||
|
|||||||
@@ -85,14 +85,15 @@ service:
|
|||||||
ingress:
|
ingress:
|
||||||
s3:
|
s3:
|
||||||
api:
|
api:
|
||||||
enabled: true
|
enabled: false
|
||||||
# Rely either on the className or the annotation below but not both
|
# Rely either on the className or the annotation below but not both
|
||||||
# replace "nginx" by an Ingress controller
|
# replace "nginx" by an Ingress controller
|
||||||
# you can find examples here https://kubernetes.io/docs/concepts/services-networking/ingress-controllers
|
# you can find examples here https://kubernetes.io/docs/concepts/services-networking/ingress-controllers
|
||||||
className: "nginx"
|
# className: "nginx"
|
||||||
annotations:
|
annotations: {}
|
||||||
# kubernetes.io/ingress.class: "nginx"
|
# kubernetes.io/ingress.class: "nginx"
|
||||||
# kubernetes.io/tls-acme: "true"
|
# kubernetes.io/tls-acme: "true"
|
||||||
|
labels: {}
|
||||||
hosts:
|
hosts:
|
||||||
- host: "s3.garage.tld" # garage S3 API endpoint
|
- host: "s3.garage.tld" # garage S3 API endpoint
|
||||||
paths:
|
paths:
|
||||||
@@ -107,11 +108,15 @@ ingress:
|
|||||||
# hosts:
|
# hosts:
|
||||||
# - kubernetes.docker.internal
|
# - kubernetes.docker.internal
|
||||||
web:
|
web:
|
||||||
enabled: true
|
enabled: false
|
||||||
className: "nginx"
|
# 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: {}
|
annotations: {}
|
||||||
# kubernetes.io/ingress.class: nginx
|
# kubernetes.io/ingress.class: nginx
|
||||||
# kubernetes.io/tls-acme: "true"
|
# kubernetes.io/tls-acme: "true"
|
||||||
|
labels: {}
|
||||||
hosts:
|
hosts:
|
||||||
- host: "*.web.garage.tld" # wildcard website access with bucket name prefix
|
- host: "*.web.garage.tld" # wildcard website access with bucket name prefix
|
||||||
paths:
|
paths:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
system ? builtins.currentSystem,
|
system ? builtins.currentSystem,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
with import ./nix/common.nix;
|
with import ./nix/common.nix;
|
||||||
@@ -71,13 +71,25 @@ function refresh_cache {
|
|||||||
for attr in clippy.amd64 test.amd64 pkgs.{amd64,i386,arm,arm64}.{debug,release}; do
|
for attr in clippy.amd64 test.amd64 pkgs.{amd64,i386,arm,arm64}.{debug,release}; do
|
||||||
echo "Updating cache for ''${attr}"
|
echo "Updating cache for ''${attr}"
|
||||||
derivation=$(nix-instantiate --attr ''${attr})
|
derivation=$(nix-instantiate --attr ''${attr})
|
||||||
nix copy \
|
nix copy -j8 \
|
||||||
--to 's3://nix?endpoint=garage.deuxfleurs.fr®ion=garage&secret-key=/tmp/nix-signing-key.sec' \
|
--to 's3://nix?endpoint=garage.deuxfleurs.fr®ion=garage&secret-key=/tmp/nix-signing-key.sec' \
|
||||||
$(nix-store -qR ''${derivation%\!bin})
|
$(nix-store -qR ''${derivation%\!bin})
|
||||||
done
|
done
|
||||||
rm /tmp/nix-signing-key.sec
|
rm /tmp/nix-signing-key.sec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refresh_flake_cache {
|
||||||
|
pass show deuxfleurs/nix_priv_key > /tmp/nix-signing-key.sec
|
||||||
|
for attr in packages.x86_64-linux.default; do
|
||||||
|
echo "Updating cache for ''${attr}"
|
||||||
|
derivation=$(nix path-info --derivation ".#''${attr}")
|
||||||
|
nix copy -j8 \
|
||||||
|
--to 's3://nix?endpoint=garage.deuxfleurs.fr®ion=garage&secret-key=/tmp/nix-signing-key.sec' \
|
||||||
|
$(nix-store -qR ''${derivation})
|
||||||
|
done
|
||||||
|
rm /tmp/nix-signing-key.sec
|
||||||
|
}
|
||||||
|
|
||||||
function to_s3 {
|
function to_s3 {
|
||||||
aws \
|
aws \
|
||||||
--endpoint-url https://garage.deuxfleurs.fr \
|
--endpoint-url https://garage.deuxfleurs.fr \
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use opentelemetry_prometheus::PrometheusExporter;
|
|||||||
use prometheus::{Encoder, TextEncoder};
|
use prometheus::{Encoder, TextEncoder};
|
||||||
|
|
||||||
use garage_model::garage::Garage;
|
use garage_model::garage::Garage;
|
||||||
|
use garage_rpc::system::ClusterHealthStatus;
|
||||||
use garage_util::error::Error as GarageError;
|
use garage_util::error::Error as GarageError;
|
||||||
|
|
||||||
use crate::generic_server::*;
|
use crate::generic_server::*;
|
||||||
@@ -76,6 +77,31 @@ impl AdminApiServer {
|
|||||||
.body(Body::empty())?)
|
.body(Body::empty())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_health(&self) -> Result<Response<Body>, Error> {
|
||||||
|
let health = self.garage.system.health();
|
||||||
|
|
||||||
|
let (status, status_str) = match health.status {
|
||||||
|
ClusterHealthStatus::Healthy => (StatusCode::OK, "Garage is fully operational"),
|
||||||
|
ClusterHealthStatus::Degraded => (
|
||||||
|
StatusCode::OK,
|
||||||
|
"Garage is operational but some storage nodes are unavailable",
|
||||||
|
),
|
||||||
|
ClusterHealthStatus::Unavailable => (
|
||||||
|
StatusCode::SERVICE_UNAVAILABLE,
|
||||||
|
"Quorum is not available for some/all partitions, reads and writes will fail",
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let status_str = format!(
|
||||||
|
"{}\nConsult the full health check API endpoint at /v0/health for more details\n",
|
||||||
|
status_str
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Response::builder()
|
||||||
|
.status(status)
|
||||||
|
.header(http::header::CONTENT_TYPE, "text/plain")
|
||||||
|
.body(Body::from(status_str))?)
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_metrics(&self) -> Result<Response<Body>, Error> {
|
fn handle_metrics(&self) -> Result<Response<Body>, Error> {
|
||||||
#[cfg(feature = "metrics")]
|
#[cfg(feature = "metrics")]
|
||||||
{
|
{
|
||||||
@@ -124,6 +150,7 @@ impl ApiHandler for AdminApiServer {
|
|||||||
) -> Result<Response<Body>, Error> {
|
) -> Result<Response<Body>, Error> {
|
||||||
let expected_auth_header =
|
let expected_auth_header =
|
||||||
match endpoint.authorization_type() {
|
match endpoint.authorization_type() {
|
||||||
|
Authorization::None => None,
|
||||||
Authorization::MetricsToken => self.metrics_token.as_ref(),
|
Authorization::MetricsToken => self.metrics_token.as_ref(),
|
||||||
Authorization::AdminToken => match &self.admin_token {
|
Authorization::AdminToken => match &self.admin_token {
|
||||||
None => return Err(Error::forbidden(
|
None => return Err(Error::forbidden(
|
||||||
@@ -147,8 +174,10 @@ impl ApiHandler for AdminApiServer {
|
|||||||
|
|
||||||
match endpoint {
|
match endpoint {
|
||||||
Endpoint::Options => self.handle_options(&req),
|
Endpoint::Options => self.handle_options(&req),
|
||||||
|
Endpoint::Health => self.handle_health(),
|
||||||
Endpoint::Metrics => self.handle_metrics(),
|
Endpoint::Metrics => self.handle_metrics(),
|
||||||
Endpoint::GetClusterStatus => handle_get_cluster_status(&self.garage).await,
|
Endpoint::GetClusterStatus => handle_get_cluster_status(&self.garage).await,
|
||||||
|
Endpoint::GetClusterHealth => handle_get_cluster_health(&self.garage).await,
|
||||||
Endpoint::ConnectClusterNodes => handle_connect_cluster_nodes(&self.garage, req).await,
|
Endpoint::ConnectClusterNodes => handle_connect_cluster_nodes(&self.garage, req).await,
|
||||||
// Layout
|
// Layout
|
||||||
Endpoint::GetClusterLayout => handle_get_cluster_layout(&self.garage).await,
|
Endpoint::GetClusterLayout => handle_get_cluster_layout(&self.garage).await,
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ async fn bucket_info_results(
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
objects: counters.get(OBJECTS).cloned().unwrap_or_default(),
|
objects: counters.get(OBJECTS).cloned().unwrap_or_default(),
|
||||||
bytes: counters.get(BYTES).cloned().unwrap_or_default(),
|
bytes: counters.get(BYTES).cloned().unwrap_or_default(),
|
||||||
unfinshed_uploads: counters
|
unfinished_uploads: counters
|
||||||
.get(UNFINISHED_UPLOADS)
|
.get(UNFINISHED_UPLOADS)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@@ -234,7 +234,7 @@ struct GetBucketInfoResult {
|
|||||||
keys: Vec<GetBucketInfoKey>,
|
keys: Vec<GetBucketInfoKey>,
|
||||||
objects: i64,
|
objects: i64,
|
||||||
bytes: i64,
|
bytes: i64,
|
||||||
unfinshed_uploads: i64,
|
unfinished_uploads: i64,
|
||||||
quotas: ApiBucketQuotas,
|
quotas: ApiBucketQuotas,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ pub async fn handle_get_cluster_status(garage: &Arc<Garage>) -> Result<Response<
|
|||||||
Ok(json_ok_response(&res)?)
|
Ok(json_ok_response(&res)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_get_cluster_health(garage: &Arc<Garage>) -> Result<Response<Body>, Error> {
|
||||||
|
let health = garage.system.health();
|
||||||
|
Ok(json_ok_response(&health)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle_connect_cluster_nodes(
|
pub async fn handle_connect_cluster_nodes(
|
||||||
garage: &Arc<Garage>,
|
garage: &Arc<Garage>,
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::admin::error::*;
|
|||||||
use crate::router_macros::*;
|
use crate::router_macros::*;
|
||||||
|
|
||||||
pub enum Authorization {
|
pub enum Authorization {
|
||||||
|
None,
|
||||||
MetricsToken,
|
MetricsToken,
|
||||||
AdminToken,
|
AdminToken,
|
||||||
}
|
}
|
||||||
@@ -16,8 +17,10 @@ router_match! {@func
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Endpoint {
|
pub enum Endpoint {
|
||||||
Options,
|
Options,
|
||||||
|
Health,
|
||||||
Metrics,
|
Metrics,
|
||||||
GetClusterStatus,
|
GetClusterStatus,
|
||||||
|
GetClusterHealth,
|
||||||
ConnectClusterNodes,
|
ConnectClusterNodes,
|
||||||
// Layout
|
// Layout
|
||||||
GetClusterLayout,
|
GetClusterLayout,
|
||||||
@@ -88,8 +91,10 @@ impl Endpoint {
|
|||||||
|
|
||||||
let res = router_match!(@gen_path_parser (req.method(), path, query) [
|
let res = router_match!(@gen_path_parser (req.method(), path, query) [
|
||||||
OPTIONS _ => Options,
|
OPTIONS _ => Options,
|
||||||
|
GET "/health" => Health,
|
||||||
GET "/metrics" => Metrics,
|
GET "/metrics" => Metrics,
|
||||||
GET "/v0/status" => GetClusterStatus,
|
GET "/v0/status" => GetClusterStatus,
|
||||||
|
GET "/v0/health" => GetClusterHealth,
|
||||||
POST "/v0/connect" => ConnectClusterNodes,
|
POST "/v0/connect" => ConnectClusterNodes,
|
||||||
// Layout endpoints
|
// Layout endpoints
|
||||||
GET "/v0/layout" => GetClusterLayout,
|
GET "/v0/layout" => GetClusterLayout,
|
||||||
@@ -130,6 +135,7 @@ impl Endpoint {
|
|||||||
/// Get the kind of authorization which is required to perform the operation.
|
/// Get the kind of authorization which is required to perform the operation.
|
||||||
pub fn authorization_type(&self) -> Authorization {
|
pub fn authorization_type(&self) -> Authorization {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Health => Authorization::None,
|
||||||
Self::Metrics => Authorization::MetricsToken,
|
Self::Metrics => Authorization::MetricsToken,
|
||||||
_ => Authorization::AdminToken,
|
_ => Authorization::AdminToken,
|
||||||
}
|
}
|
||||||
@@ -137,6 +143,7 @@ impl Endpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateQueryParameters! {
|
generateQueryParameters! {
|
||||||
|
"format" => format,
|
||||||
"id" => id,
|
"id" => id,
|
||||||
"search" => search,
|
"search" => search,
|
||||||
"globalAlias" => global_alias,
|
"globalAlias" => global_alias,
|
||||||
|
|||||||
@@ -161,6 +161,15 @@ pub async fn handle_create_bucket(
|
|||||||
return Err(CommonError::BucketAlreadyExists.into());
|
return Err(CommonError::BucketAlreadyExists.into());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Check user is allowed to create bucket
|
||||||
|
if !key_params.allow_create_bucket.get() {
|
||||||
|
return Err(CommonError::Forbidden(format!(
|
||||||
|
"Access key {} is not allowed to create buckets",
|
||||||
|
api_key.key_id
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
// Create the bucket!
|
// Create the bucket!
|
||||||
if !is_valid_bucket_name(&bucket_name) {
|
if !is_valid_bucket_name(&bucket_name) {
|
||||||
return Err(Error::bad_request(format!(
|
return Err(Error::bad_request(format!(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::pin::Pin;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use arc_swap::ArcSwapOption;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -22,6 +23,7 @@ use garage_rpc::rpc_helper::netapp::stream::{stream_asyncread, ByteStream};
|
|||||||
|
|
||||||
use garage_db as db;
|
use garage_db as db;
|
||||||
|
|
||||||
|
use garage_util::background::BackgroundRunner;
|
||||||
use garage_util::data::*;
|
use garage_util::data::*;
|
||||||
use garage_util::error::*;
|
use garage_util::error::*;
|
||||||
use garage_util::metrics::RecordDuration;
|
use garage_util::metrics::RecordDuration;
|
||||||
@@ -87,7 +89,16 @@ pub struct BlockManager {
|
|||||||
|
|
||||||
pub(crate) metrics: BlockManagerMetrics,
|
pub(crate) metrics: BlockManagerMetrics,
|
||||||
|
|
||||||
tx_scrub_command: mpsc::Sender<ScrubWorkerCommand>,
|
tx_scrub_command: ArcSwapOption<mpsc::Sender<ScrubWorkerCommand>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct BlockResyncErrorInfo {
|
||||||
|
pub hash: Hash,
|
||||||
|
pub refcount: u64,
|
||||||
|
pub error_count: u64,
|
||||||
|
pub last_try: u64,
|
||||||
|
pub next_try: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This custom struct contains functions that must only be ran
|
// This custom struct contains functions that must only be ran
|
||||||
@@ -114,9 +125,8 @@ impl BlockManager {
|
|||||||
.netapp
|
.netapp
|
||||||
.endpoint("garage_block/manager.rs/Rpc".to_string());
|
.endpoint("garage_block/manager.rs/Rpc".to_string());
|
||||||
|
|
||||||
let metrics = BlockManagerMetrics::new(resync.queue.clone(), resync.errors.clone());
|
let metrics =
|
||||||
|
BlockManagerMetrics::new(rc.rc.clone(), resync.queue.clone(), resync.errors.clone());
|
||||||
let (scrub_tx, scrub_rx) = mpsc::channel(1);
|
|
||||||
|
|
||||||
let block_manager = Arc::new(Self {
|
let block_manager = Arc::new(Self {
|
||||||
replication,
|
replication,
|
||||||
@@ -128,21 +138,24 @@ impl BlockManager {
|
|||||||
system,
|
system,
|
||||||
endpoint,
|
endpoint,
|
||||||
metrics,
|
metrics,
|
||||||
tx_scrub_command: scrub_tx,
|
tx_scrub_command: ArcSwapOption::new(None),
|
||||||
});
|
});
|
||||||
block_manager.endpoint.set_handler(block_manager.clone());
|
block_manager.endpoint.set_handler(block_manager.clone());
|
||||||
|
|
||||||
|
block_manager
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_workers(self: &Arc<Self>, bg: &BackgroundRunner) {
|
||||||
// Spawn a bunch of resync workers
|
// Spawn a bunch of resync workers
|
||||||
for index in 0..MAX_RESYNC_WORKERS {
|
for index in 0..MAX_RESYNC_WORKERS {
|
||||||
let worker = ResyncWorker::new(index, block_manager.clone());
|
let worker = ResyncWorker::new(index, self.clone());
|
||||||
block_manager.system.background.spawn_worker(worker);
|
bg.spawn_worker(worker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn scrub worker
|
// Spawn scrub worker
|
||||||
let scrub_worker = ScrubWorker::new(block_manager.clone(), scrub_rx);
|
let (scrub_tx, scrub_rx) = mpsc::channel(1);
|
||||||
block_manager.system.background.spawn_worker(scrub_worker);
|
self.tx_scrub_command.store(Some(Arc::new(scrub_tx)));
|
||||||
|
bg.spawn_worker(ScrubWorker::new(self.clone(), scrub_rx));
|
||||||
block_manager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ask nodes that might have a (possibly compressed) block for it
|
/// Ask nodes that might have a (possibly compressed) block for it
|
||||||
@@ -309,9 +322,42 @@ impl BlockManager {
|
|||||||
Ok(self.rc.rc.len()?)
|
Ok(self.rc.rc.len()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get number of items in the refcount table
|
||||||
|
pub fn rc_fast_len(&self) -> Result<Option<usize>, Error> {
|
||||||
|
Ok(self.rc.rc.fast_len()?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Send command to start/stop/manager scrub worker
|
/// Send command to start/stop/manager scrub worker
|
||||||
pub async fn send_scrub_command(&self, cmd: ScrubWorkerCommand) {
|
pub async fn send_scrub_command(&self, cmd: ScrubWorkerCommand) -> Result<(), Error> {
|
||||||
let _ = self.tx_scrub_command.send(cmd).await;
|
let tx = self.tx_scrub_command.load();
|
||||||
|
let tx = tx.as_ref().ok_or_message("scrub worker is not running")?;
|
||||||
|
tx.send(cmd).await.ok_or_message("send error")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the reference count of a block
|
||||||
|
pub fn get_block_rc(&self, hash: &Hash) -> Result<u64, Error> {
|
||||||
|
Ok(self.rc.get_block_rc(hash)?.as_u64())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all resync errors
|
||||||
|
pub fn list_resync_errors(&self) -> Result<Vec<BlockResyncErrorInfo>, Error> {
|
||||||
|
let mut blocks = Vec::with_capacity(self.resync.errors.len());
|
||||||
|
for ent in self.resync.errors.iter()? {
|
||||||
|
let (hash, cnt) = ent?;
|
||||||
|
let cnt = ErrorCounter::decode(&cnt);
|
||||||
|
blocks.push(BlockResyncErrorInfo {
|
||||||
|
hash: Hash::try_from(&hash).unwrap(),
|
||||||
|
refcount: 0,
|
||||||
|
error_count: cnt.errors,
|
||||||
|
last_try: cnt.last_try,
|
||||||
|
next_try: cnt.next_try(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for block in blocks.iter_mut() {
|
||||||
|
block.refcount = self.get_block_rc(&block.hash)?;
|
||||||
|
}
|
||||||
|
Ok(blocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
//// ----- Managing the reference counter ----
|
//// ----- Managing the reference counter ----
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use opentelemetry::{global, metrics::*};
|
use opentelemetry::{global, metrics::*};
|
||||||
|
|
||||||
|
use garage_db as db;
|
||||||
use garage_db::counted_tree_hack::CountedTree;
|
use garage_db::counted_tree_hack::CountedTree;
|
||||||
|
|
||||||
/// TableMetrics reference all counter used for metrics
|
/// TableMetrics reference all counter used for metrics
|
||||||
pub struct BlockManagerMetrics {
|
pub struct BlockManagerMetrics {
|
||||||
|
pub(crate) _rc_size: ValueObserver<u64>,
|
||||||
pub(crate) _resync_queue_len: ValueObserver<u64>,
|
pub(crate) _resync_queue_len: ValueObserver<u64>,
|
||||||
pub(crate) _resync_errored_blocks: ValueObserver<u64>,
|
pub(crate) _resync_errored_blocks: ValueObserver<u64>,
|
||||||
|
|
||||||
@@ -23,9 +25,17 @@ pub struct BlockManagerMetrics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BlockManagerMetrics {
|
impl BlockManagerMetrics {
|
||||||
pub fn new(resync_queue: CountedTree, resync_errors: CountedTree) -> Self {
|
pub fn new(rc_tree: db::Tree, resync_queue: CountedTree, resync_errors: CountedTree) -> Self {
|
||||||
let meter = global::meter("garage_model/block");
|
let meter = global::meter("garage_model/block");
|
||||||
Self {
|
Self {
|
||||||
|
_rc_size: meter
|
||||||
|
.u64_value_observer("block.rc_size", move |observer| {
|
||||||
|
if let Ok(Some(v)) = rc_tree.fast_len() {
|
||||||
|
observer.observe(v as u64, &[])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_description("Number of blocks known to the reference counter")
|
||||||
|
.init(),
|
||||||
_resync_queue_len: meter
|
_resync_queue_len: meter
|
||||||
.u64_value_observer("block.resync_queue_length", move |observer| {
|
.u64_value_observer("block.resync_queue_length", move |observer| {
|
||||||
observer.observe(resync_queue.len() as u64, &[])
|
observer.observe(resync_queue.len() as u64, &[])
|
||||||
|
|||||||
@@ -169,4 +169,11 @@ impl RcEntry {
|
|||||||
pub(crate) fn is_needed(&self) -> bool {
|
pub(crate) fn is_needed(&self) -> bool {
|
||||||
!self.is_deletable()
|
!self.is_deletable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_u64(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
RcEntry::Present { count } => *count,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ impl Worker for RepairWorker {
|
|||||||
"Block repair worker".into()
|
"Block repair worker".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(&self) -> Option<String> {
|
fn status(&self) -> WorkerStatus {
|
||||||
match self.block_iter.as_ref() {
|
match self.block_iter.as_ref() {
|
||||||
None => {
|
None => {
|
||||||
let idx_bytes = self
|
let idx_bytes = self
|
||||||
@@ -66,9 +66,20 @@ impl Worker for RepairWorker {
|
|||||||
} else {
|
} else {
|
||||||
idx_bytes
|
idx_bytes
|
||||||
};
|
};
|
||||||
Some(format!("Phase 1: {}", hex::encode(idx_bytes)))
|
WorkerStatus {
|
||||||
|
progress: Some("0.00%".into()),
|
||||||
|
freeform: vec![format!(
|
||||||
|
"Currently in phase 1, iterator position: {}",
|
||||||
|
hex::encode(idx_bytes)
|
||||||
|
)],
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(bi) => Some(format!("Phase 2: {:.2}% done", bi.progress() * 100.)),
|
Some(bi) => WorkerStatus {
|
||||||
|
progress: Some(format!("{:.2}%", bi.progress() * 100.)),
|
||||||
|
freeform: vec!["Currently in phase 2".into()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +148,7 @@ impl Worker for RepairWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_for_work(&mut self, _must_exit: &watch::Receiver<bool>) -> WorkerState {
|
async fn wait_for_work(&mut self) -> WorkerState {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,29 +282,28 @@ impl Worker for ScrubWorker {
|
|||||||
"Block scrub worker".into()
|
"Block scrub worker".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(&self) -> Option<String> {
|
fn status(&self) -> WorkerStatus {
|
||||||
let s = match &self.work {
|
let mut s = WorkerStatus {
|
||||||
ScrubWorkerState::Running(bsi) => format!(
|
persistent_errors: Some(self.persisted.corruptions_detected),
|
||||||
"{:.2}% done (tranquility = {})",
|
tranquility: Some(self.persisted.tranquility),
|
||||||
bsi.progress() * 100.,
|
..Default::default()
|
||||||
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!(
|
match &self.work {
|
||||||
"{} ; corruptions detected: {}",
|
ScrubWorkerState::Running(bsi) => {
|
||||||
s, self.persisted.corruptions_detected
|
s.progress = Some(format!("{:.2}%", bsi.progress() * 100.));
|
||||||
))
|
}
|
||||||
|
ScrubWorkerState::Paused(bsi, rt) => {
|
||||||
|
s.progress = Some(format!("{:.2}%", bsi.progress() * 100.));
|
||||||
|
s.freeform = vec![format!("Scrub paused, resumes at {}", msec_to_rfc3339(*rt))];
|
||||||
|
}
|
||||||
|
ScrubWorkerState::Finished => {
|
||||||
|
s.freeform = vec![format!(
|
||||||
|
"Last scrub completed at {}",
|
||||||
|
msec_to_rfc3339(self.persisted.time_last_complete_scrub)
|
||||||
|
)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> {
|
async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> {
|
||||||
@@ -331,7 +341,7 @@ impl Worker for ScrubWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_for_work(&mut self, _must_exit: &watch::Receiver<bool>) -> WorkerState {
|
async fn wait_for_work(&mut self) -> WorkerState {
|
||||||
let (wait_until, command) = match &self.work {
|
let (wait_until, command) = match &self.work {
|
||||||
ScrubWorkerState::Running(_) => return WorkerState::Busy,
|
ScrubWorkerState::Running(_) => return WorkerState::Busy,
|
||||||
ScrubWorkerState::Paused(_, resume_time) => (*resume_time, ScrubWorkerCommand::Resume),
|
ScrubWorkerState::Paused(_, resume_time) => (*resume_time, ScrubWorkerCommand::Resume),
|
||||||
|
|||||||
@@ -123,6 +123,24 @@ impl BlockResyncManager {
|
|||||||
Ok(self.errors.len())
|
Ok(self.errors.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear the error counter for a block and put it in queue immediately
|
||||||
|
pub fn clear_backoff(&self, hash: &Hash) -> Result<(), Error> {
|
||||||
|
let now = now_msec();
|
||||||
|
if let Some(ec) = self.errors.get(hash)? {
|
||||||
|
let mut ec = ErrorCounter::decode(&ec);
|
||||||
|
if ec.errors > 0 {
|
||||||
|
ec.last_try = now - ec.delay_msec();
|
||||||
|
self.errors.insert(hash, ec.encode())?;
|
||||||
|
self.put_to_resync_at(hash, now)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::Message(format!(
|
||||||
|
"Block {:?} was not in an errored state",
|
||||||
|
hash
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Resync loop ----
|
// ---- Resync loop ----
|
||||||
|
|
||||||
// This part manages a queue of blocks that need to be
|
// This part manages a queue of blocks that need to be
|
||||||
@@ -257,7 +275,7 @@ impl BlockResyncManager {
|
|||||||
|
|
||||||
if let Err(e) = &res {
|
if let Err(e) = &res {
|
||||||
manager.metrics.resync_error_counter.add(1);
|
manager.metrics.resync_error_counter.add(1);
|
||||||
warn!("Error when resyncing {:?}: {}", hash, e);
|
error!("Error when resyncing {:?}: {}", hash, e);
|
||||||
|
|
||||||
let err_counter = match self.errors.get(hash.as_slice())? {
|
let err_counter = match self.errors.get(hash.as_slice())? {
|
||||||
Some(ec) => ErrorCounter::decode(&ec).add1(now + 1),
|
Some(ec) => ErrorCounter::decode(&ec).add1(now + 1),
|
||||||
@@ -477,27 +495,22 @@ impl Worker for ResyncWorker {
|
|||||||
format!("Block resync worker #{}", self.index + 1)
|
format!("Block resync worker #{}", self.index + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(&self) -> Option<String> {
|
fn status(&self) -> WorkerStatus {
|
||||||
let persisted = self.manager.resync.persisted.load();
|
let persisted = self.manager.resync.persisted.load();
|
||||||
|
|
||||||
if self.index >= persisted.n_workers {
|
if self.index >= persisted.n_workers {
|
||||||
return Some("(unused)".into());
|
return WorkerStatus {
|
||||||
|
freeform: vec!["This worker is currently disabled".into()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ret = vec![];
|
WorkerStatus {
|
||||||
ret.push(format!("tranquility = {}", persisted.tranquility));
|
queue_length: Some(self.manager.resync.queue_len().unwrap_or(0) as u64),
|
||||||
|
tranquility: Some(persisted.tranquility),
|
||||||
let qlen = self.manager.resync.queue_len().unwrap_or(0);
|
persistent_errors: Some(self.manager.resync.errors_len().unwrap_or(0) as u64),
|
||||||
if qlen > 0 {
|
..Default::default()
|
||||||
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> {
|
async fn work(&mut self, _must_exit: &mut watch::Receiver<bool>) -> Result<WorkerState, Error> {
|
||||||
@@ -527,7 +540,7 @@ impl Worker for ResyncWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_for_work(&mut self, _must_exit: &watch::Receiver<bool>) -> WorkerState {
|
async fn wait_for_work(&mut self) -> WorkerState {
|
||||||
while self.index >= self.manager.resync.persisted.load().n_workers {
|
while self.index >= self.manager.resync.persisted.load().n_workers {
|
||||||
self.manager.resync.notify.notified().await
|
self.manager.resync.notify.notified().await
|
||||||
}
|
}
|
||||||
@@ -545,9 +558,9 @@ impl Worker for ResyncWorker {
|
|||||||
/// and the time of the last try.
|
/// and the time of the last try.
|
||||||
/// Used to implement exponential backoff.
|
/// Used to implement exponential backoff.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct ErrorCounter {
|
pub(crate) struct ErrorCounter {
|
||||||
errors: u64,
|
pub(crate) errors: u64,
|
||||||
last_try: u64,
|
pub(crate) last_try: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorCounter {
|
impl ErrorCounter {
|
||||||
@@ -558,12 +571,13 @@ impl ErrorCounter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode(data: &[u8]) -> Self {
|
pub(crate) fn decode(data: &[u8]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
errors: u64::from_be_bytes(data[0..8].try_into().unwrap()),
|
errors: u64::from_be_bytes(data[0..8].try_into().unwrap()),
|
||||||
last_try: u64::from_be_bytes(data[8..16].try_into().unwrap()),
|
last_try: u64::from_be_bytes(data[8..16].try_into().unwrap()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(&self) -> Vec<u8> {
|
fn encode(&self) -> Vec<u8> {
|
||||||
[
|
[
|
||||||
u64::to_be_bytes(self.errors),
|
u64::to_be_bytes(self.errors),
|
||||||
@@ -583,7 +597,8 @@ impl ErrorCounter {
|
|||||||
(RESYNC_RETRY_DELAY.as_millis() as u64)
|
(RESYNC_RETRY_DELAY.as_millis() as u64)
|
||||||
<< std::cmp::min(self.errors - 1, RESYNC_RETRY_DELAY_MAX_BACKOFF_POWER)
|
<< std::cmp::min(self.errors - 1, RESYNC_RETRY_DELAY_MAX_BACKOFF_POWER)
|
||||||
}
|
}
|
||||||
fn next_try(&self) -> u64 {
|
|
||||||
|
pub(crate) fn next_try(&self) -> u64 {
|
||||||
self.last_try + self.delay_msec()
|
self.last_try + self.delay_msec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ required-features = ["cli"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
err-derive = "0.3"
|
err-derive = "0.3"
|
||||||
|
hex = "0.4"
|
||||||
hexdump = "0.1"
|
hexdump = "0.1"
|
||||||
tracing = "0.1.30"
|
tracing = "0.1.30"
|
||||||
|
|
||||||
@@ -25,6 +26,11 @@ heed = { version = "0.11", default-features = false, features = ["lmdb"], option
|
|||||||
rusqlite = { version = "0.27", optional = true }
|
rusqlite = { version = "0.27", optional = true }
|
||||||
sled = { version = "0.34", optional = true }
|
sled = { version = "0.34", optional = true }
|
||||||
|
|
||||||
|
rmp-serde = { version = "0.15", optional = true }
|
||||||
|
serde = { version = "1.0", default-features = false, features = ["derive", "rc"], optional = true }
|
||||||
|
serde-transcode = { version = "1.1", optional = true }
|
||||||
|
nettext = { version = "0.3.2", default-features = false, features = ["serde"], optional = true, path = "../../../nettext" }
|
||||||
|
|
||||||
# cli deps
|
# cli deps
|
||||||
clap = { version = "3.1.18", optional = true, features = ["derive", "env"] }
|
clap = { version = "3.1.18", optional = true, features = ["derive", "env"] }
|
||||||
pretty_env_logger = { version = "0.4", optional = true }
|
pretty_env_logger = { version = "0.4", optional = true }
|
||||||
@@ -33,7 +39,9 @@ pretty_env_logger = { version = "0.4", optional = true }
|
|||||||
mktemp = "0.4"
|
mktemp = "0.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = [ "sled" ]
|
||||||
bundled-libs = [ "rusqlite/bundled" ]
|
bundled-libs = [ "rusqlite/bundled" ]
|
||||||
cli = ["clap", "pretty_env_logger"]
|
cli = ["clap", "pretty_env_logger"]
|
||||||
lmdb = [ "heed" ]
|
lmdb = [ "heed" ]
|
||||||
sqlite = [ "rusqlite" ]
|
sqlite = [ "rusqlite" ]
|
||||||
|
debuglog = [ "serde", "rmp-serde", "nettext", "serde-transcode" ]
|
||||||
|
|||||||
@@ -173,14 +173,31 @@ impl Tree {
|
|||||||
Db(self.0.clone())
|
Db(self.0.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn name(&self) -> Option<String> {
|
||||||
|
self.0.tree_name(self.1)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get<T: AsRef<[u8]>>(&self, key: T) -> Result<Option<Value>> {
|
pub fn get<T: AsRef<[u8]>>(&self, key: T) -> Result<Option<Value>> {
|
||||||
self.0.get(self.1, key.as_ref())
|
let res = self.0.get(self.1, key.as_ref())?;
|
||||||
|
#[cfg(feature = "debuglog")]
|
||||||
|
debuglog(
|
||||||
|
self.name(),
|
||||||
|
"GET",
|
||||||
|
key.as_ref(),
|
||||||
|
res.as_deref().unwrap_or(b"-"),
|
||||||
|
);
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn len(&self) -> Result<usize> {
|
pub fn len(&self) -> Result<usize> {
|
||||||
self.0.len(self.1)
|
self.0.len(self.1)
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn fast_len(&self) -> Result<Option<usize>> {
|
||||||
|
self.0.fast_len(self.1)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn first(&self) -> Result<Option<(Value, Value)>> {
|
pub fn first(&self) -> Result<Option<(Value, Value)>> {
|
||||||
@@ -200,7 +217,10 @@ impl Tree {
|
|||||||
key: T,
|
key: T,
|
||||||
value: U,
|
value: U,
|
||||||
) -> Result<Option<Value>> {
|
) -> Result<Option<Value>> {
|
||||||
self.0.insert(self.1, key.as_ref(), value.as_ref())
|
let res = self.0.insert(self.1, key.as_ref(), value.as_ref())?;
|
||||||
|
#[cfg(feature = "debuglog")]
|
||||||
|
debuglog(self.name(), "PUT", key.as_ref(), value.as_ref());
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
/// Returns the old value if there was one
|
/// Returns the old value if there was one
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -263,7 +283,10 @@ impl<'a> Transaction<'a> {
|
|||||||
key: T,
|
key: T,
|
||||||
value: U,
|
value: U,
|
||||||
) -> TxOpResult<Option<Value>> {
|
) -> TxOpResult<Option<Value>> {
|
||||||
self.0.insert(tree.1, key.as_ref(), value.as_ref())
|
let res = self.0.insert(tree.1, key.as_ref(), value.as_ref())?;
|
||||||
|
#[cfg(feature = "debuglog")]
|
||||||
|
debuglog(tree.name(), "txPUT", key.as_ref(), value.as_ref());
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
/// Returns the old value if there was one
|
/// Returns the old value if there was one
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -320,9 +343,13 @@ pub(crate) trait IDb: Send + Sync {
|
|||||||
fn engine(&self) -> String;
|
fn engine(&self) -> String;
|
||||||
fn open_tree(&self, name: &str) -> Result<usize>;
|
fn open_tree(&self, name: &str) -> Result<usize>;
|
||||||
fn list_trees(&self) -> Result<Vec<String>>;
|
fn list_trees(&self) -> Result<Vec<String>>;
|
||||||
|
fn tree_name(&self, tree: usize) -> Option<String>;
|
||||||
|
|
||||||
fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>>;
|
fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>>;
|
||||||
fn len(&self, tree: usize) -> Result<usize>;
|
fn len(&self, tree: usize) -> Result<usize>;
|
||||||
|
fn fast_len(&self, _tree: usize) -> Result<Option<usize>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>>;
|
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>>;
|
||||||
fn remove(&self, tree: usize, key: &[u8]) -> Result<Option<Value>>;
|
fn remove(&self, tree: usize, key: &[u8]) -> Result<Option<Value>>;
|
||||||
@@ -414,3 +441,29 @@ fn get_bound<K: AsRef<[u8]>>(b: Bound<&K>) -> Bound<&[u8]> {
|
|||||||
Bound::Unbounded => Bound::Unbounded,
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debuglog")]
|
||||||
|
fn debuglog(tree: Option<String>, action: &str, k: &[u8], v: &[u8]) {
|
||||||
|
let key = String::from_utf8(nettext::switch64::encode(k, false)).unwrap();
|
||||||
|
let tree = tree.as_deref().unwrap_or("(?)");
|
||||||
|
if let Ok(vstr) = std::str::from_utf8(v) {
|
||||||
|
eprintln!("{} {} {} S:{}", tree, action, key, vstr);
|
||||||
|
} else {
|
||||||
|
let mut vread = &v[..];
|
||||||
|
let mut vder = rmp_serde::decode::Deserializer::new(&mut vread);
|
||||||
|
let mut vser = nettext::serde::Serializer {
|
||||||
|
string_format: nettext::BytesEncoding::Switch64 {
|
||||||
|
allow_whitespace: true,
|
||||||
|
},
|
||||||
|
bytes_format: nettext::BytesEncoding::Hex { split: true },
|
||||||
|
};
|
||||||
|
if let Some(venc) = serde_transcode::transcode(&mut vder, &mut vser)
|
||||||
|
.ok()
|
||||||
|
.and_then(|x| String::from_utf8(x.encode_concise()).ok())
|
||||||
|
{
|
||||||
|
eprintln!("{} {} {} N:{}", tree, action, key, venc);
|
||||||
|
} else {
|
||||||
|
eprintln!("{} {} {} X:{}", tree, action, key, hex::encode(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ impl IDb for LmdbDb {
|
|||||||
Ok(tree.len(&tx)?.try_into().unwrap())
|
Ok(tree.len(&tx)?.try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fast_len(&self, tree: usize) -> Result<Option<usize>> {
|
||||||
|
Ok(Some(self.len(tree)?))
|
||||||
|
}
|
||||||
|
|
||||||
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>> {
|
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>> {
|
||||||
let tree = self.get_tree(tree)?;
|
let tree = self.get_tree(tree)?;
|
||||||
let mut tx = self.db.write_txn()?;
|
let mut tx = self.db.write_txn()?;
|
||||||
|
|||||||
@@ -88,6 +88,15 @@ impl IDb for SledDb {
|
|||||||
Ok(trees)
|
Ok(trees)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tree_name(&self, tree: usize) -> Option<String> {
|
||||||
|
self.trees
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.get(tree)
|
||||||
|
.and_then(|x| String::from_utf8(x.name().to_vec()).ok())
|
||||||
|
}
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
|
|
||||||
fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> {
|
fn get(&self, tree: usize, key: &[u8]) -> Result<Option<Value>> {
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ impl IDb for SqliteDb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fast_len(&self, tree: usize) -> Result<Option<usize>> {
|
||||||
|
Ok(Some(self.len(tree)?))
|
||||||
|
}
|
||||||
|
|
||||||
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>> {
|
fn insert(&self, tree: usize, key: &[u8], value: &[u8]) -> Result<Option<Value>> {
|
||||||
trace!("insert {}: lock db", tree);
|
trace!("insert {}: lock db", tree);
|
||||||
let this = self.0.lock().unwrap();
|
let this = self.0.lock().unwrap();
|
||||||
|
|||||||
@@ -30,11 +30,13 @@ garage_table = { version = "0.8.0", path = "../table" }
|
|||||||
garage_util = { version = "0.8.0", path = "../util" }
|
garage_util = { version = "0.8.0", path = "../util" }
|
||||||
garage_web = { version = "0.8.0", path = "../web" }
|
garage_web = { version = "0.8.0", path = "../web" }
|
||||||
|
|
||||||
|
backtrace = "0.3"
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
bytesize = "1.1"
|
bytesize = "1.1"
|
||||||
timeago = "0.3"
|
timeago = "0.3"
|
||||||
|
parse_duration = "2.1"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
tracing = { version = "0.1.30", features = ["log-always"] }
|
tracing = { version = "0.1.30" }
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
async-trait = "0.1.7"
|
async-trait = "0.1.7"
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ use std::sync::Arc;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use garage_util::background::BackgroundRunner;
|
||||||
use garage_util::crdt::*;
|
use garage_util::crdt::*;
|
||||||
use garage_util::data::*;
|
use garage_util::data::*;
|
||||||
use garage_util::error::Error as GarageError;
|
use garage_util::error::Error as GarageError;
|
||||||
|
use garage_util::formater::format_table_to_string;
|
||||||
use garage_util::time::*;
|
use garage_util::time::*;
|
||||||
|
|
||||||
use garage_table::replication::*;
|
use garage_table::replication::*;
|
||||||
@@ -15,6 +17,7 @@ use garage_table::*;
|
|||||||
|
|
||||||
use garage_rpc::*;
|
use garage_rpc::*;
|
||||||
|
|
||||||
|
use garage_block::manager::BlockResyncErrorInfo;
|
||||||
use garage_block::repair::ScrubWorkerCommand;
|
use garage_block::repair::ScrubWorkerCommand;
|
||||||
|
|
||||||
use garage_model::bucket_alias_table::*;
|
use garage_model::bucket_alias_table::*;
|
||||||
@@ -24,6 +27,8 @@ use garage_model::helper::error::{Error, OkOrBadRequest};
|
|||||||
use garage_model::key_table::*;
|
use garage_model::key_table::*;
|
||||||
use garage_model::migrate::Migrate;
|
use garage_model::migrate::Migrate;
|
||||||
use garage_model::permission::*;
|
use garage_model::permission::*;
|
||||||
|
use garage_model::s3::object_table::*;
|
||||||
|
use garage_model::s3::version_table::Version;
|
||||||
|
|
||||||
use crate::cli::*;
|
use crate::cli::*;
|
||||||
use crate::repair::online::launch_online_repair;
|
use crate::repair::online::launch_online_repair;
|
||||||
@@ -38,7 +43,8 @@ pub enum AdminRpc {
|
|||||||
LaunchRepair(RepairOpt),
|
LaunchRepair(RepairOpt),
|
||||||
Migrate(MigrateOpt),
|
Migrate(MigrateOpt),
|
||||||
Stats(StatsOpt),
|
Stats(StatsOpt),
|
||||||
Worker(WorkerOpt),
|
Worker(WorkerOperation),
|
||||||
|
BlockOperation(BlockOperation),
|
||||||
|
|
||||||
// Replies
|
// Replies
|
||||||
Ok(String),
|
Ok(String),
|
||||||
@@ -54,6 +60,13 @@ pub enum AdminRpc {
|
|||||||
HashMap<usize, garage_util::background::WorkerInfo>,
|
HashMap<usize, garage_util::background::WorkerInfo>,
|
||||||
WorkerListOpt,
|
WorkerListOpt,
|
||||||
),
|
),
|
||||||
|
WorkerInfo(usize, garage_util::background::WorkerInfo),
|
||||||
|
BlockErrorList(Vec<BlockResyncErrorInfo>),
|
||||||
|
BlockInfo {
|
||||||
|
hash: Hash,
|
||||||
|
refcount: u64,
|
||||||
|
versions: Vec<Result<Version, Uuid>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rpc for AdminRpc {
|
impl Rpc for AdminRpc {
|
||||||
@@ -62,17 +75,24 @@ impl Rpc for AdminRpc {
|
|||||||
|
|
||||||
pub struct AdminRpcHandler {
|
pub struct AdminRpcHandler {
|
||||||
garage: Arc<Garage>,
|
garage: Arc<Garage>,
|
||||||
|
background: Arc<BackgroundRunner>,
|
||||||
endpoint: Arc<Endpoint<AdminRpc, Self>>,
|
endpoint: Arc<Endpoint<AdminRpc, Self>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AdminRpcHandler {
|
impl AdminRpcHandler {
|
||||||
pub fn new(garage: Arc<Garage>) -> Arc<Self> {
|
pub fn new(garage: Arc<Garage>, background: Arc<BackgroundRunner>) -> Arc<Self> {
|
||||||
let endpoint = garage.system.netapp.endpoint(ADMIN_RPC_PATH.into());
|
let endpoint = garage.system.netapp.endpoint(ADMIN_RPC_PATH.into());
|
||||||
let admin = Arc::new(Self { garage, endpoint });
|
let admin = Arc::new(Self {
|
||||||
|
garage,
|
||||||
|
background,
|
||||||
|
endpoint,
|
||||||
|
});
|
||||||
admin.endpoint.set_handler(admin.clone());
|
admin.endpoint.set_handler(admin.clone());
|
||||||
admin
|
admin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================ BUCKET COMMANDS ====================
|
||||||
|
|
||||||
async fn handle_bucket_cmd(&self, cmd: &BucketOperation) -> Result<AdminRpc, Error> {
|
async fn handle_bucket_cmd(&self, cmd: &BucketOperation) -> Result<AdminRpc, Error> {
|
||||||
match cmd {
|
match cmd {
|
||||||
BucketOperation::List => self.handle_list_buckets().await,
|
BucketOperation::List => self.handle_list_buckets().await,
|
||||||
@@ -85,6 +105,9 @@ impl AdminRpcHandler {
|
|||||||
BucketOperation::Deny(query) => self.handle_bucket_deny(query).await,
|
BucketOperation::Deny(query) => self.handle_bucket_deny(query).await,
|
||||||
BucketOperation::Website(query) => self.handle_bucket_website(query).await,
|
BucketOperation::Website(query) => self.handle_bucket_website(query).await,
|
||||||
BucketOperation::SetQuotas(query) => self.handle_bucket_set_quotas(query).await,
|
BucketOperation::SetQuotas(query) => self.handle_bucket_set_quotas(query).await,
|
||||||
|
BucketOperation::CleanupIncompleteUploads(query) => {
|
||||||
|
self.handle_bucket_cleanup_incomplete_uploads(query).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,6 +535,44 @@ impl AdminRpcHandler {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_bucket_cleanup_incomplete_uploads(
|
||||||
|
&self,
|
||||||
|
query: &CleanupIncompleteUploadsOpt,
|
||||||
|
) -> Result<AdminRpc, Error> {
|
||||||
|
let mut bucket_ids = vec![];
|
||||||
|
for b in query.buckets.iter() {
|
||||||
|
bucket_ids.push(
|
||||||
|
self.garage
|
||||||
|
.bucket_helper()
|
||||||
|
.resolve_global_bucket_name(b)
|
||||||
|
.await?
|
||||||
|
.ok_or_bad_request(format!("Bucket not found: {}", b))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let duration = parse_duration::parse::parse(&query.older_than)
|
||||||
|
.ok_or_bad_request("Invalid duration passed for --older-than parameter")?;
|
||||||
|
|
||||||
|
let mut ret = String::new();
|
||||||
|
for bucket in bucket_ids {
|
||||||
|
let count = self
|
||||||
|
.garage
|
||||||
|
.bucket_helper()
|
||||||
|
.cleanup_incomplete_uploads(&bucket, duration)
|
||||||
|
.await?;
|
||||||
|
writeln!(
|
||||||
|
&mut ret,
|
||||||
|
"Bucket {:?}: {} incomplete uploads aborted",
|
||||||
|
bucket, count
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AdminRpc::Ok(ret))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================ KEY COMMANDS ====================
|
||||||
|
|
||||||
async fn handle_key_cmd(&self, cmd: &KeyOperation) -> Result<AdminRpc, Error> {
|
async fn handle_key_cmd(&self, cmd: &KeyOperation) -> Result<AdminRpc, Error> {
|
||||||
match cmd {
|
match cmd {
|
||||||
KeyOperation::List => self.handle_list_keys().await,
|
KeyOperation::List => self.handle_list_keys().await,
|
||||||
@@ -649,6 +710,8 @@ impl AdminRpcHandler {
|
|||||||
Ok(AdminRpc::KeyInfo(key, relevant_buckets))
|
Ok(AdminRpc::KeyInfo(key, relevant_buckets))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================ MIGRATION COMMANDS ====================
|
||||||
|
|
||||||
async fn handle_migrate(self: &Arc<Self>, opt: MigrateOpt) -> Result<AdminRpc, Error> {
|
async fn handle_migrate(self: &Arc<Self>, opt: MigrateOpt) -> Result<AdminRpc, Error> {
|
||||||
if !opt.yes {
|
if !opt.yes {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
@@ -665,6 +728,8 @@ impl AdminRpcHandler {
|
|||||||
Ok(AdminRpc::Ok("Migration successfull.".into()))
|
Ok(AdminRpc::Ok("Migration successfull.".into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================ REPAIR COMMANDS ====================
|
||||||
|
|
||||||
async fn handle_launch_repair(self: &Arc<Self>, opt: RepairOpt) -> Result<AdminRpc, Error> {
|
async fn handle_launch_repair(self: &Arc<Self>, opt: RepairOpt) -> Result<AdminRpc, Error> {
|
||||||
if !opt.yes {
|
if !opt.yes {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
@@ -700,7 +765,7 @@ impl AdminRpcHandler {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
launch_online_repair(self.garage.clone(), opt).await;
|
launch_online_repair(&self.garage, &self.background, opt).await?;
|
||||||
Ok(AdminRpc::Ok(format!(
|
Ok(AdminRpc::Ok(format!(
|
||||||
"Repair launched on {:?}",
|
"Repair launched on {:?}",
|
||||||
self.garage.system.id
|
self.garage.system.id
|
||||||
@@ -708,6 +773,8 @@ impl AdminRpcHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================ STATS COMMANDS ====================
|
||||||
|
|
||||||
async fn handle_stats(&self, opt: StatsOpt) -> Result<AdminRpc, Error> {
|
async fn handle_stats(&self, opt: StatsOpt) -> Result<AdminRpc, Error> {
|
||||||
if opt.all_nodes {
|
if opt.all_nodes {
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
@@ -724,11 +791,12 @@ impl AdminRpcHandler {
|
|||||||
match self
|
match self
|
||||||
.endpoint
|
.endpoint
|
||||||
.call(&node_id, AdminRpc::Stats(opt), PRIO_NORMAL)
|
.call(&node_id, AdminRpc::Stats(opt), PRIO_NORMAL)
|
||||||
.await?
|
.await
|
||||||
{
|
{
|
||||||
Ok(AdminRpc::Ok(s)) => writeln!(&mut ret, "{}", s).unwrap(),
|
Ok(Ok(AdminRpc::Ok(s))) => writeln!(&mut ret, "{}", s).unwrap(),
|
||||||
Ok(x) => writeln!(&mut ret, "Bad answer: {:?}", x).unwrap(),
|
Ok(Ok(x)) => writeln!(&mut ret, "Bad answer: {:?}", x).unwrap(),
|
||||||
Err(e) => writeln!(&mut ret, "Error: {}", e).unwrap(),
|
Ok(Err(e)) => writeln!(&mut ret, "Remote error: {}", e).unwrap(),
|
||||||
|
Err(e) => writeln!(&mut ret, "Network error: {}", e).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(AdminRpc::Ok(ret))
|
Ok(AdminRpc::Ok(ret))
|
||||||
@@ -748,6 +816,7 @@ impl AdminRpcHandler {
|
|||||||
.unwrap_or_else(|| "(unknown)".into()),
|
.unwrap_or_else(|| "(unknown)".into()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
writeln!(&mut ret, "\nDatabase engine: {}", self.garage.db.engine()).unwrap();
|
writeln!(&mut ret, "\nDatabase engine: {}", self.garage.db.engine()).unwrap();
|
||||||
|
|
||||||
// Gather ring statistics
|
// Gather ring statistics
|
||||||
@@ -766,21 +835,38 @@ impl AdminRpcHandler {
|
|||||||
writeln!(&mut ret, " {:?} {}", n, c).unwrap();
|
writeln!(&mut ret, " {:?} {}", n, c).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.gather_table_stats(&mut ret, &self.garage.bucket_table, &opt)?;
|
// Gather table statistics
|
||||||
self.gather_table_stats(&mut ret, &self.garage.key_table, &opt)?;
|
let mut table = vec![" Table\tItems\tMklItems\tMklTodo\tGcTodo".into()];
|
||||||
self.gather_table_stats(&mut ret, &self.garage.object_table, &opt)?;
|
table.push(self.gather_table_stats(&self.garage.bucket_table, opt.detailed)?);
|
||||||
self.gather_table_stats(&mut ret, &self.garage.version_table, &opt)?;
|
table.push(self.gather_table_stats(&self.garage.key_table, opt.detailed)?);
|
||||||
self.gather_table_stats(&mut ret, &self.garage.block_ref_table, &opt)?;
|
table.push(self.gather_table_stats(&self.garage.object_table, opt.detailed)?);
|
||||||
|
table.push(self.gather_table_stats(&self.garage.version_table, opt.detailed)?);
|
||||||
|
table.push(self.gather_table_stats(&self.garage.block_ref_table, opt.detailed)?);
|
||||||
|
write!(
|
||||||
|
&mut ret,
|
||||||
|
"\nTable stats:\n{}",
|
||||||
|
format_table_to_string(table)
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Gather block manager statistics
|
||||||
writeln!(&mut ret, "\nBlock manager stats:").unwrap();
|
writeln!(&mut ret, "\nBlock manager stats:").unwrap();
|
||||||
if opt.detailed {
|
let rc_len = if opt.detailed {
|
||||||
writeln!(
|
self.garage.block_manager.rc_len()?.to_string()
|
||||||
&mut ret,
|
} else {
|
||||||
" number of RC entries (~= number of blocks): {}",
|
self.garage
|
||||||
self.garage.block_manager.rc_len()?
|
.block_manager
|
||||||
)
|
.rc_fast_len()?
|
||||||
.unwrap();
|
.map(|x| x.to_string())
|
||||||
}
|
.unwrap_or_else(|| "NC".into())
|
||||||
|
};
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
&mut ret,
|
||||||
|
" number of RC entries (~= number of blocks): {}",
|
||||||
|
rc_len
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
writeln!(
|
writeln!(
|
||||||
&mut ret,
|
&mut ret,
|
||||||
" resync queue length: {}",
|
" resync queue length: {}",
|
||||||
@@ -794,67 +880,83 @@ impl AdminRpcHandler {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
if !opt.detailed {
|
||||||
|
writeln!(&mut ret, "\nIf values are missing (marked as NC), consider adding the --detailed flag - this will be slow.").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gather_table_stats<F, R>(
|
fn gather_table_stats<F, R>(
|
||||||
&self,
|
&self,
|
||||||
to: &mut String,
|
|
||||||
t: &Arc<Table<F, R>>,
|
t: &Arc<Table<F, R>>,
|
||||||
opt: &StatsOpt,
|
detailed: bool,
|
||||||
) -> Result<(), Error>
|
) -> Result<String, Error>
|
||||||
where
|
where
|
||||||
F: TableSchema + 'static,
|
F: TableSchema + 'static,
|
||||||
R: TableReplication + 'static,
|
R: TableReplication + 'static,
|
||||||
{
|
{
|
||||||
writeln!(to, "\nTable stats for {}", F::TABLE_NAME).unwrap();
|
let (data_len, mkl_len) = if detailed {
|
||||||
if opt.detailed {
|
(
|
||||||
writeln!(
|
t.data.store.len().map_err(GarageError::from)?.to_string(),
|
||||||
to,
|
t.merkle_updater.merkle_tree_len()?.to_string(),
|
||||||
" number of items: {}",
|
|
||||||
t.data.store.len().map_err(GarageError::from)?
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
} else {
|
||||||
writeln!(
|
(
|
||||||
to,
|
t.data
|
||||||
" Merkle tree size: {}",
|
.store
|
||||||
t.merkle_updater.merkle_tree_len()?
|
.fast_len()
|
||||||
|
.map_err(GarageError::from)?
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.unwrap_or_else(|| "NC".into()),
|
||||||
|
t.merkle_updater
|
||||||
|
.merkle_tree_fast_len()?
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.unwrap_or_else(|| "NC".into()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
};
|
||||||
}
|
|
||||||
writeln!(
|
|
||||||
to,
|
|
||||||
" Merkle updater todo queue length: {}",
|
|
||||||
t.merkle_updater.todo_len()?
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
writeln!(to, " GC todo queue length: {}", t.data.gc_todo_len()?).unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(format!(
|
||||||
|
" {}\t{}\t{}\t{}\t{}",
|
||||||
|
F::TABLE_NAME,
|
||||||
|
data_len,
|
||||||
|
mkl_len,
|
||||||
|
t.merkle_updater.todo_len()?,
|
||||||
|
t.data.gc_todo_len()?
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----
|
// ================ WORKER COMMANDS ====================
|
||||||
|
|
||||||
async fn handle_worker_cmd(&self, opt: WorkerOpt) -> Result<AdminRpc, Error> {
|
async fn handle_worker_cmd(&self, cmd: &WorkerOperation) -> Result<AdminRpc, Error> {
|
||||||
match opt.cmd {
|
match cmd {
|
||||||
WorkerCmd::List { opt } => {
|
WorkerOperation::List { opt } => {
|
||||||
let workers = self.garage.background.get_worker_info();
|
let workers = self.background.get_worker_info();
|
||||||
Ok(AdminRpc::WorkerList(workers, opt))
|
Ok(AdminRpc::WorkerList(workers, *opt))
|
||||||
}
|
}
|
||||||
WorkerCmd::Set { opt } => match opt {
|
WorkerOperation::Info { tid } => {
|
||||||
|
let info = self
|
||||||
|
.background
|
||||||
|
.get_worker_info()
|
||||||
|
.get(tid)
|
||||||
|
.ok_or_bad_request(format!("No worker with TID {}", tid))?
|
||||||
|
.clone();
|
||||||
|
Ok(AdminRpc::WorkerInfo(*tid, info))
|
||||||
|
}
|
||||||
|
WorkerOperation::Set { opt } => match opt {
|
||||||
WorkerSetCmd::ScrubTranquility { tranquility } => {
|
WorkerSetCmd::ScrubTranquility { tranquility } => {
|
||||||
let scrub_command = ScrubWorkerCommand::SetTranquility(tranquility);
|
let scrub_command = ScrubWorkerCommand::SetTranquility(*tranquility);
|
||||||
self.garage
|
self.garage
|
||||||
.block_manager
|
.block_manager
|
||||||
.send_scrub_command(scrub_command)
|
.send_scrub_command(scrub_command)
|
||||||
.await;
|
.await?;
|
||||||
Ok(AdminRpc::Ok("Scrub tranquility updated".into()))
|
Ok(AdminRpc::Ok("Scrub tranquility updated".into()))
|
||||||
}
|
}
|
||||||
WorkerSetCmd::ResyncNWorkers { n_workers } => {
|
WorkerSetCmd::ResyncWorkerCount { worker_count } => {
|
||||||
self.garage
|
self.garage
|
||||||
.block_manager
|
.block_manager
|
||||||
.resync
|
.resync
|
||||||
.set_n_workers(n_workers)
|
.set_n_workers(*worker_count)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(AdminRpc::Ok("Number of resync workers updated".into()))
|
Ok(AdminRpc::Ok("Number of resync workers updated".into()))
|
||||||
}
|
}
|
||||||
@@ -862,13 +964,154 @@ impl AdminRpcHandler {
|
|||||||
self.garage
|
self.garage
|
||||||
.block_manager
|
.block_manager
|
||||||
.resync
|
.resync
|
||||||
.set_tranquility(tranquility)
|
.set_tranquility(*tranquility)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(AdminRpc::Ok("Resync tranquility updated".into()))
|
Ok(AdminRpc::Ok("Resync tranquility updated".into()))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================ BLOCK COMMANDS ====================
|
||||||
|
|
||||||
|
async fn handle_block_cmd(&self, cmd: &BlockOperation) -> Result<AdminRpc, Error> {
|
||||||
|
match cmd {
|
||||||
|
BlockOperation::ListErrors => Ok(AdminRpc::BlockErrorList(
|
||||||
|
self.garage.block_manager.list_resync_errors()?,
|
||||||
|
)),
|
||||||
|
BlockOperation::Info { hash } => {
|
||||||
|
let hash = hex::decode(hash).ok_or_bad_request("invalid hash")?;
|
||||||
|
let hash = Hash::try_from(&hash).ok_or_bad_request("invalid hash")?;
|
||||||
|
let refcount = self.garage.block_manager.get_block_rc(&hash)?;
|
||||||
|
let block_refs = self
|
||||||
|
.garage
|
||||||
|
.block_ref_table
|
||||||
|
.get_range(&hash, None, None, 10000, Default::default())
|
||||||
|
.await?;
|
||||||
|
let mut versions = vec![];
|
||||||
|
for br in block_refs {
|
||||||
|
if let Some(v) = self
|
||||||
|
.garage
|
||||||
|
.version_table
|
||||||
|
.get(&br.version, &EmptyKey)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
versions.push(Ok(v));
|
||||||
|
} else {
|
||||||
|
versions.push(Err(br.version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(AdminRpc::BlockInfo {
|
||||||
|
hash,
|
||||||
|
refcount,
|
||||||
|
versions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
BlockOperation::RetryNow { all, blocks } => {
|
||||||
|
if *all {
|
||||||
|
if !blocks.is_empty() {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
"--all was specified, cannot also specify blocks".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let blocks = self.garage.block_manager.list_resync_errors()?;
|
||||||
|
for b in blocks.iter() {
|
||||||
|
self.garage.block_manager.resync.clear_backoff(&b.hash)?;
|
||||||
|
}
|
||||||
|
Ok(AdminRpc::Ok(format!(
|
||||||
|
"{} blocks returned in queue for a retry now (check logs to see results)",
|
||||||
|
blocks.len()
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
for hash in blocks {
|
||||||
|
let hash = hex::decode(hash).ok_or_bad_request("invalid hash")?;
|
||||||
|
let hash = Hash::try_from(&hash).ok_or_bad_request("invalid hash")?;
|
||||||
|
self.garage.block_manager.resync.clear_backoff(&hash)?;
|
||||||
|
}
|
||||||
|
Ok(AdminRpc::Ok(format!(
|
||||||
|
"{} blocks returned in queue for a retry now (check logs to see results)",
|
||||||
|
blocks.len()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockOperation::Purge { yes, blocks } => {
|
||||||
|
if !yes {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
"Pass the --yes flag to confirm block purge operation.".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut obj_dels = 0;
|
||||||
|
let mut ver_dels = 0;
|
||||||
|
|
||||||
|
for hash in blocks {
|
||||||
|
let hash = hex::decode(hash).ok_or_bad_request("invalid hash")?;
|
||||||
|
let hash = Hash::try_from(&hash).ok_or_bad_request("invalid hash")?;
|
||||||
|
let block_refs = self
|
||||||
|
.garage
|
||||||
|
.block_ref_table
|
||||||
|
.get_range(&hash, None, None, 10000, Default::default())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for br in block_refs {
|
||||||
|
let version = match self
|
||||||
|
.garage
|
||||||
|
.version_table
|
||||||
|
.get(&br.version, &EmptyKey)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Some(v) => v,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(object) = self
|
||||||
|
.garage
|
||||||
|
.object_table
|
||||||
|
.get(&version.bucket_id, &version.key)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let ov = object.versions().iter().rev().find(|v| v.is_complete());
|
||||||
|
if let Some(ov) = ov {
|
||||||
|
if ov.uuid == br.version {
|
||||||
|
let del_uuid = gen_uuid();
|
||||||
|
let deleted_object = Object::new(
|
||||||
|
version.bucket_id,
|
||||||
|
version.key.clone(),
|
||||||
|
vec![ObjectVersion {
|
||||||
|
uuid: del_uuid,
|
||||||
|
timestamp: ov.timestamp + 1,
|
||||||
|
state: ObjectVersionState::Complete(
|
||||||
|
ObjectVersionData::DeleteMarker,
|
||||||
|
),
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
self.garage.object_table.insert(&deleted_object).await?;
|
||||||
|
obj_dels += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !version.deleted.get() {
|
||||||
|
let deleted_version = Version::new(
|
||||||
|
version.uuid,
|
||||||
|
version.bucket_id,
|
||||||
|
version.key.clone(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
self.garage.version_table.insert(&deleted_version).await?;
|
||||||
|
ver_dels += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(AdminRpc::Ok(format!(
|
||||||
|
"{} blocks were purged: {} object deletion markers added, {} versions marked deleted",
|
||||||
|
blocks.len(),
|
||||||
|
obj_dels,
|
||||||
|
ver_dels
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -884,7 +1127,8 @@ impl EndpointHandler<AdminRpc> for AdminRpcHandler {
|
|||||||
AdminRpc::Migrate(opt) => self.handle_migrate(opt.clone()).await,
|
AdminRpc::Migrate(opt) => self.handle_migrate(opt.clone()).await,
|
||||||
AdminRpc::LaunchRepair(opt) => self.handle_launch_repair(opt.clone()).await,
|
AdminRpc::LaunchRepair(opt) => self.handle_launch_repair(opt.clone()).await,
|
||||||
AdminRpc::Stats(opt) => self.handle_stats(opt.clone()).await,
|
AdminRpc::Stats(opt) => self.handle_stats(opt.clone()).await,
|
||||||
AdminRpc::Worker(opt) => self.handle_worker_cmd(opt.clone()).await,
|
AdminRpc::Worker(wo) => self.handle_worker_cmd(wo).await,
|
||||||
|
AdminRpc::BlockOperation(bo) => self.handle_block_cmd(bo).await,
|
||||||
m => Err(GarageError::unexpected_rpc_message(m).into()),
|
m => Err(GarageError::unexpected_rpc_message(m).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ pub async fn cli_command_dispatch(
|
|||||||
}
|
}
|
||||||
Command::Stats(so) => cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::Stats(so)).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,
|
Command::Worker(wo) => cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::Worker(wo)).await,
|
||||||
|
Command::Block(bo) => {
|
||||||
|
cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::BlockOperation(bo)).await
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +189,20 @@ pub async fn cmd_admin(
|
|||||||
print_key_info(&key, &rb);
|
print_key_info(&key, &rb);
|
||||||
}
|
}
|
||||||
AdminRpc::WorkerList(wi, wlo) => {
|
AdminRpc::WorkerList(wi, wlo) => {
|
||||||
print_worker_info(wi, wlo);
|
print_worker_list(wi, wlo);
|
||||||
|
}
|
||||||
|
AdminRpc::WorkerInfo(tid, wi) => {
|
||||||
|
print_worker_info(tid, wi);
|
||||||
|
}
|
||||||
|
AdminRpc::BlockErrorList(el) => {
|
||||||
|
print_block_error_list(el);
|
||||||
|
}
|
||||||
|
AdminRpc::BlockInfo {
|
||||||
|
hash,
|
||||||
|
refcount,
|
||||||
|
versions,
|
||||||
|
} => {
|
||||||
|
print_block_info(hash, refcount, versions);
|
||||||
}
|
}
|
||||||
r => {
|
r => {
|
||||||
error!("Unexpected response: {:?}", r);
|
error!("Unexpected response: {:?}", r);
|
||||||
|
|||||||
@@ -49,7 +49,11 @@ pub enum Command {
|
|||||||
|
|
||||||
/// Manage background workers
|
/// Manage background workers
|
||||||
#[structopt(name = "worker", version = garage_version())]
|
#[structopt(name = "worker", version = garage_version())]
|
||||||
Worker(WorkerOpt),
|
Worker(WorkerOperation),
|
||||||
|
|
||||||
|
/// Low-level debug operations on data blocks
|
||||||
|
#[structopt(name = "block", version = garage_version())]
|
||||||
|
Block(BlockOperation),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
@@ -189,6 +193,10 @@ pub enum BucketOperation {
|
|||||||
/// Set the quotas for this bucket
|
/// Set the quotas for this bucket
|
||||||
#[structopt(name = "set-quotas", version = garage_version())]
|
#[structopt(name = "set-quotas", version = garage_version())]
|
||||||
SetQuotas(SetQuotasOpt),
|
SetQuotas(SetQuotasOpt),
|
||||||
|
|
||||||
|
/// Clean up (abort) old incomplete multipart uploads
|
||||||
|
#[structopt(name = "cleanup-incomplete-uploads", version = garage_version())]
|
||||||
|
CleanupIncompleteUploads(CleanupIncompleteUploadsOpt),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, StructOpt, Debug)]
|
#[derive(Serialize, Deserialize, StructOpt, Debug)]
|
||||||
@@ -290,6 +298,17 @@ pub struct SetQuotasOpt {
|
|||||||
pub max_objects: Option<String>,
|
pub max_objects: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, StructOpt, Debug)]
|
||||||
|
pub struct CleanupIncompleteUploadsOpt {
|
||||||
|
/// Abort multipart uploads older than this value
|
||||||
|
#[structopt(long = "older-than", default_value = "1d")]
|
||||||
|
pub older_than: String,
|
||||||
|
|
||||||
|
/// Name of bucket(s) to clean up
|
||||||
|
#[structopt(required = true)]
|
||||||
|
pub buckets: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, StructOpt, Debug)]
|
#[derive(Serialize, Deserialize, StructOpt, Debug)]
|
||||||
pub enum KeyOperation {
|
pub enum KeyOperation {
|
||||||
/// List keys
|
/// List keys
|
||||||
@@ -487,20 +506,17 @@ pub struct StatsOpt {
|
|||||||
pub detailed: bool,
|
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)]
|
#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)]
|
||||||
pub enum WorkerCmd {
|
pub enum WorkerOperation {
|
||||||
/// List all workers on Garage node
|
/// List all workers on Garage node
|
||||||
#[structopt(name = "list", version = garage_version())]
|
#[structopt(name = "list", version = garage_version())]
|
||||||
List {
|
List {
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
opt: WorkerListOpt,
|
opt: WorkerListOpt,
|
||||||
},
|
},
|
||||||
|
/// Get detailed information about a worker
|
||||||
|
#[structopt(name = "info", version = garage_version())]
|
||||||
|
Info { tid: usize },
|
||||||
/// Set worker parameter
|
/// Set worker parameter
|
||||||
#[structopt(name = "set", version = garage_version())]
|
#[structopt(name = "set", version = garage_version())]
|
||||||
Set {
|
Set {
|
||||||
@@ -525,9 +541,41 @@ pub enum WorkerSetCmd {
|
|||||||
#[structopt(name = "scrub-tranquility", version = garage_version())]
|
#[structopt(name = "scrub-tranquility", version = garage_version())]
|
||||||
ScrubTranquility { tranquility: u32 },
|
ScrubTranquility { tranquility: u32 },
|
||||||
/// Set number of concurrent block resync workers
|
/// Set number of concurrent block resync workers
|
||||||
#[structopt(name = "resync-n-workers", version = garage_version())]
|
#[structopt(name = "resync-worker-count", version = garage_version())]
|
||||||
ResyncNWorkers { n_workers: usize },
|
ResyncWorkerCount { worker_count: usize },
|
||||||
/// Set tranquility of block resync operations
|
/// Set tranquility of block resync operations
|
||||||
#[structopt(name = "resync-tranquility", version = garage_version())]
|
#[structopt(name = "resync-tranquility", version = garage_version())]
|
||||||
ResyncTranquility { tranquility: u32 },
|
ResyncTranquility { tranquility: u32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)]
|
||||||
|
pub enum BlockOperation {
|
||||||
|
/// List all blocks that currently have a resync error
|
||||||
|
#[structopt(name = "list-errors", version = garage_version())]
|
||||||
|
ListErrors,
|
||||||
|
/// Get detailed information about a single block
|
||||||
|
#[structopt(name = "info", version = garage_version())]
|
||||||
|
Info {
|
||||||
|
/// Hash of the block for which to retrieve information
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
|
/// Retry now the resync of one or many blocks
|
||||||
|
#[structopt(name = "retry-now", version = garage_version())]
|
||||||
|
RetryNow {
|
||||||
|
/// Retry all blocks that have a resync error
|
||||||
|
#[structopt(long = "all")]
|
||||||
|
all: bool,
|
||||||
|
/// Hashes of the block to retry to resync now
|
||||||
|
blocks: Vec<String>,
|
||||||
|
},
|
||||||
|
/// Delete all objects referencing a missing block
|
||||||
|
#[structopt(name = "purge", version = garage_version())]
|
||||||
|
Purge {
|
||||||
|
/// Mandatory to confirm this operation
|
||||||
|
#[structopt(long = "yes")]
|
||||||
|
yes: bool,
|
||||||
|
/// Hashes of the block to purge
|
||||||
|
#[structopt(required = true)]
|
||||||
|
blocks: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use garage_util::background::*;
|
use garage_util::background::*;
|
||||||
use garage_util::crdt::*;
|
use garage_util::crdt::*;
|
||||||
use garage_util::data::Uuid;
|
use garage_util::data::*;
|
||||||
use garage_util::error::*;
|
use garage_util::error::*;
|
||||||
use garage_util::formater::format_table;
|
use garage_util::formater::format_table;
|
||||||
use garage_util::time::*;
|
use garage_util::time::*;
|
||||||
|
|
||||||
|
use garage_block::manager::BlockResyncErrorInfo;
|
||||||
|
|
||||||
use garage_model::bucket_table::*;
|
use garage_model::bucket_table::*;
|
||||||
use garage_model::key_table::*;
|
use garage_model::key_table::*;
|
||||||
use garage_model::s3::object_table::{BYTES, OBJECTS, UNFINISHED_UPLOADS};
|
use garage_model::s3::object_table::{BYTES, OBJECTS, UNFINISHED_UPLOADS};
|
||||||
|
use garage_model::s3::version_table::Version;
|
||||||
|
|
||||||
use crate::cli::structs::WorkerListOpt;
|
use crate::cli::structs::WorkerListOpt;
|
||||||
|
|
||||||
@@ -241,7 +244,7 @@ pub fn find_matching_node(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_worker_info(wi: HashMap<usize, WorkerInfo>, wlo: WorkerListOpt) {
|
pub fn print_worker_list(wi: HashMap<usize, WorkerInfo>, wlo: WorkerListOpt) {
|
||||||
let mut wi = wi.into_iter().collect::<Vec<_>>();
|
let mut wi = wi.into_iter().collect::<Vec<_>>();
|
||||||
wi.sort_by_key(|(tid, info)| {
|
wi.sort_by_key(|(tid, info)| {
|
||||||
(
|
(
|
||||||
@@ -254,7 +257,7 @@ pub fn print_worker_info(wi: HashMap<usize, WorkerInfo>, wlo: WorkerListOpt) {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut table = vec![];
|
let mut table = vec!["TID\tState\tName\tTranq\tDone\tQueue\tErrors\tConsec\tLast".to_string()];
|
||||||
for (tid, info) in wi.iter() {
|
for (tid, info) in wi.iter() {
|
||||||
if wlo.busy && !matches!(info.state, WorkerState::Busy | WorkerState::Throttled(_)) {
|
if wlo.busy && !matches!(info.state, WorkerState::Busy | WorkerState::Throttled(_)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -263,33 +266,147 @@ pub fn print_worker_info(wi: HashMap<usize, WorkerInfo>, wlo: WorkerListOpt) {
|
|||||||
continue;
|
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 tf = timeago::Formatter::new();
|
||||||
let (err_ago, err_msg) = info
|
let err_ago = info
|
||||||
.last_error
|
.last_error
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|(m, t)| {
|
.map(|(_, t)| tf.convert(Duration::from_millis(now_msec() - t)))
|
||||||
(
|
.unwrap_or_default();
|
||||||
tf.convert(Duration::from_millis(now_msec() - t)),
|
let (total_err, consec_err) = if info.errors > 0 {
|
||||||
m.as_str(),
|
(info.errors.to_string(), info.consecutive_errors.to_string())
|
||||||
)
|
} else {
|
||||||
})
|
("-".into(), "-".into())
|
||||||
.unwrap_or(("(?) ago".into(), "(?)"));
|
};
|
||||||
if info.consecutive_errors > 0 {
|
|
||||||
|
table.push(format!(
|
||||||
|
"{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}",
|
||||||
|
tid,
|
||||||
|
info.state,
|
||||||
|
info.name,
|
||||||
|
info.status
|
||||||
|
.tranquility
|
||||||
|
.as_ref()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.unwrap_or_else(|| "-".into()),
|
||||||
|
info.status.progress.as_deref().unwrap_or("-"),
|
||||||
|
info.status
|
||||||
|
.queue_length
|
||||||
|
.as_ref()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.unwrap_or_else(|| "-".into()),
|
||||||
|
total_err,
|
||||||
|
consec_err,
|
||||||
|
err_ago,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
format_table(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_worker_info(tid: usize, info: WorkerInfo) {
|
||||||
|
let mut table = vec![];
|
||||||
|
table.push(format!("Task id:\t{}", tid));
|
||||||
|
table.push(format!("Worker name:\t{}", info.name));
|
||||||
|
match info.state {
|
||||||
|
WorkerState::Throttled(t) => {
|
||||||
table.push(format!(
|
table.push(format!(
|
||||||
"\t\t {} consecutive errors ({} total), last {}",
|
"Worker state:\tBusy (throttled, paused for {:.3}s)",
|
||||||
info.consecutive_errors, info.errors, err_ago,
|
t
|
||||||
));
|
));
|
||||||
table.push(format!("\t\t {}", err_msg));
|
}
|
||||||
} else if info.errors > 0 {
|
s => {
|
||||||
table.push(format!("\t\t ({} errors, last {})", info.errors, err_ago,));
|
table.push(format!("Worker state:\t{}", s));
|
||||||
if wlo.errors {
|
}
|
||||||
table.push(format!("\t\t {}", err_msg));
|
};
|
||||||
|
if let Some(tql) = info.status.tranquility {
|
||||||
|
table.push(format!("Tranquility:\t{}", tql));
|
||||||
|
}
|
||||||
|
|
||||||
|
table.push("".into());
|
||||||
|
table.push(format!("Total errors:\t{}", info.errors));
|
||||||
|
table.push(format!("Consecutive errs:\t{}", info.consecutive_errors));
|
||||||
|
if let Some((s, t)) = info.last_error {
|
||||||
|
table.push(format!("Last error:\t{}", s));
|
||||||
|
let tf = timeago::Formatter::new();
|
||||||
|
table.push(format!(
|
||||||
|
"Last error time:\t{}",
|
||||||
|
tf.convert(Duration::from_millis(now_msec() - t))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
table.push("".into());
|
||||||
|
if let Some(p) = info.status.progress {
|
||||||
|
table.push(format!("Progress:\t{}", p));
|
||||||
|
}
|
||||||
|
if let Some(ql) = info.status.queue_length {
|
||||||
|
table.push(format!("Queue length:\t{}", ql));
|
||||||
|
}
|
||||||
|
if let Some(pe) = info.status.persistent_errors {
|
||||||
|
table.push(format!("Persistent errors:\t{}", pe));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, s) in info.status.freeform.iter().enumerate() {
|
||||||
|
if i == 0 {
|
||||||
|
if table.last() != Some(&"".into()) {
|
||||||
|
table.push("".into());
|
||||||
}
|
}
|
||||||
|
table.push(format!("Message:\t{}", s));
|
||||||
|
} else {
|
||||||
|
table.push(format!("\t{}", s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
format_table(table);
|
format_table(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn print_block_error_list(el: Vec<BlockResyncErrorInfo>) {
|
||||||
|
let now = now_msec();
|
||||||
|
let tf = timeago::Formatter::new();
|
||||||
|
let mut tf2 = timeago::Formatter::new();
|
||||||
|
tf2.ago("");
|
||||||
|
|
||||||
|
let mut table = vec!["Hash\tRC\tErrors\tLast error\tNext try".into()];
|
||||||
|
for e in el {
|
||||||
|
table.push(format!(
|
||||||
|
"{}\t{}\t{}\t{}\tin {}",
|
||||||
|
hex::encode(e.hash.as_slice()),
|
||||||
|
e.refcount,
|
||||||
|
e.error_count,
|
||||||
|
tf.convert(Duration::from_millis(now - e.last_try)),
|
||||||
|
tf2.convert(Duration::from_millis(e.next_try - now))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
format_table(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_block_info(hash: Hash, refcount: u64, versions: Vec<Result<Version, Uuid>>) {
|
||||||
|
println!("Block hash: {}", hex::encode(hash.as_slice()));
|
||||||
|
println!("Refcount: {}", refcount);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
let mut table = vec!["Version\tBucket\tKey\tDeleted".into()];
|
||||||
|
let mut nondeleted_count = 0;
|
||||||
|
for v in versions.iter() {
|
||||||
|
match v {
|
||||||
|
Ok(ver) => {
|
||||||
|
table.push(format!(
|
||||||
|
"{:?}\t{:?}\t{}\t{:?}",
|
||||||
|
ver.uuid,
|
||||||
|
ver.bucket_id,
|
||||||
|
ver.key,
|
||||||
|
ver.deleted.get()
|
||||||
|
));
|
||||||
|
if !ver.deleted.get() {
|
||||||
|
nondeleted_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(vh) => {
|
||||||
|
table.push(format!("{:?}\t\t\tyes", vh));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format_table(table);
|
||||||
|
|
||||||
|
if refcount != nondeleted_count {
|
||||||
|
println!();
|
||||||
|
println!("Warning: refcount does not match number of non-deleted versions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,21 +65,6 @@ struct Opt {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
if std::env::var("RUST_LOG").is_err() {
|
|
||||||
std::env::set_var("RUST_LOG", "netapp=info,garage=info")
|
|
||||||
}
|
|
||||||
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");
|
|
||||||
|
|
||||||
// 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
|
// Initialize version and features info
|
||||||
let features = &[
|
let features = &[
|
||||||
#[cfg(feature = "k2v")]
|
#[cfg(feature = "k2v")]
|
||||||
@@ -108,14 +93,57 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
garage_util::version::init_features(features);
|
garage_util::version::init_features(features);
|
||||||
|
|
||||||
// Parse arguments
|
|
||||||
let version = format!(
|
let version = format!(
|
||||||
"{} [features: {}]",
|
"{} [features: {}]",
|
||||||
garage_util::version::garage_version(),
|
garage_util::version::garage_version(),
|
||||||
features.join(", ")
|
features.join(", ")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Initialize panic handler that aborts on panic and shows a nice message.
|
||||||
|
// By default, Tokio continues runing normally when a task panics. We want
|
||||||
|
// to avoid this behavior in Garage as this would risk putting the process in an
|
||||||
|
// unknown/uncontrollable state. We prefer to exit the process and restart it
|
||||||
|
// from scratch, so that it boots back into a fresh, known state.
|
||||||
|
let panic_version_info = version.clone();
|
||||||
|
std::panic::set_hook(Box::new(move |panic_info| {
|
||||||
|
eprintln!("======== PANIC (internal Garage error) ========");
|
||||||
|
eprintln!("{}", panic_info);
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("Panics are internal errors that Garage is unable to handle on its own.");
|
||||||
|
eprintln!("They can be caused by bugs in Garage's code, or by corrupted data in");
|
||||||
|
eprintln!("the node's storage. If you feel that this error is likely to be a bug");
|
||||||
|
eprintln!("in Garage, please report it on our issue tracker a the following address:");
|
||||||
|
eprintln!();
|
||||||
|
eprintln!(" https://git.deuxfleurs.fr/Deuxfleurs/garage/issues");
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("Please include the last log messages and the the full backtrace below in");
|
||||||
|
eprintln!("your bug report, as well as any relevant information on the context in");
|
||||||
|
eprintln!("which Garage was running when this error occurred.");
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("GARAGE VERSION: {}", panic_version_info);
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("BACKTRACE:");
|
||||||
|
eprintln!("{:?}", backtrace::Backtrace::new());
|
||||||
|
std::process::abort();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Parse arguments and dispatch command line
|
||||||
let opt = Opt::from_clap(&Opt::clap().version(version.as_str()).get_matches());
|
let opt = Opt::from_clap(&Opt::clap().version(version.as_str()).get_matches());
|
||||||
|
|
||||||
|
// Initialize logging as well as other libraries used in Garage
|
||||||
|
if std::env::var("RUST_LOG").is_err() {
|
||||||
|
let default_log = match &opt.cmd {
|
||||||
|
Command::Server => "netapp=info,garage=info",
|
||||||
|
_ => "netapp=warn,garage=warn",
|
||||||
|
};
|
||||||
|
std::env::set_var("RUST_LOG", default_log)
|
||||||
|
}
|
||||||
|
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 res = match opt.cmd {
|
let res = match opt.cmd {
|
||||||
Command::Server => server::run_server(opt.config_file).await,
|
Command::Server => server::run_server(opt.config_file).await,
|
||||||
Command::OfflineRepair(repair_opt) => {
|
Command::OfflineRepair(repair_opt) => {
|
||||||
@@ -158,9 +186,9 @@ async fn cli_command(opt: Opt) -> Result<(), Error> {
|
|||||||
let netapp = NetApp::new(GARAGE_VERSION_TAG, network_key, sk);
|
let netapp = NetApp::new(GARAGE_VERSION_TAG, network_key, sk);
|
||||||
|
|
||||||
// Find and parse the address of the target host
|
// Find and parse the address of the target host
|
||||||
let (id, addr) = if let Some(h) = opt.rpc_host {
|
let (id, addr, is_default_addr) = if let Some(h) = opt.rpc_host {
|
||||||
let (id, addrs) = parse_and_resolve_peer_addr(&h).ok_or_else(|| format!("Invalid RPC remote node identifier: {}. Expected format is <pubkey>@<IP or hostname>:<port>.", h))?;
|
let (id, addrs) = parse_and_resolve_peer_addr(&h).ok_or_else(|| format!("Invalid RPC remote node identifier: {}. Expected format is <pubkey>@<IP or hostname>:<port>.", h))?;
|
||||||
(id, addrs[0])
|
(id, addrs[0], false)
|
||||||
} else {
|
} else {
|
||||||
let node_id = garage_rpc::system::read_node_id(&config.as_ref().unwrap().metadata_dir)
|
let node_id = garage_rpc::system::read_node_id(&config.as_ref().unwrap().metadata_dir)
|
||||||
.err_context(READ_KEY_ERROR)?;
|
.err_context(READ_KEY_ERROR)?;
|
||||||
@@ -171,24 +199,26 @@ async fn cli_command(opt: Opt) -> Result<(), Error> {
|
|||||||
.ok_or_message("unable to resolve rpc_public_addr specified in config file")?
|
.ok_or_message("unable to resolve rpc_public_addr specified in config file")?
|
||||||
.next()
|
.next()
|
||||||
.ok_or_message("unable to resolve rpc_public_addr specified in config file")?;
|
.ok_or_message("unable to resolve rpc_public_addr specified in config file")?;
|
||||||
(node_id, a)
|
(node_id, a, false)
|
||||||
} else {
|
} else {
|
||||||
let default_addr = SocketAddr::new(
|
let default_addr = SocketAddr::new(
|
||||||
"127.0.0.1".parse().unwrap(),
|
"127.0.0.1".parse().unwrap(),
|
||||||
config.as_ref().unwrap().rpc_bind_addr.port(),
|
config.as_ref().unwrap().rpc_bind_addr.port(),
|
||||||
);
|
);
|
||||||
warn!(
|
(node_id, default_addr, true)
|
||||||
"Trying to contact Garage node at default address {}",
|
|
||||||
default_addr
|
|
||||||
);
|
|
||||||
warn!("If this doesn't work, consider adding rpc_public_addr in your config file or specifying the -h command line parameter.");
|
|
||||||
(node_id, default_addr)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connect to target host
|
// Connect to target host
|
||||||
netapp.clone().try_connect(addr, id).await
|
if let Err(e) = netapp.clone().try_connect(addr, id).await {
|
||||||
.err_context("Unable to connect to destination RPC host. Check that you are using the same value of rpc_secret as them, and that you have their correct public key.")?;
|
if is_default_addr {
|
||||||
|
warn!(
|
||||||
|
"Tried to contact Garage node at default address {}, which didn't work. If that address is wrong, consider setting rpc_public_addr in your config file.",
|
||||||
|
addr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e).err_context("Unable to connect to destination RPC host. Check that you are using the same value of rpc_secret as them, and that you have their correct public key.")?;
|
||||||
|
}
|
||||||
|
|
||||||
let system_rpc_endpoint = netapp.endpoint::<SystemRpc, ()>(SYSTEM_RPC_PATH.into());
|
let system_rpc_endpoint = netapp.endpoint::<SystemRpc, ()>(SYSTEM_RPC_PATH.into());
|
||||||
let admin_rpc_endpoint = netapp.endpoint::<AdminRpc, ()>(ADMIN_RPC_PATH.into());
|
let admin_rpc_endpoint = netapp.endpoint::<AdminRpc, ()>(ADMIN_RPC_PATH.into());
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use tokio::sync::watch;
|
|
||||||
|
|
||||||
use garage_util::background::*;
|
|
||||||
use garage_util::config::*;
|
use garage_util::config::*;
|
||||||
use garage_util::error::*;
|
use garage_util::error::*;
|
||||||
|
|
||||||
@@ -20,12 +17,8 @@ pub async fn offline_repair(config_file: PathBuf, opt: OfflineRepairOpt) -> Resu
|
|||||||
info!("Loading configuration...");
|
info!("Loading configuration...");
|
||||||
let config = read_config(config_file)?;
|
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...");
|
info!("Initializing Garage main data store...");
|
||||||
let garage = Garage::new(config.clone(), background)?;
|
let garage = Garage::new(config)?;
|
||||||
|
|
||||||
info!("Launching repair operation...");
|
info!("Launching repair operation...");
|
||||||
match opt.what {
|
match opt.what {
|
||||||
@@ -43,13 +36,7 @@ pub async fn offline_repair(config_file: PathBuf, opt: OfflineRepairOpt) -> Resu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Repair operation finished, shutting down Garage internals...");
|
info!("Repair operation finished, shutting down...");
|
||||||
done_tx.send(true).unwrap();
|
|
||||||
drop(garage);
|
|
||||||
|
|
||||||
await_background_done.await?;
|
|
||||||
|
|
||||||
info!("Cleaning up...");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||