diff options
-rw-r--r-- | Cargo.lock | 80 | ||||
-rw-r--r-- | Cargo.nix | 128 | ||||
-rw-r--r-- | doc/book/reference-manual/configuration.md | 97 | ||||
-rw-r--r-- | nix/compile.nix | 1 | ||||
-rw-r--r-- | src/garage/Cargo.toml | 2 | ||||
-rw-r--r-- | src/garage/main.rs | 2 | ||||
-rw-r--r-- | src/rpc/Cargo.toml | 6 | ||||
-rw-r--r-- | src/rpc/consul.rs | 234 | ||||
-rw-r--r-- | src/rpc/kubernetes.rs | 16 | ||||
-rw-r--r-- | src/rpc/lib.rs | 1 | ||||
-rw-r--r-- | src/rpc/system.rs | 73 | ||||
-rw-r--r-- | src/util/config.rs | 45 |
12 files changed, 481 insertions, 204 deletions
@@ -1183,12 +1183,12 @@ dependencies = [ "arc-swap", "async-trait", "bytes", + "err-derive", "futures", "futures-util", "garage_util", "gethostname", "hex", - "hyper", "k8s-openapi", "kube", "kuska-sodiumoxide", @@ -1196,6 +1196,7 @@ dependencies = [ "opentelemetry", "pnet_datalink", "rand 0.8.5", + "reqwest", "rmp-serde", "schemars", "serde", @@ -1629,6 +1630,12 @@ dependencies = [ ] [[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] name = "ipnetwork" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2781,6 +2788,44 @@ dependencies = [ ] [[package]] +name = "reqwest" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls 0.23.0", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.20.6", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls 0.23.4", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3159,6 +3204,18 @@ dependencies = [ ] [[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] name = "serde_yaml" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3939,6 +3996,18 @@ dependencies = [ ] [[package]] +name = "wasm-bindgen-futures" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] name = "wasm-bindgen-macro" version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4083,6 +4152,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" [[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -32,7 +32,7 @@ args@{ ignoreLockHash, }: let - nixifiedLockHash = "ef9c613771aff44da63f8df8a564a62b9d719945b707d1ec374d2666a3602fa6"; + nixifiedLockHash = "45b04ec226341a714068633cf7a3a6b421aa70ac3f78c72931d43ab8301b3c7b"; workspaceSrc = if args.workspaceSrc == null then ./. else args.workspaceSrc; currentLockHash = builtins.hashFile "sha256" (workspaceSrc + /Cargo.lock); lockHashIgnored = if ignoreLockHash @@ -1462,6 +1462,7 @@ in src = fetchCrateLocal (workspaceSrc + "/src/garage"); features = builtins.concatLists [ (lib.optional (rootFeatures' ? "garage/bundled-libs" || rootFeatures' ? "garage/default") "bundled-libs") + (lib.optional (rootFeatures' ? "garage/consul-discovery") "consul-discovery") (lib.optional (rootFeatures' ? "garage/default") "default") (lib.optional (rootFeatures' ? "garage/k2v") "k2v") (lib.optional (rootFeatures' ? "garage/kubernetes-discovery") "kubernetes-discovery") @@ -1681,9 +1682,12 @@ in registry = "unknown"; src = fetchCrateLocal (workspaceSrc + "/src/rpc"); features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery") "consul-discovery") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/err-derive") "err-derive") (lib.optional (rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/k8s-openapi" || rootFeatures' ? "garage_rpc/kubernetes-discovery") "k8s-openapi") (lib.optional (rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/kube" || rootFeatures' ? "garage_rpc/kubernetes-discovery") "kube") (lib.optional (rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/kubernetes-discovery") "kubernetes-discovery") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "reqwest") (lib.optional (rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/kubernetes-discovery" || rootFeatures' ? "garage_rpc/schemars") "schemars") (lib.optional (rootFeatures' ? "garage/system-libs" || rootFeatures' ? "garage_rpc/system-libs") "system-libs") ]; @@ -1691,12 +1695,12 @@ in 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; bytes = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/err-derive" then "err_derive" else null } = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }).out; futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.21" { inherit profileName; }).out; futures_util = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }).out; garage_util = (rustPackages."unknown".garage_util."0.8.0" { inherit profileName; }).out; gethostname = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".gethostname."0.2.3" { inherit profileName; }).out; hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out; - hyper = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }).out; ${ if rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/k8s-openapi" || rootFeatures' ? "garage_rpc/kubernetes-discovery" then "k8s_openapi" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".k8s-openapi."0.16.0" { inherit profileName; }).out; ${ if rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/kube" || rootFeatures' ? "garage_rpc/kubernetes-discovery" then "kube" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".kube."0.75.0" { inherit profileName; }).out; sodiumoxide = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; }).out; @@ -1704,6 +1708,7 @@ in opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out; pnet_datalink = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".pnet_datalink."0.28.0" { inherit profileName; }).out; rand = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "reqwest" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".reqwest."0.11.12" { inherit profileName; }).out; rmp_serde = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rmp-serde."0.15.5" { inherit profileName; }).out; ${ if rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/kubernetes-discovery" || rootFeatures' ? "garage_rpc/schemars" then "schemars" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".schemars."0.8.8" { inherit profileName; }).out; serde = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }).out; @@ -2260,6 +2265,16 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".ipnet."2.5.0" = overridableMkRustCrate (profileName: rec { + name = "ipnet"; + version = "2.5.0"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"; }; + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "default") + ]; + }); + "registry+https://github.com/rust-lang/crates.io-index".ipnetwork."0.18.0" = overridableMkRustCrate (profileName: rec { name = "ipnetwork"; version = "0.18.0"; @@ -3852,6 +3867,56 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".reqwest."0.11.12" = overridableMkRustCrate (profileName: rec { + name = "reqwest"; + version = "0.11.12"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc"; }; + features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "__rustls") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "__tls") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "hyper-rustls") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "json") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "rustls") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "rustls-pemfile") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "rustls-tls-manual-roots") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "serde_json") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "tokio-rustls") + ]; + dependencies = { + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "base64" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".base64."0.13.0" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "bytes" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.2.0" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "encoding_rs" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".encoding_rs."0.8.30" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "futures_core" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.21" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "futures_util" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.21" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "h2" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".h2."0.3.12" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "http" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.8" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "http_body" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-body."0.4.5" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "hyper" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.18" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "hyper_rustls" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper-rustls."0.23.0" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "ipnet" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".ipnet."2.5.0" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && hostPlatform.parsed.cpu.name == "wasm32" then "js_sys" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".js-sys."0.3.56" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "log" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".log."0.4.16" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "mime" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".mime."0.3.16" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "once_cell" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".once_cell."1.10.0" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "percent_encoding" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".percent-encoding."2.1.0" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "pin_project_lite" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project-lite."0.2.9" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "rustls" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rustls."0.20.6" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "rustls_pemfile" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rustls-pemfile."1.0.1" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "serde" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "serde_json" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.81" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "serde_urlencoded" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_urlencoded."0.7.1" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "tokio" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.17.0" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && !(hostPlatform.parsed.cpu.name == "wasm32") then "tokio_rustls" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-rustls."0.23.4" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "tower_service" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-service."0.3.1" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "url" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".url."2.2.2" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && hostPlatform.parsed.cpu.name == "wasm32" then "wasm_bindgen" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen."0.2.79" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && hostPlatform.parsed.cpu.name == "wasm32" then "wasm_bindgen_futures" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen-futures."0.4.29" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && hostPlatform.parsed.cpu.name == "wasm32" then "web_sys" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".web-sys."0.3.56" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && hostPlatform.isWindows then "winreg" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".winreg."0.10.1" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".ring."0.16.20" = overridableMkRustCrate (profileName: rec { name = "ring"; version = "0.16.20"; @@ -4048,8 +4113,8 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"; }; features = builtins.concatLists [ - (lib.optional (rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/kube" || rootFeatures' ? "garage_rpc/kubernetes-discovery") "dangerous_configuration") - (lib.optional (rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/kube" || rootFeatures' ? "garage_rpc/kubernetes-discovery") "default") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/kube" || rootFeatures' ? "garage_rpc/kubernetes-discovery" || rootFeatures' ? "garage_rpc/reqwest") "dangerous_configuration") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/kube" || rootFeatures' ? "garage_rpc/kubernetes-discovery" || rootFeatures' ? "garage_rpc/reqwest") "default") [ "log" ] [ "logging" ] [ "tls12" ] @@ -4346,6 +4411,19 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".serde_urlencoded."0.7.1" = overridableMkRustCrate (profileName: rec { + name = "serde_urlencoded"; + version = "0.7.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"; }; + dependencies = { + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "form_urlencoded" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".form_urlencoded."1.0.1" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "itoa" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".itoa."1.0.1" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "ryu" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".ryu."1.0.9" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "serde" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.137" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".serde_yaml."0.8.23" = overridableMkRustCrate (profileName: rec { name = "serde_yaml"; version = "0.8.23"; @@ -4877,6 +4955,7 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"; }; features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "default") [ "logging" ] [ "tls12" ] ]; @@ -5467,6 +5546,19 @@ in }; }); + "registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen-futures."0.4.29" = overridableMkRustCrate (profileName: rec { + name = "wasm-bindgen-futures"; + version = "0.4.29"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"; }; + dependencies = { + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "cfg_if" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".cfg-if."1.0.0" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "js_sys" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".js-sys."0.3.56" { inherit profileName; }).out; + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "wasm_bindgen" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen."0.2.79" { inherit profileName; }).out; + ${ if (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") && builtins.elem "atomics" hostPlatformFeatures then "web_sys" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".web-sys."0.3.56" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen-macro."0.2.79" = overridableMkRustCrate (profileName: rec { name = "wasm-bindgen-macro"; version = "0.2.79"; @@ -5511,9 +5603,24 @@ in registry = "registry+https://github.com/rust-lang/crates.io-index"; src = fetchCratesIo { inherit name version; sha256 = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"; }; features = builtins.concatLists [ + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "Blob") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "BlobPropertyBag") [ "Crypto" ] + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "Event") [ "EventTarget" ] + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "File") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "FormData") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "Headers") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "MessageEvent") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "Request") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "RequestCredentials") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "RequestInit") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "RequestMode") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "Response") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "ServiceWorkerGlobalScope") [ "Window" ] + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "Worker") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "WorkerGlobalScope") ]; dependencies = { js_sys = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".js-sys."0.3.56" { inherit profileName; }).out; @@ -5576,6 +5683,8 @@ in [ "evntrace" ] [ "fileapi" ] [ "handleapi" ] + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "impl-debug") + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "impl-default") [ "in6addr" ] [ "inaddr" ] [ "ioapiset" ] @@ -5609,6 +5718,7 @@ in [ "winerror" ] [ "winioctl" ] [ "winnt" ] + (lib.optional (rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest") "winreg") [ "winsock2" ] [ "ws2def" ] [ "ws2ipdef" ] @@ -5703,6 +5813,16 @@ in src = fetchCratesIo { inherit name version; sha256 = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"; }; }); + "registry+https://github.com/rust-lang/crates.io-index".winreg."0.10.1" = overridableMkRustCrate (profileName: rec { + name = "winreg"; + version = "0.10.1"; + registry = "registry+https://github.com/rust-lang/crates.io-index"; + src = fetchCratesIo { inherit name version; sha256 = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"; }; + dependencies = { + ${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/reqwest" then "winapi" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".winapi."0.3.9" { inherit profileName; }).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".xml-rs."0.8.4" = overridableMkRustCrate (profileName: rec { name = "xml-rs"; version = "0.8.4"; diff --git a/doc/book/reference-manual/configuration.md b/doc/book/reference-manual/configuration.md index 97da0e0e..2d9c3f0c 100644 --- a/doc/book/reference-manual/configuration.md +++ b/doc/book/reference-manual/configuration.md @@ -13,6 +13,9 @@ db_engine = "lmdb" block_size = 1048576 +sled_cache_capacity = 134217728 +sled_flush_every_ms = 2000 + replication_mode = "3" compression_level = 1 @@ -28,15 +31,20 @@ bootstrap_peers = [ "212fd62eeaca72c122b45a7f4fa0f55e012aa5e24ac384a72a3016413fa724ff@[fc00:F::1]:3901", ] -consul_host = "consul.service" -consul_service_name = "garage-daemon" -kubernetes_namespace = "garage" -kubernetes_service_name = "garage-daemon" -kubernetes_skip_crd = false +[consul_discovery] +consul_http_addr = "http://127.0.0.1:8500" +service_name = "garage-daemon" +ca_cert = "/etc/consul/consul-ca.crt" +client_cert = "/etc/consul/consul-client.crt" +client_key = "/etc/consul/consul-key.crt" +tls_skip_verify = false + +[kubernetes_discovery] +namespace = "garage" +service_name = "garage-daemon" +skip_crd = false -sled_cache_capacity = 134217728 -sled_flush_every_ms = 2000 [s3_api] api_bind_addr = "[::]:3900" @@ -129,6 +137,21 @@ files will remain available. This however means that chunks from existing files will not be deduplicated with chunks from newly uploaded files, meaning you might use more storage space that is optimally possible. +### `sled_cache_capacity` + +This parameter can be used to tune the capacity of the cache used by +[sled](https://sled.rs), the database Garage uses internally to store metadata. +Tune this to fit the RAM you wish to make available to your Garage instance. +This value has a conservative default (128MB) so that Garage doesn't use too much +RAM by default, but feel free to increase this for higher performance. + +### `sled_flush_every_ms` + +This parameters can be used to tune the flushing interval of sled. +Increase this if sled is thrashing your SSD, at the risk of losing more data in case +of a power outage (though this should not matter much as data is replicated on other +nodes). The default value, 2000ms, should be appropriate for most use cases. + ### `replication_mode` Garage supports the following replication modes: @@ -276,47 +299,57 @@ be obtained by running `garage node id` and then included directly in the key will be returned by `garage node id` and you will have to add the IP yourself. -### `consul_host` and `consul_service_name` + +## The `[consul_discovery]` section Garage supports discovering other nodes of the cluster using Consul. For this to work correctly, nodes need to know their IP address by which they can be reached by other nodes of the cluster, which should be set in `rpc_public_addr`. -The `consul_host` parameter should be set to the hostname of the Consul server, -and `consul_service_name` should be set to the service name under which Garage's +### `consul_http_addr` and `service_name` + +The `consul_http_addr` parameter should be set to the full HTTP(S) address of the Consul server. + +### `service_name` + +`service_name` should be set to the service name under which Garage's RPC ports are announced. -Garage does not yet support talking to Consul over TLS. +### `client_cert`, `client_key` -### `kubernetes_namespace`, `kubernetes_service_name` and `kubernetes_skip_crd` +TLS client certificate and client key to use when communicating with Consul over TLS. Both are mandatory when doing so. -Garage supports discovering other nodes of the cluster using kubernetes custom -resources. For this to work `kubernetes_namespace` and `kubernetes_service_name` -need to be configured. +### `ca_cert` -`kubernetes_namespace` sets the namespace in which the custom resources are -configured. `kubernetes_service_name` is added as a label to these resources to -filter them, to allow for multiple deployments in a single namespace. +TLS CA certificate to use when communicating with Consul over TLS. -`kubernetes_skip_crd` can be set to true to disable the automatic creation and -patching of the `garagenodes.deuxfleurs.fr` CRD. You will need to create the CRD -manually. +### `tls_skip_verify` -### `sled_cache_capacity` +Skip server hostname verification in TLS handshake. +`ca_cert` is ignored when this is set. -This parameter can be used to tune the capacity of the cache used by -[sled](https://sled.rs), the database Garage uses internally to store metadata. -Tune this to fit the RAM you wish to make available to your Garage instance. -This value has a conservative default (128MB) so that Garage doesn't use too much -RAM by default, but feel free to increase this for higher performance. -### `sled_flush_every_ms` +## The `[kubernetes_discovery]` section -This parameters can be used to tune the flushing interval of sled. -Increase this if sled is thrashing your SSD, at the risk of losing more data in case -of a power outage (though this should not matter much as data is replicated on other -nodes). The default value, 2000ms, should be appropriate for most use cases. +Garage supports discovering other nodes of the cluster using kubernetes custom +resources. For this to work, a `[kubernetes_discovery]` section must be present +with at least the `namespace` and `service_name` parameters. + +### `namespace` + +`namespace` sets the namespace in which the custom resources are +configured. +### `service_name` + +`service_name` is added as a label to the advertised resources to +filter them, to allow for multiple deployments in a single namespace. + +### `skip_crd` + +`skip_crd` can be set to true to disable the automatic creation and +patching of the `garagenodes.deuxfleurs.fr` CRD. You will need to create the CRD +manually. ## The `[s3_api]` section diff --git a/nix/compile.nix b/nix/compile.nix index 45c7efa1..303c3aa4 100644 --- a/nix/compile.nix +++ b/nix/compile.nix @@ -177,6 +177,7 @@ let "garage/sled" "garage/k2v" ] ++ (if release then [ + "garage/consul-discovery" "garage/kubernetes-discovery" "garage/metrics" "garage/telemetry-otlp" diff --git a/src/garage/Cargo.toml b/src/garage/Cargo.toml index ddc23170..cbc0dc61 100644 --- a/src/garage/Cargo.toml +++ b/src/garage/Cargo.toml @@ -81,6 +81,8 @@ sled = [ "garage_model/sled" ] lmdb = [ "garage_model/lmdb" ] sqlite = [ "garage_model/sqlite" ] +# Automatic registration and discovery via Consul API +consul-discovery = [ "garage_rpc/consul-discovery" ] # Automatic registration and discovery via Kubernetes API kubernetes-discovery = [ "garage_rpc/kubernetes-discovery" ] # Prometheus exporter (/metrics endpoint). diff --git a/src/garage/main.rs b/src/garage/main.rs index e5cba553..5b2a85c0 100644 --- a/src/garage/main.rs +++ b/src/garage/main.rs @@ -90,6 +90,8 @@ async fn main() { "lmdb", #[cfg(feature = "sqlite")] "sqlite", + #[cfg(feature = "consul-discovery")] + "consul-discovery", #[cfg(feature = "kubernetes-discovery")] "kubernetes-discovery", #[cfg(feature = "metrics")] diff --git a/src/rpc/Cargo.toml b/src/rpc/Cargo.toml index 883929e8..2c2ddc0b 100644 --- a/src/rpc/Cargo.toml +++ b/src/rpc/Cargo.toml @@ -29,11 +29,13 @@ rmp-serde = "0.15" serde = { version = "1.0", default-features = false, features = ["derive", "rc"] } serde_bytes = "0.11" serde_json = "1.0" +err-derive = { version = "0.3", optional = true } # newer version requires rust edition 2021 kube = { version = "0.75", default-features = false, features = ["runtime", "derive", "client", "rustls-tls"], optional = true } k8s-openapi = { version = "0.16", features = ["v1_22"], optional = true } schemars = { version = "0.8", optional = true } +reqwest = { version = "0.11", optional = true, default-features = false, features = ["rustls-tls-manual-roots", "json"] } # newer version requires rust edition 2021 pnet_datalink = "0.28" @@ -46,9 +48,7 @@ opentelemetry = "0.17" netapp = { version = "0.5.2", features = ["telemetry"] } -hyper = { version = "0.14", features = ["client", "http1", "runtime", "tcp"] } - - [features] kubernetes-discovery = [ "kube", "k8s-openapi", "schemars" ] +consul-discovery = [ "reqwest", "err-derive" ] system-libs = [ "sodiumoxide/use-pkg-config" ] diff --git a/src/rpc/consul.rs b/src/rpc/consul.rs index 15acbcef..b1772a1a 100644 --- a/src/rpc/consul.rs +++ b/src/rpc/consul.rs @@ -1,16 +1,14 @@ use std::collections::HashMap; +use std::fs::File; +use std::io::Read; use std::net::{IpAddr, SocketAddr}; -use hyper::client::Client; -use hyper::StatusCode; -use hyper::{Body, Method, Request}; +use err_derive::Error; use serde::{Deserialize, Serialize}; use netapp::NodeID; -use garage_util::error::Error; - -// ---- READING FROM CONSUL CATALOG ---- +use garage_util::config::ConsulDiscoveryConfig; #[derive(Deserialize, Clone, Debug)] struct ConsulQueryEntry { @@ -22,53 +20,6 @@ struct ConsulQueryEntry { node_meta: HashMap<String, String>, } -pub async fn get_consul_nodes( - consul_host: &str, - consul_service_name: &str, -) -> Result<Vec<(NodeID, SocketAddr)>, Error> { - let url = format!( - "http://{}/v1/catalog/service/{}", - consul_host, consul_service_name - ); - let req = Request::builder() - .uri(url) - .method(Method::GET) - .body(Body::default())?; - - let client = Client::new(); - - let resp = client.request(req).await?; - if resp.status() != StatusCode::OK { - return Err(Error::Message(format!("HTTP error {}", resp.status()))); - } - - let body = hyper::body::to_bytes(resp.into_body()).await?; - let entries = serde_json::from_slice::<Vec<ConsulQueryEntry>>(body.as_ref())?; - - let mut ret = vec![]; - for ent in entries { - let ip = ent.address.parse::<IpAddr>().ok(); - let pubkey = ent - .node_meta - .get("pubkey") - .and_then(|k| hex::decode(&k).ok()) - .and_then(|k| NodeID::from_slice(&k[..])); - if let (Some(ip), Some(pubkey)) = (ip, pubkey) { - ret.push((pubkey, SocketAddr::new(ip, ent.service_port))); - } else { - warn!( - "Could not process node spec from Consul: {:?} (invalid IP or public key)", - ent - ); - } - } - debug!("Got nodes from Consul: {:?}", ret); - - Ok(ret) -} - -// ---- PUBLISHING TO CONSUL CATALOG ---- - #[derive(Serialize, Clone, Debug)] struct ConsulPublishEntry { #[serde(rename = "Node")] @@ -95,57 +46,134 @@ struct ConsulPublishService { port: u16, } -pub async fn publish_consul_service( - consul_host: &str, - consul_service_name: &str, - node_id: NodeID, - hostname: &str, - rpc_public_addr: SocketAddr, -) -> Result<(), Error> { - let node = format!("garage:{}", hex::encode(&node_id[..8])); - - let advertisment = ConsulPublishEntry { - node: node.clone(), - address: rpc_public_addr.ip(), - node_meta: [ - ("pubkey".to_string(), hex::encode(node_id)), - ("hostname".to_string(), hostname.to_string()), - ] - .iter() - .cloned() - .collect(), - service: ConsulPublishService { - service_id: node.clone(), - service_name: consul_service_name.to_string(), - tags: vec!["advertised-by-garage".into(), hostname.into()], +// ---- + +pub struct ConsulDiscovery { + config: ConsulDiscoveryConfig, + client: reqwest::Client, +} + +impl ConsulDiscovery { + pub fn new(config: ConsulDiscoveryConfig) -> Result<Self, ConsulError> { + let client = match (&config.client_cert, &config.client_key) { + (Some(client_cert), Some(client_key)) => { + let mut client_cert_buf = vec![]; + File::open(client_cert)?.read_to_end(&mut client_cert_buf)?; + + let mut client_key_buf = vec![]; + File::open(client_key)?.read_to_end(&mut client_key_buf)?; + + let identity = reqwest::Identity::from_pem( + &[&client_cert_buf[..], &client_key_buf[..]].concat()[..], + )?; + + if config.tls_skip_verify { + reqwest::Client::builder() + .use_rustls_tls() + .danger_accept_invalid_certs(true) + .identity(identity) + .build()? + } else if let Some(ca_cert) = &config.ca_cert { + let mut ca_cert_buf = vec![]; + File::open(ca_cert)?.read_to_end(&mut ca_cert_buf)?; + + reqwest::Client::builder() + .use_rustls_tls() + .add_root_certificate(reqwest::Certificate::from_pem(&ca_cert_buf[..])?) + .identity(identity) + .build()? + } else { + reqwest::Client::builder() + .use_rustls_tls() + .identity(identity) + .build()? + } + } + (None, None) => reqwest::Client::new(), + _ => return Err(ConsulError::InvalidTLSConfig), + }; + + Ok(Self { client, config }) + } + + // ---- READING FROM CONSUL CATALOG ---- + + pub async fn get_consul_nodes(&self) -> Result<Vec<(NodeID, SocketAddr)>, ConsulError> { + let url = format!( + "{}/v1/catalog/service/{}", + self.config.consul_http_addr, self.config.service_name + ); + + let http = self.client.get(&url).send().await?; + let entries: Vec<ConsulQueryEntry> = http.json().await?; + + let mut ret = vec![]; + for ent in entries { + let ip = ent.address.parse::<IpAddr>().ok(); + let pubkey = ent + .node_meta + .get("pubkey") + .and_then(|k| hex::decode(&k).ok()) + .and_then(|k| NodeID::from_slice(&k[..])); + if let (Some(ip), Some(pubkey)) = (ip, pubkey) { + ret.push((pubkey, SocketAddr::new(ip, ent.service_port))); + } else { + warn!( + "Could not process node spec from Consul: {:?} (invalid IP or public key)", + ent + ); + } + } + debug!("Got nodes from Consul: {:?}", ret); + + Ok(ret) + } + + // ---- PUBLISHING TO CONSUL CATALOG ---- + + pub async fn publish_consul_service( + &self, + node_id: NodeID, + hostname: &str, + rpc_public_addr: SocketAddr, + ) -> Result<(), ConsulError> { + let node = format!("garage:{}", hex::encode(&node_id[..8])); + + let advertisement = ConsulPublishEntry { + node: node.clone(), address: rpc_public_addr.ip(), - port: rpc_public_addr.port(), - }, - }; - - let url = format!("http://{}/v1/catalog/register", consul_host); - let req_body = serde_json::to_string(&advertisment)?; - debug!("Request body for consul adv: {}", req_body); - - let req = Request::builder() - .uri(url) - .method(Method::PUT) - .body(Body::from(req_body))?; - - let client = Client::new(); - - let resp = client.request(req).await?; - debug!("Response of advertising to Consul: {:?}", resp); - let resp_code = resp.status(); - let resp_bytes = &hyper::body::to_bytes(resp.into_body()).await?; - debug!( - "{}", - std::str::from_utf8(resp_bytes).unwrap_or("<invalid utf8>") - ); - - if resp_code != StatusCode::OK { - return Err(Error::Message(format!("HTTP error {}", resp_code))); + node_meta: [ + ("pubkey".to_string(), hex::encode(node_id)), + ("hostname".to_string(), hostname.to_string()), + ] + .iter() + .cloned() + .collect(), + service: ConsulPublishService { + service_id: node.clone(), + service_name: self.config.service_name.clone(), + tags: vec!["advertised-by-garage".into(), hostname.into()], + address: rpc_public_addr.ip(), + port: rpc_public_addr.port(), + }, + }; + + let url = format!("{}/v1/catalog/register", self.config.consul_http_addr); + + let http = self.client.put(&url).json(&advertisement).send().await?; + http.error_for_status()?; + + Ok(()) } +} - Ok(()) +/// 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")] + InvalidTLSConfig, } diff --git a/src/rpc/kubernetes.rs b/src/rpc/kubernetes.rs index 197245aa..63c6567d 100644 --- a/src/rpc/kubernetes.rs +++ b/src/rpc/kubernetes.rs @@ -12,6 +12,8 @@ use serde::{Deserialize, Serialize}; use netapp::NodeID; +use garage_util::config::KubernetesDiscoveryConfig; + static K8S_GROUP: &str = "deuxfleurs.fr"; #[derive(CustomResource, Debug, Serialize, Deserialize, Clone, JsonSchema)] @@ -41,15 +43,14 @@ pub async fn create_kubernetes_crd() -> Result<(), kube::Error> { } pub async fn get_kubernetes_nodes( - kubernetes_service_name: &str, - kubernetes_namespace: &str, + kubernetes_config: &KubernetesDiscoveryConfig, ) -> Result<Vec<(NodeID, SocketAddr)>, kube::Error> { let client = Client::try_default().await?; - let nodes: Api<GarageNode> = Api::namespaced(client.clone(), kubernetes_namespace); + let nodes: Api<GarageNode> = Api::namespaced(client.clone(), &kubernetes_config.namespace); let lp = ListParams::default().labels(&format!( "garage.{}/service={}", - K8S_GROUP, kubernetes_service_name + K8S_GROUP, kubernetes_config.service_name )); let nodes = nodes.list(&lp).await?; @@ -73,8 +74,7 @@ pub async fn get_kubernetes_nodes( } pub async fn publish_kubernetes_node( - kubernetes_service_name: &str, - kubernetes_namespace: &str, + kubernetes_config: &KubernetesDiscoveryConfig, node_id: NodeID, hostname: &str, rpc_public_addr: SocketAddr, @@ -93,13 +93,13 @@ pub async fn publish_kubernetes_node( let labels = node.metadata.labels.insert(BTreeMap::new()); labels.insert( format!("garage.{}/service", K8S_GROUP), - kubernetes_service_name.to_string(), + kubernetes_config.service_name.to_string(), ); debug!("Node object to be applied: {:#?}", node); let client = Client::try_default().await?; - let nodes: Api<GarageNode> = Api::namespaced(client.clone(), kubernetes_namespace); + let nodes: Api<GarageNode> = Api::namespaced(client.clone(), &kubernetes_config.namespace); if let Ok(old_node) = nodes.get(&node_pubkey).await { node.metadata.resource_version = old_node.metadata.resource_version; diff --git a/src/rpc/lib.rs b/src/rpc/lib.rs index 392ff48f..92caf75d 100644 --- a/src/rpc/lib.rs +++ b/src/rpc/lib.rs @@ -3,6 +3,7 @@ #[macro_use] extern crate tracing; +#[cfg(feature = "consul-discovery")] mod consul; #[cfg(feature = "kubernetes-discovery")] mod kubernetes; diff --git a/src/rpc/system.rs b/src/rpc/system.rs index 9e0bfa11..d6576f20 100644 --- a/src/rpc/system.rs +++ b/src/rpc/system.rs @@ -23,12 +23,15 @@ use netapp::{NetApp, NetworkKey, NodeID, NodeKey}; use garage_util::background::BackgroundRunner; use garage_util::config::Config; +#[cfg(feature = "kubernetes-discovery")] +use garage_util::config::KubernetesDiscoveryConfig; use garage_util::data::*; use garage_util::error::*; use garage_util::persister::Persister; use garage_util::time::*; -use crate::consul::*; +#[cfg(feature = "consul-discovery")] +use crate::consul::ConsulDiscovery; #[cfg(feature = "kubernetes-discovery")] use crate::kubernetes::*; use crate::layout::*; @@ -90,12 +93,14 @@ pub struct System { system_endpoint: Arc<Endpoint<SystemRpc, System>>, rpc_listen_addr: SocketAddr, + #[cfg(any(feature = "consul-discovery", feature = "kubernetes-discovery"))] rpc_public_addr: Option<SocketAddr>, bootstrap_peers: Vec<String>, - consul_discovery: Option<ConsulDiscoveryParam>, + #[cfg(feature = "consul-discovery")] + consul_discovery: Option<ConsulDiscovery>, #[cfg(feature = "kubernetes-discovery")] - kubernetes_discovery: Option<KubernetesDiscoveryParam>, + kubernetes_discovery: Option<KubernetesDiscoveryConfig>, replication_factor: usize, @@ -285,29 +290,21 @@ impl System { let system_endpoint = netapp.endpoint(SYSTEM_RPC_PATH.into()); - let consul_discovery = match (&config.consul_host, &config.consul_service_name) { - (Some(ch), Some(csn)) => Some(ConsulDiscoveryParam { - consul_host: ch.to_string(), - service_name: csn.to_string(), - }), - _ => None, - }; - - #[cfg(feature = "kubernetes-discovery")] - let kubernetes_discovery = match ( - &config.kubernetes_service_name, - &config.kubernetes_namespace, - ) { - (Some(ksn), Some(kn)) => Some(KubernetesDiscoveryParam { - service_name: ksn.to_string(), - namespace: kn.to_string(), - skip_crd: config.kubernetes_skip_crd, - }), - _ => None, + #[cfg(feature = "consul-discovery")] + let consul_discovery = match &config.consul_discovery { + Some(cfg) => Some( + ConsulDiscovery::new(cfg.clone()) + .ok_or_message("Invalid Consul discovery configuration")?, + ), + None => None, }; + #[cfg(not(feature = "consul-discovery"))] + if config.consul_discovery.is_some() { + warn!("Consul discovery is not enabled in this build."); + } #[cfg(not(feature = "kubernetes-discovery"))] - if config.kubernetes_service_name.is_some() || config.kubernetes_namespace.is_some() { + if config.kubernetes_discovery.is_some() { warn!("Kubernetes discovery is not enabled in this build."); } @@ -329,11 +326,13 @@ impl System { system_endpoint, replication_factor, rpc_listen_addr: config.rpc_bind_addr, + #[cfg(any(feature = "consul-discovery", feature = "kubernetes-discovery"))] rpc_public_addr, bootstrap_peers: config.bootstrap_peers.clone(), + #[cfg(feature = "consul-discovery")] consul_discovery, #[cfg(feature = "kubernetes-discovery")] - kubernetes_discovery, + kubernetes_discovery: config.kubernetes_discovery.clone(), ring, update_ring: Mutex::new(update_ring), @@ -432,6 +431,7 @@ impl System { // ---- INTERNALS ---- + #[cfg(feature = "consul-discovery")] async fn advertise_to_consul(self: Arc<Self>) -> Result<(), Error> { let c = match &self.consul_discovery { Some(c) => c, @@ -446,9 +446,7 @@ impl System { } }; - publish_consul_service( - &c.consul_host, - &c.service_name, + c.publish_consul_service( self.netapp.id, &self.local_status.load_full().hostname, rpc_public_addr, @@ -473,8 +471,7 @@ impl System { }; publish_kubernetes_node( - &k.service_name, - &k.namespace, + k, self.netapp.id, &self.local_status.load_full().hostname, rpc_public_addr, @@ -644,8 +641,9 @@ impl System { } // Fetch peer list from Consul + #[cfg(feature = "consul-discovery")] if let Some(c) = &self.consul_discovery { - match get_consul_nodes(&c.consul_host, &c.service_name).await { + match c.get_consul_nodes().await { Ok(node_list) => { ping_list.extend(node_list); } @@ -667,7 +665,7 @@ impl System { }; } - match get_kubernetes_nodes(&k.service_name, &k.namespace).await { + match get_kubernetes_nodes(k).await { Ok(node_list) => { ping_list.extend(node_list); } @@ -691,6 +689,7 @@ impl System { warn!("Could not save peer list to file: {}", e); } + #[cfg(feature = "consul-discovery")] self.background.spawn(self.clone().advertise_to_consul()); #[cfg(feature = "kubernetes-discovery")] @@ -785,15 +784,3 @@ async fn resolve_peers(peers: &[String]) -> Vec<(NodeID, SocketAddr)> { ret } - -struct ConsulDiscoveryParam { - consul_host: String, - service_name: String, -} - -#[cfg(feature = "kubernetes-discovery")] -struct KubernetesDiscoveryParam { - service_name: String, - namespace: String, - skip_crd: bool, -} diff --git a/src/util/config.rs b/src/util/config.rs index 2d4b4f57..04f8375a 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -46,20 +46,17 @@ pub struct Config { /// Timeout for Netapp RPC calls pub rpc_timeout_msec: Option<u64>, + // -- Bootstraping and discovery /// Bootstrap peers RPC address #[serde(default)] pub bootstrap_peers: Vec<String>, - /// Consul host to connect to to discover more peers - pub consul_host: Option<String>, - /// Consul service name to use - pub consul_service_name: Option<String>, - /// Kubernetes namespace the service discovery resources are be created in - pub kubernetes_namespace: Option<String>, - /// Service name to filter for in k8s custom resources - pub kubernetes_service_name: Option<String>, - /// Skip creation of the garagenodes CRD + + /// Configuration for automatic node discovery through Consul #[serde(default)] - pub kubernetes_skip_crd: bool, + pub consul_discovery: Option<ConsulDiscoveryConfig>, + /// Configuration for automatic node discovery through Kubernetes + #[serde(default)] + pub kubernetes_discovery: Option<KubernetesDiscoveryConfig>, // -- DB /// Database engine to use for metadata (options: sled, sqlite, lmdb) @@ -129,6 +126,34 @@ pub struct AdminConfig { pub trace_sink: Option<String>, } +#[derive(Deserialize, Debug, Clone)] +pub struct ConsulDiscoveryConfig { + /// Consul http or https address to connect to to discover more peers + pub consul_http_addr: String, + /// Consul service name to use + pub service_name: String, + /// CA TLS certificate to use when connecting to Consul + pub ca_cert: Option<String>, + /// Client TLS certificate to use when connecting to Consul + pub client_cert: Option<String>, + /// Client TLS key to use when connecting to Consul + pub client_key: Option<String>, + /// Skip TLS hostname verification + #[serde(default)] + pub tls_skip_verify: bool, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct KubernetesDiscoveryConfig { + /// Kubernetes namespace the service discovery resources are be created in + pub namespace: String, + /// Service name to filter for in k8s custom resources + pub service_name: String, + /// Skip creation of the garagenodes CRD + #[serde(default)] + pub skip_crd: bool, +} + fn default_db_engine() -> String { "sled".into() } |