Compare commits

..

28 Commits

Author SHA1 Message Date
trinity-1686a d0fa38a769 wip formal algorithm 2026-01-26 10:53:07 +01:00
trinity-1686a 82ae78757f [RFC] Garbage Collector Elimination 2026-01-26 10:53:07 +01:00
Alex 0412013229 Merge pull request 'Change optimization level to 3' (#1235) from majudev/garage:main-v2 into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1235
2025-11-25 09:57:56 +00:00
majudev 090dbb412a Change optimization level to 3 2025-11-24 18:12:01 +01:00
Alex 12367d307b Merge pull request 'Add additionalTopLevelConfig to helm chart' (#1184) from florissmit10/garage:feature/add-aditional-config-helm into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1184
Reviewed-by: trinity-1686a <trinity-1686a@noreply.localhost>
2025-11-24 09:56:04 +00:00
Alex 675c1c156d Merge pull request 'helm: add existingRpcSecret option' (#1225) from rkevin/garage:krong-helm-add-existingrpcsecret into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1225
2025-11-24 09:55:37 +00:00
Alex eac3a60050 Merge pull request 'migrate to this error, garage-v2' (#1219) from thiserror-2 into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1219
Reviewed-by: Alex <lx@deuxfleurs.fr>
2025-11-12 08:05:43 +00:00
Alex a5580c99fe Merge pull request 'don't append index document to redirect urls' (#1223) from redirect-no-index into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1223
2025-11-11 17:27:39 +00:00
Alex 8083bb4a0f Merge pull request 'upgrade crc-fast' (#1217) from update-crc-fast into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1217
2025-11-11 17:25:31 +00:00
Alex 00cdefa6b3 Merge pull request 'JOSY talk' (#1191) from talk/josy into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1191
Reviewed-by: trinity-1686a <trinity-1686a@noreply.localhost>
2025-11-11 17:25:01 +00:00
rkevin 4dee3e6e04 helm: add existingRpcSecret option 2025-11-10 20:30:41 -08:00
trinity-1686a 5333285b50 don't append index document to redirect urls 2025-11-06 18:03:58 +01:00
trinity-1686a ef913843f7 fmt 2025-11-01 19:35:11 +01:00
trinity-1686a 1fe932d07f migrate to this error
it doesn't generate a bazillion warning at compile time
2025-11-01 17:25:23 +01:00
trinity-1686a 7e5bb51287 upgrade crc-fast 2025-11-01 15:43:08 +00:00
Quentin 00a5c3d8a2 Merge branch 'main-v2' into talk/josy 2025-10-28 16:02:29 +00:00
maximilien 2e7a6fccba Merge pull request 'Use OnRootMismatch for fsGroupChangePolicy to avoid prolonged Pod upstart' (#1190) from larssb/garage:on-root-mismatch-pod-upstart-time into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1190
2025-10-07 22:22:55 +00:00
Quentin Dufour 09041035d5 slides 2025-10-07 10:28:27 +02:00
Quentin Dufour ae64ecf10c tmp presentation 2025-10-06 22:06:22 +02:00
larssb c8ac4a2105 Use OnRootMismatch for fsGroupChangePolicy to avoid prolonged Pod upstart
When not used. Especially with a lot of small files on the PVC's Garage uses/is configured with,
Pod upstart time can get quite extreme. For more context see a very good description
of the issues and the technical intricacies behind it here: https://github.com/longhorn/longhorn/issues/2131#issuecomment-778897129

I think it's sane to have fsGroupChangePolicy at "OnRootMismatch" as Garage is an S3 system and thereby
is likely to process a lot of files. And somewhat likely that many of these will be small files as well.

---

Using `fsGroupChangePolicy: "OnRootMismatch"` in my env. changed the Garage Pod upstart. E.g. in an
upgrade case from 5-7minutes ( around 50GB of data ). Many small files though. To 1-15sec.

Thank you.

Signed-off-by: larssb <larssb@noreply.localhost>
2025-10-06 19:45:44 +00:00
Quentin Dufour 280c1303fa finale structure 2025-10-06 15:48:46 +02:00
Quentin Dufour 0f5b3878ca WIP new garage presentation 2025-10-06 12:39:43 +02:00
Floris Smit d863247f9f Add additionalTopLevelConfig to helm chart 2025-10-01 11:34:46 +02:00
Alex 6c740ff05c Merge pull request 'Documentation fixes' (#1169) from fix-doc into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1169
2025-09-15 18:14:34 +00:00
Alex Auvolat 95d9905524 updated instructions about metadata engines and snapshots 2025-09-15 20:12:00 +02:00
Alex Auvolat 53fe77860b update fjall support version info 2025-09-15 19:51:41 +02:00
Alex Auvolat 6d5e971974 fix typo in documentation link 2025-09-15 19:51:41 +02:00
Alex 4d8407dc0f Merge pull request 'garage v2.1.0' (#1168) from rel-v2.1.0 into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1168
2025-09-15 16:53:35 +00:00
45 changed files with 1180 additions and 269 deletions
Generated
+21 -54
View File
@@ -852,16 +852,15 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc-fast"
version = "1.2.0"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf0ab672ed7761a44410115d2e0e5ccd86b987ba186a78a4148fb02bb302a774"
checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3"
dependencies = [
"cc",
"crc",
"digest",
"libc",
"rand 0.9.1",
"regex",
"rustversion",
]
[[package]]
@@ -1113,20 +1112,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "err-derive"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"rustversion",
"syn 1.0.109",
"synstructure 0.12.6",
]
[[package]]
name = "errno"
version = "0.3.10"
@@ -1356,7 +1341,6 @@ dependencies = [
"async-trait",
"bytesize",
"chrono",
"err-derive",
"format_table",
"futures",
"garage_api_common",
@@ -1374,6 +1358,7 @@ dependencies = [
"prometheus",
"serde",
"serde_json",
"thiserror 2.0.12",
"tokio",
"tracing",
"url",
@@ -1389,7 +1374,6 @@ dependencies = [
"chrono",
"crc-fast",
"crypto-common",
"err-derive",
"futures",
"garage_model",
"garage_table",
@@ -1408,6 +1392,7 @@ dependencies = [
"serde_json",
"sha1",
"sha2",
"thiserror 2.0.12",
"tokio",
"tracing",
"url",
@@ -1418,7 +1403,6 @@ name = "garage_api_k2v"
version = "2.1.0"
dependencies = [
"base64 0.21.7",
"err-derive",
"futures",
"garage_api_common",
"garage_model",
@@ -1431,6 +1415,7 @@ dependencies = [
"percent-encoding",
"serde",
"serde_json",
"thiserror 2.0.12",
"tokio",
"tracing",
"url",
@@ -1446,7 +1431,6 @@ dependencies = [
"bytes",
"chrono",
"crc-fast",
"err-derive",
"form_urlencoded",
"futures",
"garage_api_common",
@@ -1474,6 +1458,7 @@ dependencies = [
"serde_json",
"sha1",
"sha2",
"thiserror 2.0.12",
"tokio",
"tokio-stream",
"tokio-util 0.7.14",
@@ -1509,7 +1494,6 @@ dependencies = [
name = "garage_db"
version = "2.1.0"
dependencies = [
"err-derive",
"fjall",
"heed",
"mktemp",
@@ -1517,6 +1501,7 @@ dependencies = [
"r2d2",
"r2d2_sqlite",
"rusqlite",
"thiserror 2.0.12",
"tracing",
]
@@ -1529,7 +1514,6 @@ dependencies = [
"base64 0.21.7",
"blake2",
"chrono",
"err-derive",
"futures",
"garage_block",
"garage_db",
@@ -1543,6 +1527,7 @@ dependencies = [
"rand 0.8.5",
"serde",
"serde_bytes",
"thiserror 2.0.12",
"tokio",
"tracing",
"zstd",
@@ -1555,7 +1540,6 @@ dependencies = [
"arc-swap",
"bytes",
"cfg-if",
"err-derive",
"futures",
"hex",
"kuska-handshake",
@@ -1568,6 +1552,7 @@ dependencies = [
"rand 0.8.5",
"rmp-serde",
"serde",
"thiserror 2.0.12",
"tokio",
"tokio-stream",
"tokio-util 0.7.14",
@@ -1580,7 +1565,6 @@ dependencies = [
"arc-swap",
"async-trait",
"bytesize",
"err-derive",
"format_table",
"futures",
"garage_net",
@@ -1601,6 +1585,7 @@ dependencies = [
"serde",
"serde_bytes",
"serde_json",
"thiserror 2.0.12",
"tokio",
"tracing",
]
@@ -1635,7 +1620,6 @@ dependencies = [
"blake2",
"bytesize",
"chrono",
"err-derive",
"futures",
"garage_db",
"garage_net",
@@ -1652,6 +1636,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"thiserror 2.0.12",
"tokio",
"toml",
"tracing",
@@ -1662,7 +1647,6 @@ dependencies = [
name = "garage_web"
version = "2.1.0"
dependencies = [
"err-derive",
"garage_api_common",
"garage_api_s3",
"garage_model",
@@ -1673,6 +1657,7 @@ dependencies = [
"hyper 1.6.0",
"opentelemetry",
"percent-encoding",
"thiserror 2.0.12",
"tokio",
"tracing",
]
@@ -2465,7 +2450,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"thiserror 1.0.69",
"thiserror 2.0.12",
"tokio",
"tracing-subscriber",
]
@@ -3586,13 +3571,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.1"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.9",
"regex-automata 0.4.13",
"regex-syntax 0.8.5",
]
@@ -3607,9 +3592,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.9"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
@@ -4282,18 +4267,6 @@ dependencies = [
"crossbeam-queue",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-xid",
]
[[package]]
name = "synstructure"
version = "0.13.1"
@@ -4813,12 +4786,6 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "universal-hash"
version = "0.5.1"
@@ -5344,7 +5311,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"synstructure 0.13.1",
"synstructure",
]
[[package]]
@@ -5405,7 +5372,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
"synstructure 0.13.1",
"synstructure",
]
[[package]]
+3 -4
View File
@@ -49,9 +49,8 @@ bytes = "1.0"
bytesize = "1.1"
cfg-if = "1.0"
chrono = { version = "0.4", features = ["serde"] }
crc-fast = "1.1"
crc-fast = "1.6"
crypto-common = "0.1"
err-derive = "0.3"
gethostname = "0.4"
git-version = "0.3.4"
hex = "0.4"
@@ -138,7 +137,7 @@ prometheus = "0.13"
aws-sigv4 = { version = "1.1", default-features = false }
hyper-rustls = { version = "0.26", default-features = false, features = ["http1", "http2", "ring", "rustls-native-certs"] }
log = "0.4"
thiserror = "1.0"
thiserror = "2.0"
# ---- used only as build / dev dependencies ----
assert-json-diff = "2.0"
@@ -155,5 +154,5 @@ lto = "off"
[profile.release]
lto = true
codegen-units = 1
opt-level = "s"
opt-level = 3
strip = true
+4 -1
View File
@@ -161,4 +161,7 @@ your recovery options are as follows:
- **Option 3: restoring a filesystem-level snapshot.** If you are using ZFS or
BTRFS to snapshot your metadata partition, refer to their specific
documentation on rolling back or copying files from an old snapshot.
documentation on rolling back or copying files from an old snapshot.
Note that, depending on the properties of the filesystem and of the DB engine,
if these snapshots were taken during a write operation to the database, they may
also be corrupted and thus unfit for recovery.
+29 -24
View File
@@ -98,7 +98,7 @@ The following gives details about each available configuration option.
Top-level configuration options, in alphabetical order:
[`allow_punycode`](#allow_punycode),
[`allow_world_readable_secrets`](#allow_world_readable_secrets),
[`block_max_concurrent_reads`](`block_max_concurrent_reads),
[`block_max_concurrent_reads`](#block_max_concurrent_reads),
[`block_ram_buffer_max`](#block_ram_buffer_max),
[`block_size`](#block_size),
[`bootstrap_peers`](#bootstrap_peers),
@@ -337,7 +337,7 @@ Since `v0.8.0`, Garage can use alternative storage backends as follows:
| --------- | ----------------- | ------------- |
| [LMDB](https://www.symas.com/lmdb) (since `v0.8.0`, default since `v0.9.0`) | `"lmdb"` | `<metadata_dir>/db.lmdb/` |
| [Sqlite](https://sqlite.org) (since `v0.8.0`) | `"sqlite"` | `<metadata_dir>/db.sqlite` |
| [Fjall](https://github.com/fjall-rs/fjall) (**experimental support** since `v1.3.0`) | `"fjall"` | `<metadata_dir>/db.fjall/` |
| [Fjall](https://github.com/fjall-rs/fjall) (**experimental support** since `v1.3.0`/`v2.1.0`) | `"fjall"` | `<metadata_dir>/db.fjall/` |
| [Sled](https://sled.rs) (old default, removed since `v1.0`) | `"sled"` | `<metadata_dir>/db/` |
Sled was supported until Garage v0.9.x, and was removed in Garage v1.0.
@@ -346,8 +346,16 @@ old Sled metadata databases to another engine.
Performance characteristics of the different DB engines are as follows:
- LMDB: the recommended database engine for high-performance distributed clusters.
LMDB works very well, but is known to have the following limitations:
- **LMDB:** the recommended database engine for high-performance distributed clusters
with `replication_factor` ≥ 2.
LMDB works well, but is known to have the following limitations:
- LMDB is prone to database corruption after an unclean shutdown (e.g. a process kill
or a power outage). It is recommended to configure
[`metadata_auto_snapshot_interval`](#metadata_auto_snapshot_interval) to be
able to easily recover from this situation. With `replication_factor` ≥ 2,
metadata can also be reconstructed from remote nodes upon corruption
(see [Recovering from failures](@/documentation/operations/recovering.md#corrupted_meta)).
- The data format of LMDB is not portable between architectures, so for
instance the Garage database of an x86-64 node cannot be moved to an ARM64
@@ -357,30 +365,21 @@ LMDB works very well, but is known to have the following limitations:
node to very small database sizes due to how LMDB works; it is therefore
not recommended.
- Several users have reported corrupted LMDB database files after an unclean
shutdown (e.g. a power outage). This situation can generally be recovered
from if your cluster is geo-replicated (by rebuilding your metadata db from
other nodes), or if you have saved regular snapshots at the filesystem
level.
- Keys in LMDB are limited to 511 bytes. This limit translates to limits on
object keys in S3 and sort keys in K2V that are limted to 479 bytes.
- Sqlite: Garage supports Sqlite as an alternative storage backend for
metadata, which does not have the issues listed above for LMDB.
On versions 0.8.x and earlier, Sqlite should be avoided due to abysmal
performance, which was fixed with the addition of `metadata_fsync`.
Sqlite is still probably slower than LMDB due to the way we use it,
so it is not the best choice for high-performance storage clusters,
but it should work fine in many cases.
- **Sqlite:** Garage supports Sqlite as an alternative storage backend for
metadata, which does not have the issues listed above for LMDB. Sqlite is
slower than LMDB, so it is not the best choice for high-performance storage
clusters.
- Fjall: a storage engine based on LSM trees, which theoretically allow for
- **Fjall:** a storage engine based on LSM trees, which theoretically allow for
higher write throughput than other storage engines that are based on B-trees.
Using Fjall could potentially improve Garage's performance significantly in
write-heavy workloads. **Support for Fjall is experimental at this point**,
we have added it to Garage for evaluation purposes only. **Do not use it for
production-critical workloads.**
we have added it to Garage for evaluation purposes only. **Use it only with
test data, and report any issues to our bug tracker. Do not use it for
production workloads.**
It is possible to convert Garage's metadata directory from one format to another
using the `garage convert-db` command, which should be used as follows:
@@ -439,7 +438,8 @@ if geographical replication is used.
#### `metadata_auto_snapshot_interval` (since `v0.9.4`) {#metadata_auto_snapshot_interval}
If this value is set, Garage will automatically take a snapshot of the metadata
DB file at a regular interval and save it in the metadata directory.
DB file at a regular interval and save it in the metadata directory,
or in [`metadata_snapshots_dir`](#metadata_snapshots_dir) if it is set.
This parameter can take any duration string that can be parsed by
the [`parse_duration`](https://docs.rs/parse_duration/latest/parse_duration/#syntax) crate.
@@ -448,14 +448,19 @@ corrupted, for instance after an unclean shutdown. See [this
page](@/documentation/operations/recovering.md#corrupted_meta) for details.
Garage keeps only the two most recent snapshots of the metadata DB and deletes
older ones automatically.
You can also create metadata snapshots manually at any point using the
`garage meta snapshot` command.
Using snapshots created by Garage is the best option to make snapshots of your
node's metadata for potential recovery, as they are guaranteed to be clean and
consistent, contrarily to filesystem-level snapshots that may be taken while
some writes are in-flight and thus might be corrupted.
Note that taking a metadata snapshot is a relatively intensive operation as the
entire data file is copied. A snapshot being taken might have performance
impacts on the Garage node while it is running. If the cluster is under heavy
write load when a snapshot operation is running, this might also cause the
database file to grow in size significantly as pages cannot be recycled easily.
For this reason, it might be better to use filesystem-level snapshots instead
if possible.
#### `disable_scrub` {#disable_scrub}
+142
View File
@@ -0,0 +1,142 @@
# [RFC] Garbage Collector Elimination
## Statement of problem and prior art
Currently, Garage's garbage collector has a few identified issues.
Namely, it can only be run if all nodes in a partition are currently online,
it may not be correct in front of a rebalancing (this is partially mitigated by a 24h delay added to tombstone deletion),
and it isn't resilient to a subset of nodes being restored from snapshots.
It's not clear if it is possible to implement a garbage collection process that can eliminate tombstones, but also support
a node rollback to a point in time where a key existed.
This problematic, perhaps unsurprisingly, maps very well to the general abstraction of CRDTs for sets, where a whole partition
would be a single CRDT. The semantic required in Garage demands reinsertion of deleted keys, which excludes the most simple
forms of sets such as G-Sets and 2P-Sets. As the goal is to not handle garbage collection of tombstones, a standard ORSet
is also unfitting. In fact it could be argued Garage already uses something akin to an ORSet with a garbage collector today.
There exists CRDTs supporting this feature-set, one of which is an OptORSet[^1].
It only needs a set-wide metadata proportional in the number of writers, and a per alive-key metadata proportional in the number
of writers to that key (but no metadata for dead keys).
These metadata are akin to DVVs[^2]. An element is considered new if its DVV comes causaly after the current DVV of the set.
Correctness of this algorithm depends however on having causal delivery, which isn't given in Garage, neither in general nor in
presence of snapshot restoration.
## Proposal
We devise a new kind of CRDT based on the core ideas of an OptORSet, but replacing each version inside its DVVs with a list of
range of observed updates, which we name a Seen Vector (SV). Such metadata can in the worst case grow linearly with the number of
insertion. In practice, assuming all elements are eventually known to every replica, the storage requirement of a SV is equivalent
to that of a standard DVV. Under causal delivery, a SV degenerates into a standard DVV.
To add an element to the set, a node increments its own version counter, and sends an update containing (element, replica, version).
On receiving such an update, a node checks if it has already seen this particular (replica, version), and if so ignore it.
If it hasn't seen that update, it saves the new element, and update its SV to include that new (replica, version).
TODO: describe formaly the algorithm
Algorithm 1, Seen Vector:
```
payload set S -- S: set of triple (replica i, timestamp s, timestamp e)
initial ∅
query seen (replica i, timestamp c): boolean b
let b = (∃s <= c,e > c: (i,s,e) ∈ S)
update increment ()
prepare ()
let r = myID()
let t = e|∀s,s', ∄e' > e: (r,s,e) ∈ S, (r,s',e') ∈ S -- t is the maximum end bound for this replica
effect(r, t)
if ¬seen (r, t) then
if seen (r, t - 1) ∧ seen (r, t + 1) then
let R = {∃s: (r, s, t) ∈ S}
let R' = {∃e: (r, t + 1, e) ∈ S}
let M = S \ R
let M' = M \ R'
S := M' {(r, s, e)}
else if seen (r, t - 1)then
let R = {∃s: (r, s, t) ∈ S}
let M = S \ R
S := M {(r, s, t+1)}
else if seen (r, t + 1)then
let R = {∃e: (r, t + 1, e) ∈ S}
let M = S \ R
E := E {(r, t, e)}
else
E := E {(r, t, t+1)}
merge (B)
# TODO this is correct, but largelly suboptimal. We should perform the increment.effect subroutine for all elements of B instead
S := S B.S
```
Algorithm 2, OptORSet with SV. This algorithm is largely copied and adapted from Figure 3 of [^1]
```
payload set E, SV sv -- E: elements, set of triples (element e, timestamp c, replica i)
-- sv: SeenVector of received triples
initial ∅, ∅
query contains (element e) : boolean b
let b = (∃c, i : (e, c, i) ∈ E)
query elements () : set S
let S = {e|∃c, i : (e, c, i) ∈ E}
update add (element e)
prepare (e)
let r = myID() -- r = source replica
let c = sv.increment.prepare().t
effect (e, c, r)
if ¬sv.seen(r, c) then
let O = {(e, c, r) ∈ E|c < c}
sv.increment.effect(r, c)
E := E {(e, c, r)} \ O
update remove (element e)
prepare (e) -- Collect all unique triples containing e
let R = {(e, c, i) ∈ E}
effect (R) -- Remove triples observed at source
pre causal delivery
E := E \ R
merge (B)
let M = (E ∩ B.E)
let M = {(e, c, i) ∈ E \ B.E| ¬B.sv.seen(i, c)}
let M ′′ = {(e, c, i) ∈ B.E \ E| ¬sv.seen(i, c)}
let U = M M M ′′
let O = {(e, c, i) ∈ U |∃(e, c, i) ∈ U : c < c}
E := U \ O
sv := sv.merge(B.sv)
```
## Storage evaluation
As stated, if all updates are received, the SV is similar in size to that of a standard DVV. It may be however that a node create and
immediately delete an element, creating holes in its sequence from the point of view of other replicas. These holes could be filled
through an interactive process where the replica observing holes asks the node to scan over the whole set, and for each version in these
hole, reply if no element has that exact version number. Holes caused by existing elements should be eventually fixed by an anti-entropy
process, so replying with these elements appears unnecessary.
The per-element storage requirement is proportional to the number of replicas having modified that element, even under non steady-state.
This happens because as we always exchange whole elements, we have causal delivery for individual keys.
## Replica version rollback
This scheme assumes the same node won't issue the same version twice, which isn't a given when a node might be rollback to a previous state.
The author proposes that on initialization, a replica asks all other replicas for the highest version number they know for it.
If all replicas reply with a number less than or equal to the current version, it is safe to reuse the currently known number.
If some replicas reply with a number higher, the node increase its version to that number.
If at least one replica doesn't reply, it can't make any assumption about its actual version number.
The node then increment a generation number, which is made part of its replica id, starts a new sequence from zero.
## Appendix: providing SV to the underlying elements
Some more complexe elements may want to have access to a version id and the Seen Vector to perform their own internal merge operations.
The author reckon this may help implementing S3 versioning, by giving a simple way for Objects to know if an ObjectVersion was yet
unknown or is known and already deleted.
## References
[^1]: An optimized conflict-free replicated set, https://doi.org/10.48550/arXiv.1210.3368
[^2]: Dotted Version Vectors: Logical Clocks for Optimistic Replication, https://doi.org/10.48550/arXiv.1011.5808
+17
View File
@@ -0,0 +1,17 @@
*
!*.txt
!*.md
!assets
!.gitignore
!*.svg
!*.png
!*.jpg
!*.tex
!Makefile
!.gitignore
!assets/*.drawio.pdf
!talk.pdf
+19
View File
@@ -0,0 +1,19 @@
ASSETS=../assets/lattice/lattice1.pdf_tex \
../assets/lattice/lattice2.pdf_tex \
../assets/lattice/lattice3.pdf_tex \
../assets/lattice/lattice4.pdf_tex \
../assets/lattice/lattice5.pdf_tex \
../assets/lattice/lattice6.pdf_tex \
../assets/lattice/lattice7.pdf_tex \
../assets/lattice/lattice8.pdf_tex \
../assets/logos/deuxfleurs.pdf \
../assets/timeline-22-24.pdf
talk.pdf: talk.tex $(ASSETS)
pdflatex talk.tex
%.pdf: %.svg
inkscape -D -z --file=$^ --export-pdf=$@
%.pdf_tex: %.svg
inkscape -D -z --file=$^ --export-pdf=$@ --export-latex
Binary file not shown.
+702
View File
@@ -0,0 +1,702 @@
\nonstopmode
\documentclass[aspectratio=169,xcolor={svgnames}]{beamer}
\usepackage[utf8]{inputenc}
% \usepackage[frenchb]{babel}
\usepackage{amsmath}
\usepackage{mathtools}
\usepackage{breqn}
\usepackage{multirow}
\usetheme{boxes}
\usepackage{graphicx}
\usepackage{import}
\usepackage{adjustbox}
\usepackage[absolute,overlay]{textpos}
%\useoutertheme[footline=authortitle,subsection=false]{miniframes}
%\useoutertheme[footline=authorinstitute,subsection=false]{miniframes}
\useoutertheme{infolines}
\setbeamertemplate{headline}{}
\beamertemplatenavigationsymbolsempty
\definecolor{TitleOrange}{RGB}{255,137,0}
\setbeamercolor{title}{fg=TitleOrange}
\setbeamercolor{frametitle}{fg=TitleOrange}
\definecolor{ListOrange}{RGB}{255,145,5}
\setbeamertemplate{itemize item}{\color{ListOrange}$\blacktriangleright$}
\definecolor{verygrey}{RGB}{70,70,70}
\setbeamercolor{normal text}{fg=verygrey}
\usepackage{tabu}
\usepackage{multicol}
\usepackage{vwcol}
\usepackage{stmaryrd}
\usepackage{graphicx}
\usepackage[normalem]{ulem}
\AtBeginSection[]{
\begin{frame}
\vfill
\centering
\begin{beamercolorbox}[sep=8pt,center,shadow=true,rounded=true]{title}
\usebeamerfont{title}\insertsectionhead\par%
\end{beamercolorbox}
\vfill
\end{frame}
}
\title{Garage, an S3 backend as reliable as possible}
\author{Garage Authors}
\date{JoSy S3, 2025-10-08}
\begin{document}
\begin{frame}
\centering
\includegraphics[width=.3\linewidth]{../../sticker/Garage.png}
\vspace{1em}
{\large\bf Garage, an S3 backend as reliable as possible}
\vspace{1em}
\url{https://garagehq.deuxfleurs.fr/}\\
\url{mailto:garagehq@deuxfleurs.fr}\\
\texttt{\#garage:deuxfleurs.fr} on Matrix
\end{frame}
\section{Meet Garage}
\begin{frame}
\frametitle{A non-profit initiative}
\begin{columns}[t]
\begin{column}{.2\textwidth}
\centering
\adjincludegraphics[width=.5\linewidth, valign=t]{../assets/logos/deuxfleurs.pdf}
\end{column}
\begin{column}{.8\textwidth}
\textbf{Part of a degrowth initiative}\\
Garage has been created at Deuxfleurs where we experiment running Internet services without datacenter on commodity and refurbished hardware.
\end{column}
\end{columns}
\vspace{2em}
\begin{columns}[t]
\begin{column}{.2\textwidth}
\centering
\adjincludegraphics[width=.5\linewidth, valign=t]{../assets/community.png}
\end{column}
\begin{column}{.8\textwidth}
\textbf{Developed by a community}\\
{\small Some recent contributors: Arthur C, Charles H, dongdigua, Etienne L, Jonah A, Julien K, Lapineige, MagicRR, Milas B, Niklas M, RockWolf, Schwitzd, trinity-1686a, Xavier S, babykart, Baptiste J, eddster2309, James O'C, Joker9944, Maximilien R, Renjaya RZ, Yureka...}
\end{column}
\end{columns}
\vspace{2em}
\begin{columns}[t]
\begin{column}{.2\textwidth}
\centering
\adjincludegraphics[width=.5\linewidth, valign=t]{../assets/logos/AGPLv3_Logo.png}
\end{column}
\begin{column}{.8\textwidth}
\textbf{Owned by nobody, open-core is impossible, zero VC money}\\
AGPL + no Contributor License Agreement = Garage ownership spreads among hundredth of contributors.
\end{column}
\end{columns}
\end{frame}
\begin{frame}
\frametitle{Getting support for Garage}
\begin{columns}[t]
\begin{column}{.2\textwidth}
\centering
\adjincludegraphics[width=.4\linewidth, valign=t]{../assets/alex.jpg}
\end{column}
\begin{column}{.4\textwidth}
\textbf{Alex Auvolat}\\
PhD; co-founder of Deuxfleurs\\
Garage maintainer, Freelance
\end{column}
\begin{column}{.3\textwidth}
\centering
\adjincludegraphics[width=.4\linewidth, valign=t]{../assets/support.png}
\end{column}
\begin{column}{.1\textwidth}
~
\end{column}
\end{columns}
\vspace{2em}
\begin{columns}[t]
\begin{column}{.2\textwidth}
\centering
\adjincludegraphics[width=.4\linewidth, valign=t]{../assets/quentin.jpg}
\end{column}
\begin{column}{.4\textwidth}
\textbf{Quentin Dufour}\\
PhD; co-founder of Deuxfleurs\\
Garage contributor, Freelance
\end{column}
\begin{column}{.4\textwidth}
For support requests, write at: \\
\url{garagehq@deuxfleurs.fr}
\end{column}
\end{columns}
\vspace{2em}
\begin{columns}[t]
\begin{column}{.2\textwidth}
\centering
\adjincludegraphics[width=.4\linewidth, valign=t]{../assets/armael.jpg}
\end{column}
\begin{column}{.4\textwidth}
\textbf{Armaël Guéneau}\\
PhD; member of Deuxfleurs\\
Garage contributor, Freelance
\end{column}
\begin{column}{.4\textwidth}
Eligible: email support, architecture design, specific feature development, etc.
\end{column}
\end{columns}
\end{frame}
\begin{frame}
\frametitle{Our initial goal}
\centering
\Large
Being a self-sovereign community to be free of our degrowth choice
$\big\downarrow$
As web citizens, datacenters are big black boxes. \\
We want to leave them to autonoumously manage our servers.
$\big\downarrow$
We want reliable services without relying on dedicated hardware or places.
\end{frame}
\begin{frame}
\frametitle{Building a resilient system with cheap stuff}
\only<1,4-7>{
\begin{itemize}
\item \textcolor<5->{gray}{Commodity hardware (e.g. old desktop PCs)\\
\vspace{.5em}
\visible<4->{{\footnotesize (can die at any time)}}}
\vspace{1.5em}
\item<5-> \textcolor<7->{gray}{Regular Internet (e.g. FTTB, FTTH) and power grid connections\\
\vspace{.5em}
\visible<6->{{\footnotesize (can be unavailable randomly)}}}
\vspace{1.5em}
\item<7-> \textbf{Geographical redundancy} (multi-site replication)
\end{itemize}
}
\only<2>{
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/neptune.jpg}
\end{center}
}
\only<3>{
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/atuin.jpg}
\end{center}
}
\only<8>{
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/inframap_jdll2023.pdf}
\end{center}
}
\end{frame}
\begin{frame}
\frametitle{Object storage: a crucial component}
\begin{center}
\includegraphics[height=6em]{../assets/logos/Amazon-S3.jpg}
\hspace{3em}
\visible<2->{\includegraphics[height=5em]{../assets/logos/minio.png}}
\hspace{3em}
\visible<3>{\includegraphics[height=6em]{../../logo/garage_hires_crop.png}}
\end{center}
\vspace{1em}
S3: a de-facto standard, many compatible applications
\vspace{1em}
\visible<2->{MinIO is self-hostable but not suited for geo-distributed deployments}
\vspace{1em}
\visible<3->{\textbf{Garage is a self-hosted drop-in replacement for the Amazon S3 object store}}
\end{frame}
\begin{frame}
\frametitle{CRDTs / weak consistency instead of consensus}
\underline{Internally, Garage uses only CRDTs} (conflict-free replicated data types)
\vspace{2em}
Why not Raft, Paxos, ...? Issues of consensus algorithms:
\vspace{1em}
\begin{itemize}
\item<2-> \textbf{Software complexity}
\vspace{1em}
\item<3-> \textbf{Performance issues:}
\vspace{.5em}
\begin{itemize}
\item<4-> The leader is a \textbf{bottleneck} for all requests\\
\vspace{.5em}
\item<5-> \textbf{Sensitive to higher latency} between nodes
\vspace{.5em}
\item<6-> \textbf{Takes time to reconverge} when disrupted (e.g. node going down)
\end{itemize}
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{The data model of object storage}
Object storage is basically a \textbf{key-value store}:
\vspace{.5em}
{\scriptsize
\begin{center}
\begin{tabular}{|l|p{7cm}|}
\hline
\textbf{Key: file path + name} & \textbf{Value: file data + metadata} \\
\hline
\hline
\texttt{index.html} &
\texttt{Content-Type: text/html; charset=utf-8} \newline
\texttt{Content-Length: 24929} \newline
\texttt{<binary blob>} \\
\hline
\texttt{img/logo.svg} &
\texttt{Content-Type: text/svg+xml} \newline
\texttt{Content-Length: 13429} \newline
\texttt{<binary blob>} \\
\hline
\texttt{download/index.html} &
\texttt{Content-Type: text/html; charset=utf-8} \newline
\texttt{Content-Length: 26563} \newline
\texttt{<binary blob>} \\
\hline
\end{tabular}
\end{center}
}
\vspace{1em}
\begin{itemize}
\item<2> Maps well to CRDT data types
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Performance gains in practice}
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/perf/endpoint_latency_0.7_0.8_minio.png}
\end{center}
\end{frame}
% ======================================== OPERATING
% ======================================== OPERATING
% ======================================== OPERATING
\section{Production clusters}
\begin{frame}
\frametitle{Deployment kinds}
\includegraphics[width=.9\linewidth]{../assets/cluster_kind.png}
\vspace{1em}
\end{frame}
\begin{frame}
\frametitle{How big they are?}
\includegraphics[width=.9\linewidth]{../assets/cluster_size.png}
\vspace{1em}
\textit{"Petabyte storage setup for a video site. Nginx as CDN in-front using garage-s3-website feature. Each storage node has ~64TB storage with raid10, no replication within garage. 25gbit nic. haproxy to loadbalance across 5 nodes. mostly reads with very few writes."}
\vspace{1em}
\textit{"We currently manage 7 Garage nodes, 28TB total storage, 6M blocks for 3M objects and 4TB of object data. We have been running Garage in production for 2.5 years."}
\end{frame}
\begin{frame}
\frametitle{Operating Garage}
\begin{center}
\only<1-2>{
\includegraphics[width=.9\linewidth]{../assets/screenshots/garage_status_0.10.png}
\\\vspace{1em}
\visible<2>{\includegraphics[width=.9\linewidth]{../assets/screenshots/garage_status_unhealthy_0.10.png}}
}
\end{center}
\end{frame}
\begin{frame}
\frametitle{Garage's architecture}
\begin{center}
\only<1>{\includegraphics[width=.45\linewidth]{../assets/garage.drawio.pdf}}%
\only<2>{\includegraphics[width=.6\linewidth]{../assets/garage_sync.drawio.pdf}}%
\end{center}
\end{frame}
\begin{frame}
\frametitle{Digging deeper}
\begin{center}
\only<1>{\includegraphics[width=.9\linewidth]{../assets/screenshots/garage_stats_0.10.png}}
\only<2>{\includegraphics[width=.5\linewidth]{../assets/screenshots/garage_worker_list_0.10.png}}
\only<3>{\includegraphics[width=.6\linewidth]{../assets/screenshots/garage_worker_param_0.10.png}}
\end{center}
\end{frame}
\begin{frame}
\frametitle{Potential limitations and bottlenecks}
\begin{itemize}
\item Global:
\begin{itemize}
\item Max. $\sim$100 nodes per cluster (excluding gateways)
\end{itemize}
\vspace{1em}
\item Metadata:
\begin{itemize}
\item One big bucket = bottleneck, object list on 3 nodes only
\end{itemize}
\vspace{1em}
\item Block manager:
\begin{itemize}
\item Lots of small files on disk
\item Processing the resync queue can be slow
\end{itemize}
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Deployment advice for very large clusters}
\begin{itemize}
\item Metadata storage:
\begin{itemize}
\item ZFS mirror (x2) on fast NVMe
\item Use LMDB storage engine
\end{itemize}
\vspace{.5em}
\item Data block storage:
\begin{itemize}
\item Use Garage's native multi-HDD support
\item XFS on individual drives
\item Increase block size (1MB $\to$ 10MB, requires more RAM and good networking)
\item Tune \texttt{resync-tranquility} and \texttt{resync-worker-count} dynamically
\end{itemize}
\vspace{.5em}
\item Other :
\begin{itemize}
\item Split data over several buckets
\item Use less than 100 storage nodes
\item Use gateway nodes
\end{itemize}
\vspace{.5em}
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Focus on Deuxfleurs}
Host institutional websites, partnership with a web agency.
Matrix media backend.
Plan to use it as an email backend for an internally developed email server.
\end{frame}
% ======================================== TIMELINE
% ======================================== TIMELINE
% ======================================== TIMELINE
\section{Recent developments}
% ====================== v0.7.0 ===============================
\begin{frame}
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/tl.drawio.png}
\end{center}
\end{frame}
\begin{frame}
\frametitle{April 2022 - Garage v0.7.0}
Focus on \underline{observability and ecosystem integration}
\vspace{2em}
\begin{itemize}
\item \textbf{Monitoring:} metrics and traces, using OpenTelemetry
\vspace{1em}
\item Replication modes with 1 or 2 copies / weaker consistency
\vspace{1em}
\item Kubernetes integration for node discovery
\vspace{1em}
\item Admin API (v0.7.2)
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Metrics (Prometheus + Grafana)}
\begin{center}
\includegraphics[width=.9\linewidth]{../assets/screenshots/grafana_dashboard.png}
\end{center}
\end{frame}
\begin{frame}
\frametitle{Traces (Jaeger)}
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/screenshots/jaeger_listobjects.png}
\end{center}
\end{frame}
% ====================== v0.8.0 ===============================
\begin{frame}
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/tl.drawio.png}
\end{center}
\end{frame}
\begin{frame}
\frametitle{November 2022 - Garage v0.8.0}
Focus on \underline{performance}
\vspace{2em}
\begin{itemize}
\item \textbf{Alternative metadata DB engines} (LMDB, Sqlite)
\vspace{1em}
\item \textbf{Performance improvements:} block streaming, various optimizations...
\vspace{1em}
\item Bucket quotas (max size, max \#objects)
\vspace{1em}
\item Quality of life improvements, observability, etc.
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{About metadata DB engines}
\textbf{Issues with Sled:}
\vspace{1em}
\begin{itemize}
\item Huge files on disk
\vspace{.5em}
\item Unpredictable performance, especially on HDD
\vspace{.5em}
\item API limitations
\vspace{.5em}
\item Not actively maintained
\end{itemize}
\vspace{2em}
\textbf{LMDB:} very stable, good performance, file size is reasonable\\
\textbf{Sqlite} also available as a second choice
\vspace{1em}
Sled will be removed in Garage v1.0
\end{frame}
\begin{frame}
\frametitle{DB engine performance comparison}
\begin{center}
\includegraphics[width=.6\linewidth]{../assets/perf/db_engine.png}
\end{center}
NB: Sqlite was slow due to synchronous mode, now configurable
\end{frame}
\begin{frame}
\frametitle{Block streaming}
\begin{center}
\only<1>{\includegraphics[width=.8\linewidth]{../assets/schema-streaming-1.png}}
\only<2>{\includegraphics[width=.8\linewidth]{../assets/schema-streaming-2.png}}
\end{center}
\end{frame}
\begin{frame}
\frametitle{TTFB benchmark}
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/perf/ttfb.png}
\end{center}
\end{frame}
\begin{frame}
\frametitle{Throughput benchmark}
\begin{center}
\includegraphics[width=.7\linewidth]{../assets/perf/io-0.7-0.8-minio.png}
\end{center}
\end{frame}
% ====================== v0.9.0 ===============================
\begin{frame}
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/tl.drawio.png}
\end{center}
\end{frame}
\begin{frame}
\frametitle{October 2023 - Garage v0.9.0}
Focus on \underline{streamlining \& usability}
\vspace{2em}
\begin{itemize}
\item Support multiple HDDs per node
\vspace{1em}
\item S3 compatibility:
\vspace{1em}
\begin{itemize}
\item support basic lifecycle configurations
\vspace{.5em}
\item allow for multipart upload part retries
\end{itemize}
\vspace{1em}
\item LMDB by default, deprecation of Sled
\vspace{1em}
\item New layout computation algorithm
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Layout computation}
\begin{overprint}
\onslide<1>
\begin{center}
\includegraphics[width=\linewidth, trim=0 0 0 -4cm]{../assets/screenshots/garage_status_0.9_prod_zonehl.png}
\end{center}
\onslide<2>
\begin{center}
\includegraphics[width=.7\linewidth]{../assets/map.png}
\end{center}
\end{overprint}
\vspace{1em}
Garage stores replicas on different zones when possible
\end{frame}
\begin{frame}
\frametitle{What a "layout" is}
\textbf{A layout is a precomputed index table:}
\vspace{1em}
{\footnotesize
\begin{center}
\begin{tabular}{|l|l|l|l|}
\hline
\textbf{Partition} & \textbf{Node 1} & \textbf{Node 2} & \textbf{Node 3} \\
\hline
\hline
Partition 0 & df-ymk (bespin) & Abricot (scorpio) & Courgette (neptune) \\
\hline
Partition 1 & Ananas (scorpio) & Courgette (neptune) & df-ykl (bespin) \\
\hline
Partition 2 & df-ymf (bespin) & Celeri (neptune) & Abricot (scorpio) \\
\hline
\hspace{1em}$\vdots$ & \hspace{1em}$\vdots$ & \hspace{1em}$\vdots$ & \hspace{1em}$\vdots$ \\
\hline
Partition 255 & Concombre (neptune) & df-ykl (bespin) & Abricot (scorpio) \\
\hline
\end{tabular}
\end{center}
}
\vspace{2em}
\visible<2->{
The index table is built centrally using an optimal algorithm,\\
then propagated to all nodes
}
\vspace{1em}
\visible<3->{
\footnotesize
Oulamara, M., \& Auvolat, A. (2023). \emph{An algorithm for geo-distributed and redundant storage in Garage}.\\ arXiv preprint arXiv:2302.13798.
}
\end{frame}
% ====================== v1.0.0 ===============================
\begin{frame}
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/tl.drawio.png}
\end{center}
\end{frame}
\begin{frame}
\frametitle{April 2024 - Garage v1.0.0}
Focus on \underline{consistency, security \& stability}
\vspace{2em}
\begin{itemize}
\item Fix consistency issues when reshuffling data (Jepsen testing)
\vspace{1em}
\item \textbf{Security audit} by Radically Open Security
\vspace{1em}
\item Misc. S3 features (SSE-C, checksums, ...) and compatibility fixes
\end{itemize}
\end{frame}
% ====================== v2.0.0 ===============================
\begin{frame}
\begin{center}
\includegraphics[width=.8\linewidth]{../assets/tl.drawio.png}
\end{center}
\end{frame}
\begin{frame}
\frametitle{Garage v2.0.0}
Focus on \underline{}
\vspace{2em}
\begin{itemize}
\item TODO
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Currently funding...}
\textit{...}
\end{frame}
\begin{frame}
\frametitle{We run community surveys}
\begin{center}
\includegraphics[width=.6\linewidth]{../assets/survey_requested_features.png}
\end{center}
\end{frame}
% ======================================== END
% ======================================== END
% ======================================== END
\begin{frame}
\frametitle{Where to find us}
\begin{center}
\includegraphics[width=.25\linewidth]{../../logo/garage_hires.png}\\
\vspace{-1em}
\url{https://garagehq.deuxfleurs.fr/}\\
\url{mailto:garagehq@deuxfleurs.fr}\\
\texttt{\#garage:deuxfleurs.fr} on Matrix
\vspace{1.5em}
\includegraphics[width=.06\linewidth]{../assets/logos/rust_logo.png}
\includegraphics[width=.13\linewidth]{../assets/logos/AGPLv3_Logo.png}
\end{center}
\end{frame}
\end{document}
%% vim: set ts=4 sw=4 tw=0 noet spelllang=en :
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

+1 -1
View File
@@ -27,7 +27,7 @@ If release name contains chart name it will be used as a full name.
Create the name of the rpc secret
*/}}
{{- define "garage.rpcSecretName" -}}
{{- printf "%s-rpc-secret" (include "garage.fullname" .) -}}
{{- .Values.garage.existingRpcSecret | default (printf "%s-rpc-secret" (include "garage.fullname" .)) -}}
{{- end }}
{{/*
@@ -30,6 +30,10 @@ data:
bootstrap_peers = {{ .Values.garage.bootstrapPeers }}
{{- if .Values.garage.additionalTopLevelConfig }}
{{ .Values.garage.additionalTopLevelConfig | nindent 4 }}
{{- end }}
[kubernetes_discovery]
namespace = "{{ .Release.Namespace }}"
service_name = "{{ include "garage.fullname" . }}"
+2
View File
@@ -1,3 +1,4 @@
{{- if not .Values.garage.existingRpcSecret }}
apiVersion: v1
kind: Secret
metadata:
@@ -12,3 +13,4 @@ data:
{{- $prevRpcSecret := $prevSecretData.rpcSecret | default "" | b64dec }}
{{/* Priority is: 1. from values, 2. previous value, 3. generate random */}}
rpcSecret: {{ .Values.garage.rpcSecret | default $prevRpcSecret | default (include "jupyterhub.randHex" 64) | b64enc | quote }}
{{- end }}
+11
View File
@@ -37,6 +37,10 @@ garage:
rpcBindAddr: "[::]:3901"
# -- If not given, a random secret will be generated and stored in a Secret object
rpcSecret: ""
# -- If you want to provide an rpcSecret within an existing k8s secret,
# specify the secret name here, and store the value under the secret key `rpcSecret`
# the default secret will not be created
existingRpcSecret: ""
# -- This is not required if you use the integrated kubernetes discovery
bootstrapPeers: []
# -- Set to true if you want to use k8s discovery but install the CRDs manually outside
@@ -50,6 +54,12 @@ garage:
rootDomain: ".web.garage.tld"
index: "index.html"
# -- Additional configuration to append to garage.toml. Use a multi-line string for custom config.
# Example:
# additionalTopLevelConfig: |-
# data_fsync = true
additionalTopLevelConfig: ""
# -- if not empty string, allow using an existing ConfigMap for the garage.toml,
# if set, ignores garage.toml
existingConfigMap: ""
@@ -117,6 +127,7 @@ podSecurityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: "OnRootMismatch"
runAsNonRoot: true
securityContext:
+1 -1
View File
@@ -26,7 +26,7 @@ argon2.workspace = true
async-trait.workspace = true
bytesize.workspace = true
chrono.workspace = true
err-derive.workspace = true
thiserror.workspace = true
hex.workspace = true
paste.workspace = true
tracing.workspace = true
+9 -12
View File
@@ -1,8 +1,8 @@
use std::convert::TryFrom;
use err_derive::Error;
use hyper::header::HeaderValue;
use hyper::{HeaderMap, StatusCode};
use thiserror::Error;
pub use garage_model::helper::error::Error as HelperError;
@@ -16,36 +16,33 @@ use garage_api_common::helpers::*;
/// Errors of this crate
#[derive(Debug, Error)]
pub enum Error {
#[error(display = "{}", _0)]
#[error("{0}")]
/// Error from common error
Common(#[error(source)] CommonError),
Common(#[from] CommonError),
// Category: cannot process
/// The admin API token does not exist
#[error(display = "Admin token not found: {}", _0)]
#[error("Admin token not found: {0}")]
NoSuchAdminToken(String),
/// The API access key does not exist
#[error(display = "Access key not found: {}", _0)]
#[error("Access key not found: {00}")]
NoSuchAccessKey(String),
/// The requested block does not exist
#[error(display = "Block not found: {}", _0)]
#[error("Block not found: {0}")]
NoSuchBlock(String),
/// The requested worker does not exist
#[error(display = "Worker not found: {}", _0)]
#[error("Worker not found: {0}")]
NoSuchWorker(u64),
/// The object requested don't exists
#[error(display = "Key not found")]
#[error("Key not found")]
NoSuchKey,
/// In Import key, the key already exists
#[error(
display = "Key {} already exists in data store. Even if it is deleted, we can't let you create a new key with the same ID. Sorry.",
_0
)]
#[error("Key {0} already exists in data store. Even if it is deleted, we can't let you create a new key with the same ID. Sorry.")]
KeyAlreadyExists(String),
}
+1 -1
View File
@@ -23,7 +23,7 @@ bytes.workspace = true
chrono.workspace = true
crc-fast.workspace = true
crypto-common.workspace = true
err-derive.workspace = true
thiserror.workspace = true
hex.workspace = true
hmac.workspace = true
md-5.workspace = true
+15 -15
View File
@@ -1,7 +1,7 @@
use std::convert::TryFrom;
use err_derive::Error;
use hyper::StatusCode;
use thiserror::Error;
use garage_util::error::Error as GarageError;
@@ -12,48 +12,48 @@ use garage_model::helper::error::Error as HelperError;
pub enum CommonError {
// ---- INTERNAL ERRORS ----
/// Error related to deeper parts of Garage
#[error(display = "Internal error: {}", _0)]
InternalError(#[error(source)] GarageError),
#[error("Internal error: {0}")]
InternalError(#[from] GarageError),
/// Error related to Hyper
#[error(display = "Internal error (Hyper error): {}", _0)]
Hyper(#[error(source)] hyper::Error),
#[error("Internal error (Hyper error): {0}")]
Hyper(#[from] hyper::Error),
/// Error related to HTTP
#[error(display = "Internal error (HTTP error): {}", _0)]
Http(#[error(source)] http::Error),
#[error("Internal error (HTTP error): {0}")]
Http(#[from] http::Error),
// ---- GENERIC CLIENT ERRORS ----
/// Proper authentication was not provided
#[error(display = "Forbidden: {}", _0)]
#[error("Forbidden: {0}")]
Forbidden(String),
/// Generic bad request response with custom message
#[error(display = "Bad request: {}", _0)]
#[error("Bad request: {0}")]
BadRequest(String),
/// The client sent a header with invalid value
#[error(display = "Invalid header value: {}", _0)]
InvalidHeader(#[error(source)] hyper::header::ToStrError),
#[error("Invalid header value: {0}")]
InvalidHeader(#[from] hyper::header::ToStrError),
// ---- SPECIFIC ERROR CONDITIONS ----
// These have to be error codes referenced in the S3 spec here:
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList
/// The bucket requested don't exists
#[error(display = "Bucket not found: {}", _0)]
#[error("Bucket not found: {0}")]
NoSuchBucket(String),
/// Tried to create a bucket that already exist
#[error(display = "Bucket already exists")]
#[error("Bucket already exists")]
BucketAlreadyExists,
/// Tried to delete a non-empty bucket
#[error(display = "Tried to delete a non-empty bucket")]
#[error("Tried to delete a non-empty bucket")]
BucketNotEmpty,
// Category: bad request
/// Bucket name is not valid according to AWS S3 specs
#[error(display = "Invalid bucket name: {}", _0)]
#[error("Invalid bucket name: {0}")]
InvalidBucketName(String),
}
+6 -6
View File
@@ -1,4 +1,4 @@
use err_derive::Error;
use thiserror::Error;
use crate::common_error::CommonError;
pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInternalError};
@@ -6,21 +6,21 @@ pub use crate::common_error::{CommonErrorDerivative, OkOrBadRequest, OkOrInterna
/// Errors of this crate
#[derive(Debug, Error)]
pub enum Error {
#[error(display = "{}", _0)]
#[error("{0}")]
/// Error from common error
Common(CommonError),
/// Authorization Header Malformed
#[error(display = "Authorization header malformed, unexpected scope: {}", _0)]
#[error("Authorization header malformed, unexpected scope: {0}")]
AuthorizationHeaderMalformed(String),
// Category: bad request
/// The request contained an invalid UTF-8 sequence in its path or in other parameters
#[error(display = "Invalid UTF-8: {}", _0)]
InvalidUtf8Str(#[error(source)] std::str::Utf8Error),
#[error("Invalid UTF-8: {0}")]
InvalidUtf8Str(#[from] std::str::Utf8Error),
/// The provided digest (checksum) value was invalid
#[error(display = "Invalid digest: {}", _0)]
#[error("Invalid digest: {0}")]
InvalidDigest(String),
}
+1 -1
View File
@@ -20,7 +20,7 @@ garage_util = { workspace = true, features = [ "k2v" ] }
garage_api_common.workspace = true
base64.workspace = true
err-derive.workspace = true
thiserror.workspace = true
tracing.workspace = true
futures.workspace = true
+12 -12
View File
@@ -1,6 +1,6 @@
use err_derive::Error;
use hyper::header::HeaderValue;
use hyper::{HeaderMap, StatusCode};
use thiserror::Error;
pub(crate) use garage_api_common::common_error::pass_helper_error;
use garage_api_common::common_error::{commonErrorDerivative, CommonError};
@@ -14,38 +14,38 @@ use garage_api_common::signature::error::Error as SignatureError;
/// Errors of this crate
#[derive(Debug, Error)]
pub enum Error {
#[error(display = "{}", _0)]
#[error("{0}")]
/// Error from common error
Common(#[error(source)] CommonError),
Common(#[from] CommonError),
// Category: cannot process
/// Authorization Header Malformed
#[error(display = "Authorization header malformed, unexpected scope: {}", _0)]
#[error("Authorization header malformed, unexpected scope: {0}")]
AuthorizationHeaderMalformed(String),
/// The provided digest (checksum) value was invalid
#[error(display = "Invalid digest: {}", _0)]
#[error("Invalid digest: {0}")]
InvalidDigest(String),
/// The object requested don't exists
#[error(display = "Key not found")]
#[error("Key not found")]
NoSuchKey,
/// Some base64 encoded data was badly encoded
#[error(display = "Invalid base64: {}", _0)]
InvalidBase64(#[error(source)] base64::DecodeError),
#[error("Invalid base64: {0}")]
InvalidBase64(#[from] base64::DecodeError),
/// Invalid causality token
#[error(display = "Invalid causality token")]
#[error("Invalid causality token")]
InvalidCausalityToken,
/// The client asked for an invalid return format (invalid Accept header)
#[error(display = "Not acceptable: {}", _0)]
#[error("Not acceptable: {0}")]
NotAcceptable(String),
/// The request contained an invalid UTF-8 sequence in its path or in other parameters
#[error(display = "Invalid UTF-8: {}", _0)]
InvalidUtf8Str(#[error(source)] std::str::Utf8Error),
#[error("Invalid UTF-8: {0}")]
InvalidUtf8Str(#[from] std::str::Utf8Error),
}
commonErrorDerivative!(Error);
+1 -1
View File
@@ -28,7 +28,7 @@ base64.workspace = true
bytes.workspace = true
chrono.workspace = true
crc-fast.workspace = true
err-derive.workspace = true
thiserror.workspace = true
hex.workspace = true
hmac.workspace = true
tracing.workspace = true
+26 -20
View File
@@ -1,8 +1,8 @@
use std::convert::TryInto;
use err_derive::Error;
use hyper::header::HeaderValue;
use hyper::{HeaderMap, StatusCode};
use thiserror::Error;
use garage_model::helper::error::Error as HelperError;
@@ -25,67 +25,67 @@ use crate::xml as s3_xml;
/// Errors of this crate
#[derive(Debug, Error)]
pub enum Error {
#[error(display = "{}", _0)]
#[error("{0}")]
/// Error from common error
Common(#[error(source)] CommonError),
Common(#[from] CommonError),
// Category: cannot process
/// Authorization Header Malformed
#[error(display = "Authorization header malformed, unexpected scope: {}", _0)]
#[error("Authorization header malformed, unexpected scope: {0}")]
AuthorizationHeaderMalformed(String),
/// The object requested don't exists
#[error(display = "Key not found")]
#[error("Key not found")]
NoSuchKey,
/// The multipart upload requested don't exists
#[error(display = "Upload not found")]
#[error("Upload not found")]
NoSuchUpload,
/// Precondition failed (e.g. x-amz-copy-source-if-match)
#[error(display = "At least one of the preconditions you specified did not hold")]
#[error("At least one of the preconditions you specified did not hold")]
PreconditionFailed,
/// Parts specified in CMU request do not match parts actually uploaded
#[error(display = "Parts given to CompleteMultipartUpload do not match uploaded parts")]
#[error("Parts given to CompleteMultipartUpload do not match uploaded parts")]
InvalidPart,
/// Parts given to CompleteMultipartUpload were not in ascending order
#[error(display = "Parts given to CompleteMultipartUpload were not in ascending order")]
#[error("Parts given to CompleteMultipartUpload were not in ascending order")]
InvalidPartOrder,
/// In CompleteMultipartUpload: not enough data
/// (here we are more lenient than AWS S3)
#[error(display = "Proposed upload is smaller than the minimum allowed object size")]
#[error("Proposed upload is smaller than the minimum allowed object size")]
EntityTooSmall,
// Category: bad request
/// The request contained an invalid UTF-8 sequence in its path or in other parameters
#[error(display = "Invalid UTF-8: {}", _0)]
InvalidUtf8Str(#[error(source)] std::str::Utf8Error),
#[error("Invalid UTF-8: {0}")]
InvalidUtf8Str(#[from] std::str::Utf8Error),
/// The request used an invalid path
#[error(display = "Invalid UTF-8: {}", _0)]
InvalidUtf8String(#[error(source)] std::string::FromUtf8Error),
#[error("Invalid UTF-8: {0}")]
InvalidUtf8String(#[from] std::string::FromUtf8Error),
/// The client sent invalid XML data
#[error(display = "Invalid XML: {}", _0)]
#[error("Invalid XML: {0}")]
InvalidXml(String),
/// The client sent a range header with invalid value
#[error(display = "Invalid HTTP range: {:?}", _0)]
InvalidRange(#[error(from)] (http_range::HttpRangeParseError, u64)),
#[error("Invalid HTTP range: {0:?}")]
InvalidRange((http_range::HttpRangeParseError, u64)),
/// The client sent a range header with invalid value
#[error(display = "Invalid encryption algorithm: {:?}, should be AES256", _0)]
#[error("Invalid encryption algorithm: {0:?}, should be AES256")]
InvalidEncryptionAlgorithm(String),
/// The provided digest (checksum) value was invalid
#[error(display = "Invalid digest: {}", _0)]
#[error("Invalid digest: {0}")]
InvalidDigest(String),
/// The client sent a request for an action not supported by garage
#[error(display = "Unimplemented action: {}", _0)]
#[error("Unimplemented action: {0}")]
NotImplemented(String),
}
@@ -99,6 +99,12 @@ impl From<HelperError> for Error {
}
}
impl From<(http_range::HttpRangeParseError, u64)> for Error {
fn from(err: (http_range::HttpRangeParseError, u64)) -> Error {
Error::InvalidRange(err)
}
}
impl From<roxmltree::Error> for Error {
fn from(err: roxmltree::Error) -> Self {
Self::InvalidXml(format!("{}", err))
+1 -1
View File
@@ -12,7 +12,7 @@ readme = "../../README.md"
path = "lib.rs"
[dependencies]
err-derive.workspace = true
thiserror.workspace = true
tracing.workspace = true
heed = { workspace = true, optional = true }
+3 -3
View File
@@ -20,7 +20,7 @@ use std::cell::Cell;
use std::path::PathBuf;
use std::sync::Arc;
use err_derive::Error;
use thiserror::Error;
pub use open::*;
@@ -44,7 +44,7 @@ pub type TxValueIter<'a> = Box<dyn std::iter::Iterator<Item = TxOpResult<(Value,
// ----
#[derive(Debug, Error)]
#[error(display = "{}", _0)]
#[error("{0}")]
pub struct Error(pub Cow<'static, str>);
impl From<std::io::Error> for Error {
@@ -56,7 +56,7 @@ impl From<std::io::Error> for Error {
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
#[error(display = "{}", _0)]
#[error("{0}")]
pub struct TxOpError(pub(crate) Error);
pub type TxOpResult<T> = std::result::Result<T, TxOpError>;
+61 -17
View File
@@ -573,24 +573,68 @@ async fn test_website_redirect_full_bucket() {
.await
.unwrap();
let req = Request::builder()
.method("GET")
.uri(format!("http://127.0.0.1:{}/my-path", ctx.garage.web_port))
.header("Host", format!("{}.web.garage", BCKT_NAME))
.body(Body::new(Bytes::new()))
.unwrap();
{
let req = Request::builder()
.method("GET")
.uri(format!("http://127.0.0.1:{}/my-path", ctx.garage.web_port))
.header("Host", format!("{}.web.garage", BCKT_NAME))
.body(Body::new(Bytes::new()))
.unwrap();
let client = Client::builder(TokioExecutor::new()).build_http();
let resp = client.request(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(
resp.headers()
.get(hyper::header::LOCATION)
.unwrap()
.to_str()
.unwrap(),
"https://other.tld/my-path"
);
let client = Client::builder(TokioExecutor::new()).build_http();
let resp = client.request(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(
resp.headers()
.get(hyper::header::LOCATION)
.unwrap()
.to_str()
.unwrap(),
"https://other.tld/my-path"
);
}
{
let req = Request::builder()
.method("GET")
.uri(format!("http://127.0.0.1:{}/my-path/", ctx.garage.web_port))
.header("Host", format!("{}.web.garage", BCKT_NAME))
.body(Body::new(Bytes::new()))
.unwrap();
let client = Client::builder(TokioExecutor::new()).build_http();
let resp = client.request(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(
resp.headers()
.get(hyper::header::LOCATION)
.unwrap()
.to_str()
.unwrap(),
"https://other.tld/my-path/"
);
}
{
let req = Request::builder()
.method("GET")
.uri(format!("http://127.0.0.1:{}/", ctx.garage.web_port))
.header("Host", format!("{}.web.garage", BCKT_NAME))
.body(Body::new(Bytes::new()))
.unwrap();
let client = Client::builder(TokioExecutor::new()).build_http();
let resp = client.request(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::FOUND);
assert_eq!(
resp.headers()
.get(hyper::header::LOCATION)
.unwrap()
.to_str()
.unwrap(),
"https://other.tld/"
);
}
}
#[tokio::test]
+1 -1
View File
@@ -25,7 +25,7 @@ argon2.workspace = true
async-trait.workspace = true
blake2.workspace = true
chrono.workspace = true
err-derive.workspace = true
thiserror.workspace = true
hex.workspace = true
http.workspace = true
base64.workspace = true
+3 -3
View File
@@ -321,15 +321,15 @@ impl Garage {
Ok(())
}
pub fn bucket_helper(&self) -> helper::bucket::BucketHelper {
pub fn bucket_helper(&self) -> helper::bucket::BucketHelper<'_> {
helper::bucket::BucketHelper(self)
}
pub fn key_helper(&self) -> helper::key::KeyHelper {
pub fn key_helper(&self) -> helper::key::KeyHelper<'_> {
helper::key::KeyHelper(self)
}
pub async fn locked_helper(&self) -> helper::locked::LockedHelper {
pub async fn locked_helper(&self) -> helper::locked::LockedHelper<'_> {
let lock = self.bucket_lock.lock().await;
helper::locked::LockedHelper(self, Some(lock))
}
+7 -7
View File
@@ -1,24 +1,24 @@
use err_derive::Error;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use garage_util::error::Error as GarageError;
#[derive(Debug, Error, Serialize, Deserialize)]
pub enum Error {
#[error(display = "Internal error: {}", _0)]
Internal(#[error(source)] GarageError),
#[error("Internal error: {0}")]
Internal(#[from] GarageError),
#[error(display = "Bad request: {}", _0)]
#[error("Bad request: {0}")]
BadRequest(String),
/// Bucket name is not valid according to AWS S3 specs
#[error(display = "Invalid bucket name: {}", _0)]
#[error("Invalid bucket name: {0}")]
InvalidBucketName(String),
#[error(display = "Access key not found: {}", _0)]
#[error("Access key not found: {0}")]
NoSuchAccessKey(String),
#[error(display = "Bucket not found: {}", _0)]
#[error("Bucket not found: {0}")]
NoSuchBucket(String),
}
+1 -1
View File
@@ -30,7 +30,7 @@ rand.workspace = true
log.workspace = true
arc-swap.workspace = true
err-derive.workspace = true
thiserror.workspace = true
bytes.workspace = true
cfg-if.workspace = true
+2 -2
View File
@@ -159,7 +159,7 @@ where
pub(crate) type DynEndpoint = Box<dyn GenericEndpoint + Send + Sync>;
pub(crate) trait GenericEndpoint {
fn handle(&self, req_enc: ReqEnc, from: NodeID) -> BoxFuture<Result<RespEnc, Error>>;
fn handle(&self, req_enc: ReqEnc, from: NodeID) -> BoxFuture<'_, Result<RespEnc, Error>>;
fn drop_handler(&self);
fn clone_endpoint(&self) -> DynEndpoint;
}
@@ -175,7 +175,7 @@ where
M: Message,
H: StreamingEndpointHandler<M> + 'static,
{
fn handle(&self, req_enc: ReqEnc, from: NodeID) -> BoxFuture<Result<RespEnc, Error>> {
fn handle(&self, req_enc: ReqEnc, from: NodeID) -> BoxFuture<'_, Result<RespEnc, Error>> {
async move {
match self.0.handler.load_full() {
None => Err(Error::NoHandler),
+22 -22
View File
@@ -1,49 +1,49 @@
use std::io;
use err_derive::Error;
use log::error;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(display = "IO error: {}", _0)]
Io(#[error(source)] io::Error),
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error(display = "Messagepack encode error: {}", _0)]
RMPEncode(#[error(source)] rmp_serde::encode::Error),
#[error(display = "Messagepack decode error: {}", _0)]
RMPDecode(#[error(source)] rmp_serde::decode::Error),
#[error("Messagepack encode error: {0}")]
RMPEncode(#[from] rmp_serde::encode::Error),
#[error("Messagepack decode error: {0}")]
RMPDecode(#[from] rmp_serde::decode::Error),
#[error(display = "Tokio join error: {}", _0)]
TokioJoin(#[error(source)] tokio::task::JoinError),
#[error("Tokio join error: {0}")]
TokioJoin(#[from] tokio::task::JoinError),
#[error(display = "oneshot receive error: {}", _0)]
OneshotRecv(#[error(source)] tokio::sync::oneshot::error::RecvError),
#[error("oneshot receive error: {0}")]
OneshotRecv(#[from] tokio::sync::oneshot::error::RecvError),
#[error(display = "Handshake error: {}", _0)]
Handshake(#[error(source)] kuska_handshake::async_std::Error),
#[error("Handshake error: {0}")]
Handshake(#[from] kuska_handshake::async_std::Error),
#[error(display = "UTF8 error: {}", _0)]
UTF8(#[error(source)] std::string::FromUtf8Error),
#[error("UTF8 error: {0}")]
UTF8(#[from] std::string::FromUtf8Error),
#[error(display = "Framing protocol error")]
#[error("Framing protocol error")]
Framing,
#[error(display = "Remote error ({:?}): {}", _0, _1)]
#[error("Remote error ({0:?}): {1}")]
Remote(io::ErrorKind, String),
#[error(display = "Request ID collision")]
#[error("Request ID collision")]
IdCollision,
#[error(display = "{}", _0)]
#[error("{0}")]
Message(String),
#[error(display = "No handler / shutting down")]
#[error("No handler / shutting down")]
NoHandler,
#[error(display = "Connection closed")]
#[error("Connection closed")]
ConnectionClosed,
#[error(display = "Version mismatch: {}", _0)]
#[error("Version mismatch: {0}")]
VersionMismatch(String),
}
+2 -2
View File
@@ -33,7 +33,7 @@ async-trait.workspace = true
serde.workspace = true
serde_bytes.workspace = true
serde_json.workspace = true
err-derive = { workspace = true, optional = true }
thiserror = { workspace = true, optional = true }
# newer version requires rust edition 2021
kube = { workspace = true, optional = true }
@@ -49,5 +49,5 @@ opentelemetry.workspace = true
[features]
kubernetes-discovery = [ "kube", "k8s-openapi", "schemars" ]
consul-discovery = [ "reqwest", "err-derive" ]
consul-discovery = [ "reqwest", "thiserror" ]
system-libs = [ "sodiumoxide/use-pkg-config" ]
+8 -8
View File
@@ -3,8 +3,8 @@ use std::fs::File;
use std::io::Read;
use std::net::{IpAddr, SocketAddr};
use err_derive::Error;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use garage_net::NodeID;
@@ -219,12 +219,12 @@ impl ConsulDiscovery {
/// Regroup all Consul discovery errors
#[derive(Debug, Error)]
pub enum ConsulError {
#[error(display = "IO error: {}", _0)]
Io(#[error(source)] std::io::Error),
#[error(display = "HTTP error: {}", _0)]
Reqwest(#[error(source)] reqwest::Error),
#[error(display = "Invalid Consul TLS configuration")]
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("HTTP error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("Invalid Consul TLS configuration")]
InvalidTLSConfig,
#[error(display = "Token error: {}", _0)]
Token(#[error(source)] reqwest::header::InvalidHeaderValue),
#[error("Token error: {0}")]
Token(#[from] reqwest::header::InvalidHeaderValue),
}
+1 -1
View File
@@ -21,7 +21,7 @@ arc-swap.workspace = true
async-trait.workspace = true
blake2.workspace = true
bytesize.workspace = true
err-derive.workspace = true
thiserror.workspace = true
hexdump.workspace = true
xxhash-rust.workspace = true
hex.workspace = true
+35 -42
View File
@@ -2,7 +2,7 @@
use std::fmt;
use std::io;
use err_derive::Error;
use thiserror::Error;
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
@@ -12,71 +12,64 @@ use crate::encode::debug_serialize;
/// Regroup all Garage errors
#[derive(Debug, Error)]
pub enum Error {
#[error(display = "IO error: {}", _0)]
Io(#[error(source)] io::Error),
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error(display = "Hyper error: {}", _0)]
Hyper(#[error(source)] hyper::Error),
#[error("Hyper error: {0}")]
Hyper(#[from] hyper::Error),
#[error(display = "HTTP error: {}", _0)]
Http(#[error(source)] http::Error),
#[error("HTTP error: {0}")]
Http(#[from] http::Error),
#[error(display = "Invalid HTTP header value: {}", _0)]
HttpHeader(#[error(source)] http::header::ToStrError),
#[error("Invalid HTTP header value: {0}")]
HttpHeader(#[from] http::header::ToStrError),
#[error(display = "Network error: {}", _0)]
Net(#[error(source)] garage_net::error::Error),
#[error("Network error: {0}")]
Net(#[from] garage_net::error::Error),
#[error(display = "DB error: {}", _0)]
Db(#[error(source)] garage_db::Error),
#[error("DB error: {0}")]
Db(#[from] garage_db::Error),
#[error(display = "Messagepack encode error: {}", _0)]
RmpEncode(#[error(source)] rmp_serde::encode::Error),
#[error(display = "Messagepack decode error: {}", _0)]
RmpDecode(#[error(source)] rmp_serde::decode::Error),
#[error(display = "JSON error: {}", _0)]
Json(#[error(source)] serde_json::error::Error),
#[error(display = "TOML decode error: {}", _0)]
TomlDecode(#[error(source)] toml::de::Error),
#[error("Messagepack encode error: {0}")]
RmpEncode(#[from] rmp_serde::encode::Error),
#[error("Messagepack decode error: {0}")]
RmpDecode(#[from] rmp_serde::decode::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::error::Error),
#[error("TOML decode error: {0}")]
TomlDecode(#[from] toml::de::Error),
#[error(display = "Tokio join error: {}", _0)]
TokioJoin(#[error(source)] tokio::task::JoinError),
#[error("Tokio join error: {0}")]
TokioJoin(#[from] tokio::task::JoinError),
#[error(display = "Tokio semaphore acquire error: {}", _0)]
TokioSemAcquire(#[error(source)] tokio::sync::AcquireError),
#[error("Tokio semaphore acquire error: {0}")]
TokioSemAcquire(#[from] tokio::sync::AcquireError),
#[error(display = "Tokio broadcast receive error: {}", _0)]
TokioBcastRecv(#[error(source)] tokio::sync::broadcast::error::RecvError),
#[error("Tokio broadcast receive error: {0}")]
TokioBcastRecv(#[from] tokio::sync::broadcast::error::RecvError),
#[error(display = "Remote error: {}", _0)]
#[error("Remote error: {0}")]
RemoteError(String),
#[error(display = "Timeout")]
#[error("Timeout")]
Timeout,
#[error(display = "Layout not ready")]
#[error("Layout not ready")]
LayoutNotReady,
#[error(
display = "Could not reach quorum of {} (sets={:?}). {} of {} request succeeded, others returned errors: {:?}",
_0,
_1,
_2,
_3,
_4
)]
#[error("Could not reach quorum of {0} (sets={1:?}). {2} of {3} request succeeded, others returned errors: {4:?}")]
Quorum(usize, Option<usize>, usize, usize, Vec<String>),
#[error(display = "Unexpected RPC message: {}", _0)]
#[error("Unexpected RPC message: {0}")]
UnexpectedRpcMessage(String),
#[error(display = "Corrupt data: does not match hash {:?}", _0)]
#[error("Corrupt data: does not match hash {0:?}")]
CorruptData(Hash),
#[error(display = "Missing block {:?}: no node returned a valid block", _0)]
#[error("Missing block {0:?}: no node returned a valid block")]
MissingBlock(Hash),
#[error(display = "{}", _0)]
#[error("{0}")]
Message(String),
}
+1 -1
View File
@@ -20,7 +20,7 @@ garage_model.workspace = true
garage_util.workspace = true
garage_table.workspace = true
err-derive.workspace = true
thiserror.workspace = true
tracing.workspace = true
percent-encoding.workspace = true
+4 -4
View File
@@ -1,6 +1,6 @@
use err_derive::Error;
use hyper::header::HeaderValue;
use hyper::{HeaderMap, StatusCode};
use thiserror::Error;
use garage_api_common::generic_server::ApiError;
@@ -8,15 +8,15 @@ use garage_api_common::generic_server::ApiError;
#[derive(Debug, Error)]
pub enum Error {
/// An error received from the API crate
#[error(display = "API error: {}", _0)]
#[error("API error: {0}")]
ApiError(garage_api_s3::error::Error),
/// The file does not exist
#[error(display = "Not found")]
#[error("Not found")]
NotFound,
/// The client sent a request without host, or with unsupported method
#[error(display = "Bad request: {}", _0)]
#[error("Bad request: {0}")]
BadRequest(String),
}
+1 -1
View File
@@ -579,7 +579,7 @@ fn path_to_keys(
};
if let Some(condition) = &routing_rule.condition {
let suffix = if let Some(prefix) = &condition.prefix {
let Some(suffix) = key.strip_prefix(prefix) else {
let Some(suffix) = base_key.strip_prefix(prefix) else {
continue;
};
Some(suffix)