diff --git a/.github/buildomat/jobs/deploy.sh b/.github/buildomat/jobs/deploy.sh index a9f1bf8a9c..502909ec2d 100755 --- a/.github/buildomat/jobs/deploy.sh +++ b/.github/buildomat/jobs/deploy.sh @@ -340,7 +340,7 @@ E2E_TLS_CERT="/opt/oxide/sled-agent/pkg/initial-tls-cert.pem" # pfexec mkdir -p /usr/oxide pfexec curl -sSfL -o /usr/oxide/oxide \ - http://catacomb.eng.oxide.computer:12346/oxide-v0.1.0 + http://catacomb.eng.oxide.computer:12346/oxide-v0.1.1 pfexec chmod +x /usr/oxide/oxide curl -sSfL -o debian-11-genericcloud-amd64.raw \ @@ -418,11 +418,9 @@ done /usr/oxide/oxide --resolve "$OXIDE_RESOLVE" --cacert "$E2E_TLS_CERT" \ project create --name images --description "some images" -# NOTE: Use a relatively large timeout on this call, to avoid #6771 /usr/oxide/oxide \ --resolve "$OXIDE_RESOLVE" \ --cacert "$E2E_TLS_CERT" \ - --timeout 60 \ disk import \ --path debian-11-genericcloud-amd64.raw \ --disk debian11-boot \ @@ -432,7 +430,8 @@ done --image debian11 \ --image-description "debian 11 original base image" \ --image-os debian \ - --image-version "11" + --image-version "11" \ + --parallelism 1 /usr/oxide/oxide --resolve "$OXIDE_RESOLVE" --cacert "$E2E_TLS_CERT" \ image promote --project images --image debian11 diff --git a/.github/workflows/hakari.yml b/.github/workflows/hakari.yml index 96866bcee9..6c90f6a170 100644 --- a/.github/workflows/hakari.yml +++ b/.github/workflows/hakari.yml @@ -23,7 +23,7 @@ jobs: with: toolchain: stable - name: Install cargo-hakari - uses: taiki-e/install-action@939f4af9602e15ef93b3329722569cb907a004ff # v2 + uses: taiki-e/install-action@4b40a9728e3c110fabe8850443d8bbe69daddb22 # v2 with: tool: cargo-hakari - name: Check workspace-hack Cargo.toml is up-to-date diff --git a/Cargo.lock b/Cargo.lock index 87899e699c..03a4c9fc0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" dependencies = [ "backtrace", ] @@ -178,6 +178,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arc-swap" version = "1.7.1" @@ -656,7 +665,7 @@ dependencies = [ [[package]] name = "bhyve_api" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=e0c83fd0e0760eec1af306286c50081689d11a51#e0c83fd0e0760eec1af306286c50081689d11a51" +source = "git+https://github.com/oxidecomputer/propolis?rev=86101eaf80b55e7f405b5cafe9b0de0e9f331656#86101eaf80b55e7f405b5cafe9b0de0e9f331656" dependencies = [ "bhyve_api_sys", "libc", @@ -666,7 +675,7 @@ dependencies = [ [[package]] name = "bhyve_api_sys" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=e0c83fd0e0760eec1af306286c50081689d11a51#e0c83fd0e0760eec1af306286c50081689d11a51" +source = "git+https://github.com/oxidecomputer/propolis?rev=86101eaf80b55e7f405b5cafe9b0de0e9f331656#86101eaf80b55e7f405b5cafe9b0de0e9f331656" dependencies = [ "libc", "strum", @@ -1346,6 +1355,7 @@ dependencies = [ "atomicwrites", "camino", "camino-tempfile", + "chrono", "derive_more", "expectorate", "itertools 0.13.0", @@ -2111,6 +2121,17 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "derive_builder" version = "0.20.2" @@ -2303,9 +2324,20 @@ dependencies = [ [[package]] name = "display-error-chain" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d305e5a3904ee14166439a70feef04853c1234226dbb27ede127b88dc5a4a9d" +checksum = "0bc2146e86bc19f52f4c064a64782f05f139ca464ed72937301631e73f8d6cf5" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] [[package]] name = "dlpi" @@ -3248,7 +3280,7 @@ dependencies = [ "usdt", "uuid", "version_check", - "zip", + "zip 0.6.6", ] [[package]] @@ -3957,7 +3989,7 @@ dependencies = [ "toml 0.7.8", "x509-cert", "zerocopy 0.6.6", - "zip", + "zip 0.6.6", ] [[package]] @@ -4805,7 +4837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -4978,6 +5010,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.22" @@ -6638,7 +6676,7 @@ dependencies = [ "pq-sys", "pretty_assertions", "progenitor-client", - "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=e0c83fd0e0760eec1af306286c50081689d11a51)", + "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=86101eaf80b55e7f405b5cafe9b0de0e9f331656)", "qorb", "rand", "rcgen", @@ -6871,6 +6909,7 @@ dependencies = [ "glob", "guppy", "hex", + "hex-literal", "http 1.1.0", "hyper 1.4.1", "hyper-staticfile", @@ -6900,17 +6939,20 @@ dependencies = [ "oximeter-producer", "oxnet", "pretty_assertions", - "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=e0c83fd0e0760eec1af306286c50081689d11a51)", + "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=86101eaf80b55e7f405b5cafe9b0de0e9f331656)", "propolis-mock-server", "propolis_api_types", "rand", "rcgen", + "repo-depot-api", + "repo-depot-client", "reqwest 0.12.8", "schemars", "semver 1.0.23", "serde", "serde_human_bytes", "serde_json", + "sha2", "sha3", "sled-agent-api", "sled-agent-client", @@ -6971,6 +7013,7 @@ dependencies = [ "reqwest 0.12.8", "ring 0.17.8", "rustls 0.22.4", + "serde", "slog", "subprocess", "tar", @@ -7125,6 +7168,7 @@ dependencies = [ "x509-cert", "zerocopy 0.7.35", "zeroize", + "zip 0.6.6", ] [[package]] @@ -7214,6 +7258,7 @@ dependencies = [ "openapiv3", "owo-colors", "oximeter-api", + "repo-depot-api", "serde_json", "similar", "sled-agent-api", @@ -7503,6 +7548,7 @@ dependencies = [ "chrono-tz", "clap", "clickward", + "const_format", "criterion", "crossterm", "debug-ignore", @@ -7524,8 +7570,10 @@ dependencies = [ "oximeter", "oximeter-test-utils", "oxql-types", + "parse-display", "peg", "qorb", + "quote", "reedline", "regex", "reqwest 0.12.8", @@ -7670,6 +7718,7 @@ dependencies = [ "omicron-common", "omicron-workspace-hack", "oximeter-macro-impl", + "parse-display", "rand", "rand_distr", "regex", @@ -7873,9 +7922,9 @@ dependencies = [ [[package]] name = "parse-size" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" +checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b" [[package]] name = "parse-zoneinfo" @@ -8580,7 +8629,7 @@ dependencies = [ [[package]] name = "propolis-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=e0c83fd0e0760eec1af306286c50081689d11a51#e0c83fd0e0760eec1af306286c50081689d11a51" +source = "git+https://github.com/oxidecomputer/propolis?rev=86101eaf80b55e7f405b5cafe9b0de0e9f331656#86101eaf80b55e7f405b5cafe9b0de0e9f331656" dependencies = [ "async-trait", "base64 0.21.7", @@ -8622,7 +8671,7 @@ dependencies = [ [[package]] name = "propolis-mock-server" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=e0c83fd0e0760eec1af306286c50081689d11a51#e0c83fd0e0760eec1af306286c50081689d11a51" +source = "git+https://github.com/oxidecomputer/propolis?rev=86101eaf80b55e7f405b5cafe9b0de0e9f331656#86101eaf80b55e7f405b5cafe9b0de0e9f331656" dependencies = [ "anyhow", "atty", @@ -8664,7 +8713,7 @@ dependencies = [ [[package]] name = "propolis_api_types" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=e0c83fd0e0760eec1af306286c50081689d11a51#e0c83fd0e0760eec1af306286c50081689d11a51" +source = "git+https://github.com/oxidecomputer/propolis?rev=86101eaf80b55e7f405b5cafe9b0de0e9f331656#86101eaf80b55e7f405b5cafe9b0de0e9f331656" dependencies = [ "crucible-client-types", "propolis_types", @@ -8677,7 +8726,7 @@ dependencies = [ [[package]] name = "propolis_types" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=e0c83fd0e0760eec1af306286c50081689d11a51#e0c83fd0e0760eec1af306286c50081689d11a51" +source = "git+https://github.com/oxidecomputer/propolis?rev=86101eaf80b55e7f405b5cafe9b0de0e9f331656#86101eaf80b55e7f405b5cafe9b0de0e9f331656" dependencies = [ "schemars", "serde", @@ -8721,9 +8770,9 @@ dependencies = [ [[package]] name = "qorb" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25f71eb7c5ba56a99f0721fd771b2503aa6de4ec73f0891f9b7ac115ca34723" +checksum = "9cd19ad8fae9abd8da01d8f435b633b567d53835cf3bce89d6f616617d10583c" dependencies = [ "anyhow", "async-trait", @@ -8741,6 +8790,7 @@ dependencies = [ "tokio-stream", "tokio-tungstenite 0.24.0", "tracing", + "usdt", ] [[package]] @@ -9128,6 +9178,29 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "repo-depot-api" +version = "0.1.0" +dependencies = [ + "dropshot", + "omicron-common", + "omicron-workspace-hack", + "schemars", + "serde", +] + +[[package]] +name = "repo-depot-client" +version = "0.1.0" +dependencies = [ + "omicron-workspace-hack", + "progenitor", + "reqwest 0.12.8", + "schemars", + "serde", + "slog", +] + [[package]] name = "reqwest" version = "0.11.27" @@ -10220,6 +10293,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "similar" version = "2.6.0" @@ -10326,7 +10405,7 @@ dependencies = [ "omicron-uuid-kinds", "omicron-workspace-hack", "oxnet", - "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=e0c83fd0e0760eec1af306286c50081689d11a51)", + "propolis-client 0.1.0 (git+https://github.com/oxidecomputer/propolis?rev=86101eaf80b55e7f405b5cafe9b0de0e9f331656)", "rcgen", "schemars", "serde", @@ -11838,7 +11917,7 @@ dependencies = [ "toml 0.8.19", "tough", "url", - "zip", + "zip 2.1.3", ] [[package]] @@ -13175,6 +13254,24 @@ dependencies = [ "flate2", ] +[[package]] +name = "zip" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" +dependencies = [ + "arbitrary", + "bzip2", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.6.0", + "memchr", + "thiserror", + "zopfli", +] + [[package]] name = "zone" version = "0.1.8" @@ -13244,3 +13341,17 @@ dependencies = [ "quote", "syn 1.0.109", ] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 4fe0f4deac..ab82cbef54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "clients/nexus-client", "clients/oxide-client", "clients/oximeter-client", + "clients/repo-depot-client", "clients/sled-agent-client", "clients/wicketd-client", "cockroach-admin", @@ -100,6 +101,7 @@ members = [ "sled-agent", "sled-agent/api", "sled-agent/bootstrap-agent-api", + "sled-agent/repo-depot-api", "sled-agent/types", "sled-hardware", "sled-hardware/types", @@ -140,6 +142,7 @@ default-members = [ "clients/nexus-client", "clients/oxide-client", "clients/oximeter-client", + "clients/repo-depot-client", "clients/sled-agent-client", "clients/wicketd-client", "cockroach-admin", @@ -225,6 +228,7 @@ default-members = [ "sled-agent", "sled-agent/api", "sled-agent/bootstrap-agent-api", + "sled-agent/repo-depot-api", "sled-agent/types", "sled-hardware", "sled-hardware/types", @@ -336,7 +340,7 @@ crucible-common = { git = "https://github.com/oxidecomputer/crucible", rev = "b7 csv = "1.3.0" curve25519-dalek = "4" datatest-stable = "0.2.9" -display-error-chain = "0.2.1" +display-error-chain = "0.2.2" omicron-ddm-admin-client = { path = "clients/ddm-admin-client" } db-macros = { path = "nexus/db-macros" } debug-ignore = "1.0.5" @@ -515,12 +519,12 @@ prettyplease = { version = "0.2.22", features = ["verbatim"] } proc-macro2 = "1.0" progenitor = "0.8.0" progenitor-client = "0.8.0" -bhyve_api = { git = "https://github.com/oxidecomputer/propolis", rev = "e0c83fd0e0760eec1af306286c50081689d11a51" } -propolis_api_types = { git = "https://github.com/oxidecomputer/propolis", rev = "e0c83fd0e0760eec1af306286c50081689d11a51" } -propolis-client = { git = "https://github.com/oxidecomputer/propolis", rev = "e0c83fd0e0760eec1af306286c50081689d11a51" } -propolis-mock-server = { git = "https://github.com/oxidecomputer/propolis", rev = "e0c83fd0e0760eec1af306286c50081689d11a51" } +bhyve_api = { git = "https://github.com/oxidecomputer/propolis", rev = "86101eaf80b55e7f405b5cafe9b0de0e9f331656" } +propolis_api_types = { git = "https://github.com/oxidecomputer/propolis", rev = "86101eaf80b55e7f405b5cafe9b0de0e9f331656" } +propolis-client = { git = "https://github.com/oxidecomputer/propolis", rev = "86101eaf80b55e7f405b5cafe9b0de0e9f331656" } +propolis-mock-server = { git = "https://github.com/oxidecomputer/propolis", rev = "86101eaf80b55e7f405b5cafe9b0de0e9f331656" } proptest = "1.5.0" -qorb = "0.1.2" +qorb = "0.2.0" quote = "1.0" rand = "0.8.5" rand_core = "0.6.4" @@ -533,6 +537,8 @@ reedline = "0.35.0" ref-cast = "1.0" regex = "1.11.0" regress = "0.9.1" +repo-depot-api = { path = "sled-agent/repo-depot-api" } +repo-depot-client = { path = "clients/repo-depot-client" } reqwest = { version = "0.12", default-features = false } ring = "0.17.8" rpassword = "7.3.1" @@ -632,7 +638,8 @@ wicket-common = { path = "wicket-common" } wicketd-api = { path = "wicketd-api" } wicketd-client = { path = "clients/wicketd-client" } zeroize = { version = "1.8.1", features = ["zeroize_derive", "std"] } -zip = { version = "0.6.6", default-features = false, features = ["deflate","bzip2"] } +# NOTE: Avoid upgrading zip until https://github.com/zip-rs/zip2/issues/231 is resolved +zip = { version = "=2.1.3", default-features = false, features = ["deflate","bzip2"] } zone = { version = "0.3", default-features = false, features = ["async"] } # newtype-uuid is set to default-features = false because we don't want to diff --git a/clickhouse-admin/api/src/lib.rs b/clickhouse-admin/api/src/lib.rs index 19cd2b3e8e..10ba028016 100644 --- a/clickhouse-admin/api/src/lib.rs +++ b/clickhouse-admin/api/src/lib.rs @@ -3,8 +3,8 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use clickhouse_admin_types::{ - ClickhouseKeeperClusterMembership, KeeperConf, KeeperConfig, - KeeperConfigurableSettings, Lgif, RaftConfig, ReplicaConfig, + ClickhouseKeeperClusterMembership, DistributedDdlQueue, KeeperConf, + KeeperConfig, KeeperConfigurableSettings, Lgif, RaftConfig, ReplicaConfig, ServerConfigurableSettings, }; use dropshot::{ @@ -105,4 +105,14 @@ pub trait ClickhouseAdminServerApi { rqctx: RequestContext, body: TypedBody, ) -> Result, HttpError>; + + /// Contains information about distributed ddl queries (ON CLUSTER clause) + /// that were executed on a cluster. + #[endpoint { + method = GET, + path = "/distributed-ddl-queue", + }] + async fn distributed_ddl_queue( + rqctx: RequestContext, + ) -> Result>, HttpError>; } diff --git a/clickhouse-admin/src/clickhouse_cli.rs b/clickhouse-admin/src/clickhouse_cli.rs index 32afdc4ef8..fbdbe46e5f 100644 --- a/clickhouse-admin/src/clickhouse_cli.rs +++ b/clickhouse-admin/src/clickhouse_cli.rs @@ -5,7 +5,8 @@ use anyhow::Result; use camino::Utf8PathBuf; use clickhouse_admin_types::{ - ClickhouseKeeperClusterMembership, KeeperConf, KeeperId, Lgif, RaftConfig, + ClickhouseKeeperClusterMembership, DistributedDdlQueue, KeeperConf, + KeeperId, Lgif, RaftConfig, OXIMETER_CLUSTER, }; use dropshot::HttpError; use illumos_utils::{output_to_exec_error, ExecutionError}; @@ -13,6 +14,7 @@ use slog::Logger; use slog_error_chain::{InlineErrorChain, SlogInlineError}; use std::collections::BTreeSet; use std::ffi::OsStr; +use std::fmt::Display; use std::io; use std::net::SocketAddrV6; use tokio::process::Command; @@ -56,6 +58,21 @@ impl From for HttpError { } } +enum ClickhouseClientType { + Server, + Keeper, +} + +impl Display for ClickhouseClientType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + ClickhouseClientType::Server => "client", + ClickhouseClientType::Keeper => "keeper-client", + }; + write!(f, "{s}") + } +} + #[derive(Debug)] pub struct ClickhouseCli { /// Path to where the clickhouse binary is located @@ -76,7 +93,8 @@ impl ClickhouseCli { } pub async fn lgif(&self) -> Result { - self.keeper_client_non_interactive( + self.client_non_interactive( + ClickhouseClientType::Keeper, "lgif", "Retrieve logically grouped information file", Lgif::parse, @@ -86,7 +104,8 @@ impl ClickhouseCli { } pub async fn raft_config(&self) -> Result { - self.keeper_client_non_interactive( + self.client_non_interactive( + ClickhouseClientType::Keeper, "get /keeper/config", "Retrieve raft configuration information", RaftConfig::parse, @@ -96,7 +115,8 @@ impl ClickhouseCli { } pub async fn keeper_conf(&self) -> Result { - self.keeper_client_non_interactive( + self.client_non_interactive( + ClickhouseClientType::Keeper, "conf", "Retrieve keeper node configuration information", KeeperConf::parse, @@ -121,8 +141,29 @@ impl ClickhouseCli { }) } - async fn keeper_client_non_interactive( + pub async fn distributed_ddl_queue( + &self, + ) -> Result, ClickhouseCliError> { + self.client_non_interactive( + ClickhouseClientType::Server, + format!( + "SELECT * FROM system.distributed_ddl_queue WHERE cluster = '{}' + SETTINGS date_time_output_format = 'iso', + output_format_json_quote_64bit_integers = '0' + FORMAT JSONEachRow", + OXIMETER_CLUSTER + ).as_str(), + "Retrieve information about distributed ddl queries (ON CLUSTER clause) + that were executed on a cluster", + DistributedDdlQueue::parse, + self.log.clone().unwrap(), + ) + .await + } + + async fn client_non_interactive( &self, + client: ClickhouseClientType, query: &str, subcommand_description: &'static str, parse: F, @@ -133,7 +174,7 @@ impl ClickhouseCli { { let mut command = Command::new(&self.binary_path); command - .arg("keeper-client") + .arg(client.to_string()) .arg("--host") .arg(&format!("[{}]", self.listen_address.ip())) .arg("--port") diff --git a/clickhouse-admin/src/http_entrypoints.rs b/clickhouse-admin/src/http_entrypoints.rs index 49138b9cc3..e1974cfc7e 100644 --- a/clickhouse-admin/src/http_entrypoints.rs +++ b/clickhouse-admin/src/http_entrypoints.rs @@ -5,8 +5,8 @@ use crate::context::ServerContext; use clickhouse_admin_api::*; use clickhouse_admin_types::{ - ClickhouseKeeperClusterMembership, KeeperConf, KeeperConfig, - KeeperConfigurableSettings, Lgif, RaftConfig, ReplicaConfig, + ClickhouseKeeperClusterMembership, DistributedDdlQueue, KeeperConf, + KeeperConfig, KeeperConfigurableSettings, Lgif, RaftConfig, ReplicaConfig, ServerConfigurableSettings, }; use dropshot::{ @@ -47,6 +47,14 @@ impl ClickhouseAdminServerApi for ClickhouseAdminServerImpl { Ok(HttpResponseCreated(output)) } + + async fn distributed_ddl_queue( + rqctx: RequestContext, + ) -> Result>, HttpError> { + let ctx = rqctx.context(); + let output = ctx.clickhouse_cli().distributed_ddl_queue().await?; + Ok(HttpResponseOk(output)) + } } enum ClickhouseAdminKeeperImpl {} diff --git a/clickhouse-admin/types/Cargo.toml b/clickhouse-admin/types/Cargo.toml index a7f1da1c86..f57b1c5052 100644 --- a/clickhouse-admin/types/Cargo.toml +++ b/clickhouse-admin/types/Cargo.toml @@ -12,6 +12,7 @@ anyhow.workspace = true atomicwrites.workspace = true camino.workspace = true camino-tempfile.workspace = true +chrono.workspace = true derive_more.workspace = true itertools.workspace = true omicron-common.workspace = true diff --git a/clickhouse-admin/types/src/config.rs b/clickhouse-admin/types/src/config.rs index 20aa3d71f8..27eb569b91 100644 --- a/clickhouse-admin/types/src/config.rs +++ b/clickhouse-admin/types/src/config.rs @@ -154,6 +154,11 @@ impl ReplicaConfig { 1000 + + + + 1.0 + {macros} {remote_servers} {keepers} diff --git a/clickhouse-admin/types/src/lib.rs b/clickhouse-admin/types/src/lib.rs index 021b9db356..e563f6da75 100644 --- a/clickhouse-admin/types/src/lib.rs +++ b/clickhouse-admin/types/src/lib.rs @@ -5,6 +5,7 @@ use anyhow::{bail, Context, Error, Result}; use atomicwrites::AtomicFile; use camino::Utf8PathBuf; +use chrono::{DateTime, Utc}; use derive_more::{Add, AddAssign, Display, From}; use itertools::Itertools; use omicron_common::api::external::Generation; @@ -15,7 +16,7 @@ use schemars::{ }; use serde::{Deserialize, Serialize}; use slog::{info, Logger}; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::fs::create_dir; use std::io::{ErrorKind, Write}; use std::net::Ipv6Addr; @@ -965,19 +966,88 @@ pub struct ClickhouseKeeperClusterMembership { pub raft_config: BTreeSet, } +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(rename_all = "snake_case")] +/// Contains information about distributed ddl queries (ON CLUSTER clause) that were +/// executed on a cluster. +pub struct DistributedDdlQueue { + /// Query id + pub entry: String, + /// Version of the entry + pub entry_version: u64, + /// Host that initiated the DDL operation + pub initiator_host: String, + /// Port used by the initiator + pub initiator_port: u16, + /// Cluster name + pub cluster: String, + /// Query executed + pub query: String, + /// Settings used in the DDL operation + pub settings: BTreeMap, + /// Query created time + pub query_create_time: DateTime, + /// Hostname + pub host: Ipv6Addr, + /// Host Port + pub port: u16, + /// Status of the query + pub status: String, + /// Exception code + pub exception_code: u64, + /// Exception message + pub exception_text: String, + /// Query finish time + pub query_finish_time: DateTime, + /// Duration of query execution (in milliseconds) + pub query_duration_ms: u64, +} + +impl DistributedDdlQueue { + pub fn parse(log: &Logger, data: &[u8]) -> Result> { + let s = String::from_utf8_lossy(data); + info!( + log, + "Retrieved data from `system.distributed_ddl_queue`"; + "output" => ?s + ); + + let mut ddl = vec![]; + + for line in s.lines() { + let item: DistributedDdlQueue = serde_json::from_str(line)?; + ddl.push(item); + } + + Ok(ddl) + } +} + #[cfg(test)] mod tests { use camino::Utf8PathBuf; use camino_tempfile::Builder; + use chrono::{DateTime, Utc}; use slog::{o, Drain}; use slog_term::{FullFormat, PlainDecorator, TestStdoutWriter}; + use std::collections::BTreeMap; use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; use crate::{ - ClickhouseHost, KeeperConf, KeeperId, KeeperServerInfo, - KeeperServerType, KeeperSettings, Lgif, LogLevel, RaftConfig, - RaftServerSettings, ServerId, ServerSettings, + ClickhouseHost, DistributedDdlQueue, KeeperConf, KeeperId, + KeeperServerInfo, KeeperServerType, KeeperSettings, Lgif, LogLevel, + RaftConfig, RaftServerSettings, ServerId, ServerSettings, }; fn log() -> slog::Logger { @@ -1736,4 +1806,85 @@ snapshot_storage_disk=LocalSnapshotDisk "Extracted key `\"session_timeout_fake\"` from output differs from expected key `session_timeout_ms`" ); } + + #[test] + fn test_distributed_ddl_queries_parse_success() { + let log = log(); + let data = + "{\"entry\":\"query-0000000000\",\"entry_version\":5,\"initiator_host\":\"ixchel\",\"initiator_port\":22001,\"cluster\":\"oximeter_cluster\",\"query\":\"CREATE DATABASE IF NOT EXISTS db1 UUID 'a49757e4-179e-42bd-866f-93ac43136e2d' ON CLUSTER oximeter_cluster\",\"settings\":{\"load_balancing\":\"random\"},\"query_create_time\":\"2024-11-01T16:16:45Z\",\"host\":\"::1\",\"port\":22001,\"status\":\"Finished\",\"exception_code\":0,\"exception_text\":\"\",\"query_finish_time\":\"2024-11-01T16:16:45Z\",\"query_duration_ms\":4} +{\"entry\":\"query-0000000000\",\"entry_version\":5,\"initiator_host\":\"ixchel\",\"initiator_port\":22001,\"cluster\":\"oximeter_cluster\",\"query\":\"CREATE DATABASE IF NOT EXISTS db1 UUID 'a49757e4-179e-42bd-866f-93ac43136e2d' ON CLUSTER oximeter_cluster\",\"settings\":{\"load_balancing\":\"random\"},\"query_create_time\":\"2024-11-01T16:16:45Z\",\"host\":\"::1\",\"port\":22002,\"status\":\"Finished\",\"exception_code\":0,\"exception_text\":\"\",\"query_finish_time\":\"2024-11-01T16:16:45Z\",\"query_duration_ms\":4} +" + .as_bytes(); + let ddl = DistributedDdlQueue::parse(&log, data).unwrap(); + + let expected_result = vec![ + DistributedDdlQueue{ + entry: "query-0000000000".to_string(), + entry_version: 5, + initiator_host: "ixchel".to_string(), + initiator_port: 22001, + cluster: "oximeter_cluster".to_string(), + query: "CREATE DATABASE IF NOT EXISTS db1 UUID 'a49757e4-179e-42bd-866f-93ac43136e2d' ON CLUSTER oximeter_cluster".to_string(), + settings: BTreeMap::from([ + ("load_balancing".to_string(), "random".to_string()), +]), + query_create_time: "2024-11-01T16:16:45Z".parse::>().unwrap(), + host: Ipv6Addr::from_str("::1").unwrap(), + port: 22001, + exception_code: 0, + exception_text: "".to_string(), + status: "Finished".to_string(), + query_finish_time: "2024-11-01T16:16:45Z".parse::>().unwrap(), + query_duration_ms: 4, + }, + DistributedDdlQueue{ + entry: "query-0000000000".to_string(), + entry_version: 5, + initiator_host: "ixchel".to_string(), + initiator_port: 22001, + cluster: "oximeter_cluster".to_string(), + query: "CREATE DATABASE IF NOT EXISTS db1 UUID 'a49757e4-179e-42bd-866f-93ac43136e2d' ON CLUSTER oximeter_cluster".to_string(), + settings: BTreeMap::from([ + ("load_balancing".to_string(), "random".to_string()), +]), + query_create_time: "2024-11-01T16:16:45Z".parse::>().unwrap(), + host: Ipv6Addr::from_str("::1").unwrap(), + port: 22002, + exception_code: 0, + exception_text: "".to_string(), + status: "Finished".to_string(), + query_finish_time: "2024-11-01T16:16:45Z".parse::>().unwrap(), + query_duration_ms: 4, + }, + ]; + assert!(ddl == expected_result); + } + + #[test] + fn test_empty_distributed_ddl_queries_parse_success() { + let log = log(); + let data = "".as_bytes(); + let ddl = DistributedDdlQueue::parse(&log, data).unwrap(); + + let expected_result = vec![]; + assert!(ddl == expected_result); + } + + #[test] + fn test_misshapen_distributed_ddl_queries_parse_fail() { + let log = log(); + let data = + "{\"entry\":\"query-0000000000\",\"initiator_host\":\"ixchel\",\"initiator_port\":22001,\"cluster\":\"oximeter_cluster\",\"query\":\"CREATE DATABASE IF NOT EXISTS db1 UUID 'a49757e4-179e-42bd-866f-93ac43136e2d' ON CLUSTER oximeter_cluster\",\"settings\":{\"load_balancing\":\"random\"},\"query_create_time\":\"2024-11-01T16:16:45Z\",\"host\":\"::1\",\"port\":22001,\"status\":\"Finished\",\"exception_code\":0,\"exception_text\":\"\",\"query_finish_time\":\"2024-11-01T16:16:45Z\",\"query_duration_ms\":4} +" +.as_bytes(); + let result = DistributedDdlQueue::parse(&log, data); + + let error = result.unwrap_err(); + let root_cause = error.root_cause(); + + assert_eq!( + format!("{}", root_cause), + "missing field `entry_version` at line 1 column 454", + ); + } } diff --git a/clickhouse-admin/types/testutils/replica-server-config.xml b/clickhouse-admin/types/testutils/replica-server-config.xml index cd79cf4a68..3aeacd073d 100644 --- a/clickhouse-admin/types/testutils/replica-server-config.xml +++ b/clickhouse-admin/types/testutils/replica-server-config.xml @@ -72,6 +72,11 @@ 1000 + + + 1.0 + + 1 1 diff --git a/clients/clickhouse-admin-server-client/src/lib.rs b/clients/clickhouse-admin-server-client/src/lib.rs index 3092160d65..73fe828824 100644 --- a/clients/clickhouse-admin-server-client/src/lib.rs +++ b/clients/clickhouse-admin-server-client/src/lib.rs @@ -4,6 +4,7 @@ //! Interface for making API requests to a clickhouse-admin-server server //! running in an omicron zone. +use std::clone::Clone; progenitor::generate_api!( spec = "../../openapi/clickhouse-admin-server.json", diff --git a/clients/dpd-client/build.rs b/clients/dpd-client/build.rs index 02a685632c..313c1a452f 100644 --- a/clients/dpd-client/build.rs +++ b/clients/dpd-client/build.rs @@ -15,7 +15,6 @@ use anyhow::Context; use anyhow::Result; use omicron_zone_package::config::Config; use omicron_zone_package::package::PackageSource; -use progenitor::TypePatch; use quote::quote; use std::env; use std::fs; @@ -89,22 +88,7 @@ fn main() -> Result<()> { slog::debug!(state.log, "client response"; "result" => ?result); } }) - .with_patch("LinkId", &TypePatch::default() - .with_derive("Eq") - .with_derive("PartialEq") - ) - .with_patch("LinkCreate", &TypePatch::default() - .with_derive("Eq") - .with_derive("PartialEq") - ) - .with_patch("LinkSettings", &TypePatch::default() - .with_derive("Eq") - .with_derive("PartialEq") - ) - .with_patch("PortSettings", &TypePatch::default() - .with_derive("Eq") - .with_derive("PartialEq") - ) + .with_derive("PartialEq") ) .generate_tokens(&spec) .with_context(|| { diff --git a/clients/repo-depot-client/Cargo.toml b/clients/repo-depot-client/Cargo.toml new file mode 100644 index 0000000000..858c75632f --- /dev/null +++ b/clients/repo-depot-client/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "repo-depot-client" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[lints] +workspace = true + +[dependencies] +omicron-workspace-hack.workspace = true +progenitor.workspace = true +reqwest.workspace = true +schemars.workspace = true +serde.workspace = true +slog.workspace = true diff --git a/clients/repo-depot-client/src/lib.rs b/clients/repo-depot-client/src/lib.rs new file mode 100644 index 0000000000..69e21cdaf3 --- /dev/null +++ b/clients/repo-depot-client/src/lib.rs @@ -0,0 +1,24 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Interface for Sled Agent's Repo Depot to make API requests. + +progenitor::generate_api!( + spec = "../../openapi/repo-depot.json", + inner_type = slog::Logger, + pre_hook = (|log: &slog::Logger, request: &reqwest::Request| { + slog::debug!(log, "client request"; + "method" => %request.method(), + "uri" => %request.url(), + "body" => ?&request.body(), + ); + }), + post_hook = (|log: &slog::Logger, result: &Result<_, _>| { + slog::debug!(log, "client response"; "result" => ?result); + }), + derives = [schemars::JsonSchema], +); + +/// A type alias for errors returned by this crate. +pub type ClientError = crate::Error; diff --git a/clients/sled-agent-client/src/lib.rs b/clients/sled-agent-client/src/lib.rs index 304a93439a..800123b116 100644 --- a/clients/sled-agent-client/src/lib.rs +++ b/clients/sled-agent-client/src/lib.rs @@ -31,6 +31,7 @@ progenitor::generate_api!( BgpConfig = { derives = [Eq, Hash] }, BgpPeerConfig = { derives = [Eq, Hash] }, LldpPortConfig = { derives = [Eq, Hash, PartialOrd, Ord] }, + TxEqConfig = { derives = [Eq, Hash] }, OmicronPhysicalDiskConfig = { derives = [Eq, Hash, PartialOrd, Ord] }, PortConfigV2 = { derives = [Eq, Hash] }, RouteConfig = { derives = [Eq, Hash] }, @@ -390,36 +391,6 @@ impl From } } -impl From - for types::SledIdentifiers -{ - fn from( - value: omicron_common::api::internal::shared::SledIdentifiers, - ) -> Self { - Self { - model: value.model, - rack_id: value.rack_id, - revision: value.revision, - serial: value.serial, - sled_id: value.sled_id, - } - } -} - -impl From - for omicron_common::api::internal::shared::SledIdentifiers -{ - fn from(value: types::SledIdentifiers) -> Self { - Self { - model: value.model, - rack_id: value.rack_id, - revision: value.revision, - serial: value.serial, - sled_id: value.sled_id, - } - } -} - /// Exposes additional [`Client`] interfaces for use by the test suite. These /// are bonus endpoints, not generated in the real client. #[async_trait] diff --git a/common/src/address.rs b/common/src/address.rs index 7cf00d5228..7e6d68ebc8 100644 --- a/common/src/address.rs +++ b/common/src/address.rs @@ -29,6 +29,7 @@ pub const MIN_PORT: u16 = u16::MIN; pub const DNS_PORT: u16 = 53; pub const DNS_HTTP_PORT: u16 = 5353; pub const SLED_AGENT_PORT: u16 = 12345; +pub const REPO_DEPOT_PORT: u16 = 12348; pub const COCKROACH_PORT: u16 = 32221; pub const COCKROACH_ADMIN_PORT: u16 = 32222; diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 419e5e94c9..18343ac44a 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -2358,6 +2358,10 @@ pub struct SwitchPortSettingsView { /// Link-layer discovery protocol (LLDP) settings. pub link_lldp: Vec, + /// TX equalization settings. These are optional, and most links will not + /// need them. + pub tx_eq: Vec>, + /// Layer 3 interface settings. pub interfaces: Vec, @@ -2500,6 +2504,9 @@ pub struct SwitchPortLinkConfig { /// link. pub lldp_link_config_id: Option, + /// The tx_eq configuration id for this link. + pub tx_eq_config_id: Option, + /// The name of this link. pub link_name: String, @@ -2544,6 +2551,34 @@ pub struct LldpLinkConfig { pub management_ip: Option, } +/// Per-port tx-eq overrides. This can be used to fine-tune the transceiver +/// equalization settings to improve signal integrity. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct TxEqConfig { + /// Pre-cursor tap1 + pub pre1: Option, + /// Pre-cursor tap2 + pub pre2: Option, + /// Main tap + pub main: Option, + /// Post-cursor tap2 + pub post2: Option, + /// Post-cursor tap1 + pub post1: Option, +} + +impl From for TxEqConfig { + fn from(x: crate::api::internal::shared::TxEqConfig) -> TxEqConfig { + TxEqConfig { + pre1: x.pre1, + pre2: x.pre2, + main: x.main, + post2: x.post2, + post1: x.post1, + } + } +} + /// Describes the kind of an switch interface. #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] #[serde(rename_all = "snake_case")] diff --git a/common/src/api/internal/shared.rs b/common/src/api/internal/shared.rs index a5d93ada2b..5b1a528b36 100644 --- a/common/src/api/internal/shared.rs +++ b/common/src/api/internal/shared.rs @@ -458,6 +458,24 @@ pub struct LldpPortConfig { pub management_addrs: Option>, } +/// Per-port tx-eq overrides. This can be used to fine-tune the transceiver +/// equalization settings to improve signal integrity. +#[derive( + Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema, +)] +pub struct TxEqConfig { + /// Pre-cursor tap1 + pub pre1: Option, + /// Pre-cursor tap2 + pub pre2: Option, + /// Main tap + pub main: Option, + /// Post-cursor tap2 + pub post2: Option, + /// Post-cursor tap1 + pub post1: Option, +} + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] pub struct PortConfigV2 { /// The set of routes associated with this port. @@ -479,6 +497,8 @@ pub struct PortConfigV2 { pub autoneg: bool, /// LLDP configuration for this port pub lldp: Option, + /// TX-EQ configuration for this port + pub tx_eq: Option, } /// A set of switch uplinks. @@ -497,11 +517,17 @@ pub struct HostPortConfig { pub addrs: Vec, pub lldp: Option, + pub tx_eq: Option, } impl From for HostPortConfig { fn from(x: PortConfigV2) -> Self { - Self { port: x.port, addrs: x.addresses, lldp: x.lldp.clone() } + Self { + port: x.port, + addrs: x.addresses, + lldp: x.lldp.clone(), + tx_eq: x.tx_eq, + } } } diff --git a/dev-tools/ls-apis/api-manifest.toml b/dev-tools/ls-apis/api-manifest.toml index b273b908fc..ab5dd4dec8 100644 --- a/dev-tools/ls-apis/api-manifest.toml +++ b/dev-tools/ls-apis/api-manifest.toml @@ -278,6 +278,11 @@ client_package_name = "sled-agent-client" label = "Sled Agent" server_package_name = "sled-agent-api" +[[apis]] +client_package_name = "repo-depot-client" +label = "Repo Depot API" +server_package_name = "repo-depot-api" + [[apis]] client_package_name = "wicketd-client" label = "Wicketd" diff --git a/dev-tools/ls-apis/tests/api_dependencies.out b/dev-tools/ls-apis/tests/api_dependencies.out index 938091cefb..aee8cd7a70 100644 --- a/dev-tools/ls-apis/tests/api_dependencies.out +++ b/dev-tools/ls-apis/tests/api_dependencies.out @@ -71,6 +71,9 @@ Propolis (client: propolis-client) Crucible Repair (client: repair-client) consumed by: crucible-downstairs (crucible/downstairs) via 1 path +Repo Depot API (client: repo-depot-client) + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path + Sled Agent (client: sled-agent-client) consumed by: dpd (dendrite/dpd) via 1 path consumed by: omicron-nexus (omicron/nexus) via 7 paths diff --git a/dev-tools/omdb/src/bin/omdb/db.rs b/dev-tools/omdb/src/bin/omdb/db.rs index 9c43d0d670..fc9bb9cc2f 100644 --- a/dev-tools/omdb/src/bin/omdb/db.rs +++ b/dev-tools/omdb/src/bin/omdb/db.rs @@ -50,6 +50,7 @@ use indicatif::ProgressDrawTarget; use indicatif::ProgressStyle; use internal_dns_types::names::ServiceName; use ipnetwork::IpNetwork; +use itertools::Itertools; use nexus_config::PostgresConfigWithUrl; use nexus_db_model::Dataset; use nexus_db_model::Disk; @@ -1299,10 +1300,10 @@ async fn lookup_project( #[derive(Tabled)] #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] struct DiskIdentity { - name: String, id: Uuid, size: String, state: String, + name: String, } impl From<&'_ db::model::Disk> for DiskIdentity { @@ -3325,17 +3326,14 @@ async fn cmd_db_instance_info( println!("\n{:=<80}", "== ATTACHED DISKS "); check_limit(&disks, fetch_opts.fetch_limit, ctx); - let table = if fetch_opts.include_deleted { + let mut table = if fetch_opts.include_deleted { tabled::Table::new(disks.iter().map(MaybeDeletedDiskRow::from)) - .with(tabled::settings::Style::empty()) - .with(tabled::settings::Padding::new(0, 1, 0, 0)) - .to_string() } else { tabled::Table::new(disks.iter().map(DiskRow::from)) - .with(tabled::settings::Style::empty()) - .with(tabled::settings::Padding::new(0, 1, 0, 0)) - .to_string() }; + table + .with(tabled::settings::Style::empty()) + .with(tabled::settings::Padding::new(0, 1, 0, 0)); println!("{table}"); } @@ -3502,11 +3500,11 @@ struct VmmStateRow { #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] struct CustomerInstanceRow { id: String, - name: String, state: String, propolis_id: MaybePropolisId, sled_id: MaybeSledId, host_serial: String, + name: String, } /// Run `omdb db instances`: list data about customer VMs. @@ -5192,6 +5190,7 @@ async fn cmd_db_inventory_collections_show( let nerrors = inv_collection_print_errors(&collection).await?; inv_collection_print_devices(&collection, &long_string_formatter).await?; inv_collection_print_sleds(&collection); + inv_collection_print_keeper_membership(&collection); if nerrors > 0 { eprintln!( @@ -5517,6 +5516,24 @@ fn inv_collection_print_sleds(collection: &Collection) { } } +fn inv_collection_print_keeper_membership(collection: &Collection) { + println!("\nKEEPER MEMBERSHIP"); + for k in &collection.clickhouse_keeper_cluster_membership { + println!("\n queried keeper: {}", k.queried_keeper); + println!( + " leader_committed_log_index: {}", + k.leader_committed_log_index + ); + + let s = k.raft_config.iter().join(", "); + println!(" raft config: {s}"); + } + if collection.clickhouse_keeper_cluster_membership.is_empty() { + println!("No membership retrieved."); + } + println!(""); +} + #[derive(Debug)] struct LongStringFormatter { show_long_strings: bool, diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index 8d67cba3c6..cb05bb575b 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -35,6 +35,7 @@ use nexus_client::types::SagaState; use nexus_client::types::SledSelector; use nexus_client::types::UninitializedSledId; use nexus_db_queries::db::lookup::LookupPath; +use nexus_db_queries::db::DataStore; use nexus_inventory::now_db_precision; use nexus_saga_recovery::LastPass; use nexus_types::deployment::Blueprint; @@ -64,6 +65,7 @@ use slog_error_chain::InlineErrorChain; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::str::FromStr; +use std::sync::Arc; use tabled::settings::object::Columns; use tabled::settings::Padding; use tabled::Tabled; @@ -2709,6 +2711,27 @@ async fn cmd_nexus_sled_expunge( args: &SledExpungeArgs, omdb: &Omdb, log: &slog::Logger, + destruction_token: DestructiveOperationToken, +) -> Result<(), anyhow::Error> { + let datastore = args.db_url_opts.connect(omdb, log).await?; + let result = cmd_nexus_sled_expunge_with_datastore( + &datastore, + client, + args, + log, + destruction_token, + ) + .await; + datastore.terminate().await; + result +} + +// `omdb nexus sleds expunge`, but borrowing a datastore +async fn cmd_nexus_sled_expunge_with_datastore( + datastore: &Arc, + client: &nexus_client::Client, + args: &SledExpungeArgs, + log: &slog::Logger, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { // This is an extremely dangerous and irreversible operation. We put a @@ -2720,7 +2743,6 @@ async fn cmd_nexus_sled_expunge( // most recent inventory collection use nexus_db_queries::context::OpContext; - let datastore = args.db_url_opts.connect(omdb, log).await?; let opctx = OpContext::for_tests(log.clone(), datastore.clone()); let opctx = &opctx; @@ -2800,11 +2822,30 @@ async fn cmd_nexus_sled_expunge_disk( args: &DiskExpungeArgs, omdb: &Omdb, log: &slog::Logger, + destruction_token: DestructiveOperationToken, +) -> Result<(), anyhow::Error> { + let datastore = args.db_url_opts.connect(omdb, log).await?; + let result = cmd_nexus_sled_expunge_disk_with_datastore( + &datastore, + client, + args, + log, + destruction_token, + ) + .await; + datastore.terminate().await; + result +} + +async fn cmd_nexus_sled_expunge_disk_with_datastore( + datastore: &Arc, + client: &nexus_client::Client, + args: &DiskExpungeArgs, + log: &slog::Logger, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { use nexus_db_queries::context::OpContext; - let datastore = args.db_url_opts.connect(omdb, log).await?; let opctx = OpContext::for_tests(log.clone(), datastore.clone()); let opctx = &opctx; diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index 1e99dbd3a8..6974c0b36b 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -2,7 +2,7 @@ EXECUTING COMMAND: omdb ["db", "disks", "list"] termination: Exited(0) --------------------------------------------- stdout: -NAME ID SIZE STATE ATTACHED_TO +ID SIZE STATE NAME ATTACHED_TO --------------------------------------------- stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable @@ -52,7 +52,7 @@ EXECUTING COMMAND: omdb ["db", "instances"] termination: Exited(0) --------------------------------------------- stdout: -ID NAME STATE PROPOLIS_ID SLED_ID HOST_SERIAL +ID STATE PROPOLIS_ID SLED_ID HOST_SERIAL NAME --------------------------------------------- stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable diff --git a/dev-tools/openapi-manager/Cargo.toml b/dev-tools/openapi-manager/Cargo.toml index 211e134016..d32477caf3 100644 --- a/dev-tools/openapi-manager/Cargo.toml +++ b/dev-tools/openapi-manager/Cargo.toml @@ -12,9 +12,9 @@ anyhow.workspace = true atomicwrites.workspace = true bootstrap-agent-api.workspace = true camino.workspace = true +clap.workspace = true clickhouse-admin-api.workspace = true cockroach-admin-api.workspace = true -clap.workspace = true dns-server-api.workspace = true dropshot.workspace = true fs-err.workspace = true @@ -24,13 +24,14 @@ installinator-api.workspace = true nexus-external-api.workspace = true nexus-internal-api.workspace = true omicron-workspace-hack.workspace = true -openapiv3.workspace = true openapi-lint.workspace = true openapi-manager-types.workspace = true +openapiv3.workspace = true owo-colors.workspace = true oximeter-api.workspace = true +repo-depot-api.workspace = true serde_json.workspace = true -sled-agent-api.workspace = true similar.workspace = true +sled-agent-api.workspace = true supports-color.workspace = true wicketd-api.workspace = true diff --git a/dev-tools/openapi-manager/src/spec.rs b/dev-tools/openapi-manager/src/spec.rs index dafcebac05..ff55bbeff5 100644 --- a/dev-tools/openapi-manager/src/spec.rs +++ b/dev-tools/openapi-manager/src/spec.rs @@ -121,6 +121,15 @@ pub fn all_apis() -> Vec { filename: "oximeter.json", extra_validation: None, }, + ApiSpec { + title: "Oxide TUF Repo Depot API", + version: "0.0.1", + description: "API for fetching update artifacts", + boundary: ApiBoundary::Internal, + api_description: repo_depot_api::repo_depot_api_mod::stub_api_description, + filename: "repo-depot.json", + extra_validation: None, + }, ApiSpec { title: "Oxide Sled Agent API", version: "0.0.1", diff --git a/dev-tools/reconfigurator-cli/tests/output/cmd-example-stdout b/dev-tools/reconfigurator-cli/tests/output/cmd-example-stdout index 93e705c6af..48664c5e6d 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmd-example-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmd-example-stdout @@ -220,6 +220,59 @@ to: blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a fake-vendor fake-model serial-d792c8cb-7490-40cb-bb1c-d4917242edf4 + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_088ed702-551e-453b-80d7-57700372a844/crucible 10eea692-add6-4430-ba17-2c9ca58082b5 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/clickhouse e5ed5cac-9580-4016-84c2-dc6b8464b9ce none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/debug 6fb8df45-63f4-4f51-972a-29904a575c6e 100 GiB none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/internal_dns 64053399-514e-4e01-822c-e81e7187ef64 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone 9e68dbfc-4ccb-4bff-ba97-5d81425fc584 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_clickhouse_fe79023f-c5d5-4be5-ad2c-da4e9e9237e4 ac51a283-2d5d-4b96-a922-9688cd489315 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_crucible_f97aa057-6485-45d0-9cb4-4af5b0831d48 e4ef7aab-4a43-4ce0-a5cf-1436735ee712 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_crucible_pantry_eaec16c0-0d44-4847-b2d6-31a5151bae52 02df7e3b-b094-4c0e-9292-2e874ea8382c none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_internal_dns_8b8f7c02-7a18-4268-b045-2e286b464c5d 7848aeea-725e-4871-8c60-c25102c77d1d none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_nexus_94b45ce9-d3d8-413a-a76b-865da1f67930 a892e4f7-1893-44b1-92bd-bbc080850c9e none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_ntp_c67dd9a4-0d6c-4e9f-b28d-20003f211f7d 701c81e2-41fa-490a-aa2e-0387aeb210ed none none off + oxp_09e51697-abad-47c0-a193-eaf74bc5d3cd/crucible 9edffe2a-5df5-4f42-9421-82ba7c9416ab none none off + oxp_09e51697-abad-47c0-a193-eaf74bc5d3cd/crypt/debug 63433fe0-87aa-4189-ad00-72bfa9923d28 100 GiB none off + oxp_09e51697-abad-47c0-a193-eaf74bc5d3cd/crypt/zone 2a69ae17-3329-429e-b99b-e4d607451490 none none off + oxp_09e51697-abad-47c0-a193-eaf74bc5d3cd/crypt/zone/oxz_crucible_054f64a5-182c-4c28-8994-d2e082550201 31c95682-7bae-4182-8e43-54c403a2a35c none none off + oxp_3a512d49-edbe-47f3-8d0b-6051bfdc4044/crucible 8fac3fae-eadc-4391-ba80-2f3b8d6cb708 none none off + oxp_3a512d49-edbe-47f3-8d0b-6051bfdc4044/crypt/debug 476cb7b1-06a2-4abc-9b59-157eba6b4fa9 100 GiB none off + oxp_3a512d49-edbe-47f3-8d0b-6051bfdc4044/crypt/zone 290f96c3-031b-496c-937d-9b157ecdc0fb none none off + oxp_3a512d49-edbe-47f3-8d0b-6051bfdc4044/crypt/zone/oxz_crucible_3b5bffea-e5ed-44df-8468-fd4fa69757d8 9df6db90-95c6-45bc-aba7-70bb72d87c7c none none off + oxp_40517680-aa77-413c-bcf4-b9041dcf6612/crucible fec44f34-a838-4a3d-9fa2-70133f3db3a1 none none off + oxp_40517680-aa77-413c-bcf4-b9041dcf6612/crypt/debug cf3dc221-a35a-4f16-935f-635cb87dc399 100 GiB none off + oxp_40517680-aa77-413c-bcf4-b9041dcf6612/crypt/zone 751bad28-e0f5-494e-91c1-432ded709c2f none none off + oxp_40517680-aa77-413c-bcf4-b9041dcf6612/crypt/zone/oxz_crucible_bc095417-e2f0-4e95-b390-9cc3fc6e3c6d 6169ada0-c238-44e8-a67b-d50eecb419ea none none off + oxp_78d3cb96-9295-4644-bf78-2e32191c71f9/crucible 8d4092ca-d9fd-4fa3-9d12-a44f638fccc6 none none off + oxp_78d3cb96-9295-4644-bf78-2e32191c71f9/crypt/debug dbdb5ae6-9064-427f-850f-8c45d07c6af4 100 GiB none off + oxp_78d3cb96-9295-4644-bf78-2e32191c71f9/crypt/zone 90efb135-dded-4acf-81b5-9ee75669b1d8 none none off + oxp_78d3cb96-9295-4644-bf78-2e32191c71f9/crypt/zone/oxz_crucible_95ad9a1d-4063-4874-974c-2fc92830be27 923c27c2-0687-4799-9cba-a439e5c56854 none none off + oxp_853595e7-77da-404e-bc35-aba77478d55c/crucible 8bf51c09-0d33-42d9-9ac0-2761d2f84378 none none off + oxp_853595e7-77da-404e-bc35-aba77478d55c/crypt/debug 32713f0b-b162-4e23-bd9e-2cc4b208f98f 100 GiB none off + oxp_853595e7-77da-404e-bc35-aba77478d55c/crypt/zone b9360096-7b96-450b-b6d8-33fc06dd3f07 none none off + oxp_853595e7-77da-404e-bc35-aba77478d55c/crypt/zone/oxz_crucible_53dd7fa4-899e-49ed-9fc2-48222db3e20d 5475d130-9244-4e3b-8e21-bc3233bc3074 none none off + oxp_8926e0e7-65d9-4e2e-ac6d-f1298af81ef1/crucible fe743e12-6be9-48c6-a1e8-c129f00b43d9 none none off + oxp_8926e0e7-65d9-4e2e-ac6d-f1298af81ef1/crypt/debug dbf4924f-cdb3-4d1c-8e32-5c566c3e4a98 100 GiB none off + oxp_8926e0e7-65d9-4e2e-ac6d-f1298af81ef1/crypt/zone 70c3efb9-1f72-42d4-a0e1-c8c573c833af none none off + oxp_8926e0e7-65d9-4e2e-ac6d-f1298af81ef1/crypt/zone/oxz_crucible_d90401f1-fbc2-42cb-bf17-309ee0f922fe 2db8cea6-9af5-4b9f-8db1-d59006b8fca6 none none off + oxp_9c0b9151-17f3-4857-94cc-b5bfcd402326/crucible b4a8e70b-dfe8-4392-9518-8fe6dd952b94 none none off + oxp_9c0b9151-17f3-4857-94cc-b5bfcd402326/crypt/debug abab81af-ef4d-4832-862d-1760b2fe6e65 100 GiB none off + oxp_9c0b9151-17f3-4857-94cc-b5bfcd402326/crypt/zone f7dee56f-2cb0-470c-8fba-8c60c56d6bb9 none none off + oxp_9c0b9151-17f3-4857-94cc-b5bfcd402326/crypt/zone/oxz_crucible_7db307d4-a6ed-4c47-bddf-6759161bf64a 3c5a1b55-b719-4c61-9865-46aeb8736164 none none off + oxp_d61354fa-48d2-47c6-90bf-546e3ed1708b/crucible ff0d3669-e237-4797-854b-dcb51d6c289d none none off + oxp_d61354fa-48d2-47c6-90bf-546e3ed1708b/crypt/debug 6735caf5-1dd2-464b-93e2-f9080f9ff58d 100 GiB none off + oxp_d61354fa-48d2-47c6-90bf-546e3ed1708b/crypt/zone 76f5cbd5-9e1f-4ccb-b4a8-65cc8bcc4afc none none off + oxp_d61354fa-48d2-47c6-90bf-546e3ed1708b/crypt/zone/oxz_crucible_e8f994c0-0a1b-40e6-8db1-40a8ca89e503 25e14454-4215-46a2-9c24-50aed0d1b131 none none off + oxp_d792c8cb-7490-40cb-bb1c-d4917242edf4/crucible cfa8cf24-8dc1-43c8-bf88-3e8d4b058f7a none none off + oxp_d792c8cb-7490-40cb-bb1c-d4917242edf4/crypt/debug edaad63c-f9ae-4552-8197-2c95d138da93 100 GiB none off + oxp_d792c8cb-7490-40cb-bb1c-d4917242edf4/crypt/zone bc098466-0717-42ce-9c42-5da3b5c8de4a none none off + oxp_d792c8cb-7490-40cb-bb1c-d4917242edf4/crypt/zone/oxz_crucible_e9bf481e-323e-466e-842f-8107078c7137 d81cd9b7-7df5-4cb5-86d1-126a9a700fb8 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -259,6 +312,57 @@ to: blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a fake-vendor fake-model serial-d7410a1c-e01d-49a4-be9c-f861f086760a + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crucible dddf5d66-838c-4253-a42f-075c8e264bf1 none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/debug ea2cc4fe-93ce-437e-9a1f-1d203bca8784 100 GiB none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/internal_dns 393388ac-4fbe-4e3b-840f-17afed583ee8 none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone 2dc77339-9cbf-4d86-89a8-e65a64f35673 none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_crucible_cc1dc86d-bd6f-4929-aa4a-9619012e9393 965c1e70-f075-4dcb-b25a-d856bb44551b none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_crucible_pantry_728db429-8621-4e1e-9915-282aadfa27d1 d2434e6c-1c0d-4443-a756-db277c0fd684 none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_internal_dns_e7dd3e98-7fe7-4827-be7f-395ff9a5f542 5da5c7e8-b603-4eb9-acc2-3e137e9c9b8f none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_nexus_c8aa84a5-a802-46c9-adcd-d61e9c8393c9 cfff8484-c5a9-4430-a46d-92e62577b3bb none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_ntp_4f2eb088-7d28-4c4e-a27c-746400ec65ba ebf8caa8-c75d-4318-9759-41dea4572e8b none none off + oxp_43ae0f4e-b0cf-4d74-8636-df0567ba01e6/crucible 4b6e313b-839a-405f-b7b2-c85456f77d6e none none off + oxp_43ae0f4e-b0cf-4d74-8636-df0567ba01e6/crypt/debug 11eedda2-f710-49db-b358-6a763e530643 100 GiB none off + oxp_43ae0f4e-b0cf-4d74-8636-df0567ba01e6/crypt/zone d1549ccd-d9a8-4899-9dc2-5991ae3e0300 none none off + oxp_43ae0f4e-b0cf-4d74-8636-df0567ba01e6/crypt/zone/oxz_crucible_e8971ab3-fb7d-4ad8-aae3-7f2fe87c51f3 6e8c587e-b25b-43d3-b05f-72713da53cd0 none none off + oxp_4e9806d0-41cd-48c2-86ef-7f815c3ce3b1/crucible 3759d324-78ac-4855-a89f-cdf28d6bff92 none none off + oxp_4e9806d0-41cd-48c2-86ef-7f815c3ce3b1/crypt/debug 6a8adc00-8b5a-4135-b20a-c862c509a7d7 100 GiB none off + oxp_4e9806d0-41cd-48c2-86ef-7f815c3ce3b1/crypt/zone b3de9a5e-01e0-48e6-b032-f53de00285c1 none none off + oxp_4e9806d0-41cd-48c2-86ef-7f815c3ce3b1/crypt/zone/oxz_crucible_a999e5fa-3edc-4dac-919a-d7b554cdae58 f7da941d-47da-40ad-9d21-1a36a83c9d48 none none off + oxp_70bb6d98-111f-4015-9d97-9ef1b2d6dcac/crucible 9c53961d-4259-4215-a083-669234e4ed33 none none off + oxp_70bb6d98-111f-4015-9d97-9ef1b2d6dcac/crypt/debug c784968a-27ca-40a3-9570-736593bba5ab 100 GiB none off + oxp_70bb6d98-111f-4015-9d97-9ef1b2d6dcac/crypt/zone 609f9bcf-d01e-4d0b-89f6-96e75397b7f6 none none off + oxp_70bb6d98-111f-4015-9d97-9ef1b2d6dcac/crypt/zone/oxz_crucible_b416f299-c23c-46c8-9820-be2b66ffea0a 2de47948-bd82-46b5-8341-f9381d1817cf none none off + oxp_7ce5029f-703c-4c08-8164-9af9cf1acf23/crucible 50a7450d-4069-4f18-9d80-de273e801af8 none none off + oxp_7ce5029f-703c-4c08-8164-9af9cf1acf23/crypt/debug 71d6b4d8-1204-4f0f-b0e3-f47946fb7a8c 100 GiB none off + oxp_7ce5029f-703c-4c08-8164-9af9cf1acf23/crypt/zone 3e6a4121-1ea9-4361-ad1c-14c7dd945f37 none none off + oxp_7ce5029f-703c-4c08-8164-9af9cf1acf23/crypt/zone/oxz_crucible_fae49024-6cec-444d-a6c4-83658ab015a4 b49a89b5-7aad-4dac-be75-e10960fcb530 none none off + oxp_b113c11f-44e6-4fb4-a56e-1d91bd652faf/crucible d4b204bb-9b37-463b-b5d4-fb0573b87988 none none off + oxp_b113c11f-44e6-4fb4-a56e-1d91bd652faf/crypt/debug 1c8bff2b-a7ca-4852-b94b-7cff9026bb2d 100 GiB none off + oxp_b113c11f-44e6-4fb4-a56e-1d91bd652faf/crypt/zone 82ff9ea6-429c-449a-89d9-c3a42a5ecb06 none none off + oxp_b113c11f-44e6-4fb4-a56e-1d91bd652faf/crypt/zone/oxz_crucible_cd3bb540-e605-465f-8c62-177ac482d850 eae5919a-5b33-4ec7-bd27-163120ba55a4 none none off + oxp_bf149c80-2498-481c-9989-6344da914081/crucible 4af3d49c-22c1-4ea5-9992-bdff0467d023 none none off + oxp_bf149c80-2498-481c-9989-6344da914081/crypt/debug badbe224-0a39-4ccf-92d5-847905b00bc5 100 GiB none off + oxp_bf149c80-2498-481c-9989-6344da914081/crypt/zone 49a3e230-e22a-4bc3-8bb4-f188e5b23c19 none none off + oxp_bf149c80-2498-481c-9989-6344da914081/crypt/zone/oxz_crucible_f52aa245-7e1b-46c0-8a31-e09725f02caf 4ffbef53-46e9-4698-afc9-12fc9e22e6af none none off + oxp_c69b6237-09f9-45aa-962c-5dbdd1d894be/crucible c7bdd567-1585-4e5d-8e00-9fc8a582b9a8 none none off + oxp_c69b6237-09f9-45aa-962c-5dbdd1d894be/crypt/debug b2b44420-be84-4974-99a2-60b03c895811 100 GiB none off + oxp_c69b6237-09f9-45aa-962c-5dbdd1d894be/crypt/zone 8b47156e-0faa-4cba-ba05-d1e0d1d3c382 none none off + oxp_c69b6237-09f9-45aa-962c-5dbdd1d894be/crypt/zone/oxz_crucible_b5d5491d-b3aa-4727-8b55-f66e0581ea4f 962343a7-2374-4d3e-b8df-99dded0f1bc9 none none off + oxp_ccd5a87b-00ae-42ad-85da-b37d70436cb1/crucible b058857e-6b86-474c-ac69-2851f9cd00c9 none none off + oxp_ccd5a87b-00ae-42ad-85da-b37d70436cb1/crypt/debug 523fd0cd-8d22-4f7a-a3b0-3fd0f4f88852 100 GiB none off + oxp_ccd5a87b-00ae-42ad-85da-b37d70436cb1/crypt/zone 9e78cb9f-d533-44e5-8641-d6be0fb5868b none none off + oxp_ccd5a87b-00ae-42ad-85da-b37d70436cb1/crypt/zone/oxz_crucible_09937ebb-bb6a-495b-bc97-b58076b70a78 819b9236-ad12-45ea-8dbf-d06c394003d7 none none off + oxp_d7410a1c-e01d-49a4-be9c-f861f086760a/crucible ab8c57d6-494c-4845-b9bb-9d7e3ff5f43d none none off + oxp_d7410a1c-e01d-49a4-be9c-f861f086760a/crypt/debug d8246b47-51dc-44c0-8a74-edbb80330f19 100 GiB none off + oxp_d7410a1c-e01d-49a4-be9c-f861f086760a/crypt/zone 8915abc3-13c9-4ad2-86cd-f6dcf933aca9 none none off + oxp_d7410a1c-e01d-49a4-be9c-f861f086760a/crypt/zone/oxz_crucible_f3628f0a-2301-4fc8-bcbf-961199771731 32706642-7c88-4dde-b2e5-2c66237051ef none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -297,6 +401,57 @@ to: blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a fake-vendor fake-model serial-fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crucible 0c44736d-76e8-464f-9c5b-1df35743d849 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/debug 5caa41ff-a043-44bd-ac2f-6bd35f686953 100 GiB none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/internal_dns d10b2deb-f770-40d3-8b4f-03a14e47ccde none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone 01d56c05-8fd5-48f0-8fde-7b8a4df1f1e5 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_crucible_413d3e02-e19f-400a-9718-a662347538f0 ba98af8c-d358-4a22-b172-767b17075307 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_crucible_pantry_315a3670-d019-425c-b7a6-c9429428b671 8426cb5d-176f-46f0-8111-f1792f20833c none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_internal_dns_8b47e1e8-0396-4e44-a4a5-ea891405c9f2 fee2f9a5-fef1-4217-ae29-822983085f71 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_nexus_b43ce109-90d6-46f9-9df0-8c68bfe6d4a0 eeee1d0d-cc48-45c2-8718-2bebfdf4d5e3 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_ntp_cbe91cdc-cbb6-4760-aece-6ce08b67e85a 668b7c91-1466-43e3-bb8f-becdb1c20493 none none off + oxp_5265edc6-debf-4687-a758-a9746893ebd3/crucible 64a69811-e0a8-427d-8f4b-2fe77f4eaaef none none off + oxp_5265edc6-debf-4687-a758-a9746893ebd3/crypt/debug e21195e1-d410-4ba0-a78c-2fb91310ab9e 100 GiB none off + oxp_5265edc6-debf-4687-a758-a9746893ebd3/crypt/zone 30e61fef-5a8c-498a-835e-ffada88d73b7 none none off + oxp_5265edc6-debf-4687-a758-a9746893ebd3/crypt/zone/oxz_crucible_b5443ebd-1f5b-448c-8edc-b4ca25c25db1 037fc003-d941-4639-8ab8-ce67926b107d none none off + oxp_532fbd69-b472-4445-86af-4c4c85afb313/crucible 6ca42c2b-ac96-4d42-bfb4-120bbab62fe8 none none off + oxp_532fbd69-b472-4445-86af-4c4c85afb313/crypt/debug aa9d2a0b-37bc-450a-a18b-53ac178dfa9a 100 GiB none off + oxp_532fbd69-b472-4445-86af-4c4c85afb313/crypt/zone 4ca68409-860f-4606-9118-223a99784048 none none off + oxp_532fbd69-b472-4445-86af-4c4c85afb313/crypt/zone/oxz_crucible_e135441d-637e-4de9-8023-5ea0096347f3 bb5be931-233b-4741-a9cd-951f5977ac57 none none off + oxp_54fd6fa6-ce3c-4abe-8c9d-7e107e159e84/crucible ecfa453d-3c6c-4ac2-8be1-439208df2eac none none off + oxp_54fd6fa6-ce3c-4abe-8c9d-7e107e159e84/crypt/debug 3fd90a28-4348-4331-b52d-ad933520add3 100 GiB none off + oxp_54fd6fa6-ce3c-4abe-8c9d-7e107e159e84/crypt/zone 6cd435eb-07d5-40bc-a50b-c9bf61fc3de2 none none off + oxp_54fd6fa6-ce3c-4abe-8c9d-7e107e159e84/crypt/zone/oxz_crucible_bb55534c-1042-4af4-ad2f-9590803695ac e67687c5-2699-4630-9092-9a42d2ff160f none none off + oxp_8562317c-4736-4cfc-9292-7dcab96a6fee/crucible abbbaf81-fd62-498b-8e18-221c68065b2c none none off + oxp_8562317c-4736-4cfc-9292-7dcab96a6fee/crypt/debug ce331e9e-8f0c-4c3a-9565-561d6760710d 100 GiB none off + oxp_8562317c-4736-4cfc-9292-7dcab96a6fee/crypt/zone 40fb0fd8-3ad0-4fb3-a22f-59c628905e93 none none off + oxp_8562317c-4736-4cfc-9292-7dcab96a6fee/crypt/zone/oxz_crucible_6d725df0-0189-4429-b270-3eeb891d39c8 46135910-450d-4949-9fab-a9b0a54a68c1 none none off + oxp_9a1327e4-d11b-4d98-8454-8c41862e9832/crucible 5afdb92a-28ab-4de1-954d-6192fea1a18b none none off + oxp_9a1327e4-d11b-4d98-8454-8c41862e9832/crypt/debug f166a5e8-6790-42eb-8bff-6856d4471247 100 GiB none off + oxp_9a1327e4-d11b-4d98-8454-8c41862e9832/crypt/zone 430ebbbf-a9ef-488e-9e1b-54e74ff73922 none none off + oxp_9a1327e4-d11b-4d98-8454-8c41862e9832/crypt/zone/oxz_crucible_6cb330f9-4609-4d6c-98ad-b5cc34245813 943211c9-063e-4860-8d94-54d55c499349 none none off + oxp_bf9d6692-64bc-459a-87dd-e7a83080a210/crucible d0f2d2a4-95f6-4767-92b7-05a5a3b2822c none none off + oxp_bf9d6692-64bc-459a-87dd-e7a83080a210/crypt/debug 89eef0ca-8367-4343-9390-c17d53beb0e1 100 GiB none off + oxp_bf9d6692-64bc-459a-87dd-e7a83080a210/crypt/zone 101a1e8b-362a-49bf-b209-dcd4aedaa3fb none none off + oxp_bf9d6692-64bc-459a-87dd-e7a83080a210/crypt/zone/oxz_crucible_fee71ee6-da42-4a7f-a00e-f56b6a3327ce bdd191e4-cd8f-49c5-b887-7e7c9ba81051 none none off + oxp_ce1c13f3-bef2-4306-b0f2-4e39bd4a18b6/crucible d8a968b5-8cc3-4dbc-a22d-723adce3d82f none none off + oxp_ce1c13f3-bef2-4306-b0f2-4e39bd4a18b6/crypt/debug 93255645-6328-4f3f-83df-a67f1206fdbf 100 GiB none off + oxp_ce1c13f3-bef2-4306-b0f2-4e39bd4a18b6/crypt/zone 358ef33b-11da-4ba6-b0a2-6d881e4d3b29 none none off + oxp_ce1c13f3-bef2-4306-b0f2-4e39bd4a18b6/crypt/zone/oxz_crucible_de65f128-30f7-422b-a234-d1fc8dd6ef78 65569fd8-0748-4bf6-9ee9-c9cb6fbbb43b none none off + oxp_f931ec80-a3e3-4adb-a8ba-fa5adbd2294c/crucible 82c773a2-9a18-4c31-8a65-fb5419f9b853 none none off + oxp_f931ec80-a3e3-4adb-a8ba-fa5adbd2294c/crypt/debug 95a0e096-5120-43a1-9b6d-ab685ce100c6 100 GiB none off + oxp_f931ec80-a3e3-4adb-a8ba-fa5adbd2294c/crypt/zone 455e96ed-bdc6-4d46-9774-22a299a513b2 none none off + oxp_f931ec80-a3e3-4adb-a8ba-fa5adbd2294c/crypt/zone/oxz_crucible_d14c165f-6370-4cce-9dba-3c6deb762cfc 3eeca505-f147-4afa-a032-1643c2ef87ef none none off + oxp_fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c/crucible 317e2495-e4b4-4fe5-9e46-a582b797f0f1 none none off + oxp_fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c/crypt/debug abb97aa4-9deb-45b5-8844-51200e8e190d 100 GiB none off + oxp_fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c/crypt/zone bcf1d9c3-3c77-4f80-9f07-52d0abf4cd25 none none off + oxp_fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c/crypt/zone/oxz_crucible_c4296f9f-f902-4fc7-b896-178e56e60732 13f5b476-412a-4879-903b-5a9ac4ca6fd3 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -355,6 +510,59 @@ to: blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a fake-vendor fake-model serial-d792c8cb-7490-40cb-bb1c-d4917242edf4 + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_088ed702-551e-453b-80d7-57700372a844/crucible 10eea692-add6-4430-ba17-2c9ca58082b5 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/clickhouse e5ed5cac-9580-4016-84c2-dc6b8464b9ce none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/debug 6fb8df45-63f4-4f51-972a-29904a575c6e 100 GiB none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/internal_dns 64053399-514e-4e01-822c-e81e7187ef64 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone 9e68dbfc-4ccb-4bff-ba97-5d81425fc584 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_clickhouse_fe79023f-c5d5-4be5-ad2c-da4e9e9237e4 ac51a283-2d5d-4b96-a922-9688cd489315 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_crucible_f97aa057-6485-45d0-9cb4-4af5b0831d48 e4ef7aab-4a43-4ce0-a5cf-1436735ee712 none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_crucible_pantry_eaec16c0-0d44-4847-b2d6-31a5151bae52 02df7e3b-b094-4c0e-9292-2e874ea8382c none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_internal_dns_8b8f7c02-7a18-4268-b045-2e286b464c5d 7848aeea-725e-4871-8c60-c25102c77d1d none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_nexus_94b45ce9-d3d8-413a-a76b-865da1f67930 a892e4f7-1893-44b1-92bd-bbc080850c9e none none off + oxp_088ed702-551e-453b-80d7-57700372a844/crypt/zone/oxz_ntp_c67dd9a4-0d6c-4e9f-b28d-20003f211f7d 701c81e2-41fa-490a-aa2e-0387aeb210ed none none off + oxp_09e51697-abad-47c0-a193-eaf74bc5d3cd/crucible 9edffe2a-5df5-4f42-9421-82ba7c9416ab none none off + oxp_09e51697-abad-47c0-a193-eaf74bc5d3cd/crypt/debug 63433fe0-87aa-4189-ad00-72bfa9923d28 100 GiB none off + oxp_09e51697-abad-47c0-a193-eaf74bc5d3cd/crypt/zone 2a69ae17-3329-429e-b99b-e4d607451490 none none off + oxp_09e51697-abad-47c0-a193-eaf74bc5d3cd/crypt/zone/oxz_crucible_054f64a5-182c-4c28-8994-d2e082550201 31c95682-7bae-4182-8e43-54c403a2a35c none none off + oxp_3a512d49-edbe-47f3-8d0b-6051bfdc4044/crucible 8fac3fae-eadc-4391-ba80-2f3b8d6cb708 none none off + oxp_3a512d49-edbe-47f3-8d0b-6051bfdc4044/crypt/debug 476cb7b1-06a2-4abc-9b59-157eba6b4fa9 100 GiB none off + oxp_3a512d49-edbe-47f3-8d0b-6051bfdc4044/crypt/zone 290f96c3-031b-496c-937d-9b157ecdc0fb none none off + oxp_3a512d49-edbe-47f3-8d0b-6051bfdc4044/crypt/zone/oxz_crucible_3b5bffea-e5ed-44df-8468-fd4fa69757d8 9df6db90-95c6-45bc-aba7-70bb72d87c7c none none off + oxp_40517680-aa77-413c-bcf4-b9041dcf6612/crucible fec44f34-a838-4a3d-9fa2-70133f3db3a1 none none off + oxp_40517680-aa77-413c-bcf4-b9041dcf6612/crypt/debug cf3dc221-a35a-4f16-935f-635cb87dc399 100 GiB none off + oxp_40517680-aa77-413c-bcf4-b9041dcf6612/crypt/zone 751bad28-e0f5-494e-91c1-432ded709c2f none none off + oxp_40517680-aa77-413c-bcf4-b9041dcf6612/crypt/zone/oxz_crucible_bc095417-e2f0-4e95-b390-9cc3fc6e3c6d 6169ada0-c238-44e8-a67b-d50eecb419ea none none off + oxp_78d3cb96-9295-4644-bf78-2e32191c71f9/crucible 8d4092ca-d9fd-4fa3-9d12-a44f638fccc6 none none off + oxp_78d3cb96-9295-4644-bf78-2e32191c71f9/crypt/debug dbdb5ae6-9064-427f-850f-8c45d07c6af4 100 GiB none off + oxp_78d3cb96-9295-4644-bf78-2e32191c71f9/crypt/zone 90efb135-dded-4acf-81b5-9ee75669b1d8 none none off + oxp_78d3cb96-9295-4644-bf78-2e32191c71f9/crypt/zone/oxz_crucible_95ad9a1d-4063-4874-974c-2fc92830be27 923c27c2-0687-4799-9cba-a439e5c56854 none none off + oxp_853595e7-77da-404e-bc35-aba77478d55c/crucible 8bf51c09-0d33-42d9-9ac0-2761d2f84378 none none off + oxp_853595e7-77da-404e-bc35-aba77478d55c/crypt/debug 32713f0b-b162-4e23-bd9e-2cc4b208f98f 100 GiB none off + oxp_853595e7-77da-404e-bc35-aba77478d55c/crypt/zone b9360096-7b96-450b-b6d8-33fc06dd3f07 none none off + oxp_853595e7-77da-404e-bc35-aba77478d55c/crypt/zone/oxz_crucible_53dd7fa4-899e-49ed-9fc2-48222db3e20d 5475d130-9244-4e3b-8e21-bc3233bc3074 none none off + oxp_8926e0e7-65d9-4e2e-ac6d-f1298af81ef1/crucible fe743e12-6be9-48c6-a1e8-c129f00b43d9 none none off + oxp_8926e0e7-65d9-4e2e-ac6d-f1298af81ef1/crypt/debug dbf4924f-cdb3-4d1c-8e32-5c566c3e4a98 100 GiB none off + oxp_8926e0e7-65d9-4e2e-ac6d-f1298af81ef1/crypt/zone 70c3efb9-1f72-42d4-a0e1-c8c573c833af none none off + oxp_8926e0e7-65d9-4e2e-ac6d-f1298af81ef1/crypt/zone/oxz_crucible_d90401f1-fbc2-42cb-bf17-309ee0f922fe 2db8cea6-9af5-4b9f-8db1-d59006b8fca6 none none off + oxp_9c0b9151-17f3-4857-94cc-b5bfcd402326/crucible b4a8e70b-dfe8-4392-9518-8fe6dd952b94 none none off + oxp_9c0b9151-17f3-4857-94cc-b5bfcd402326/crypt/debug abab81af-ef4d-4832-862d-1760b2fe6e65 100 GiB none off + oxp_9c0b9151-17f3-4857-94cc-b5bfcd402326/crypt/zone f7dee56f-2cb0-470c-8fba-8c60c56d6bb9 none none off + oxp_9c0b9151-17f3-4857-94cc-b5bfcd402326/crypt/zone/oxz_crucible_7db307d4-a6ed-4c47-bddf-6759161bf64a 3c5a1b55-b719-4c61-9865-46aeb8736164 none none off + oxp_d61354fa-48d2-47c6-90bf-546e3ed1708b/crucible ff0d3669-e237-4797-854b-dcb51d6c289d none none off + oxp_d61354fa-48d2-47c6-90bf-546e3ed1708b/crypt/debug 6735caf5-1dd2-464b-93e2-f9080f9ff58d 100 GiB none off + oxp_d61354fa-48d2-47c6-90bf-546e3ed1708b/crypt/zone 76f5cbd5-9e1f-4ccb-b4a8-65cc8bcc4afc none none off + oxp_d61354fa-48d2-47c6-90bf-546e3ed1708b/crypt/zone/oxz_crucible_e8f994c0-0a1b-40e6-8db1-40a8ca89e503 25e14454-4215-46a2-9c24-50aed0d1b131 none none off + oxp_d792c8cb-7490-40cb-bb1c-d4917242edf4/crucible cfa8cf24-8dc1-43c8-bf88-3e8d4b058f7a none none off + oxp_d792c8cb-7490-40cb-bb1c-d4917242edf4/crypt/debug edaad63c-f9ae-4552-8197-2c95d138da93 100 GiB none off + oxp_d792c8cb-7490-40cb-bb1c-d4917242edf4/crypt/zone bc098466-0717-42ce-9c42-5da3b5c8de4a none none off + oxp_d792c8cb-7490-40cb-bb1c-d4917242edf4/crypt/zone/oxz_crucible_e9bf481e-323e-466e-842f-8107078c7137 d81cd9b7-7df5-4cb5-86d1-126a9a700fb8 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -394,6 +602,57 @@ to: blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a fake-vendor fake-model serial-d7410a1c-e01d-49a4-be9c-f861f086760a + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crucible dddf5d66-838c-4253-a42f-075c8e264bf1 none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/debug ea2cc4fe-93ce-437e-9a1f-1d203bca8784 100 GiB none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/internal_dns 393388ac-4fbe-4e3b-840f-17afed583ee8 none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone 2dc77339-9cbf-4d86-89a8-e65a64f35673 none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_crucible_cc1dc86d-bd6f-4929-aa4a-9619012e9393 965c1e70-f075-4dcb-b25a-d856bb44551b none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_crucible_pantry_728db429-8621-4e1e-9915-282aadfa27d1 d2434e6c-1c0d-4443-a756-db277c0fd684 none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_internal_dns_e7dd3e98-7fe7-4827-be7f-395ff9a5f542 5da5c7e8-b603-4eb9-acc2-3e137e9c9b8f none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_nexus_c8aa84a5-a802-46c9-adcd-d61e9c8393c9 cfff8484-c5a9-4430-a46d-92e62577b3bb none none off + oxp_128b0f04-229b-48dc-9c5c-555cb5723ed8/crypt/zone/oxz_ntp_4f2eb088-7d28-4c4e-a27c-746400ec65ba ebf8caa8-c75d-4318-9759-41dea4572e8b none none off + oxp_43ae0f4e-b0cf-4d74-8636-df0567ba01e6/crucible 4b6e313b-839a-405f-b7b2-c85456f77d6e none none off + oxp_43ae0f4e-b0cf-4d74-8636-df0567ba01e6/crypt/debug 11eedda2-f710-49db-b358-6a763e530643 100 GiB none off + oxp_43ae0f4e-b0cf-4d74-8636-df0567ba01e6/crypt/zone d1549ccd-d9a8-4899-9dc2-5991ae3e0300 none none off + oxp_43ae0f4e-b0cf-4d74-8636-df0567ba01e6/crypt/zone/oxz_crucible_e8971ab3-fb7d-4ad8-aae3-7f2fe87c51f3 6e8c587e-b25b-43d3-b05f-72713da53cd0 none none off + oxp_4e9806d0-41cd-48c2-86ef-7f815c3ce3b1/crucible 3759d324-78ac-4855-a89f-cdf28d6bff92 none none off + oxp_4e9806d0-41cd-48c2-86ef-7f815c3ce3b1/crypt/debug 6a8adc00-8b5a-4135-b20a-c862c509a7d7 100 GiB none off + oxp_4e9806d0-41cd-48c2-86ef-7f815c3ce3b1/crypt/zone b3de9a5e-01e0-48e6-b032-f53de00285c1 none none off + oxp_4e9806d0-41cd-48c2-86ef-7f815c3ce3b1/crypt/zone/oxz_crucible_a999e5fa-3edc-4dac-919a-d7b554cdae58 f7da941d-47da-40ad-9d21-1a36a83c9d48 none none off + oxp_70bb6d98-111f-4015-9d97-9ef1b2d6dcac/crucible 9c53961d-4259-4215-a083-669234e4ed33 none none off + oxp_70bb6d98-111f-4015-9d97-9ef1b2d6dcac/crypt/debug c784968a-27ca-40a3-9570-736593bba5ab 100 GiB none off + oxp_70bb6d98-111f-4015-9d97-9ef1b2d6dcac/crypt/zone 609f9bcf-d01e-4d0b-89f6-96e75397b7f6 none none off + oxp_70bb6d98-111f-4015-9d97-9ef1b2d6dcac/crypt/zone/oxz_crucible_b416f299-c23c-46c8-9820-be2b66ffea0a 2de47948-bd82-46b5-8341-f9381d1817cf none none off + oxp_7ce5029f-703c-4c08-8164-9af9cf1acf23/crucible 50a7450d-4069-4f18-9d80-de273e801af8 none none off + oxp_7ce5029f-703c-4c08-8164-9af9cf1acf23/crypt/debug 71d6b4d8-1204-4f0f-b0e3-f47946fb7a8c 100 GiB none off + oxp_7ce5029f-703c-4c08-8164-9af9cf1acf23/crypt/zone 3e6a4121-1ea9-4361-ad1c-14c7dd945f37 none none off + oxp_7ce5029f-703c-4c08-8164-9af9cf1acf23/crypt/zone/oxz_crucible_fae49024-6cec-444d-a6c4-83658ab015a4 b49a89b5-7aad-4dac-be75-e10960fcb530 none none off + oxp_b113c11f-44e6-4fb4-a56e-1d91bd652faf/crucible d4b204bb-9b37-463b-b5d4-fb0573b87988 none none off + oxp_b113c11f-44e6-4fb4-a56e-1d91bd652faf/crypt/debug 1c8bff2b-a7ca-4852-b94b-7cff9026bb2d 100 GiB none off + oxp_b113c11f-44e6-4fb4-a56e-1d91bd652faf/crypt/zone 82ff9ea6-429c-449a-89d9-c3a42a5ecb06 none none off + oxp_b113c11f-44e6-4fb4-a56e-1d91bd652faf/crypt/zone/oxz_crucible_cd3bb540-e605-465f-8c62-177ac482d850 eae5919a-5b33-4ec7-bd27-163120ba55a4 none none off + oxp_bf149c80-2498-481c-9989-6344da914081/crucible 4af3d49c-22c1-4ea5-9992-bdff0467d023 none none off + oxp_bf149c80-2498-481c-9989-6344da914081/crypt/debug badbe224-0a39-4ccf-92d5-847905b00bc5 100 GiB none off + oxp_bf149c80-2498-481c-9989-6344da914081/crypt/zone 49a3e230-e22a-4bc3-8bb4-f188e5b23c19 none none off + oxp_bf149c80-2498-481c-9989-6344da914081/crypt/zone/oxz_crucible_f52aa245-7e1b-46c0-8a31-e09725f02caf 4ffbef53-46e9-4698-afc9-12fc9e22e6af none none off + oxp_c69b6237-09f9-45aa-962c-5dbdd1d894be/crucible c7bdd567-1585-4e5d-8e00-9fc8a582b9a8 none none off + oxp_c69b6237-09f9-45aa-962c-5dbdd1d894be/crypt/debug b2b44420-be84-4974-99a2-60b03c895811 100 GiB none off + oxp_c69b6237-09f9-45aa-962c-5dbdd1d894be/crypt/zone 8b47156e-0faa-4cba-ba05-d1e0d1d3c382 none none off + oxp_c69b6237-09f9-45aa-962c-5dbdd1d894be/crypt/zone/oxz_crucible_b5d5491d-b3aa-4727-8b55-f66e0581ea4f 962343a7-2374-4d3e-b8df-99dded0f1bc9 none none off + oxp_ccd5a87b-00ae-42ad-85da-b37d70436cb1/crucible b058857e-6b86-474c-ac69-2851f9cd00c9 none none off + oxp_ccd5a87b-00ae-42ad-85da-b37d70436cb1/crypt/debug 523fd0cd-8d22-4f7a-a3b0-3fd0f4f88852 100 GiB none off + oxp_ccd5a87b-00ae-42ad-85da-b37d70436cb1/crypt/zone 9e78cb9f-d533-44e5-8641-d6be0fb5868b none none off + oxp_ccd5a87b-00ae-42ad-85da-b37d70436cb1/crypt/zone/oxz_crucible_09937ebb-bb6a-495b-bc97-b58076b70a78 819b9236-ad12-45ea-8dbf-d06c394003d7 none none off + oxp_d7410a1c-e01d-49a4-be9c-f861f086760a/crucible ab8c57d6-494c-4845-b9bb-9d7e3ff5f43d none none off + oxp_d7410a1c-e01d-49a4-be9c-f861f086760a/crypt/debug d8246b47-51dc-44c0-8a74-edbb80330f19 100 GiB none off + oxp_d7410a1c-e01d-49a4-be9c-f861f086760a/crypt/zone 8915abc3-13c9-4ad2-86cd-f6dcf933aca9 none none off + oxp_d7410a1c-e01d-49a4-be9c-f861f086760a/crypt/zone/oxz_crucible_f3628f0a-2301-4fc8-bcbf-961199771731 32706642-7c88-4dde-b2e5-2c66237051ef none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -432,6 +691,57 @@ to: blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a fake-vendor fake-model serial-fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crucible 0c44736d-76e8-464f-9c5b-1df35743d849 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/debug 5caa41ff-a043-44bd-ac2f-6bd35f686953 100 GiB none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/internal_dns d10b2deb-f770-40d3-8b4f-03a14e47ccde none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone 01d56c05-8fd5-48f0-8fde-7b8a4df1f1e5 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_crucible_413d3e02-e19f-400a-9718-a662347538f0 ba98af8c-d358-4a22-b172-767b17075307 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_crucible_pantry_315a3670-d019-425c-b7a6-c9429428b671 8426cb5d-176f-46f0-8111-f1792f20833c none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_internal_dns_8b47e1e8-0396-4e44-a4a5-ea891405c9f2 fee2f9a5-fef1-4217-ae29-822983085f71 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_nexus_b43ce109-90d6-46f9-9df0-8c68bfe6d4a0 eeee1d0d-cc48-45c2-8718-2bebfdf4d5e3 none none off + oxp_44fa7024-c2bc-4d2c-b478-c4997e4aece8/crypt/zone/oxz_ntp_cbe91cdc-cbb6-4760-aece-6ce08b67e85a 668b7c91-1466-43e3-bb8f-becdb1c20493 none none off + oxp_5265edc6-debf-4687-a758-a9746893ebd3/crucible 64a69811-e0a8-427d-8f4b-2fe77f4eaaef none none off + oxp_5265edc6-debf-4687-a758-a9746893ebd3/crypt/debug e21195e1-d410-4ba0-a78c-2fb91310ab9e 100 GiB none off + oxp_5265edc6-debf-4687-a758-a9746893ebd3/crypt/zone 30e61fef-5a8c-498a-835e-ffada88d73b7 none none off + oxp_5265edc6-debf-4687-a758-a9746893ebd3/crypt/zone/oxz_crucible_b5443ebd-1f5b-448c-8edc-b4ca25c25db1 037fc003-d941-4639-8ab8-ce67926b107d none none off + oxp_532fbd69-b472-4445-86af-4c4c85afb313/crucible 6ca42c2b-ac96-4d42-bfb4-120bbab62fe8 none none off + oxp_532fbd69-b472-4445-86af-4c4c85afb313/crypt/debug aa9d2a0b-37bc-450a-a18b-53ac178dfa9a 100 GiB none off + oxp_532fbd69-b472-4445-86af-4c4c85afb313/crypt/zone 4ca68409-860f-4606-9118-223a99784048 none none off + oxp_532fbd69-b472-4445-86af-4c4c85afb313/crypt/zone/oxz_crucible_e135441d-637e-4de9-8023-5ea0096347f3 bb5be931-233b-4741-a9cd-951f5977ac57 none none off + oxp_54fd6fa6-ce3c-4abe-8c9d-7e107e159e84/crucible ecfa453d-3c6c-4ac2-8be1-439208df2eac none none off + oxp_54fd6fa6-ce3c-4abe-8c9d-7e107e159e84/crypt/debug 3fd90a28-4348-4331-b52d-ad933520add3 100 GiB none off + oxp_54fd6fa6-ce3c-4abe-8c9d-7e107e159e84/crypt/zone 6cd435eb-07d5-40bc-a50b-c9bf61fc3de2 none none off + oxp_54fd6fa6-ce3c-4abe-8c9d-7e107e159e84/crypt/zone/oxz_crucible_bb55534c-1042-4af4-ad2f-9590803695ac e67687c5-2699-4630-9092-9a42d2ff160f none none off + oxp_8562317c-4736-4cfc-9292-7dcab96a6fee/crucible abbbaf81-fd62-498b-8e18-221c68065b2c none none off + oxp_8562317c-4736-4cfc-9292-7dcab96a6fee/crypt/debug ce331e9e-8f0c-4c3a-9565-561d6760710d 100 GiB none off + oxp_8562317c-4736-4cfc-9292-7dcab96a6fee/crypt/zone 40fb0fd8-3ad0-4fb3-a22f-59c628905e93 none none off + oxp_8562317c-4736-4cfc-9292-7dcab96a6fee/crypt/zone/oxz_crucible_6d725df0-0189-4429-b270-3eeb891d39c8 46135910-450d-4949-9fab-a9b0a54a68c1 none none off + oxp_9a1327e4-d11b-4d98-8454-8c41862e9832/crucible 5afdb92a-28ab-4de1-954d-6192fea1a18b none none off + oxp_9a1327e4-d11b-4d98-8454-8c41862e9832/crypt/debug f166a5e8-6790-42eb-8bff-6856d4471247 100 GiB none off + oxp_9a1327e4-d11b-4d98-8454-8c41862e9832/crypt/zone 430ebbbf-a9ef-488e-9e1b-54e74ff73922 none none off + oxp_9a1327e4-d11b-4d98-8454-8c41862e9832/crypt/zone/oxz_crucible_6cb330f9-4609-4d6c-98ad-b5cc34245813 943211c9-063e-4860-8d94-54d55c499349 none none off + oxp_bf9d6692-64bc-459a-87dd-e7a83080a210/crucible d0f2d2a4-95f6-4767-92b7-05a5a3b2822c none none off + oxp_bf9d6692-64bc-459a-87dd-e7a83080a210/crypt/debug 89eef0ca-8367-4343-9390-c17d53beb0e1 100 GiB none off + oxp_bf9d6692-64bc-459a-87dd-e7a83080a210/crypt/zone 101a1e8b-362a-49bf-b209-dcd4aedaa3fb none none off + oxp_bf9d6692-64bc-459a-87dd-e7a83080a210/crypt/zone/oxz_crucible_fee71ee6-da42-4a7f-a00e-f56b6a3327ce bdd191e4-cd8f-49c5-b887-7e7c9ba81051 none none off + oxp_ce1c13f3-bef2-4306-b0f2-4e39bd4a18b6/crucible d8a968b5-8cc3-4dbc-a22d-723adce3d82f none none off + oxp_ce1c13f3-bef2-4306-b0f2-4e39bd4a18b6/crypt/debug 93255645-6328-4f3f-83df-a67f1206fdbf 100 GiB none off + oxp_ce1c13f3-bef2-4306-b0f2-4e39bd4a18b6/crypt/zone 358ef33b-11da-4ba6-b0a2-6d881e4d3b29 none none off + oxp_ce1c13f3-bef2-4306-b0f2-4e39bd4a18b6/crypt/zone/oxz_crucible_de65f128-30f7-422b-a234-d1fc8dd6ef78 65569fd8-0748-4bf6-9ee9-c9cb6fbbb43b none none off + oxp_f931ec80-a3e3-4adb-a8ba-fa5adbd2294c/crucible 82c773a2-9a18-4c31-8a65-fb5419f9b853 none none off + oxp_f931ec80-a3e3-4adb-a8ba-fa5adbd2294c/crypt/debug 95a0e096-5120-43a1-9b6d-ab685ce100c6 100 GiB none off + oxp_f931ec80-a3e3-4adb-a8ba-fa5adbd2294c/crypt/zone 455e96ed-bdc6-4d46-9774-22a299a513b2 none none off + oxp_f931ec80-a3e3-4adb-a8ba-fa5adbd2294c/crypt/zone/oxz_crucible_d14c165f-6370-4cce-9dba-3c6deb762cfc 3eeca505-f147-4afa-a032-1643c2ef87ef none none off + oxp_fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c/crucible 317e2495-e4b4-4fe5-9e46-a582b797f0f1 none none off + oxp_fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c/crypt/debug abb97aa4-9deb-45b5-8844-51200e8e190d 100 GiB none off + oxp_fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c/crypt/zone bcf1d9c3-3c77-4f80-9f07-52d0abf4cd25 none none off + oxp_fe1d5b9f-8db7-4e2d-bf17-c4b80e1f897c/crypt/zone/oxz_crucible_c4296f9f-f902-4fc7-b896-178e56e60732 13f5b476-412a-4879-903b-5a9ac4ca6fd3 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP diff --git a/flake.lock b/flake.lock index 2c0393f722..c2655cbe43 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1723175592, - "narHash": "sha256-M0xJ3FbDUc4fRZ84dPGx5VvgFsOzds77KiBMW/mMTnI=", + "lastModified": 1730200266, + "narHash": "sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5e0ca22929f3342b19569b21b2f3462f053e497b", + "rev": "807e9154dcb16384b1b765ebe9cd2bba2ac287fd", "type": "github" }, "original": { @@ -29,11 +29,11 @@ ] }, "locked": { - "lastModified": 1723429325, - "narHash": "sha256-4x/32xTCd+xCwFoI/kKSiCr5LQA2ZlyTRYXKEni5HR8=", + "lastModified": 1730428392, + "narHash": "sha256-2aRfq1P0usr+TlW9LUCoefqqpPum873ac0TgZzXYHKI=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "65e3dc0fe079fe8df087cd38f1fe6836a0373aad", + "rev": "17eda17f5596a84e92ba94160139eb70f3c3e734", "type": "github" }, "original": { diff --git a/illumos-utils/src/zfs.rs b/illumos-utils/src/zfs.rs index f92fd5d60f..fa09fb22c5 100644 --- a/illumos-utils/src/zfs.rs +++ b/illumos-utils/src/zfs.rs @@ -289,6 +289,28 @@ impl FromStr for DatasetProperties { } } +#[derive(Debug, Copy, Clone)] +pub enum PropertySource { + Local, + Default, + Inherited, + Temporary, + None, +} + +impl fmt::Display for PropertySource { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let ps = match self { + PropertySource::Local => "local", + PropertySource::Default => "default", + PropertySource::Inherited => "inherited", + PropertySource::Temporary => "temporary", + PropertySource::None => "none", + }; + write!(f, "{ps}") + } +} + #[cfg_attr(any(test, feature = "testing"), mockall::automock, allow(dead_code))] impl Zfs { /// Lists all datasets within a pool or existing dataset. @@ -646,7 +668,13 @@ impl Zfs { filesystem_name: &str, name: &str, ) -> Result { - Zfs::get_value(filesystem_name, &format!("oxide:{}", name)) + let property = format!("oxide:{name}"); + let [value] = Self::get_values( + filesystem_name, + &[&property], + Some(PropertySource::Local), + )?; + Ok(value) } /// Calls "zfs get" with a single value @@ -654,7 +682,7 @@ impl Zfs { filesystem_name: &str, name: &str, ) -> Result { - let [value] = Self::get_values(filesystem_name, &[name])?; + let [value] = Self::get_values(filesystem_name, &[name], None)?; Ok(value) } @@ -722,14 +750,24 @@ impl Zfs { // These methods don't work with mockall, so they exist in a separate impl block impl Zfs { /// Calls "zfs get" to acquire multiple values + /// + /// - `names`: The properties being acquired + /// - `source`: The optioanl property source (origin of the property) + /// Defaults to "all sources" when unspecified. pub fn get_values( filesystem_name: &str, names: &[&str; N], + source: Option, ) -> Result<[String; N], GetValueError> { let mut cmd = std::process::Command::new(PFEXEC); let all_names = names.into_iter().map(|n| *n).collect::>().join(","); - cmd.args(&[ZFS, "get", "-Ho", "value", &all_names, filesystem_name]); + + cmd.args(&[ZFS, "get", "-Ho", "value", &all_names]); + if let Some(source) = source { + cmd.args(&["-s", &source.to_string()]); + } + cmd.arg(filesystem_name); let output = execute(&mut cmd).map_err(|err| GetValueError { filesystem: filesystem_name.to_string(), name: format!("{:?}", names), diff --git a/nexus-config/src/nexus_config.rs b/nexus-config/src/nexus_config.rs index a0af65b6ec..82362f2f0d 100644 --- a/nexus-config/src/nexus_config.rs +++ b/nexus-config/src/nexus_config.rs @@ -250,8 +250,12 @@ pub struct SchemaConfig { /// Optional configuration for the timeseries database. #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] pub struct TimeseriesDbConfig { + /// The HTTP address of the ClickHouse server. #[serde(default, skip_serializing_if = "Option::is_none")] pub address: Option, + /// The native TCP address of the ClickHouse server. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub native_address: Option, } /// Configuration for the `Dendrite` dataplane daemon. @@ -774,7 +778,9 @@ impl std::fmt::Display for SchemeName { mod test { use super::*; - use omicron_common::address::{Ipv6Subnet, RACK_PREFIX}; + use omicron_common::address::{ + Ipv6Subnet, CLICKHOUSE_HTTP_PORT, CLICKHOUSE_TCP_PORT, RACK_PREFIX, + }; use omicron_common::api::internal::shared::SwitchLocation; use camino::{Utf8Path, Utf8PathBuf}; @@ -784,7 +790,7 @@ mod test { use dropshot::ConfigLoggingLevel; use std::collections::HashMap; use std::fs; - use std::net::{Ipv6Addr, SocketAddr}; + use std::net::{Ipv6Addr, SocketAddr, SocketAddrV6}; use std::str::FromStr; use std::time::Duration; @@ -889,6 +895,7 @@ mod test { if_exists = "fail" [timeseries_db] address = "[::1]:8123" + native_address = "[::1]:9000" [updates] trusted_root = "/path/to/root.json" [tunables] @@ -1007,7 +1014,20 @@ mod test { path: "/nonexistent/path".into() }, timeseries_db: TimeseriesDbConfig { - address: Some("[::1]:8123".parse().unwrap()) + address: Some(SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::LOCALHOST, + CLICKHOUSE_HTTP_PORT, + 0, + 0, + ))), + native_address: Some(SocketAddr::V6( + SocketAddrV6::new( + Ipv6Addr::LOCALHOST, + CLICKHOUSE_TCP_PORT, + 0, + 0, + ) + )), }, updates: Some(UpdatesConfig { trusted_root: Utf8PathBuf::from("/path/to/root.json"), diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index aea9c6a6bd..8a97e43639 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -145,6 +145,7 @@ table! { speed -> crate::SwitchLinkSpeedEnum, autoneg -> Bool, lldp_link_config_id -> Nullable, + tx_eq_config_id -> Nullable, } } @@ -164,6 +165,17 @@ table! { } } +table! { + tx_eq_config (id) { + id -> Uuid, + pre1 -> Nullable, + pre2 -> Nullable, + main -> Nullable, + post2 -> Nullable, + post1 -> Nullable, + } +} + table! { switch_port_settings_interface_config (id) { port_settings_id -> Uuid, diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index bc43152ace..14eba1cb1c 100644 --- a/nexus/db-model/src/schema_versions.rs +++ b/nexus/db-model/src/schema_versions.rs @@ -17,7 +17,7 @@ use std::collections::BTreeMap; /// /// This must be updated when you change the database schema. Refer to /// schema/crdb/README.adoc in the root of this repository for details. -pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(112, 0, 0); +pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(113, 0, 0); /// List of all past database schema versions, in *reverse* order /// @@ -29,6 +29,7 @@ static KNOWN_VERSIONS: Lazy> = Lazy::new(|| { // | leaving the first copy as an example for the next person. // v // KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"), + KnownVersion::new(113, "add-tx-eq"), KnownVersion::new(112, "blueprint-dataset"), KnownVersion::new(111, "drop-omicron-zone-underlay-address"), KnownVersion::new(110, "clickhouse-policy"), diff --git a/nexus/db-model/src/switch_port.rs b/nexus/db-model/src/switch_port.rs index 2420482cce..2db9ef04df 100644 --- a/nexus/db-model/src/switch_port.rs +++ b/nexus/db-model/src/switch_port.rs @@ -11,6 +11,7 @@ use crate::schema::{ switch_port_settings_group, switch_port_settings_groups, switch_port_settings_interface_config, switch_port_settings_link_config, switch_port_settings_port_config, switch_port_settings_route_config, + tx_eq_config, }; use crate::{impl_enum_type, SqlU32}; use crate::{SqlU16, SqlU8}; @@ -387,9 +388,11 @@ pub struct SwitchPortLinkConfig { pub fec: SwitchLinkFec, pub speed: SwitchLinkSpeed, pub autoneg: bool, + pub tx_eq_config_id: Option, } impl SwitchPortLinkConfig { + #[allow(clippy::too_many_arguments)] pub fn new( port_settings_id: Uuid, lldp_link_config_id: Uuid, @@ -398,6 +401,7 @@ impl SwitchPortLinkConfig { fec: SwitchLinkFec, speed: SwitchLinkSpeed, autoneg: bool, + tx_eq_config_id: Option, ) -> Self { Self { port_settings_id, @@ -407,6 +411,7 @@ impl SwitchPortLinkConfig { speed, autoneg, mtu: mtu.into(), + tx_eq_config_id, } } } @@ -416,6 +421,7 @@ impl Into for SwitchPortLinkConfig { external::SwitchPortLinkConfig { port_settings_id: self.port_settings_id, lldp_link_config_id: self.lldp_link_config_id, + tx_eq_config_id: self.tx_eq_config_id, link_name: self.link_name.clone(), mtu: self.mtu.into(), fec: self.fec.into(), @@ -494,6 +500,52 @@ impl Into for LldpLinkConfig { } } +#[derive( + Queryable, + Insertable, + Selectable, + Clone, + Debug, + Serialize, + Deserialize, + AsChangeset, +)] +#[diesel(table_name = tx_eq_config)] +pub struct TxEqConfig { + pub id: Uuid, + pub pre1: Option, + pub pre2: Option, + pub main: Option, + pub post2: Option, + pub post1: Option, +} + +impl TxEqConfig { + pub fn new( + pre1: Option, + pre2: Option, + main: Option, + post2: Option, + post1: Option, + ) -> Self { + Self { id: Uuid::new_v4(), pre1, pre2, main, post2, post1 } + } +} + +// This converts the internal database version of the config into the +// user-facing version. +impl Into for TxEqConfig { + fn into(self) -> external::TxEqConfig { + external::TxEqConfig { + pre1: self.pre1, + pre2: self.pre2, + main: self.main, + post2: self.post2, + post1: self.post1, + } + } +} + #[derive( Queryable, Insertable, diff --git a/nexus/db-queries/src/db/datastore/deployment/external_networking.rs b/nexus/db-queries/src/db/datastore/deployment/external_networking.rs index e48dcf37c2..99d7cffdfb 100644 --- a/nexus/db-queries/src/db/datastore/deployment/external_networking.rs +++ b/nexus/db-queries/src/db/datastore/deployment/external_networking.rs @@ -869,8 +869,7 @@ mod tests { #[tokio::test] async fn test_allocate_external_networking() { // Set up. - usdt::register_probes().unwrap(); - let logctx = dev::test_setup_log("test_service_ip_list"); + let logctx = dev::test_setup_log("test_allocate_external_networking"); let db = TestDatabase::new_with_datastore(&logctx.log).await; let (opctx, datastore) = (db.opctx(), db.datastore()); @@ -1130,8 +1129,7 @@ mod tests { #[tokio::test] async fn test_deallocate_external_networking() { // Set up. - usdt::register_probes().unwrap(); - let logctx = dev::test_setup_log("test_service_ip_list"); + let logctx = dev::test_setup_log("test_deallocate_external_networking"); let db = TestDatabase::new_with_datastore(&logctx.log).await; let (opctx, datastore) = (db.opctx(), db.datastore()); diff --git a/nexus/db-queries/src/db/datastore/external_ip.rs b/nexus/db-queries/src/db/datastore/external_ip.rs index 7311e12da8..9447d552e4 100644 --- a/nexus/db-queries/src/db/datastore/external_ip.rs +++ b/nexus/db-queries/src/db/datastore/external_ip.rs @@ -1161,7 +1161,6 @@ mod tests { #[tokio::test] async fn test_service_ip_list() { - usdt::register_probes().unwrap(); let logctx = dev::test_setup_log("test_service_ip_list"); let db = TestDatabase::new_with_datastore(&logctx.log).await; let (opctx, datastore) = (db.opctx(), db.datastore()); diff --git a/nexus/db-queries/src/db/datastore/network_interface.rs b/nexus/db-queries/src/db/datastore/network_interface.rs index f1415817a9..e4cf7079d7 100644 --- a/nexus/db-queries/src/db/datastore/network_interface.rs +++ b/nexus/db-queries/src/db/datastore/network_interface.rs @@ -920,7 +920,6 @@ mod tests { #[tokio::test] async fn test_service_network_interfaces_list() { - usdt::register_probes().unwrap(); let logctx = dev::test_setup_log("test_service_network_interfaces_list"); let db = TestDatabase::new_with_datastore(&logctx.log).await; diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 2a0f47f0f6..e0092459b5 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -19,7 +19,7 @@ use crate::db::model::{ SwitchPortAddressConfig, SwitchPortBgpPeerConfig, SwitchPortConfig, SwitchPortLinkConfig, SwitchPortRouteConfig, SwitchPortSettings, SwitchPortSettingsGroup, SwitchPortSettingsGroups, - SwitchVlanInterfaceConfig, + SwitchVlanInterfaceConfig, TxEqConfig, }; use crate::db::pagination::paginated; use crate::transaction_retry::OptionalError; @@ -102,6 +102,7 @@ pub struct SwitchPortSettingsCombinedResult { pub port: SwitchPortConfig, pub links: Vec, pub link_lldp: Vec, + pub tx_eq: Vec>, pub interfaces: Vec, pub vlan_interfaces: Vec, pub routes: Vec, @@ -117,6 +118,7 @@ impl SwitchPortSettingsCombinedResult { groups: Vec::new(), links: Vec::new(), link_lldp: Vec::new(), + tx_eq: Vec::new(), interfaces: Vec::new(), vlan_interfaces: Vec::new(), routes: Vec::new(), @@ -136,6 +138,11 @@ impl Into groups: self.groups.into_iter().map(Into::into).collect(), links: self.links.into_iter().map(Into::into).collect(), link_lldp: self.link_lldp.into_iter().map(Into::into).collect(), + tx_eq: self + .tx_eq + .into_iter() + .map(|t| if let Some(t) = t { Some(t.into()) } else { None }) + .collect(), interfaces: self.interfaces.into_iter().map(Into::into).collect(), vlan_interfaces: self .vlan_interfaces @@ -466,6 +473,31 @@ impl DataStore { .load_async::(&conn) .await?; + let tx_eq_ids_and_nulls :Vec>= result + .links + .iter() + .map(|link| link.tx_eq_config_id) + .collect(); + let tx_eq_ids: Vec = tx_eq_ids_and_nulls + .iter() + .cloned() + .flatten() + .collect(); + + use db::schema::tx_eq_config; + let configs = tx_eq_config::dsl::tx_eq_config + .filter(tx_eq_config::id.eq_any(tx_eq_ids)) + .select(TxEqConfig::as_select()) + .limit(1) + .load_async::(&conn) + .await?; + result.tx_eq = tx_eq_ids_and_nulls.iter().map(|x| + if let Some(id) = x { + configs.iter().find(|c| c.id == *id).cloned() + } else { + None + }).collect(); + // get the interface configs use db::schema::switch_port_settings_interface_config::{ self as interface_config, dsl as interface_config_dsl, @@ -887,12 +919,15 @@ impl DataStore { Ok(v) => Ok(Some(v)), Err(e) => { let msg = "failed to check if bgp peer exists in switch port settings"; - error!(opctx.log, "{msg}"; "error" => ?e); match e { diesel::result::Error::NotFound => { + debug!(opctx.log, "{msg}"; "error" => ?e); Ok(None) - } - _ => Err(err.bail(Error::internal_error(msg))), + }, + _ => { + error!(opctx.log, "{msg}"; "error" => ?e); + Err(err.bail(Error::internal_error(msg))) + } } } }?; @@ -1112,6 +1147,7 @@ async fn do_switch_port_settings_create( switch_port_settings_port_config::dsl as port_config_dsl, switch_port_settings_route_config::dsl as route_config_dsl, switch_vlan_interface_config::dsl as vlan_config_dsl, + tx_eq_config::dsl as tx_eq_config_dsl, }; // create the top level port settings object @@ -1146,6 +1182,7 @@ async fn do_switch_port_settings_create( port: db_port_config, links: Vec::new(), link_lldp: Vec::new(), + tx_eq: Vec::new(), interfaces: Vec::new(), vlan_interfaces: Vec::new(), routes: Vec::new(), @@ -1158,6 +1195,7 @@ async fn do_switch_port_settings_create( let mut lldp_config = Vec::with_capacity(params.links.len()); let mut link_config = Vec::with_capacity(params.links.len()); + let mut tx_eq_config = Vec::with_capacity(params.links.len()); for (link_name, c) in ¶ms.links { let lldp_link_config = LldpLinkConfig::new( @@ -1172,6 +1210,20 @@ async fn do_switch_port_settings_create( let lldp_config_id = lldp_link_config.id; lldp_config.push(lldp_link_config); + let tx_eq_config_id = match &c.tx_eq { + Some(t) => { + let config = + TxEqConfig::new(t.pre1, t.pre2, t.main, t.post2, t.post1); + let tx_eq_config_id = config.id; + tx_eq_config.push(Some(config)); + Some(tx_eq_config_id) + } + _ => { + tx_eq_config.push(None); + None + } + }; + link_config.push(SwitchPortLinkConfig::new( psid, lldp_config_id, @@ -1180,6 +1232,7 @@ async fn do_switch_port_settings_create( c.fec.into(), c.speed.into(), c.autoneg, + tx_eq_config_id, )); } result.link_lldp = @@ -1189,6 +1242,16 @@ async fn do_switch_port_settings_create( .get_results_async(conn) .await?; + // We want to insert the Some(config) values into the table, but preserve the + // full vector of None/Some values. + let v: Vec = tx_eq_config.iter().flatten().cloned().collect(); + let _ = diesel::insert_into(tx_eq_config_dsl::tx_eq_config) + .values(v) + .returning(TxEqConfig::as_returning()) + .get_results_async(conn) + .await?; + result.tx_eq = tx_eq_config; + result.links = diesel::insert_into(link_config_dsl::switch_port_settings_link_config) .values(link_config) @@ -1517,6 +1580,15 @@ async fn do_switch_port_settings_delete( .execute_async(conn) .await?; + // delete tx_eq configs + use db::schema::tx_eq_config; + let tx_eq_ids: Vec = + links.iter().filter_map(|link| link.tx_eq_config_id).collect(); + diesel::delete(tx_eq_config::dsl::tx_eq_config) + .filter(tx_eq_config::id.eq_any(tx_eq_ids)) + .execute_async(conn) + .await?; + // delete interface configs use db::schema::switch_port_settings_interface_config::{ self as sps_interface_config, dsl as interface_config_dsl, diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 0538e24633..30033e96a2 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -2792,7 +2792,6 @@ mod tests { // `project_create_vpc_raw` call. #[tokio::test] async fn test_project_create_vpc_raw_returns_none_on_vni_exhaustion() { - usdt::register_probes().unwrap(); let logctx = dev::test_setup_log( "test_project_create_vpc_raw_returns_none_on_vni_exhaustion", ); @@ -2898,7 +2897,6 @@ mod tests { // and then check that we correctly retry #[tokio::test] async fn test_project_create_vpc_retries() { - usdt::register_probes().unwrap(); let logctx = dev::test_setup_log("test_project_create_vpc_retries"); let log = &logctx.log; let db = TestDatabase::new_with_datastore(log).await; @@ -3042,7 +3040,6 @@ mod tests { #[tokio::test] async fn test_vpc_resolve_to_sleds_uses_current_target_blueprint() { // Test setup. - usdt::register_probes().unwrap(); let logctx = dev::test_setup_log( "test_vpc_resolve_to_sleds_uses_current_target_blueprint", ); @@ -3404,7 +3401,6 @@ mod tests { // and that these resolve to the v4/6 subnets of each. #[tokio::test] async fn test_vpc_system_router_sync_to_subnets() { - usdt::register_probes().unwrap(); let logctx = dev::test_setup_log("test_vpc_system_router_sync_to_subnets"); let log = &logctx.log; @@ -3631,7 +3627,6 @@ mod tests { // of an instance NIC. #[tokio::test] async fn test_vpc_router_rule_instance_resolve() { - usdt::register_probes().unwrap(); let logctx = dev::test_setup_log("test_vpc_router_rule_instance_resolve"); let log = &logctx.log; diff --git a/nexus/db-queries/src/db/pool.rs b/nexus/db-queries/src/db/pool.rs index c42158a64f..ab032e022a 100644 --- a/nexus/db-queries/src/db/pool.rs +++ b/nexus/db-queries/src/db/pool.rs @@ -84,17 +84,20 @@ impl Pool { /// Creating this pool does not necessarily wait for connections to become /// available, as backends may shift over time. pub fn new(log: &Logger, resolver: &QorbResolver) -> Self { - // Make sure diesel-dtrace's USDT probes are enabled. - usdt::register_probes().expect("Failed to register USDT DTrace probes"); - let resolver = resolver.for_service(ServiceName::Cockroach); let connector = make_postgres_connector(log); - let policy = Policy::default(); - Pool { - inner: qorb::pool::Pool::new(resolver, connector, policy), - terminated: std::sync::atomic::AtomicBool::new(false), - } + let inner = match qorb::pool::Pool::new(resolver, connector, policy) { + Ok(pool) => { + debug!(log, "registered USDT probes"); + pool + } + Err(err) => { + error!(log, "failed to register USDT probes"); + err.into_inner() + } + }; + Pool { inner, terminated: std::sync::atomic::AtomicBool::new(false) } } /// Creates a new qorb-backed connection pool to a single instance of the @@ -105,17 +108,20 @@ impl Pool { /// /// In production, [Self::new] should be preferred. pub fn new_single_host(log: &Logger, db_config: &DbConfig) -> Self { - // Make sure diesel-dtrace's USDT probes are enabled. - usdt::register_probes().expect("Failed to register USDT DTrace probes"); - let resolver = make_single_host_resolver(db_config); let connector = make_postgres_connector(log); - let policy = Policy::default(); - Pool { - inner: qorb::pool::Pool::new(resolver, connector, policy), - terminated: std::sync::atomic::AtomicBool::new(false), - } + let inner = match qorb::pool::Pool::new(resolver, connector, policy) { + Ok(pool) => { + debug!(log, "registered USDT probes"); + pool + } + Err(err) => { + error!(log, "failed to register USDT probes"); + err.into_inner() + } + }; + Pool { inner, terminated: std::sync::atomic::AtomicBool::new(false) } } /// Creates a new qorb-backed connection pool which returns an error @@ -129,20 +135,23 @@ impl Pool { log: &Logger, db_config: &DbConfig, ) -> Self { - // Make sure diesel-dtrace's USDT probes are enabled. - usdt::register_probes().expect("Failed to register USDT DTrace probes"); - let resolver = make_single_host_resolver(db_config); let connector = make_postgres_connector(log); - let policy = Policy { claim_timeout: tokio::time::Duration::from_millis(1), ..Default::default() }; - Pool { - inner: qorb::pool::Pool::new(resolver, connector, policy), - terminated: std::sync::atomic::AtomicBool::new(false), - } + let inner = match qorb::pool::Pool::new(resolver, connector, policy) { + Ok(pool) => { + debug!(log, "registered USDT probes"); + pool + } + Err(err) => { + error!(log, "failed to register USDT probes"); + err.into_inner() + } + }; + Pool { inner, terminated: std::sync::atomic::AtomicBool::new(false) } } /// Returns a connection from the pool diff --git a/nexus/inventory/src/collector.rs b/nexus/inventory/src/collector.rs index 77d8056709..d32d00ed8f 100644 --- a/nexus/inventory/src/collector.rs +++ b/nexus/inventory/src/collector.rs @@ -359,10 +359,16 @@ impl<'a> Collector<'a> { /// Collect inventory from about keepers from all `ClickhouseAdminKeeper` /// clients async fn collect_all_keepers(&mut self) { + debug!(self.log, "begin collecting all keepers"; + "nkeeper_admin_clients" => self.keeper_admin_clients.len()); + for client in &self.keeper_admin_clients { Self::collect_one_keeper(&client, &self.log, &mut self.in_progress) .await; } + + debug!(self.log, "end collecting all keepers"; + "nkeeper_admin_clients" => self.keeper_admin_clients.len()); } /// Collect inventory about one keeper from one `ClickhouseAdminKeeper` @@ -384,9 +390,14 @@ impl<'a> Collector<'a> { in_progress.found_error(InventoryError::from(error)); } Ok(membership) => { - in_progress.found_clickhouse_keeper_cluster_membership( - membership.into_inner(), + let membership = membership.into_inner(); + debug!(log, "found keeper membership"; + "keeper_admin_url" => client.baseurl(), + "leader_committed_log_index" => + membership.leader_committed_log_index ); + in_progress + .found_clickhouse_keeper_cluster_membership(membership); } } } diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index 11a44321d9..c1237fb2bf 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -5,6 +5,7 @@ //! Low-level facility for generating Blueprints use crate::ip_allocator::IpAllocator; +use crate::planner::rng::PlannerRng; use crate::planner::zone_needs_expungement; use crate::planner::ZoneExpungeReason; use anyhow::anyhow; @@ -59,16 +60,12 @@ use omicron_common::disk::DatasetConfig; use omicron_common::disk::DatasetName; use omicron_common::policy::INTERNAL_DNS_REDUNDANCY; use omicron_uuid_kinds::DatasetUuid; -use omicron_uuid_kinds::ExternalIpKind; use omicron_uuid_kinds::GenericUuid; -use omicron_uuid_kinds::OmicronZoneKind; use omicron_uuid_kinds::OmicronZoneUuid; use omicron_uuid_kinds::PhysicalDiskUuid; use omicron_uuid_kinds::SledUuid; use omicron_uuid_kinds::ZpoolUuid; use once_cell::unsync::OnceCell; -use rand::rngs::StdRng; -use rand::SeedableRng; use slog::debug; use slog::error; use slog::info; @@ -84,8 +81,6 @@ use std::net::Ipv6Addr; use std::net::SocketAddr; use std::net::SocketAddrV6; use thiserror::Error; -use typed_rng::TypedUuidRng; -use typed_rng::UuidRng; use super::clickhouse::ClickhouseAllocator; use super::external_networking::BuilderExternalNetworking; @@ -296,7 +291,7 @@ pub struct BlueprintBuilder<'a> { comments: Vec, // Random number generator for new UUIDs - rng: BlueprintBuilderRng, + rng: PlannerRng, } impl<'a> BlueprintBuilder<'a> { @@ -306,11 +301,7 @@ impl<'a> BlueprintBuilder<'a> { sled_ids: impl Iterator, creator: &str, ) -> Blueprint { - Self::build_empty_with_sleds_impl( - sled_ids, - creator, - BlueprintBuilderRng::new(), - ) + Self::build_empty_with_sleds_impl(sled_ids, creator, PlannerRng::new()) } /// A version of [`Self::build_empty_with_sleds`] that allows the @@ -320,7 +311,7 @@ impl<'a> BlueprintBuilder<'a> { creator: &str, seed: H, ) -> Blueprint { - let mut rng = BlueprintBuilderRng::new(); + let mut rng = PlannerRng::new(); rng.set_seed(seed); Self::build_empty_with_sleds_impl(sled_ids, creator, rng) } @@ -328,7 +319,7 @@ impl<'a> BlueprintBuilder<'a> { fn build_empty_with_sleds_impl( sled_ids: impl Iterator, creator: &str, - mut rng: BlueprintBuilderRng, + mut rng: PlannerRng, ) -> Blueprint { let blueprint_zones = sled_ids .map(|sled_id| { @@ -347,7 +338,7 @@ impl<'a> BlueprintBuilder<'a> { .collect(); Blueprint { - id: rng.blueprint_rng.next(), + id: rng.next_blueprint(), blueprint_zones, blueprint_disks: BTreeMap::new(), blueprint_datasets: BTreeMap::new(), @@ -459,7 +450,7 @@ impl<'a> BlueprintBuilder<'a> { creator: creator.to_owned(), operations: Vec::new(), comments: Vec::new(), - rng: BlueprintBuilderRng::new(), + rng: PlannerRng::new(), }) } @@ -552,7 +543,7 @@ impl<'a> BlueprintBuilder<'a> { } }); Blueprint { - id: self.rng.blueprint_rng.next(), + id: self.rng.next_blueprint(), blueprint_zones, blueprint_disks, blueprint_datasets, @@ -878,17 +869,22 @@ impl<'a> BlueprintBuilder<'a> { let (mut additions, mut updates, mut expunges, removals) = { let mut datasets_builder = BlueprintSledDatasetsBuilder::new( self.log.clone(), + &mut self.rng, sled_id, &self.datasets, resources, ); // Ensure each zpool has a "Debug" and "Zone Root" dataset. - let bp_zpools = self + let mut bp_zpools = self .disks .current_sled_disks(sled_id) .map(|disk_config| disk_config.pool_id) .collect::>(); + // We iterate over the zpools in a deterministic order to ensure + // that "new Dataset UUIDs" are distributed in a reliable order. + bp_zpools.sort(); + for zpool_id in bp_zpools { let zpool = ZpoolName::new_external(zpool_id); let address = None; @@ -1065,7 +1061,7 @@ impl<'a> BlueprintBuilder<'a> { let zone = BlueprintZoneConfig { disposition: BlueprintZoneDisposition::InService, - id: self.rng.zone_rng.next(), + id: self.rng.next_zone(), filesystem_pool: Some(zpool), zone_type, }; @@ -1115,7 +1111,7 @@ impl<'a> BlueprintBuilder<'a> { &mut self, sled_id: SledUuid, ) -> Result { - let id = self.rng.zone_rng.next(); + let id = self.rng.next_zone(); let ExternalNetworkingChoice { external_ip, nic_ip, @@ -1123,7 +1119,7 @@ impl<'a> BlueprintBuilder<'a> { nic_mac, } = self.external_networking()?.for_new_external_dns()?; let nic = NetworkInterface { - id: self.rng.network_interface_rng.next(), + id: self.rng.next_network_interface(), kind: NetworkInterfaceKind::Service { id: id.into_untyped_uuid() }, name: format!("external-dns-{id}").parse().unwrap(), ip: nic_ip, @@ -1139,7 +1135,7 @@ impl<'a> BlueprintBuilder<'a> { let http_address = SocketAddrV6::new(underlay_address, DNS_HTTP_PORT, 0, 0); let dns_address = OmicronZoneExternalFloatingAddr { - id: self.rng.external_ip_rng.next(), + id: self.rng.next_external_ip(), addr: SocketAddr::new(external_ip, DNS_PORT), }; let pool_name = @@ -1231,7 +1227,7 @@ impl<'a> BlueprintBuilder<'a> { let zone = BlueprintZoneConfig { disposition: BlueprintZoneDisposition::InService, - id: self.rng.zone_rng.next(), + id: self.rng.next_zone(), filesystem_pool: Some(filesystem_pool), zone_type, }; @@ -1287,7 +1283,7 @@ impl<'a> BlueprintBuilder<'a> { let zone = BlueprintZoneConfig { disposition: BlueprintZoneDisposition::InService, - id: self.rng.zone_rng.next(), + id: self.rng.next_zone(), filesystem_pool: Some(filesystem_pool), zone_type, }; @@ -1371,7 +1367,7 @@ impl<'a> BlueprintBuilder<'a> { }; for _ in 0..num_nexus_to_add { - let nexus_id = self.rng.zone_rng.next(); + let nexus_id = self.rng.next_zone(); let ExternalNetworkingChoice { external_ip, nic_ip, @@ -1379,12 +1375,12 @@ impl<'a> BlueprintBuilder<'a> { nic_mac, } = self.external_networking()?.for_new_nexus()?; let external_ip = OmicronZoneExternalFloatingIp { - id: self.rng.external_ip_rng.next(), + id: self.rng.next_external_ip(), ip: external_ip, }; let nic = NetworkInterface { - id: self.rng.network_interface_rng.next(), + id: self.rng.next_network_interface(), kind: NetworkInterfaceKind::Service { id: nexus_id.into_untyped_uuid(), }, @@ -1451,7 +1447,7 @@ impl<'a> BlueprintBuilder<'a> { }; for _ in 0..num_oximeter_to_add { - let oximeter_id = self.rng.zone_rng.next(); + let oximeter_id = self.rng.next_zone(); let ip = self.sled_alloc_ip(sled_id)?; let port = omicron_common::address::OXIMETER_PORT; let address = SocketAddrV6::new(ip, port, 0, 0); @@ -1501,7 +1497,7 @@ impl<'a> BlueprintBuilder<'a> { }; for _ in 0..num_pantry_to_add { - let pantry_id = self.rng.zone_rng.next(); + let pantry_id = self.rng.next_zone(); let ip = self.sled_alloc_ip(sled_id)?; let port = omicron_common::address::CRUCIBLE_PANTRY_PORT; let address = SocketAddrV6::new(ip, port, 0, 0); @@ -1555,7 +1551,7 @@ impl<'a> BlueprintBuilder<'a> { } }; for _ in 0..num_crdb_to_add { - let zone_id = self.rng.zone_rng.next(); + let zone_id = self.rng.next_zone(); let underlay_ip = self.sled_alloc_ip(sled_id)?; let pool_name = self.sled_select_zpool(sled_id, ZoneKind::CockroachDb)?; @@ -1592,7 +1588,7 @@ impl<'a> BlueprintBuilder<'a> { &mut self, sled_id: SledUuid, ) -> Result { - let id = self.rng.zone_rng.next(); + let id = self.rng.next_zone(); let underlay_address = self.sled_alloc_ip(sled_id)?; let address = SocketAddrV6::new(underlay_address, CLICKHOUSE_HTTP_PORT, 0, 0); @@ -1667,7 +1663,7 @@ impl<'a> BlueprintBuilder<'a> { } }; for _ in 0..num_clickhouse_servers_to_add { - let zone_id = self.rng.zone_rng.next(); + let zone_id = self.rng.next_zone(); let underlay_ip = self.sled_alloc_ip(sled_id)?; let pool_name = self.sled_select_zpool(sled_id, ZoneKind::ClickhouseServer)?; @@ -1724,7 +1720,7 @@ impl<'a> BlueprintBuilder<'a> { }; for _ in 0..num_clickhouse_keepers_to_add { - let zone_id = self.rng.zone_rng.next(); + let zone_id = self.rng.next_zone(); let underlay_ip = self.sled_alloc_ip(sled_id)?; let pool_name = self.sled_select_zpool(sled_id, ZoneKind::ClickhouseKeeper)?; @@ -1835,7 +1831,7 @@ impl<'a> BlueprintBuilder<'a> { })?; // Add the new boundary NTP zone. - let new_zone_id = self.rng.zone_rng.next(); + let new_zone_id = self.rng.next_zone(); let ExternalSnatNetworkingChoice { snat_cfg, nic_ip, @@ -1843,11 +1839,11 @@ impl<'a> BlueprintBuilder<'a> { nic_mac, } = self.external_networking()?.for_new_boundary_ntp()?; let external_ip = OmicronZoneExternalSnatIp { - id: self.rng.external_ip_rng.next(), + id: self.rng.next_external_ip(), snat_cfg, }; let nic = NetworkInterface { - id: self.rng.network_interface_rng.next(), + id: self.rng.next_network_interface(), kind: NetworkInterfaceKind::Service { id: new_zone_id.into_untyped_uuid(), }, @@ -2056,49 +2052,6 @@ impl<'a> BlueprintBuilder<'a> { } } -#[derive(Debug)] -struct BlueprintBuilderRng { - // Have separate RNGs for the different kinds of UUIDs we might add, - // generated from the main RNG. This is so that e.g. adding a new network - // interface doesn't alter the blueprint or sled UUID. - // - // In the future, when we switch to typed UUIDs, each of these will be - // associated with a specific `TypedUuidKind`. - blueprint_rng: UuidRng, - zone_rng: TypedUuidRng, - network_interface_rng: UuidRng, - external_ip_rng: TypedUuidRng, -} - -impl BlueprintBuilderRng { - fn new() -> Self { - Self::new_from_parent(StdRng::from_entropy()) - } - - fn new_from_parent(mut parent: StdRng) -> Self { - let blueprint_rng = UuidRng::from_parent_rng(&mut parent, "blueprint"); - let zone_rng = TypedUuidRng::from_parent_rng(&mut parent, "zone"); - let network_interface_rng = - UuidRng::from_parent_rng(&mut parent, "network_interface"); - let external_ip_rng = - TypedUuidRng::from_parent_rng(&mut parent, "external_ip"); - - BlueprintBuilderRng { - blueprint_rng, - zone_rng, - network_interface_rng, - external_ip_rng, - } - } - - fn set_seed(&mut self, seed: H) { - // Important to add some more bytes here, so that builders with the - // same seed but different purposes don't end up with the same UUIDs. - const SEED_EXTRA: &str = "blueprint-builder"; - *self = Self::new_from_parent(typed_rng::from_seed(seed, SEED_EXTRA)); - } -} - /// Helper for working with sets of zones on each sled /// /// Tracking the set of zones is slightly non-trivial because we need to bump @@ -2409,6 +2362,7 @@ impl<'a> BlueprintDatasetsBuilder<'a> { #[derive(Debug)] struct BlueprintSledDatasetsBuilder<'a> { log: Logger, + rng: &'a mut PlannerRng, blueprint_datasets: BTreeMap>, database_datasets: @@ -2429,6 +2383,7 @@ struct BlueprintSledDatasetsBuilder<'a> { impl<'a> BlueprintSledDatasetsBuilder<'a> { pub fn new( log: Logger, + rng: &'a mut PlannerRng, sled_id: SledUuid, datasets: &'a BlueprintDatasetsBuilder<'_>, resources: &'a SledResources, @@ -2459,6 +2414,7 @@ impl<'a> BlueprintSledDatasetsBuilder<'a> { Self { log, + rng, blueprint_datasets, database_datasets, unchanged_datasets: BTreeMap::new(), @@ -2522,7 +2478,7 @@ impl<'a> BlueprintSledDatasetsBuilder<'a> { let id = if let Some(old_config) = self.get_from_db(zpool_id, kind) { old_config.id } else { - DatasetUuid::new_v4() + self.rng.next_dataset() }; let new_config = make_config(id); diff --git a/nexus/reconfigurator/planning/src/example.rs b/nexus/reconfigurator/planning/src/example.rs index a0761a9b6c..4745d5dc68 100644 --- a/nexus/reconfigurator/planning/src/example.rs +++ b/nexus/reconfigurator/planning/src/example.rs @@ -410,6 +410,20 @@ impl ExampleSystemBuilder { } } + // We just ensured that a handful of datasets should exist in + // the blueprint, but they don't yet exist in the SystemDescription. + // + // Go back and add them so that the blueprint is consistent with + // inventory. + for (sled_id, datasets) in &blueprint.blueprint_datasets { + let sled = system.get_sled_mut(*sled_id).unwrap(); + + for dataset_config in datasets.datasets.values() { + let config = dataset_config.clone().try_into().unwrap(); + sled.add_synthetic_dataset(config); + } + } + let mut builder = system.to_collection_builder().expect("failed to build collection"); builder.set_rng_seed((&self.test_name, "ExampleSystem collection")); diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index 647d883ef1..dc2050cb10 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -40,6 +40,7 @@ use self::omicron_zone_placement::OmicronZonePlacement; use self::omicron_zone_placement::OmicronZonePlacementSledState; mod omicron_zone_placement; +pub(crate) mod rng; pub struct Planner<'a> { log: Logger, @@ -949,6 +950,10 @@ mod test { assert_eq!(diff.zones.errors.len(), 0); assert_eq!(diff.physical_disks.added.len(), 0); assert_eq!(diff.physical_disks.removed.len(), 0); + assert_eq!(diff.datasets.added.len(), 0); + assert_eq!(diff.datasets.removed.len(), 0); + assert_eq!(diff.datasets.modified.len(), 0); + assert_eq!(diff.datasets.unchanged.len(), 3); verify_blueprint(&blueprint2); // Now add a new sled. @@ -980,6 +985,8 @@ mod test { &diff.display().to_string(), ); assert_eq!(diff.sleds_added.len(), 1); + assert_eq!(diff.physical_disks.added.len(), 1); + assert_eq!(diff.datasets.added.len(), 1); let sled_id = *diff.sleds_added.first().unwrap(); let sled_zones = diff.zones.added.get(&sled_id).unwrap(); // We have defined elsewhere that the first generation contains no @@ -1152,6 +1159,12 @@ mod test { assert_eq!(*changed_sled_id, sled_id); assert_eq!(diff.zones.removed.len(), 0); assert_eq!(diff.zones.modified.len(), 0); + assert_eq!(diff.physical_disks.added.len(), 0); + assert_eq!(diff.physical_disks.removed.len(), 0); + assert_eq!(diff.datasets.added.len(), 1); + assert_eq!(diff.datasets.modified.len(), 0); + assert_eq!(diff.datasets.removed.len(), 0); + let zones_added = diff.zones.added.get(changed_sled_id).unwrap(); assert_eq!( zones_added.zones.len(), @@ -1742,6 +1755,11 @@ mod test { NEW_IN_SERVICE_DISKS ); assert!(!diff.zones.removed.contains_key(sled_id)); + assert_eq!(diff.physical_disks.added.len(), 1); + assert_eq!(diff.physical_disks.removed.len(), 0); + assert_eq!(diff.datasets.added.len(), 1); + assert_eq!(diff.datasets.modified.len(), 0); + assert_eq!(diff.datasets.removed.len(), 0); // Test a no-op planning iteration. assert_planning_makes_no_changes( @@ -1754,6 +1772,79 @@ mod test { logctx.cleanup_successful(); } + #[test] + fn test_dataset_settings_modified_in_place() { + static TEST_NAME: &str = "planner_dataset_settings_modified_in_place"; + let logctx = test_setup_log(TEST_NAME); + + // Create an example system with a single sled + let (example, mut blueprint1) = + ExampleSystemBuilder::new(&logctx.log, TEST_NAME).nsleds(1).build(); + let collection = example.collection; + let input = example.input; + + let mut builder = input.into_builder(); + + // Avoid churning on the quantity of Nexus and internal DNS zones - + // we're okay staying at one each. + builder.policy_mut().target_nexus_zone_count = 1; + builder.policy_mut().target_internal_dns_zone_count = 1; + + // Manually update the blueprint to report an abnormal "Debug dataset" + let (_sled_id, datasets_config) = + blueprint1.blueprint_datasets.iter_mut().next().unwrap(); + let (_dataset_id, dataset_config) = datasets_config + .datasets + .iter_mut() + .find(|(_, config)| { + matches!(config.kind, omicron_common::disk::DatasetKind::Debug) + }) + .expect("No debug dataset found"); + + // These values are out-of-sync with what the blueprint will typically + // enforce. + dataset_config.quota = None; + dataset_config.reservation = Some( + omicron_common::api::external::ByteCount::from_gibibytes_u32(1), + ); + + let input = builder.build(); + + let blueprint2 = Planner::new_based_on( + logctx.log.clone(), + &blueprint1, + &input, + "test: fix a dataset", + &collection, + ) + .expect("failed to create planner") + .with_rng_seed((TEST_NAME, "bp2")) + .plan() + .expect("failed to plan"); + + let diff = blueprint2.diff_since_blueprint(&blueprint1); + println!("1 -> 2 (modify a dataset):\n{}", diff.display()); + assert_contents( + "tests/output/planner_dataset_settings_modified_in_place_1_2.txt", + &diff.display().to_string(), + ); + + assert_eq!(diff.sleds_added.len(), 0); + assert_eq!(diff.sleds_removed.len(), 0); + assert_eq!(diff.sleds_modified.len(), 1); + + assert_eq!(diff.zones.added.len(), 0); + assert_eq!(diff.zones.removed.len(), 0); + assert_eq!(diff.zones.modified.len(), 0); + assert_eq!(diff.physical_disks.added.len(), 0); + assert_eq!(diff.physical_disks.removed.len(), 0); + assert_eq!(diff.datasets.added.len(), 0); + assert_eq!(diff.datasets.removed.len(), 0); + assert_eq!(diff.datasets.modified.len(), 1); + + logctx.cleanup_successful(); + } + #[test] fn test_disk_expungement_removes_zones_durable_zpool() { static TEST_NAME: &str = @@ -1827,6 +1918,13 @@ mod test { assert_eq!(diff.zones.added.len(), 0); assert_eq!(diff.zones.removed.len(), 0); assert_eq!(diff.zones.modified.len(), 1); + assert_eq!(diff.physical_disks.added.len(), 0); + assert_eq!(diff.physical_disks.removed.len(), 1); + assert_eq!(diff.datasets.added.len(), 0); + // NOTE: Expunging a disk doesn't immediately delete datasets; see the + // "decommissioned_disk_cleaner" background task for more context. + assert_eq!(diff.datasets.removed.len(), 0); + assert_eq!(diff.datasets.modified.len(), 0); let (_zone_id, modified_zones) = diff.zones.modified.iter().next().unwrap(); diff --git a/nexus/reconfigurator/planning/src/planner/rng.rs b/nexus/reconfigurator/planning/src/planner/rng.rs new file mode 100644 index 0000000000..93c74ee34e --- /dev/null +++ b/nexus/reconfigurator/planning/src/planner/rng.rs @@ -0,0 +1,85 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! RNG for blueprint planning to allow reproducibility (particularly for +//! tests). + +use omicron_uuid_kinds::DatasetKind; +use omicron_uuid_kinds::DatasetUuid; +use omicron_uuid_kinds::ExternalIpKind; +use omicron_uuid_kinds::ExternalIpUuid; +use omicron_uuid_kinds::OmicronZoneKind; +use omicron_uuid_kinds::OmicronZoneUuid; +use rand::rngs::StdRng; +use rand::SeedableRng as _; +use std::hash::Hash; +use typed_rng::TypedUuidRng; +use typed_rng::UuidRng; +use uuid::Uuid; + +#[derive(Debug)] +pub(crate) struct PlannerRng { + // Have separate RNGs for the different kinds of UUIDs we might add, + // generated from the main RNG. This is so that e.g. adding a new network + // interface doesn't alter the blueprint or sled UUID. + // + // In the future, when we switch to typed UUIDs, each of these will be + // associated with a specific `TypedUuidKind`. + blueprint_rng: UuidRng, + zone_rng: TypedUuidRng, + dataset_rng: TypedUuidRng, + network_interface_rng: UuidRng, + external_ip_rng: TypedUuidRng, +} + +impl PlannerRng { + pub fn new() -> Self { + Self::new_from_parent(StdRng::from_entropy()) + } + + pub fn new_from_parent(mut parent: StdRng) -> Self { + let blueprint_rng = UuidRng::from_parent_rng(&mut parent, "blueprint"); + let zone_rng = TypedUuidRng::from_parent_rng(&mut parent, "zone"); + let dataset_rng = TypedUuidRng::from_parent_rng(&mut parent, "dataset"); + let network_interface_rng = + UuidRng::from_parent_rng(&mut parent, "network_interface"); + let external_ip_rng = + TypedUuidRng::from_parent_rng(&mut parent, "external_ip"); + + Self { + blueprint_rng, + zone_rng, + dataset_rng, + network_interface_rng, + external_ip_rng, + } + } + + pub fn set_seed(&mut self, seed: H) { + // Important to add some more bytes here, so that builders with the + // same seed but different purposes don't end up with the same UUIDs. + const SEED_EXTRA: &str = "blueprint-builder"; + *self = Self::new_from_parent(typed_rng::from_seed(seed, SEED_EXTRA)); + } + + pub fn next_blueprint(&mut self) -> Uuid { + self.blueprint_rng.next() + } + + pub fn next_zone(&mut self) -> OmicronZoneUuid { + self.zone_rng.next() + } + + pub fn next_dataset(&mut self) -> DatasetUuid { + self.dataset_rng.next() + } + + pub fn next_network_interface(&mut self) -> Uuid { + self.network_interface_rng.next() + } + + pub fn next_external_ip(&mut self) -> ExternalIpUuid { + self.external_ip_rng.next() + } +} diff --git a/nexus/reconfigurator/planning/src/system.rs b/nexus/reconfigurator/planning/src/system.rs index 9ffd99bd5f..66a5393275 100644 --- a/nexus/reconfigurator/planning/src/system.rs +++ b/nexus/reconfigurator/planning/src/system.rs @@ -14,7 +14,9 @@ use ipnet::Ipv6Subnets; use nexus_inventory::CollectionBuilder; use nexus_sled_agent_shared::inventory::Baseboard; use nexus_sled_agent_shared::inventory::Inventory; +use nexus_sled_agent_shared::inventory::InventoryDataset; use nexus_sled_agent_shared::inventory::InventoryDisk; +use nexus_sled_agent_shared::inventory::InventoryZpool; use nexus_sled_agent_shared::inventory::OmicronZonesConfig; use nexus_sled_agent_shared::inventory::SledRole; use nexus_types::deployment::ClickhousePolicy; @@ -247,6 +249,16 @@ impl SystemDescription { self } + pub fn get_sled_mut( + &mut self, + sled_id: SledUuid, + ) -> anyhow::Result<&mut Sled> { + let Some(sled) = self.sleds.get_mut(&sled_id) else { + bail!("Sled not found with id {sled_id}"); + }; + Ok(Arc::make_mut(sled)) + } + /// Add a sled to the system, as described by a SledBuilder pub fn sled(&mut self, sled: SledBuilder) -> anyhow::Result<&mut Self> { let sled_id = sled.id.unwrap_or_else(SledUuid::new_v4); @@ -537,7 +549,7 @@ pub struct SledHwInventory<'a> { /// This needs to be rich enough to generate a PlanningInput and inventory /// Collection. #[derive(Clone, Debug)] -struct Sled { +pub struct Sled { sled_id: SledUuid, inventory_sp: Option<(u16, SpState)>, inventory_sled_agent: Inventory, @@ -662,9 +674,13 @@ impl Sled { )], }) .collect(), - // Zpools & Datasets won't necessarily show up until our first - // request to provision storage, so we omit them. - zpools: vec![], + zpools: zpools + .keys() + .map(|id| InventoryZpool { + id: *id, + total_size: ByteCount::from_gibibytes_u32(100), + }) + .collect(), datasets: vec![], } }; @@ -816,6 +832,25 @@ impl Sled { } } + /// Adds a dataset to the system description. + /// + /// The inventory values for "available space" and "used space" are + /// made up, since this is a synthetic dataset. + pub fn add_synthetic_dataset( + &mut self, + config: omicron_common::disk::DatasetConfig, + ) { + self.inventory_sled_agent.datasets.push(InventoryDataset { + id: Some(config.id), + name: config.name.full_name(), + available: ByteCount::from_gibibytes_u32(1), + used: ByteCount::from_gibibytes_u32(0), + quota: config.quota, + reservation: config.reservation, + compression: config.compression.to_string(), + }); + } + fn sp_state(&self) -> Option<&(u16, SpState)> { self.inventory_sp.as_ref() } diff --git a/nexus/reconfigurator/planning/tests/output/blueprint_builder_initial_diff.txt b/nexus/reconfigurator/planning/tests/output/blueprint_builder_initial_diff.txt index 1c792a15c2..ebd62a8bf6 100644 --- a/nexus/reconfigurator/planning/tests/output/blueprint_builder_initial_diff.txt +++ b/nexus/reconfigurator/planning/tests/output/blueprint_builder_initial_diff.txt @@ -20,6 +20,59 @@ to: blueprint e4aeb3b3-272f-4967-be34-2d34daa46aa1 fake-vendor fake-model serial-f9bcdb70-6846-4330-9d37-bfdd5583aea6 + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crucible 750b5b15-b541-4506-9244-b93b277a9646 none none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/clickhouse 1123460d-14bc-4651-8e92-b71a4cb1320a none none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/debug 613c5d20-d3c3-4d43-ae5a-dda783ae87ba 100 GiB none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/internal_dns 1b14ab69-234b-456e-a253-5309c45d0cdd none none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/zone 70b3a51d-a3bf-4019-ad3d-04c5ce992ffe none none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/zone/oxz_clickhouse_44afce85-3377-4b20-a398-517c1579df4d 7ef76726-083e-4f84-a818-8394b820f862 none none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/zone/oxz_crucible_79552859-fbd3-43bb-a9d3-6baba25558f8 43b758fe-e7e6-4bed-bcb6-64789c180fe1 none none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/zone/oxz_crucible_pantry_55f4d117-0b9d-4256-a2c0-f46d3ed5fff9 f22149ee-144a-4c39-89bb-c6cea2a5173e none none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/zone/oxz_internal_dns_7004cab9-dfc0-43ba-92d3-58d4ced66025 6b2e018c-8f22-40fa-8a30-7533ccf35a54 none none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/zone/oxz_nexus_b2573120-9c91-4ed7-8b4f-a7bfe8dbc807 170a525e-dda8-4ce6-9cf1-fbe1b8e8b69d none none off + oxp_3fb05590-2632-413e-989f-aaaabaf01fab/crypt/zone/oxz_ntp_c81c9d4a-36d7-4796-9151-f564d3735152 a605a65f-1430-4e04-8d39-74f3f9ecd99c none none off + oxp_4711ce46-43f6-4732-9769-a69ea519b62d/crucible 73deef16-ed89-4217-b4ee-245208ccddbc none none off + oxp_4711ce46-43f6-4732-9769-a69ea519b62d/crypt/debug 5f11f7e2-c868-4494-bb58-9f5d1691624e 100 GiB none off + oxp_4711ce46-43f6-4732-9769-a69ea519b62d/crypt/zone da15dac3-157f-428c-b114-0f151ab8e3d5 none none off + oxp_4711ce46-43f6-4732-9769-a69ea519b62d/crypt/zone/oxz_crucible_90696819-9b53-485a-9c65-ca63602e843e 8539d147-7b9f-4746-a00f-b61bafa3a46e none none off + oxp_4a2cc08d-8e18-4f0e-8fc6-443cb2016858/crucible 6afc575c-d188-45ee-9f8e-4b67f4328fdc none none off + oxp_4a2cc08d-8e18-4f0e-8fc6-443cb2016858/crypt/debug d52b83be-2c94-495c-baae-f9a051db37d2 100 GiB none off + oxp_4a2cc08d-8e18-4f0e-8fc6-443cb2016858/crypt/zone 03ad2792-8fac-4c2a-8e81-0d5de681a4ba none none off + oxp_4a2cc08d-8e18-4f0e-8fc6-443cb2016858/crypt/zone/oxz_crucible_5c6a4628-8831-483b-995f-79b9126c4d04 c479e900-76d3-460f-82f1-eb6467becd91 none none off + oxp_7032a67e-2ff6-45cc-af34-8b3502965cc9/crucible fd3fee94-220f-46e1-b60c-2db3c484dd89 none none off + oxp_7032a67e-2ff6-45cc-af34-8b3502965cc9/crypt/debug 04440028-c8bf-484f-929e-c2811db1d6c4 100 GiB none off + oxp_7032a67e-2ff6-45cc-af34-8b3502965cc9/crypt/zone 5c40200b-a261-40fc-8e82-1741ffa4d7c5 none none off + oxp_7032a67e-2ff6-45cc-af34-8b3502965cc9/crypt/zone/oxz_crucible_6a01210c-45ed-41a5-9230-8e05ecf5dd8f 6b879024-3f61-4272-a022-05fb63bd75aa none none off + oxp_908218e9-26ea-4d75-86f9-4b99ff72dcb5/crucible d5b0ab35-b0c0-4a4b-929d-f907ac927638 none none off + oxp_908218e9-26ea-4d75-86f9-4b99ff72dcb5/crypt/debug c57d915c-9e21-4177-ba19-f58846ac3946 100 GiB none off + oxp_908218e9-26ea-4d75-86f9-4b99ff72dcb5/crypt/zone 2b7326fa-d02e-4c08-baa8-5eaf2d20b33b none none off + oxp_908218e9-26ea-4d75-86f9-4b99ff72dcb5/crypt/zone/oxz_crucible_c99525b3-3680-4df6-9214-2ee3e1020e8b 14da6b65-4e17-4974-9e05-345bd8e48dfb none none off + oxp_9bc4e63d-b8fe-4ac6-ac3a-cf097d06cc6d/crucible 3d181bb0-dfb8-460c-9fdd-81d45451f751 none none off + oxp_9bc4e63d-b8fe-4ac6-ac3a-cf097d06cc6d/crypt/debug 218ba88a-838f-4397-9dfb-49f67099d48b 100 GiB none off + oxp_9bc4e63d-b8fe-4ac6-ac3a-cf097d06cc6d/crypt/zone 32c80edc-5ccd-4f02-98bb-8e891b09e683 none none off + oxp_9bc4e63d-b8fe-4ac6-ac3a-cf097d06cc6d/crypt/zone/oxz_crucible_f42959d3-9eef-4e3b-b404-6177ce3ec7a1 4d7a3506-392e-429a-9295-10d14e7c3ae7 none none off + oxp_9f343299-ef7a-46aa-9904-061be15abfeb/crucible 10e97a60-9424-4332-9f36-1f7d7adfcc16 none none off + oxp_9f343299-ef7a-46aa-9904-061be15abfeb/crypt/debug d048b1c6-4730-4005-a964-fce3d86e76ad 100 GiB none off + oxp_9f343299-ef7a-46aa-9904-061be15abfeb/crypt/zone 7857daa4-ef48-4e7d-9961-d0d34a344746 none none off + oxp_9f343299-ef7a-46aa-9904-061be15abfeb/crypt/zone/oxz_crucible_4644ea0c-0ec3-41be-a356-660308e1c3fc c1e703f4-5547-4cac-bd3b-938364961df7 none none off + oxp_c8523dd7-4e87-4e4b-8e46-04b806f0763c/crucible cbe0b7c1-326d-4c7e-a871-eb8b08653fc9 none none off + oxp_c8523dd7-4e87-4e4b-8e46-04b806f0763c/crypt/debug b4e82f4d-bc70-49f0-b0aa-194aa2dc6b23 100 GiB none off + oxp_c8523dd7-4e87-4e4b-8e46-04b806f0763c/crypt/zone 22d59c0d-1687-432c-a847-5ef3c4ea1707 none none off + oxp_c8523dd7-4e87-4e4b-8e46-04b806f0763c/crypt/zone/oxz_crucible_38b047ea-e3de-4859-b8e0-70cac5871446 8c98392d-df57-4c21-a0d3-85bd81438049 none none off + oxp_e02245bc-ca0d-4f08-ac1e-870c4fa2a17c/crucible 49c827a1-3125-451d-9213-3276118a5a44 none none off + oxp_e02245bc-ca0d-4f08-ac1e-870c4fa2a17c/crypt/debug a51f6079-d3e1-4c1e-b93f-b5d406ea37d6 100 GiB none off + oxp_e02245bc-ca0d-4f08-ac1e-870c4fa2a17c/crypt/zone c28e6261-80ed-4553-828a-f6031a2fa866 none none off + oxp_e02245bc-ca0d-4f08-ac1e-870c4fa2a17c/crypt/zone/oxz_crucible_fb36b9dc-273a-4bc3-aaa9-19ee4d0ef552 26378c99-c953-45ba-a045-37d4f8b91173 none none off + oxp_f9bcdb70-6846-4330-9d37-bfdd5583aea6/crucible 48885295-bb47-4090-983a-1c444e5399d1 none none off + oxp_f9bcdb70-6846-4330-9d37-bfdd5583aea6/crypt/debug f26fd0f8-03ce-459b-8309-785e8c306d83 100 GiB none off + oxp_f9bcdb70-6846-4330-9d37-bfdd5583aea6/crypt/zone 43fbca70-69af-4c55-af3f-7ff3326a99d2 none none off + oxp_f9bcdb70-6846-4330-9d37-bfdd5583aea6/crypt/zone/oxz_crucible_a9a6a974-8953-4783-b815-da46884f2c02 ff93df28-7830-43f8-b4bc-0bd328679411 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -59,6 +112,57 @@ to: blueprint e4aeb3b3-272f-4967-be34-2d34daa46aa1 fake-vendor fake-model serial-ff66a45c-38a8-4b62-825e-e7c9470bc8bc + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_092eaf94-328b-45b6-99da-c850a06e8592/crucible d848e35e-5967-419d-abc7-9fa1384349f2 none none off + oxp_092eaf94-328b-45b6-99da-c850a06e8592/crypt/debug 8742a54b-0996-46ca-a20d-db07b83487a8 100 GiB none off + oxp_092eaf94-328b-45b6-99da-c850a06e8592/crypt/internal_dns 1e5e8f8d-bf9f-47a6-a820-33d23e8d6b13 none none off + oxp_092eaf94-328b-45b6-99da-c850a06e8592/crypt/zone 2837517b-9d80-4757-bb71-48c2da364297 none none off + oxp_092eaf94-328b-45b6-99da-c850a06e8592/crypt/zone/oxz_crucible_f7e434f9-6d4a-476b-a9e2-48d6ee28a08e bd687fb0-87bb-49ea-9155-d24060de6c19 none none off + oxp_092eaf94-328b-45b6-99da-c850a06e8592/crypt/zone/oxz_crucible_pantry_b6f2dd1e-7f98-4a68-9df2-b33c69d1f7ea e30bb8e5-290f-425c-b7f1-3592f190db92 none none off + oxp_092eaf94-328b-45b6-99da-c850a06e8592/crypt/zone/oxz_internal_dns_dc22d470-dc46-436b-9750-25c8d7d369e2 83c2dc46-edc2-4c3f-81b0-cef8ec80614f none none off + oxp_092eaf94-328b-45b6-99da-c850a06e8592/crypt/zone/oxz_nexus_5b44003e-1a3d-4152-b606-872c72efce0e 207e9adb-290e-4d13-9b7a-beea1fdc66eb none none off + oxp_092eaf94-328b-45b6-99da-c850a06e8592/crypt/zone/oxz_ntp_95c3b6d1-2592-4252-b5c1-5d0faf3ce9c9 1ced3421-0de2-4d49-b295-ebbf2d231ab7 none none off + oxp_3195b46a-d32d-458e-ad7f-b0b2f91af483/crucible a2ca9da2-c139-4458-a84a-51fb94310a90 none none off + oxp_3195b46a-d32d-458e-ad7f-b0b2f91af483/crypt/debug ded2e1eb-91ce-467d-86ef-150d27273986 100 GiB none off + oxp_3195b46a-d32d-458e-ad7f-b0b2f91af483/crypt/zone 7905fda4-3c4f-4621-94a0-8c5be4f779e4 none none off + oxp_3195b46a-d32d-458e-ad7f-b0b2f91af483/crypt/zone/oxz_crucible_943fea7a-9458-4935-9dc7-01ee5cfe5a02 f72eb456-23b9-4414-adc5-c4bd3f983022 none none off + oxp_327bdbbb-c40e-4784-888e-18492a753708/crucible 0974afa0-87d2-4f03-b623-50cc687603e3 none none off + oxp_327bdbbb-c40e-4784-888e-18492a753708/crypt/debug fa0a338a-13fb-4e2d-b42d-657751c90fa4 100 GiB none off + oxp_327bdbbb-c40e-4784-888e-18492a753708/crypt/zone 7ddb052f-24d9-4616-93fd-9fee744ac811 none none off + oxp_327bdbbb-c40e-4784-888e-18492a753708/crypt/zone/oxz_crucible_aa25add8-60b0-4ace-ac60-15adcdd32d50 267cfac7-9559-41ea-9f0c-f72b46b6cc50 none none off + oxp_4f067c20-2860-49b1-8a03-6715a3c12c0e/crucible 9b7cdf59-fa1e-4815-9108-3c2e00757e93 none none off + oxp_4f067c20-2860-49b1-8a03-6715a3c12c0e/crypt/debug 9862e4af-f588-4957-b3a6-2d8ea7e55b87 100 GiB none off + oxp_4f067c20-2860-49b1-8a03-6715a3c12c0e/crypt/zone ff8b51f9-b765-447e-ac95-e8c822183598 none none off + oxp_4f067c20-2860-49b1-8a03-6715a3c12c0e/crypt/zone/oxz_crucible_a5a0b7a9-37c9-4dbd-8393-ec7748ada3b0 47c4203e-6fb4-4e6b-9514-4bdfa2b134bb none none off + oxp_564460fe-7357-4883-a3af-1c931f473e83/crucible 6c89c1a5-1716-4758-b63d-09e8672ca3cb none none off + oxp_564460fe-7357-4883-a3af-1c931f473e83/crypt/debug 3c0027e3-68b5-4c7e-a508-50ffc47371cd 100 GiB none off + oxp_564460fe-7357-4883-a3af-1c931f473e83/crypt/zone 9ad3f33b-7789-4e9d-95c5-cc3b33868722 none none off + oxp_564460fe-7357-4883-a3af-1c931f473e83/crypt/zone/oxz_crucible_0faa9350-2c02-47c7-a0a6-9f4afd69152c be03d0f5-4769-423f-b06e-5f5f9aad6cd3 none none off + oxp_66f85be6-1143-48a1-a898-504c6b540035/crucible 92a5bf2e-c559-4ae4-8aca-eff0069ff95d none none off + oxp_66f85be6-1143-48a1-a898-504c6b540035/crypt/debug 5827cf48-90f2-456a-a5f1-e03aef056b1d 100 GiB none off + oxp_66f85be6-1143-48a1-a898-504c6b540035/crypt/zone cff02d61-c543-49f1-9109-e81c68cefd83 none none off + oxp_66f85be6-1143-48a1-a898-504c6b540035/crypt/zone/oxz_crucible_aac3ab51-9e2b-4605-9bf6-e3eb3681c2b5 f179fdef-52ce-4cff-8a97-89148f43b6b8 none none off + oxp_97b3f199-b488-4ce0-bd34-484d4d3bd194/crucible 08ebb666-a034-4645-be11-975e628dc24b none none off + oxp_97b3f199-b488-4ce0-bd34-484d4d3bd194/crypt/debug d08c6f7e-553f-4024-8958-41cdfb2f3e04 100 GiB none off + oxp_97b3f199-b488-4ce0-bd34-484d4d3bd194/crypt/zone 8cf86314-701b-4ba6-8f03-ed1bf6a353af none none off + oxp_97b3f199-b488-4ce0-bd34-484d4d3bd194/crypt/zone/oxz_crucible_29278a22-1ba1-4117-bfdb-39fcb9ae7fd1 ccb37b34-455c-4131-b2d5-2afa86844af7 none none off + oxp_a38f8150-2efd-4b55-9ffb-3c98e2939e13/crucible f6286214-eee2-4d21-9beb-d56d6a9ff647 none none off + oxp_a38f8150-2efd-4b55-9ffb-3c98e2939e13/crypt/debug 3354cd57-f010-468c-bf03-8fee73f9cbd4 100 GiB none off + oxp_a38f8150-2efd-4b55-9ffb-3c98e2939e13/crypt/zone 953584c2-3e10-49f2-b3e0-731334ace4ba none none off + oxp_a38f8150-2efd-4b55-9ffb-3c98e2939e13/crypt/zone/oxz_crucible_9b722fea-a186-4bc3-bc37-ce7f6de6a796 b8b9edf8-61ad-4be9-9158-d19dc0d55c8e none none off + oxp_d788bf53-35dd-4fa7-a820-c1233e859d03/crucible 69afa9b4-0b5b-44b7-959b-2e0c919a902c none none off + oxp_d788bf53-35dd-4fa7-a820-c1233e859d03/crypt/debug 76fb829c-faf6-482a-8cee-ad65fbf90061 100 GiB none off + oxp_d788bf53-35dd-4fa7-a820-c1233e859d03/crypt/zone 301df8ce-ca9a-40d8-bf0c-a894f05a07c6 none none off + oxp_d788bf53-35dd-4fa7-a820-c1233e859d03/crypt/zone/oxz_crucible_4330134c-41b9-4097-aa0b-3eaefa06d473 b62ae708-bda3-4e55-aeec-4f6ebde36147 none none off + oxp_ff66a45c-38a8-4b62-825e-e7c9470bc8bc/crucible f48b7d15-5deb-43c4-a60e-3e0fbcf97181 none none off + oxp_ff66a45c-38a8-4b62-825e-e7c9470bc8bc/crypt/debug a86deb8c-63b7-4b0f-9684-b7bec755ee93 100 GiB none off + oxp_ff66a45c-38a8-4b62-825e-e7c9470bc8bc/crypt/zone ac5d249f-271c-434b-b979-0f6f2a1482fb none none off + oxp_ff66a45c-38a8-4b62-825e-e7c9470bc8bc/crypt/zone/oxz_crucible_65d03287-e43f-45f4-902e-0a5e4638f31a 59a882c6-2590-4e1b-8bac-6c80bde22cd0 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -97,6 +201,57 @@ to: blueprint e4aeb3b3-272f-4967-be34-2d34daa46aa1 fake-vendor fake-model serial-e90056a4-dd19-4ebb-b484-c677aea31d80 + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_29867b4d-f12b-40ea-b59e-5169d0b2a831/crucible d662f33a-ad6e-464a-8bab-789e94cf52c6 none none off + oxp_29867b4d-f12b-40ea-b59e-5169d0b2a831/crypt/debug 4cda93d9-ceb2-4dab-8ded-146338f74201 100 GiB none off + oxp_29867b4d-f12b-40ea-b59e-5169d0b2a831/crypt/internal_dns 9e1fe3b4-5a48-45ba-b940-8d57446ea9b1 none none off + oxp_29867b4d-f12b-40ea-b59e-5169d0b2a831/crypt/zone 9824b691-56a2-4969-b2bb-86434fcffb63 none none off + oxp_29867b4d-f12b-40ea-b59e-5169d0b2a831/crypt/zone/oxz_crucible_b3583b5f-4a62-4471-9be7-41e61578de4c 9739aee2-e6e2-49de-8d28-04c225c73620 none none off + oxp_29867b4d-f12b-40ea-b59e-5169d0b2a831/crypt/zone/oxz_crucible_pantry_353b0aff-4c71-4fae-a6bd-adcb1d2a1a1d 2a240d7d-a428-4ff5-8ded-3206ef2cec95 none none off + oxp_29867b4d-f12b-40ea-b59e-5169d0b2a831/crypt/zone/oxz_internal_dns_bac92034-b9e6-4e8b-9ffb-dbba9caec88d cee2172a-5d28-43e1-abcd-a8e615a6ee6b none none off + oxp_29867b4d-f12b-40ea-b59e-5169d0b2a831/crypt/zone/oxz_nexus_6a5901b1-f9d7-425c-8ecb-a786c900f217 de3fa3f8-d348-4520-9025-d8e21ae467d4 none none off + oxp_29867b4d-f12b-40ea-b59e-5169d0b2a831/crypt/zone/oxz_ntp_edaca77e-5806-446a-b00c-125962cd551d 0153190b-60fb-438c-9635-2d979c7ebe62 none none off + oxp_54b3613b-a80e-4eda-aa1c-0d92050de367/crucible c6b9b0cf-3f83-4893-acfe-302e20e1cced none none off + oxp_54b3613b-a80e-4eda-aa1c-0d92050de367/crypt/debug b71328f5-ec79-4655-a46d-ca65b97737eb 100 GiB none off + oxp_54b3613b-a80e-4eda-aa1c-0d92050de367/crypt/zone ccba4005-6b82-4ad7-896c-486f921f11c7 none none off + oxp_54b3613b-a80e-4eda-aa1c-0d92050de367/crypt/zone/oxz_crucible_d9653001-f671-4905-a410-6a7abc358318 0a6e84ba-dbda-46c6-b464-c503c446908c none none off + oxp_75939bd0-28f6-428b-8ce2-e55241d201ce/crucible 2f200dce-e6f6-414c-a974-c4d57b911e65 none none off + oxp_75939bd0-28f6-428b-8ce2-e55241d201ce/crypt/debug 74883587-3810-4b3a-8b96-b75c0d175790 100 GiB none off + oxp_75939bd0-28f6-428b-8ce2-e55241d201ce/crypt/zone eeacb046-ceb7-46be-a500-d0895285c8e8 none none off + oxp_75939bd0-28f6-428b-8ce2-e55241d201ce/crypt/zone/oxz_crucible_248db330-56e6-4c7e-b5ff-9cd6cbcb210a 12ea5763-4588-44d7-9729-9d68717a737e none none off + oxp_7bcb41c3-6fc7-4cab-ac5f-b2e09f62567d/crucible 0682d710-685d-4ad6-a450-ac664f3b04e7 none none off + oxp_7bcb41c3-6fc7-4cab-ac5f-b2e09f62567d/crypt/debug a90f2686-9ab9-475b-b266-3068ce3daf2e 100 GiB none off + oxp_7bcb41c3-6fc7-4cab-ac5f-b2e09f62567d/crypt/zone 97e91593-1d0a-44b4-a858-217bf649101d none none off + oxp_7bcb41c3-6fc7-4cab-ac5f-b2e09f62567d/crypt/zone/oxz_crucible_cf766535-9b6f-4263-a83a-86f45f7b005b 8eaf41af-a705-4205-a700-47df8245a9fb none none off + oxp_92aeebc3-4154-4147-b721-0ccf5e337d8d/crucible 127e27ba-4500-4c26-b165-cb128dae596a none none off + oxp_92aeebc3-4154-4147-b721-0ccf5e337d8d/crypt/debug 1565d6ec-e8e8-48fa-a278-466eba9242fd 100 GiB none off + oxp_92aeebc3-4154-4147-b721-0ccf5e337d8d/crypt/zone 56c84928-1960-4e9d-b3f1-be1e471eee29 none none off + oxp_92aeebc3-4154-4147-b721-0ccf5e337d8d/crypt/zone/oxz_crucible_c240ec8c-cec5-4117-944d-faeb5672d568 dd816da2-4f69-4f5f-a68e-25707508b7e8 none none off + oxp_960229b6-dbb2-4df0-ad93-83ddb28484bc/crucible b50d5d5a-f963-4527-9374-c625782b98d7 none none off + oxp_960229b6-dbb2-4df0-ad93-83ddb28484bc/crypt/debug 2551466c-2832-43cf-819f-c4a5d38f8649 100 GiB none off + oxp_960229b6-dbb2-4df0-ad93-83ddb28484bc/crypt/zone 3731ccdd-e64a-40ff-aa8a-d80e2a99cc18 none none off + oxp_960229b6-dbb2-4df0-ad93-83ddb28484bc/crypt/zone/oxz_crucible_b97bdef5-ed14-4e11-9d3b-3379c18ea694 327b3447-1dd6-4e62-8315-284751de6169 none none off + oxp_9e1428de-ad48-4655-8ccc-bbf4cb1badda/crucible 4e988aef-33a7-41b4-8542-7139906e3aca none none off + oxp_9e1428de-ad48-4655-8ccc-bbf4cb1badda/crypt/debug ddc5e970-52e6-4318-b8ff-4dcce27f019f 100 GiB none off + oxp_9e1428de-ad48-4655-8ccc-bbf4cb1badda/crypt/zone f25c3f52-5701-477d-86da-2c223b58d6c9 none none off + oxp_9e1428de-ad48-4655-8ccc-bbf4cb1badda/crypt/zone/oxz_crucible_b7bf29a5-ef5f-4942-a3be-e943f7e6be80 3461362c-c93f-456d-ae57-c9f7e23b830e none none off + oxp_e03fee18-20c9-4c61-8927-bf80525f9b78/crucible ba32b0ca-d1de-4dec-b5f4-a83265f52497 none none off + oxp_e03fee18-20c9-4c61-8927-bf80525f9b78/crypt/debug 60758be8-1109-4a6c-86e4-4f94570f9b75 100 GiB none off + oxp_e03fee18-20c9-4c61-8927-bf80525f9b78/crypt/zone 22055c6e-87c5-4843-a270-b839de792db2 none none off + oxp_e03fee18-20c9-4c61-8927-bf80525f9b78/crypt/zone/oxz_crucible_75b0a160-7923-4f87-b7f3-f2d40b340e27 0c3ee3c8-82c5-43e3-9e91-cb168d35d1a8 none none off + oxp_e25482d4-7111-4acf-b621-4aab851ffda5/crucible ec84038d-6764-4e92-984e-bdc5fce4bef2 none none off + oxp_e25482d4-7111-4acf-b621-4aab851ffda5/crypt/debug 1952c1a0-a291-4c9c-b671-5b9bc26cebdd 100 GiB none off + oxp_e25482d4-7111-4acf-b621-4aab851ffda5/crypt/zone 91b21b39-f0f0-4844-ae69-ffb20c8fef3c none none off + oxp_e25482d4-7111-4acf-b621-4aab851ffda5/crypt/zone/oxz_crucible_3bff7b8a-1737-4f13-ba1c-713785f00c69 e236af3f-9926-46fe-be8e-99d7c91710f2 none none off + oxp_e90056a4-dd19-4ebb-b484-c677aea31d80/crucible 305fee2f-d06e-4cff-8a6a-044bb8771f4e none none off + oxp_e90056a4-dd19-4ebb-b484-c677aea31d80/crypt/debug 6af89615-bdf5-4f35-9b17-c121184d055c 100 GiB none off + oxp_e90056a4-dd19-4ebb-b484-c677aea31d80/crypt/zone 4cd0d29c-fc49-4264-958b-63d0d58b1bfc none none off + oxp_e90056a4-dd19-4ebb-b484-c677aea31d80/crypt/zone/oxz_crucible_d9f181c5-bda0-409f-ae72-a46a906ca931 b578393a-4e0d-4be5-9f6f-f1220d28a376 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP diff --git a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt index afb18592fc..8a8fc4848f 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt @@ -21,6 +21,59 @@ to: blueprint 4171ad05-89dd-474b-846b-b007e4346366 fake-vendor fake-model serial-f4a96860-bdeb-4435-bdf5-2a10beb3d44a + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crucible a9c330b0-1f48-4bd7-b82f-c3419131b69b none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/clickhouse 7c20d4b3-b783-4ba7-94a9-a8b789b04674 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/debug 0a6be80b-87c5-41fd-bd3c-36275b4ec494 100 GiB none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/internal_dns e2a6246d-00f0-4526-a2ab-99b2db7e1d51 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone 1e7c17fa-63d5-4a62-8037-663745516d18 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_clickhouse_b40f7c7b-526c-46c8-ae33-67280c280eb7 38af39e8-406d-4ffe-8afb-f15d80fbea27 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_crucible_be97b92b-38d6-422a-8c76-d37060f75bd2 574ac2b5-0233-488c-afd1-7ef4adc00b61 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_crucible_pantry_747d2426-68bf-4c22-8806-41d290b5d5f5 a820d4d1-87d3-419d-8ed3-0dda65f8e808 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_internal_dns_322ee9f1-8903-4542-a0a8-a54cefabdeca d907b775-c3e6-4b0f-90e7-a2923c5790bf none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_nexus_cc816cfe-3869-4dde-b596-397d41198628 5f84acc5-e9f0-4f12-93e9-d2a271cb8ea0 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_ntp_267ed614-92af-4b9d-bdba-c2881c2e43a2 bf1b301a-436c-4cdc-b3cd-de08dfcadc86 none none off + oxp_31a3bc64-7a3b-496d-b644-785dc44b6e37/crucible 2cd64120-ea51-450a-bac8-d419f9ff7570 none none off + oxp_31a3bc64-7a3b-496d-b644-785dc44b6e37/crypt/debug bbc7b434-12d0-441f-86c1-73d8355db38b 100 GiB none off + oxp_31a3bc64-7a3b-496d-b644-785dc44b6e37/crypt/zone 5e5c6b62-7785-4180-b901-6f33d676d657 none none off + oxp_31a3bc64-7a3b-496d-b644-785dc44b6e37/crypt/zone/oxz_crucible_64aa65f8-1ccb-4cd6-9953-027aebdac8ff 70a5b1f3-dbd7-4cc0-8a30-9a8c5ff33402 none none off + oxp_7bb40bd6-9c43-4b63-8337-18313c72aea2/crucible cd1cc777-1813-427a-bf3b-8b4cb372608c none none off + oxp_7bb40bd6-9c43-4b63-8337-18313c72aea2/crypt/debug 43858cf5-148f-48d8-9e52-b878d2efb6a5 100 GiB none off + oxp_7bb40bd6-9c43-4b63-8337-18313c72aea2/crypt/zone 536e5566-25b0-4df3-946e-3c59ca7d7921 none none off + oxp_7bb40bd6-9c43-4b63-8337-18313c72aea2/crypt/zone/oxz_crucible_b14d5478-1a0e-4b90-b526-36b06339dfc4 32f26a28-c25b-4706-9fc0-712ea55b8d88 none none off + oxp_988aa8c2-cb5e-406b-9289-425dc2e5bc3a/crucible 3aa68ad8-0539-4d41-a709-50fa4a0f97e5 none none off + oxp_988aa8c2-cb5e-406b-9289-425dc2e5bc3a/crypt/debug bb0c5843-4fd2-4c5a-a7a1-2bc1e9180caf 100 GiB none off + oxp_988aa8c2-cb5e-406b-9289-425dc2e5bc3a/crypt/zone 14d03496-7ebd-4272-a58b-4c193ba0da82 none none off + oxp_988aa8c2-cb5e-406b-9289-425dc2e5bc3a/crypt/zone/oxz_crucible_8e9e923e-62b1-4cbc-9f59-d6397e338b6b 724bd053-39f3-4c24-8109-a4d8b4c27787 none none off + oxp_ad574c09-2ae0-4534-a2a4-f923ce20ae87/crucible eb598474-ce25-4878-87b9-3c26318fb192 none none off + oxp_ad574c09-2ae0-4534-a2a4-f923ce20ae87/crypt/debug cb06ffe2-7f4d-4936-8310-ebf5e032a32c 100 GiB none off + oxp_ad574c09-2ae0-4534-a2a4-f923ce20ae87/crypt/zone 3fa2a825-bfec-42ca-801b-fc901f7a7729 none none off + oxp_ad574c09-2ae0-4534-a2a4-f923ce20ae87/crypt/zone/oxz_crucible_4ab1650f-32c5-447f-939d-64b8103a7645 e008db37-e6ad-433a-8aa1-ab06f8bad5b4 none none off + oxp_ad91e238-4901-4ff4-a91b-75233c936426/crucible 7600ccdd-7b1a-46b6-85c2-800b2d4cf4d8 none none off + oxp_ad91e238-4901-4ff4-a91b-75233c936426/crypt/debug 0a585f79-acdb-4de1-9e32-e54906b1cfb8 100 GiB none off + oxp_ad91e238-4901-4ff4-a91b-75233c936426/crypt/zone 63720ef8-1aad-4f61-a20b-078a1eb4d436 none none off + oxp_ad91e238-4901-4ff4-a91b-75233c936426/crypt/zone/oxz_crucible_6e811d86-8aa7-4660-935b-84b4b7721b10 b833252f-8df1-4188-9282-0b4e90caec37 none none off + oxp_ce58d463-d442-4c97-a6b4-f7d98c3fd902/crucible 3243c276-0a62-4c1f-9976-c353618b2585 none none off + oxp_ce58d463-d442-4c97-a6b4-f7d98c3fd902/crypt/debug e86ad99b-5040-4863-86db-0375ed3004f4 100 GiB none off + oxp_ce58d463-d442-4c97-a6b4-f7d98c3fd902/crypt/zone b58e8fae-21e3-4131-a37a-7c8bfaa242f5 none none off + oxp_ce58d463-d442-4c97-a6b4-f7d98c3fd902/crypt/zone/oxz_crucible_7fbd2c38-5dc3-48c4-b061-558a2041d70f 8a3cdc7a-802b-4b83-84bd-14f62c43ee7e none none off + oxp_f18f7689-0059-4b79-880e-34faf7a0fe0e/crucible f68ba9d0-a5cf-4148-a754-010656ae5f7b none none off + oxp_f18f7689-0059-4b79-880e-34faf7a0fe0e/crypt/debug f0945e1c-9c5e-463c-bee9-bee695299a0b 100 GiB none off + oxp_f18f7689-0059-4b79-880e-34faf7a0fe0e/crypt/zone e7e785bc-f3db-44ab-a4a2-71cca70507e4 none none off + oxp_f18f7689-0059-4b79-880e-34faf7a0fe0e/crypt/zone/oxz_crucible_08c7f8aa-1ea9-469b-8cac-2fdbfc11ebcb e78dca52-4122-428a-b8c5-911fcce624de none none off + oxp_f1d6cea4-640f-415e-89fe-2b1784ce3db8/crucible cea3cbac-de83-4a0c-a003-e42086fd418c none none off + oxp_f1d6cea4-640f-415e-89fe-2b1784ce3db8/crypt/debug dd7111a0-555c-4b82-82b3-2cc93a2b1771 100 GiB none off + oxp_f1d6cea4-640f-415e-89fe-2b1784ce3db8/crypt/zone 1e998426-2b49-4abc-877d-e7b972e5aadb none none off + oxp_f1d6cea4-640f-415e-89fe-2b1784ce3db8/crypt/zone/oxz_crucible_c66ab6d5-ff7a-46d1-9fd0-70cefa352d25 cc0d38c6-17f2-4861-889f-82398f0eade2 none none off + oxp_f4a96860-bdeb-4435-bdf5-2a10beb3d44a/crucible 88143ddc-773c-43e6-9859-5033535b566c none none off + oxp_f4a96860-bdeb-4435-bdf5-2a10beb3d44a/crypt/debug b8abfcc9-d636-4cee-b0a1-dd4b62ceade5 100 GiB none off + oxp_f4a96860-bdeb-4435-bdf5-2a10beb3d44a/crypt/zone c02e8b44-3f25-4529-9d97-9a908e52af1d none none off + oxp_f4a96860-bdeb-4435-bdf5-2a10beb3d44a/crypt/zone/oxz_crucible_3eda924f-22a9-4f3e-9a1b-91d1c47601ab 53c8cf89-1838-4638-8a27-683d134cbd9c none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -60,6 +113,57 @@ to: blueprint 4171ad05-89dd-474b-846b-b007e4346366 fake-vendor fake-model serial-db6686c8-2dd9-4032-8444-2a06b43baa68 + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crucible c333b519-9dde-4ada-b507-2bb0b6903d17 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/debug 438902f5-ff09-48f2-8cd1-56dc910d86e1 100 GiB none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/internal_dns 52de1771-5f5b-422d-89e9-96a708c66d47 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone 5a5afb9f-850c-4501-959a-8ff760eca117 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_crucible_8f3a1cc5-9195-4a30-ad02-b804278fe639 42a06863-188a-4755-99f7-659df14958f7 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_crucible_pantry_02acbe6a-1c88-47e3-94c3-94084cbde098 f7256e30-6e62-4d4b-ab06-8e9b3c9472a3 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_internal_dns_07c3c805-8888-4fe5-9543-3d2479dbe6f3 54774860-da9a-4ff5-8d63-242b353c3115 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_nexus_a1696cd4-588c-484a-b95b-66e824c0ce05 5dae2769-982f-485d-9776-57c2ae0a83b9 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_ntp_10d98a73-ec88-4aff-a7e8-7db6a87880e6 c5dc843c-fa8c-46a8-9998-aa0b0397cb0c none none off + oxp_29758363-6c77-40c3-8740-9c0c64f6e14a/crucible 2e09fba5-0c1d-41db-a310-bef9290c6332 none none off + oxp_29758363-6c77-40c3-8740-9c0c64f6e14a/crypt/debug bc7e5ae9-81f0-437b-9655-d11259f5037b 100 GiB none off + oxp_29758363-6c77-40c3-8740-9c0c64f6e14a/crypt/zone fe22839c-29a4-4711-8290-44dc001b9a4f none none off + oxp_29758363-6c77-40c3-8740-9c0c64f6e14a/crypt/zone/oxz_crucible_2a455c35-eb3c-4c73-ab6c-d0a706e25316 a3e5c0e0-3a73-4f7a-abd4-63f3434767aa none none off + oxp_3f331c10-7882-48ab-85d9-05108490b55b/crucible 76ab0eb2-4356-4a8a-b555-a83930ef3dce none none off + oxp_3f331c10-7882-48ab-85d9-05108490b55b/crypt/debug 70aadfba-de91-4157-ab8c-98609d278d08 100 GiB none off + oxp_3f331c10-7882-48ab-85d9-05108490b55b/crypt/zone 3b733c7c-d0b0-427b-952a-200518837f87 none none off + oxp_3f331c10-7882-48ab-85d9-05108490b55b/crypt/zone/oxz_crucible_a2079cbc-a69e-41a1-b1e0-fbcb972d03f6 94407842-70fd-48cd-933f-b0d7768fa45b none none off + oxp_5152d1aa-9045-4e06-9ef6-6eadac3696e4/crucible 52255a04-77be-486f-a59f-f17de4916cc2 none none off + oxp_5152d1aa-9045-4e06-9ef6-6eadac3696e4/crypt/debug d6835e81-73e5-46cc-a188-93050a2e4d40 100 GiB none off + oxp_5152d1aa-9045-4e06-9ef6-6eadac3696e4/crypt/zone 7aabaeaa-fd10-4686-bd7e-65a961ea3f87 none none off + oxp_5152d1aa-9045-4e06-9ef6-6eadac3696e4/crypt/zone/oxz_crucible_587be699-a320-4c79-b320-128d9ecddc0b 40cae2bc-0505-4d53-9d94-b199aaf5600a none none off + oxp_5c0dd424-d905-4fc5-a73c-36254fdd470c/crucible ab598283-0338-4124-af32-71aaf478908d none none off + oxp_5c0dd424-d905-4fc5-a73c-36254fdd470c/crypt/debug 4939df35-a183-4c85-9872-5ced516bd154 100 GiB none off + oxp_5c0dd424-d905-4fc5-a73c-36254fdd470c/crypt/zone e991d7ef-7ca9-444e-ad9d-6f9d165a3b80 none none off + oxp_5c0dd424-d905-4fc5-a73c-36254fdd470c/crypt/zone/oxz_crucible_6fa06115-4959-4913-8e7b-dd70d7651f07 8879f080-4bd0-4073-ab2f-08e6f289bb4f none none off + oxp_794df76f-bca0-4635-9eb6-773ad0108f7e/crucible e54385d1-cead-4fb6-a28e-c13db4e9561f none none off + oxp_794df76f-bca0-4635-9eb6-773ad0108f7e/crypt/debug 35f7f5fc-0d30-442d-b74a-7ef63b13bb6d 100 GiB none off + oxp_794df76f-bca0-4635-9eb6-773ad0108f7e/crypt/zone f06995fc-99ad-4184-80b4-3d0a42ac858f none none off + oxp_794df76f-bca0-4635-9eb6-773ad0108f7e/crypt/zone/oxz_crucible_47199d48-534c-4267-a654-d2d90e64b498 3e03f4d9-b2e7-4176-91e4-dc5771ccc769 none none off + oxp_9024d350-38a7-459b-8550-3b2c4a88b5c1/crucible 8aa9c334-3980-4875-b858-b1739848474d none none off + oxp_9024d350-38a7-459b-8550-3b2c4a88b5c1/crypt/debug d6fee205-15ef-41f9-8719-bc0be41ca283 100 GiB none off + oxp_9024d350-38a7-459b-8550-3b2c4a88b5c1/crypt/zone 61be50c0-fa4b-405a-86aa-51da8c92580a none none off + oxp_9024d350-38a7-459b-8550-3b2c4a88b5c1/crypt/zone/oxz_crucible_704e1fed-f8d6-4cfa-a470-bad27fdc06d1 c1fc8d09-f44c-4a2e-b989-066f2787d4c1 none none off + oxp_95e86080-e162-4980-a589-db6bb1a95ca7/crucible de88d3ee-683a-4550-99de-aca94b4a276a none none off + oxp_95e86080-e162-4980-a589-db6bb1a95ca7/crypt/debug c2e8fc9e-40d5-4ba5-b9e6-bd4ac88b0e7b 100 GiB none off + oxp_95e86080-e162-4980-a589-db6bb1a95ca7/crypt/zone 15bb82a6-77c3-4c3c-ac8b-95c524526704 none none off + oxp_95e86080-e162-4980-a589-db6bb1a95ca7/crypt/zone/oxz_crucible_af322036-371f-437c-8c08-7f40f3f1403b a9f4142b-b18d-4f1a-8274-6fbf12a10efd none none off + oxp_d55d36d7-df92-4615-944d-440a1f8b5001/crucible f6df8087-1122-4715-851a-127309740840 none none off + oxp_d55d36d7-df92-4615-944d-440a1f8b5001/crypt/debug 05b52b8d-dd52-4a9e-b647-445959885d68 100 GiB none off + oxp_d55d36d7-df92-4615-944d-440a1f8b5001/crypt/zone b7b7c0d6-b2dd-41eb-b5fe-3e131356552f none none off + oxp_d55d36d7-df92-4615-944d-440a1f8b5001/crypt/zone/oxz_crucible_edabedf3-839c-488d-ad6f-508ffa864674 3b94580a-8ff4-4302-8d49-30ff90ef7edc none none off + oxp_db6686c8-2dd9-4032-8444-2a06b43baa68/crucible 882faa8a-7df8-4f67-abba-3d97a2615932 none none off + oxp_db6686c8-2dd9-4032-8444-2a06b43baa68/crypt/debug f5e9ef79-2d90-4839-aeac-a1048516011e 100 GiB none off + oxp_db6686c8-2dd9-4032-8444-2a06b43baa68/crypt/zone 51701069-af21-4697-8655-d2a8e455dceb none none off + oxp_db6686c8-2dd9-4032-8444-2a06b43baa68/crypt/zone/oxz_crucible_d637264f-6f40-44c2-8b7e-a179430210d2 bde5f2d8-ac7d-40e8-b4b7-2cdf1cff6e57 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -98,6 +202,57 @@ to: blueprint 4171ad05-89dd-474b-846b-b007e4346366 fake-vendor fake-model serial-f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crucible 35656b97-3eef-46f2-aab0-4d33a3548e62 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/debug bdf9f107-3a76-4d6a-bea0-5cfb4d3cd6a6 100 GiB none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/internal_dns b0fcbafd-55cc-49f1-9e02-20503673b34f none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone 0b216cae-701c-4341-a48a-690543b8e1e9 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_crucible_18f8fe40-646e-4962-b17a-20e201f3a6e5 eec6f425-d377-42b2-890f-c8c3efe173d1 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_crucible_pantry_56d5d7cf-db2c-40a3-a775-003241ad4820 ada776f0-dbb1-4430-a60a-f7462570a19c none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_internal_dns_ab7ba6df-d401-40bd-940e-faf57c57aa2a 2fe942e9-085c-4155-a415-c2dcc19dbd91 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_nexus_dce226c9-7373-4bfa-8a94-79dc472857a6 4abeefa4-e5ad-46e9-b056-79f5950a5639 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_ntp_7a9f60d3-2b66-4547-9b63-7d4f7a8b6382 63337e4d-61a8-4dc7-a322-1442bc19b347 none none off + oxp_32456d15-f5b6-4efc-90c8-dbba979b69cb/crucible 3b4f5bbb-cc50-49ae-a208-ca88dff1f361 none none off + oxp_32456d15-f5b6-4efc-90c8-dbba979b69cb/crypt/debug cc56ea63-0ae3-48ce-8faf-1bd7d83eb148 100 GiB none off + oxp_32456d15-f5b6-4efc-90c8-dbba979b69cb/crypt/zone dce1da41-4cf0-4f70-a9e5-f4b7fad54228 none none off + oxp_32456d15-f5b6-4efc-90c8-dbba979b69cb/crypt/zone/oxz_crucible_6af7f4d6-33b6-4eb3-a146-d8e9e4ae9d66 a33e3d63-6413-42b2-8cf0-02c774666916 none none off + oxp_416fe9f9-5161-4b0f-9e11-c9d81563ded5/crucible 8b82f804-50d1-4e6f-8899-181433d82299 none none off + oxp_416fe9f9-5161-4b0f-9e11-c9d81563ded5/crypt/debug abe29a15-fef4-47ef-8f08-71645f396213 100 GiB none off + oxp_416fe9f9-5161-4b0f-9e11-c9d81563ded5/crypt/zone 5dd03902-3f33-4f7e-896a-c08d7c6e37f1 none none off + oxp_416fe9f9-5161-4b0f-9e11-c9d81563ded5/crypt/zone/oxz_crucible_93f2f40c-5616-4d8d-8519-ec6debdcede0 4c0303d2-205d-4644-b55a-5c74bbce54c3 none none off + oxp_4c68800e-23f8-485b-b251-628fd151e445/crucible 226cea90-799d-4f6c-90c6-459d2d7674c1 none none off + oxp_4c68800e-23f8-485b-b251-628fd151e445/crypt/debug 32535344-4e21-4fb3-9c43-f305ac367efb 100 GiB none off + oxp_4c68800e-23f8-485b-b251-628fd151e445/crypt/zone 447cc163-589e-479c-a73d-0071e5dc59bf none none off + oxp_4c68800e-23f8-485b-b251-628fd151e445/crypt/zone/oxz_crucible_1cc3f503-2001-4d85-80e5-c7c40d2e3b10 5a210076-5d38-42f0-915c-c09f67ff38af none none off + oxp_9dd87c4d-5fb4-475a-86fa-c0da81a3e00a/crucible d5cd0c03-b555-46b2-b626-cc70bd931cee none none off + oxp_9dd87c4d-5fb4-475a-86fa-c0da81a3e00a/crypt/debug 7d983611-ab39-4083-93d0-f57f3ada9a0f 100 GiB none off + oxp_9dd87c4d-5fb4-475a-86fa-c0da81a3e00a/crypt/zone 0568d976-0079-4d6e-9d1f-a8cfc4b044df none none off + oxp_9dd87c4d-5fb4-475a-86fa-c0da81a3e00a/crypt/zone/oxz_crucible_0565e7e4-f13a-4123-8928-d715f83e36aa e2de5537-a289-49ac-8a61-65805a35755e none none off + oxp_be93a517-445e-46c2-aa21-3dc526d4a413/crucible 52fde46c-85f4-470e-8c1e-38b13c042620 none none off + oxp_be93a517-445e-46c2-aa21-3dc526d4a413/crypt/debug 731caeb5-c24f-45b7-aafa-4e747302eaa4 100 GiB none off + oxp_be93a517-445e-46c2-aa21-3dc526d4a413/crypt/zone f75ef0e7-663d-4418-99b6-71e2a77a27e3 none none off + oxp_be93a517-445e-46c2-aa21-3dc526d4a413/crypt/zone/oxz_crucible_62058f4c-c747-4e21-a8dc-2fd4a160c98c 0679ecb0-7759-4561-a81a-d8d7125ed693 none none off + oxp_d9344e2b-84d2-4392-84ab-41b86ed02237/crucible 4b593475-e809-4822-a2fe-c58b2f7964d5 none none off + oxp_d9344e2b-84d2-4392-84ab-41b86ed02237/crypt/debug 19bdebd5-9757-409f-ac06-2b309379975e 100 GiB none off + oxp_d9344e2b-84d2-4392-84ab-41b86ed02237/crypt/zone aa37e2bb-8d16-4dc2-a549-5f265fdad77c none none off + oxp_d9344e2b-84d2-4392-84ab-41b86ed02237/crypt/zone/oxz_crucible_062ce37d-7448-4d44-b1f4-4937cd2eb174 bfbfe7dd-06b3-4d01-988d-488c7b9400ee none none off + oxp_eab188d0-b34a-4673-b254-12e705597654/crucible 444a65f5-d04e-4cb0-a29d-ba57e34143dd none none off + oxp_eab188d0-b34a-4673-b254-12e705597654/crypt/debug 411e3457-3113-40d2-8a7d-747b108e0de7 100 GiB none off + oxp_eab188d0-b34a-4673-b254-12e705597654/crypt/zone a79b0ad5-06b8-466c-928c-39e311ace5ff none none off + oxp_eab188d0-b34a-4673-b254-12e705597654/crypt/zone/oxz_crucible_78d6ab36-e8c8-4ff8-9f89-75c7fe2d32e6 ebc48adb-4dad-41dd-a547-13f2517a9db7 none none off + oxp_f1e0386f-11b6-4cdf-8250-826d256db6b5/crucible 826e2ca8-f30d-4ca3-8360-f03ddd883a6a none none off + oxp_f1e0386f-11b6-4cdf-8250-826d256db6b5/crypt/debug 79ce0088-d8a8-46ff-ac72-958b8db65e67 100 GiB none off + oxp_f1e0386f-11b6-4cdf-8250-826d256db6b5/crypt/zone 1635dad8-971f-4409-aca3-6204eafec699 none none off + oxp_f1e0386f-11b6-4cdf-8250-826d256db6b5/crypt/zone/oxz_crucible_9f824c30-6360-46b9-87c4-cd60586476fe c4e2119e-4cd0-48e7-878f-fd676cb95094 none none off + oxp_f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b/crucible 2c6ade05-a626-4b25-a28c-551b91867dbe none none off + oxp_f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b/crypt/debug 2059b139-6510-4447-aa56-cf995ef469a2 100 GiB none off + oxp_f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b/crypt/zone 1b5a2379-ec60-47f0-8def-fa3ab5c2487c none none off + oxp_f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b/crypt/zone/oxz_crucible_1211a68e-69a1-4ef4-b790-45b0279f9159 32a3e82a-b97a-455a-b428-1f934c59eb46 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -138,6 +293,33 @@ to: blueprint 4171ad05-89dd-474b-846b-b007e4346366 + fake-vendor fake-model serial-e6f289fe-142e-4778-8629-dc87adb53f06 + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ++ oxp_1bb5ee5d-c2c6-4eaa-86c4-817d89cf10cf/crypt/debug bbab775c-07b1-48b1-96a3-9aa155396112 100 GiB none off ++ oxp_1bb5ee5d-c2c6-4eaa-86c4-817d89cf10cf/crypt/zone 07381a6a-e397-4ff9-a2a6-8f1e46c47eb3 none none off ++ oxp_1bb5ee5d-c2c6-4eaa-86c4-817d89cf10cf/crypt/zone/oxz_ntp_2d73d30e-ca47-46a8-9c12-917d4ab824b6 15e9b1ea-8947-48a2-8d78-54665e4d2f03 none none off ++ oxp_298d1eec-0313-4a42-8af9-0e51299a14ef/crypt/debug 75c1432c-19e1-4c06-8393-9907a09efcbe 100 GiB none off ++ oxp_298d1eec-0313-4a42-8af9-0e51299a14ef/crypt/zone b4c23ac2-86af-4b47-9d8d-35eff7997788 none none off ++ oxp_2eed666f-a10b-42d0-b626-68335d3270b8/crypt/debug 5501c13c-0500-41a6-9c48-85924383fdcc 100 GiB none off ++ oxp_2eed666f-a10b-42d0-b626-68335d3270b8/crypt/zone 32143abb-e501-450b-9398-7d4dbebdf4a1 none none off ++ oxp_6cc4d7a7-2a89-4f2f-aa55-5e7a10d0fc08/crypt/debug bcdd7a5a-59da-4a47-8a1c-79281428d71b 100 GiB none off ++ oxp_6cc4d7a7-2a89-4f2f-aa55-5e7a10d0fc08/crypt/zone 6ec35c5c-9d6c-4d72-8bec-af91069ef7a9 none none off ++ oxp_7aad6fd9-b698-4c77-af6b-947be10ba953/crypt/debug 8ed9d1a2-3c60-4f38-9e80-5c56d80e67c9 100 GiB none off ++ oxp_7aad6fd9-b698-4c77-af6b-947be10ba953/crypt/zone 8529e7f8-8c11-4869-b330-83ddc45ed17a none none off ++ oxp_a5a15e51-c48a-40e4-a2d8-1c7198c1d46b/crypt/debug 4aef9a3a-0829-4ada-a0e9-a45c91e74249 100 GiB none off ++ oxp_a5a15e51-c48a-40e4-a2d8-1c7198c1d46b/crypt/zone 940f06cb-822f-4034-8d34-e14bcc6ea998 none none off ++ oxp_b81d4993-ea5b-4720-b8c8-2360c1121d6e/crypt/debug 2b23d885-836a-4270-886d-08640aae90aa 100 GiB none off ++ oxp_b81d4993-ea5b-4720-b8c8-2360c1121d6e/crypt/zone 0507b005-e018-4b69-9d84-50faf61e792f none none off ++ oxp_d0064c4d-f5f7-4c89-9f37-0ca475048e79/crypt/debug feac64a1-ade2-4f88-8c17-64d2863e2be6 100 GiB none off ++ oxp_d0064c4d-f5f7-4c89-9f37-0ca475048e79/crypt/zone 1acaf776-970a-49cf-9f1c-7d8e3146ef11 none none off ++ oxp_dba739c1-76e4-4b6a-a173-89c938fa13ef/crypt/debug 91edd9a1-178d-4aa5-83a7-b2f4ef1fc44a 100 GiB none off ++ oxp_dba739c1-76e4-4b6a-a173-89c938fa13ef/crypt/zone 1bd5ab24-2a54-438f-bae0-af1b06a3cc41 none none off ++ oxp_e6f289fe-142e-4778-8629-dc87adb53f06/crypt/debug 8840ccd9-e8c9-48d9-affb-8587e468b204 100 GiB none off ++ oxp_e6f289fe-142e-4778-8629-dc87adb53f06/crypt/zone b501adbf-be36-4724-9409-329f690fb09d none none off + + omicron zones at generation 2: ------------------------------------------------------------------------------------------ zone type zone id disposition underlay IP diff --git a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt index 9a5ed1c37a..baf38dd2a5 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt @@ -21,6 +21,59 @@ to: blueprint f432fcd5-1284-4058-8b4a-9286a3de6163 fake-vendor fake-model serial-f4a96860-bdeb-4435-bdf5-2a10beb3d44a + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crucible a9c330b0-1f48-4bd7-b82f-c3419131b69b none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/clickhouse 7c20d4b3-b783-4ba7-94a9-a8b789b04674 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/debug 0a6be80b-87c5-41fd-bd3c-36275b4ec494 100 GiB none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/internal_dns e2a6246d-00f0-4526-a2ab-99b2db7e1d51 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone 1e7c17fa-63d5-4a62-8037-663745516d18 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_clickhouse_b40f7c7b-526c-46c8-ae33-67280c280eb7 38af39e8-406d-4ffe-8afb-f15d80fbea27 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_crucible_be97b92b-38d6-422a-8c76-d37060f75bd2 574ac2b5-0233-488c-afd1-7ef4adc00b61 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_crucible_pantry_747d2426-68bf-4c22-8806-41d290b5d5f5 a820d4d1-87d3-419d-8ed3-0dda65f8e808 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_internal_dns_322ee9f1-8903-4542-a0a8-a54cefabdeca d907b775-c3e6-4b0f-90e7-a2923c5790bf none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_nexus_cc816cfe-3869-4dde-b596-397d41198628 5f84acc5-e9f0-4f12-93e9-d2a271cb8ea0 none none off + oxp_014eb1e9-04fe-4f36-8339-0a090b053ada/crypt/zone/oxz_ntp_267ed614-92af-4b9d-bdba-c2881c2e43a2 bf1b301a-436c-4cdc-b3cd-de08dfcadc86 none none off + oxp_31a3bc64-7a3b-496d-b644-785dc44b6e37/crucible 2cd64120-ea51-450a-bac8-d419f9ff7570 none none off + oxp_31a3bc64-7a3b-496d-b644-785dc44b6e37/crypt/debug bbc7b434-12d0-441f-86c1-73d8355db38b 100 GiB none off + oxp_31a3bc64-7a3b-496d-b644-785dc44b6e37/crypt/zone 5e5c6b62-7785-4180-b901-6f33d676d657 none none off + oxp_31a3bc64-7a3b-496d-b644-785dc44b6e37/crypt/zone/oxz_crucible_64aa65f8-1ccb-4cd6-9953-027aebdac8ff 70a5b1f3-dbd7-4cc0-8a30-9a8c5ff33402 none none off + oxp_7bb40bd6-9c43-4b63-8337-18313c72aea2/crucible cd1cc777-1813-427a-bf3b-8b4cb372608c none none off + oxp_7bb40bd6-9c43-4b63-8337-18313c72aea2/crypt/debug 43858cf5-148f-48d8-9e52-b878d2efb6a5 100 GiB none off + oxp_7bb40bd6-9c43-4b63-8337-18313c72aea2/crypt/zone 536e5566-25b0-4df3-946e-3c59ca7d7921 none none off + oxp_7bb40bd6-9c43-4b63-8337-18313c72aea2/crypt/zone/oxz_crucible_b14d5478-1a0e-4b90-b526-36b06339dfc4 32f26a28-c25b-4706-9fc0-712ea55b8d88 none none off + oxp_988aa8c2-cb5e-406b-9289-425dc2e5bc3a/crucible 3aa68ad8-0539-4d41-a709-50fa4a0f97e5 none none off + oxp_988aa8c2-cb5e-406b-9289-425dc2e5bc3a/crypt/debug bb0c5843-4fd2-4c5a-a7a1-2bc1e9180caf 100 GiB none off + oxp_988aa8c2-cb5e-406b-9289-425dc2e5bc3a/crypt/zone 14d03496-7ebd-4272-a58b-4c193ba0da82 none none off + oxp_988aa8c2-cb5e-406b-9289-425dc2e5bc3a/crypt/zone/oxz_crucible_8e9e923e-62b1-4cbc-9f59-d6397e338b6b 724bd053-39f3-4c24-8109-a4d8b4c27787 none none off + oxp_ad574c09-2ae0-4534-a2a4-f923ce20ae87/crucible eb598474-ce25-4878-87b9-3c26318fb192 none none off + oxp_ad574c09-2ae0-4534-a2a4-f923ce20ae87/crypt/debug cb06ffe2-7f4d-4936-8310-ebf5e032a32c 100 GiB none off + oxp_ad574c09-2ae0-4534-a2a4-f923ce20ae87/crypt/zone 3fa2a825-bfec-42ca-801b-fc901f7a7729 none none off + oxp_ad574c09-2ae0-4534-a2a4-f923ce20ae87/crypt/zone/oxz_crucible_4ab1650f-32c5-447f-939d-64b8103a7645 e008db37-e6ad-433a-8aa1-ab06f8bad5b4 none none off + oxp_ad91e238-4901-4ff4-a91b-75233c936426/crucible 7600ccdd-7b1a-46b6-85c2-800b2d4cf4d8 none none off + oxp_ad91e238-4901-4ff4-a91b-75233c936426/crypt/debug 0a585f79-acdb-4de1-9e32-e54906b1cfb8 100 GiB none off + oxp_ad91e238-4901-4ff4-a91b-75233c936426/crypt/zone 63720ef8-1aad-4f61-a20b-078a1eb4d436 none none off + oxp_ad91e238-4901-4ff4-a91b-75233c936426/crypt/zone/oxz_crucible_6e811d86-8aa7-4660-935b-84b4b7721b10 b833252f-8df1-4188-9282-0b4e90caec37 none none off + oxp_ce58d463-d442-4c97-a6b4-f7d98c3fd902/crucible 3243c276-0a62-4c1f-9976-c353618b2585 none none off + oxp_ce58d463-d442-4c97-a6b4-f7d98c3fd902/crypt/debug e86ad99b-5040-4863-86db-0375ed3004f4 100 GiB none off + oxp_ce58d463-d442-4c97-a6b4-f7d98c3fd902/crypt/zone b58e8fae-21e3-4131-a37a-7c8bfaa242f5 none none off + oxp_ce58d463-d442-4c97-a6b4-f7d98c3fd902/crypt/zone/oxz_crucible_7fbd2c38-5dc3-48c4-b061-558a2041d70f 8a3cdc7a-802b-4b83-84bd-14f62c43ee7e none none off + oxp_f18f7689-0059-4b79-880e-34faf7a0fe0e/crucible f68ba9d0-a5cf-4148-a754-010656ae5f7b none none off + oxp_f18f7689-0059-4b79-880e-34faf7a0fe0e/crypt/debug f0945e1c-9c5e-463c-bee9-bee695299a0b 100 GiB none off + oxp_f18f7689-0059-4b79-880e-34faf7a0fe0e/crypt/zone e7e785bc-f3db-44ab-a4a2-71cca70507e4 none none off + oxp_f18f7689-0059-4b79-880e-34faf7a0fe0e/crypt/zone/oxz_crucible_08c7f8aa-1ea9-469b-8cac-2fdbfc11ebcb e78dca52-4122-428a-b8c5-911fcce624de none none off + oxp_f1d6cea4-640f-415e-89fe-2b1784ce3db8/crucible cea3cbac-de83-4a0c-a003-e42086fd418c none none off + oxp_f1d6cea4-640f-415e-89fe-2b1784ce3db8/crypt/debug dd7111a0-555c-4b82-82b3-2cc93a2b1771 100 GiB none off + oxp_f1d6cea4-640f-415e-89fe-2b1784ce3db8/crypt/zone 1e998426-2b49-4abc-877d-e7b972e5aadb none none off + oxp_f1d6cea4-640f-415e-89fe-2b1784ce3db8/crypt/zone/oxz_crucible_c66ab6d5-ff7a-46d1-9fd0-70cefa352d25 cc0d38c6-17f2-4861-889f-82398f0eade2 none none off + oxp_f4a96860-bdeb-4435-bdf5-2a10beb3d44a/crucible 88143ddc-773c-43e6-9859-5033535b566c none none off + oxp_f4a96860-bdeb-4435-bdf5-2a10beb3d44a/crypt/debug b8abfcc9-d636-4cee-b0a1-dd4b62ceade5 100 GiB none off + oxp_f4a96860-bdeb-4435-bdf5-2a10beb3d44a/crypt/zone c02e8b44-3f25-4529-9d97-9a908e52af1d none none off + oxp_f4a96860-bdeb-4435-bdf5-2a10beb3d44a/crypt/zone/oxz_crucible_3eda924f-22a9-4f3e-9a1b-91d1c47601ab 53c8cf89-1838-4638-8a27-683d134cbd9c none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -60,6 +113,57 @@ to: blueprint f432fcd5-1284-4058-8b4a-9286a3de6163 fake-vendor fake-model serial-db6686c8-2dd9-4032-8444-2a06b43baa68 + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crucible c333b519-9dde-4ada-b507-2bb0b6903d17 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/debug 438902f5-ff09-48f2-8cd1-56dc910d86e1 100 GiB none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/internal_dns 52de1771-5f5b-422d-89e9-96a708c66d47 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone 5a5afb9f-850c-4501-959a-8ff760eca117 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_crucible_8f3a1cc5-9195-4a30-ad02-b804278fe639 42a06863-188a-4755-99f7-659df14958f7 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_crucible_pantry_02acbe6a-1c88-47e3-94c3-94084cbde098 f7256e30-6e62-4d4b-ab06-8e9b3c9472a3 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_internal_dns_07c3c805-8888-4fe5-9543-3d2479dbe6f3 54774860-da9a-4ff5-8d63-242b353c3115 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_nexus_a1696cd4-588c-484a-b95b-66e824c0ce05 5dae2769-982f-485d-9776-57c2ae0a83b9 none none off + oxp_12057b4a-0b06-4f70-ba22-336de2385bfe/crypt/zone/oxz_ntp_10d98a73-ec88-4aff-a7e8-7db6a87880e6 c5dc843c-fa8c-46a8-9998-aa0b0397cb0c none none off + oxp_29758363-6c77-40c3-8740-9c0c64f6e14a/crucible 2e09fba5-0c1d-41db-a310-bef9290c6332 none none off + oxp_29758363-6c77-40c3-8740-9c0c64f6e14a/crypt/debug bc7e5ae9-81f0-437b-9655-d11259f5037b 100 GiB none off + oxp_29758363-6c77-40c3-8740-9c0c64f6e14a/crypt/zone fe22839c-29a4-4711-8290-44dc001b9a4f none none off + oxp_29758363-6c77-40c3-8740-9c0c64f6e14a/crypt/zone/oxz_crucible_2a455c35-eb3c-4c73-ab6c-d0a706e25316 a3e5c0e0-3a73-4f7a-abd4-63f3434767aa none none off + oxp_3f331c10-7882-48ab-85d9-05108490b55b/crucible 76ab0eb2-4356-4a8a-b555-a83930ef3dce none none off + oxp_3f331c10-7882-48ab-85d9-05108490b55b/crypt/debug 70aadfba-de91-4157-ab8c-98609d278d08 100 GiB none off + oxp_3f331c10-7882-48ab-85d9-05108490b55b/crypt/zone 3b733c7c-d0b0-427b-952a-200518837f87 none none off + oxp_3f331c10-7882-48ab-85d9-05108490b55b/crypt/zone/oxz_crucible_a2079cbc-a69e-41a1-b1e0-fbcb972d03f6 94407842-70fd-48cd-933f-b0d7768fa45b none none off + oxp_5152d1aa-9045-4e06-9ef6-6eadac3696e4/crucible 52255a04-77be-486f-a59f-f17de4916cc2 none none off + oxp_5152d1aa-9045-4e06-9ef6-6eadac3696e4/crypt/debug d6835e81-73e5-46cc-a188-93050a2e4d40 100 GiB none off + oxp_5152d1aa-9045-4e06-9ef6-6eadac3696e4/crypt/zone 7aabaeaa-fd10-4686-bd7e-65a961ea3f87 none none off + oxp_5152d1aa-9045-4e06-9ef6-6eadac3696e4/crypt/zone/oxz_crucible_587be699-a320-4c79-b320-128d9ecddc0b 40cae2bc-0505-4d53-9d94-b199aaf5600a none none off + oxp_5c0dd424-d905-4fc5-a73c-36254fdd470c/crucible ab598283-0338-4124-af32-71aaf478908d none none off + oxp_5c0dd424-d905-4fc5-a73c-36254fdd470c/crypt/debug 4939df35-a183-4c85-9872-5ced516bd154 100 GiB none off + oxp_5c0dd424-d905-4fc5-a73c-36254fdd470c/crypt/zone e991d7ef-7ca9-444e-ad9d-6f9d165a3b80 none none off + oxp_5c0dd424-d905-4fc5-a73c-36254fdd470c/crypt/zone/oxz_crucible_6fa06115-4959-4913-8e7b-dd70d7651f07 8879f080-4bd0-4073-ab2f-08e6f289bb4f none none off + oxp_794df76f-bca0-4635-9eb6-773ad0108f7e/crucible e54385d1-cead-4fb6-a28e-c13db4e9561f none none off + oxp_794df76f-bca0-4635-9eb6-773ad0108f7e/crypt/debug 35f7f5fc-0d30-442d-b74a-7ef63b13bb6d 100 GiB none off + oxp_794df76f-bca0-4635-9eb6-773ad0108f7e/crypt/zone f06995fc-99ad-4184-80b4-3d0a42ac858f none none off + oxp_794df76f-bca0-4635-9eb6-773ad0108f7e/crypt/zone/oxz_crucible_47199d48-534c-4267-a654-d2d90e64b498 3e03f4d9-b2e7-4176-91e4-dc5771ccc769 none none off + oxp_9024d350-38a7-459b-8550-3b2c4a88b5c1/crucible 8aa9c334-3980-4875-b858-b1739848474d none none off + oxp_9024d350-38a7-459b-8550-3b2c4a88b5c1/crypt/debug d6fee205-15ef-41f9-8719-bc0be41ca283 100 GiB none off + oxp_9024d350-38a7-459b-8550-3b2c4a88b5c1/crypt/zone 61be50c0-fa4b-405a-86aa-51da8c92580a none none off + oxp_9024d350-38a7-459b-8550-3b2c4a88b5c1/crypt/zone/oxz_crucible_704e1fed-f8d6-4cfa-a470-bad27fdc06d1 c1fc8d09-f44c-4a2e-b989-066f2787d4c1 none none off + oxp_95e86080-e162-4980-a589-db6bb1a95ca7/crucible de88d3ee-683a-4550-99de-aca94b4a276a none none off + oxp_95e86080-e162-4980-a589-db6bb1a95ca7/crypt/debug c2e8fc9e-40d5-4ba5-b9e6-bd4ac88b0e7b 100 GiB none off + oxp_95e86080-e162-4980-a589-db6bb1a95ca7/crypt/zone 15bb82a6-77c3-4c3c-ac8b-95c524526704 none none off + oxp_95e86080-e162-4980-a589-db6bb1a95ca7/crypt/zone/oxz_crucible_af322036-371f-437c-8c08-7f40f3f1403b a9f4142b-b18d-4f1a-8274-6fbf12a10efd none none off + oxp_d55d36d7-df92-4615-944d-440a1f8b5001/crucible f6df8087-1122-4715-851a-127309740840 none none off + oxp_d55d36d7-df92-4615-944d-440a1f8b5001/crypt/debug 05b52b8d-dd52-4a9e-b647-445959885d68 100 GiB none off + oxp_d55d36d7-df92-4615-944d-440a1f8b5001/crypt/zone b7b7c0d6-b2dd-41eb-b5fe-3e131356552f none none off + oxp_d55d36d7-df92-4615-944d-440a1f8b5001/crypt/zone/oxz_crucible_edabedf3-839c-488d-ad6f-508ffa864674 3b94580a-8ff4-4302-8d49-30ff90ef7edc none none off + oxp_db6686c8-2dd9-4032-8444-2a06b43baa68/crucible 882faa8a-7df8-4f67-abba-3d97a2615932 none none off + oxp_db6686c8-2dd9-4032-8444-2a06b43baa68/crypt/debug f5e9ef79-2d90-4839-aeac-a1048516011e 100 GiB none off + oxp_db6686c8-2dd9-4032-8444-2a06b43baa68/crypt/zone 51701069-af21-4697-8655-d2a8e455dceb none none off + oxp_db6686c8-2dd9-4032-8444-2a06b43baa68/crypt/zone/oxz_crucible_d637264f-6f40-44c2-8b7e-a179430210d2 bde5f2d8-ac7d-40e8-b4b7-2cdf1cff6e57 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -98,6 +202,57 @@ to: blueprint f432fcd5-1284-4058-8b4a-9286a3de6163 fake-vendor fake-model serial-f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crucible 35656b97-3eef-46f2-aab0-4d33a3548e62 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/debug bdf9f107-3a76-4d6a-bea0-5cfb4d3cd6a6 100 GiB none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/internal_dns b0fcbafd-55cc-49f1-9e02-20503673b34f none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone 0b216cae-701c-4341-a48a-690543b8e1e9 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_crucible_18f8fe40-646e-4962-b17a-20e201f3a6e5 eec6f425-d377-42b2-890f-c8c3efe173d1 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_crucible_pantry_56d5d7cf-db2c-40a3-a775-003241ad4820 ada776f0-dbb1-4430-a60a-f7462570a19c none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_internal_dns_ab7ba6df-d401-40bd-940e-faf57c57aa2a 2fe942e9-085c-4155-a415-c2dcc19dbd91 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_nexus_dce226c9-7373-4bfa-8a94-79dc472857a6 4abeefa4-e5ad-46e9-b056-79f5950a5639 none none off + oxp_2a94863d-16e2-4535-973b-e98dd47fd18d/crypt/zone/oxz_ntp_7a9f60d3-2b66-4547-9b63-7d4f7a8b6382 63337e4d-61a8-4dc7-a322-1442bc19b347 none none off + oxp_32456d15-f5b6-4efc-90c8-dbba979b69cb/crucible 3b4f5bbb-cc50-49ae-a208-ca88dff1f361 none none off + oxp_32456d15-f5b6-4efc-90c8-dbba979b69cb/crypt/debug cc56ea63-0ae3-48ce-8faf-1bd7d83eb148 100 GiB none off + oxp_32456d15-f5b6-4efc-90c8-dbba979b69cb/crypt/zone dce1da41-4cf0-4f70-a9e5-f4b7fad54228 none none off + oxp_32456d15-f5b6-4efc-90c8-dbba979b69cb/crypt/zone/oxz_crucible_6af7f4d6-33b6-4eb3-a146-d8e9e4ae9d66 a33e3d63-6413-42b2-8cf0-02c774666916 none none off + oxp_416fe9f9-5161-4b0f-9e11-c9d81563ded5/crucible 8b82f804-50d1-4e6f-8899-181433d82299 none none off + oxp_416fe9f9-5161-4b0f-9e11-c9d81563ded5/crypt/debug abe29a15-fef4-47ef-8f08-71645f396213 100 GiB none off + oxp_416fe9f9-5161-4b0f-9e11-c9d81563ded5/crypt/zone 5dd03902-3f33-4f7e-896a-c08d7c6e37f1 none none off + oxp_416fe9f9-5161-4b0f-9e11-c9d81563ded5/crypt/zone/oxz_crucible_93f2f40c-5616-4d8d-8519-ec6debdcede0 4c0303d2-205d-4644-b55a-5c74bbce54c3 none none off + oxp_4c68800e-23f8-485b-b251-628fd151e445/crucible 226cea90-799d-4f6c-90c6-459d2d7674c1 none none off + oxp_4c68800e-23f8-485b-b251-628fd151e445/crypt/debug 32535344-4e21-4fb3-9c43-f305ac367efb 100 GiB none off + oxp_4c68800e-23f8-485b-b251-628fd151e445/crypt/zone 447cc163-589e-479c-a73d-0071e5dc59bf none none off + oxp_4c68800e-23f8-485b-b251-628fd151e445/crypt/zone/oxz_crucible_1cc3f503-2001-4d85-80e5-c7c40d2e3b10 5a210076-5d38-42f0-915c-c09f67ff38af none none off + oxp_9dd87c4d-5fb4-475a-86fa-c0da81a3e00a/crucible d5cd0c03-b555-46b2-b626-cc70bd931cee none none off + oxp_9dd87c4d-5fb4-475a-86fa-c0da81a3e00a/crypt/debug 7d983611-ab39-4083-93d0-f57f3ada9a0f 100 GiB none off + oxp_9dd87c4d-5fb4-475a-86fa-c0da81a3e00a/crypt/zone 0568d976-0079-4d6e-9d1f-a8cfc4b044df none none off + oxp_9dd87c4d-5fb4-475a-86fa-c0da81a3e00a/crypt/zone/oxz_crucible_0565e7e4-f13a-4123-8928-d715f83e36aa e2de5537-a289-49ac-8a61-65805a35755e none none off + oxp_be93a517-445e-46c2-aa21-3dc526d4a413/crucible 52fde46c-85f4-470e-8c1e-38b13c042620 none none off + oxp_be93a517-445e-46c2-aa21-3dc526d4a413/crypt/debug 731caeb5-c24f-45b7-aafa-4e747302eaa4 100 GiB none off + oxp_be93a517-445e-46c2-aa21-3dc526d4a413/crypt/zone f75ef0e7-663d-4418-99b6-71e2a77a27e3 none none off + oxp_be93a517-445e-46c2-aa21-3dc526d4a413/crypt/zone/oxz_crucible_62058f4c-c747-4e21-a8dc-2fd4a160c98c 0679ecb0-7759-4561-a81a-d8d7125ed693 none none off + oxp_d9344e2b-84d2-4392-84ab-41b86ed02237/crucible 4b593475-e809-4822-a2fe-c58b2f7964d5 none none off + oxp_d9344e2b-84d2-4392-84ab-41b86ed02237/crypt/debug 19bdebd5-9757-409f-ac06-2b309379975e 100 GiB none off + oxp_d9344e2b-84d2-4392-84ab-41b86ed02237/crypt/zone aa37e2bb-8d16-4dc2-a549-5f265fdad77c none none off + oxp_d9344e2b-84d2-4392-84ab-41b86ed02237/crypt/zone/oxz_crucible_062ce37d-7448-4d44-b1f4-4937cd2eb174 bfbfe7dd-06b3-4d01-988d-488c7b9400ee none none off + oxp_eab188d0-b34a-4673-b254-12e705597654/crucible 444a65f5-d04e-4cb0-a29d-ba57e34143dd none none off + oxp_eab188d0-b34a-4673-b254-12e705597654/crypt/debug 411e3457-3113-40d2-8a7d-747b108e0de7 100 GiB none off + oxp_eab188d0-b34a-4673-b254-12e705597654/crypt/zone a79b0ad5-06b8-466c-928c-39e311ace5ff none none off + oxp_eab188d0-b34a-4673-b254-12e705597654/crypt/zone/oxz_crucible_78d6ab36-e8c8-4ff8-9f89-75c7fe2d32e6 ebc48adb-4dad-41dd-a547-13f2517a9db7 none none off + oxp_f1e0386f-11b6-4cdf-8250-826d256db6b5/crucible 826e2ca8-f30d-4ca3-8360-f03ddd883a6a none none off + oxp_f1e0386f-11b6-4cdf-8250-826d256db6b5/crypt/debug 79ce0088-d8a8-46ff-ac72-958b8db65e67 100 GiB none off + oxp_f1e0386f-11b6-4cdf-8250-826d256db6b5/crypt/zone 1635dad8-971f-4409-aca3-6204eafec699 none none off + oxp_f1e0386f-11b6-4cdf-8250-826d256db6b5/crypt/zone/oxz_crucible_9f824c30-6360-46b9-87c4-cd60586476fe c4e2119e-4cd0-48e7-878f-fd676cb95094 none none off + oxp_f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b/crucible 2c6ade05-a626-4b25-a28c-551b91867dbe none none off + oxp_f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b/crypt/debug 2059b139-6510-4447-aa56-cf995ef469a2 100 GiB none off + oxp_f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b/crypt/zone 1b5a2379-ec60-47f0-8def-fa3ab5c2487c none none off + oxp_f8c9c9a9-d73e-4cdf-a9af-03cfbbbce12b/crypt/zone/oxz_crucible_1211a68e-69a1-4ef4-b790-45b0279f9159 32a3e82a-b97a-455a-b428-1f934c59eb46 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -138,6 +293,53 @@ to: blueprint f432fcd5-1284-4058-8b4a-9286a3de6163 fake-vendor fake-model serial-e6f289fe-142e-4778-8629-dc87adb53f06 + datasets generation 1 -> 2: + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_1bb5ee5d-c2c6-4eaa-86c4-817d89cf10cf/crypt/debug bbab775c-07b1-48b1-96a3-9aa155396112 100 GiB none off + oxp_1bb5ee5d-c2c6-4eaa-86c4-817d89cf10cf/crypt/zone 07381a6a-e397-4ff9-a2a6-8f1e46c47eb3 none none off + oxp_1bb5ee5d-c2c6-4eaa-86c4-817d89cf10cf/crypt/zone/oxz_ntp_2d73d30e-ca47-46a8-9c12-917d4ab824b6 15e9b1ea-8947-48a2-8d78-54665e4d2f03 none none off + oxp_298d1eec-0313-4a42-8af9-0e51299a14ef/crypt/debug 75c1432c-19e1-4c06-8393-9907a09efcbe 100 GiB none off + oxp_298d1eec-0313-4a42-8af9-0e51299a14ef/crypt/zone b4c23ac2-86af-4b47-9d8d-35eff7997788 none none off + oxp_2eed666f-a10b-42d0-b626-68335d3270b8/crypt/debug 5501c13c-0500-41a6-9c48-85924383fdcc 100 GiB none off + oxp_2eed666f-a10b-42d0-b626-68335d3270b8/crypt/zone 32143abb-e501-450b-9398-7d4dbebdf4a1 none none off + oxp_6cc4d7a7-2a89-4f2f-aa55-5e7a10d0fc08/crypt/debug bcdd7a5a-59da-4a47-8a1c-79281428d71b 100 GiB none off + oxp_6cc4d7a7-2a89-4f2f-aa55-5e7a10d0fc08/crypt/zone 6ec35c5c-9d6c-4d72-8bec-af91069ef7a9 none none off + oxp_7aad6fd9-b698-4c77-af6b-947be10ba953/crypt/debug 8ed9d1a2-3c60-4f38-9e80-5c56d80e67c9 100 GiB none off + oxp_7aad6fd9-b698-4c77-af6b-947be10ba953/crypt/zone 8529e7f8-8c11-4869-b330-83ddc45ed17a none none off + oxp_a5a15e51-c48a-40e4-a2d8-1c7198c1d46b/crypt/debug 4aef9a3a-0829-4ada-a0e9-a45c91e74249 100 GiB none off + oxp_a5a15e51-c48a-40e4-a2d8-1c7198c1d46b/crypt/zone 940f06cb-822f-4034-8d34-e14bcc6ea998 none none off + oxp_b81d4993-ea5b-4720-b8c8-2360c1121d6e/crypt/debug 2b23d885-836a-4270-886d-08640aae90aa 100 GiB none off + oxp_b81d4993-ea5b-4720-b8c8-2360c1121d6e/crypt/zone 0507b005-e018-4b69-9d84-50faf61e792f none none off + oxp_d0064c4d-f5f7-4c89-9f37-0ca475048e79/crypt/debug feac64a1-ade2-4f88-8c17-64d2863e2be6 100 GiB none off + oxp_d0064c4d-f5f7-4c89-9f37-0ca475048e79/crypt/zone 1acaf776-970a-49cf-9f1c-7d8e3146ef11 none none off + oxp_dba739c1-76e4-4b6a-a173-89c938fa13ef/crypt/debug 91edd9a1-178d-4aa5-83a7-b2f4ef1fc44a 100 GiB none off + oxp_dba739c1-76e4-4b6a-a173-89c938fa13ef/crypt/zone 1bd5ab24-2a54-438f-bae0-af1b06a3cc41 none none off + oxp_e6f289fe-142e-4778-8629-dc87adb53f06/crypt/debug 8840ccd9-e8c9-48d9-affb-8587e468b204 100 GiB none off + oxp_e6f289fe-142e-4778-8629-dc87adb53f06/crypt/zone b501adbf-be36-4724-9409-329f690fb09d none none off ++ oxp_1bb5ee5d-c2c6-4eaa-86c4-817d89cf10cf/crucible c842fd37-6d1b-430c-83c5-bb49523434e3 none none off ++ oxp_1bb5ee5d-c2c6-4eaa-86c4-817d89cf10cf/crypt/zone/oxz_crucible_45556184-7092-4a3d-873f-637976bb133b 88f174e0-09e5-4a04-a21f-4885fc7c776b none none off ++ oxp_298d1eec-0313-4a42-8af9-0e51299a14ef/crucible 33b4b373-748a-44ce-bae3-d08a6f760f88 none none off ++ oxp_298d1eec-0313-4a42-8af9-0e51299a14ef/crypt/zone/oxz_crucible_9d75abfe-47ab-434a-93dd-af50dc0dddde be00a599-be07-4eb1-8d83-c53a9cdc66cc none none off ++ oxp_2eed666f-a10b-42d0-b626-68335d3270b8/crucible 6cd2973c-3e14-433f-ba9b-d06dc973814f none none off ++ oxp_2eed666f-a10b-42d0-b626-68335d3270b8/crypt/zone/oxz_crucible_f86e19d2-9145-41cf-be89-6aaa34a73873 889580ca-b8a3-4b65-a162-5f5257f193c8 none none off ++ oxp_6cc4d7a7-2a89-4f2f-aa55-5e7a10d0fc08/crucible 8f5bdd29-1382-42ea-9e3c-b1ac434b8356 none none off ++ oxp_6cc4d7a7-2a89-4f2f-aa55-5e7a10d0fc08/crypt/zone/oxz_crucible_8215bf7a-10d6-4f40-aeb7-27a196307c37 5acdb519-bb18-4e82-ab44-f2dacfc64ce7 none none off ++ oxp_7aad6fd9-b698-4c77-af6b-947be10ba953/crucible 7cab9095-e83c-46d5-8290-db28ed5d6909 none none off ++ oxp_7aad6fd9-b698-4c77-af6b-947be10ba953/crypt/zone/oxz_crucible_f6125d45-b9cc-4721-ba60-ed4dbb177e41 42a16f5e-9510-48ca-82a7-7229c2cda8c2 none none off ++ oxp_a5a15e51-c48a-40e4-a2d8-1c7198c1d46b/crucible 2503ac08-6839-43c5-a2c3-2b08c234ef5f none none off ++ oxp_a5a15e51-c48a-40e4-a2d8-1c7198c1d46b/crypt/zone/oxz_crucible_a36d291c-7f68-462f-830e-bc29e5841ce2 e079d4a4-8b51-4bf2-9f65-f13cb57584ea none none off ++ oxp_b81d4993-ea5b-4720-b8c8-2360c1121d6e/crucible 47880a38-bb35-4619-80fc-2f4578efb231 none none off ++ oxp_b81d4993-ea5b-4720-b8c8-2360c1121d6e/crypt/zone/oxz_crucible_cf5b636b-a505-4db6-bc32-baf9f53f4371 915d03a8-1902-4f81-9d46-0f1987d7a404 none none off ++ oxp_d0064c4d-f5f7-4c89-9f37-0ca475048e79/crucible f99ff9c5-e110-4972-a6d9-627bb6aae3b8 none none off ++ oxp_d0064c4d-f5f7-4c89-9f37-0ca475048e79/crypt/zone/oxz_crucible_28852beb-d0e5-4cba-9adb-e7f0cd4bb864 bfbfbd9d-c656-4f4c-80cd-c91d38d6bdc9 none none off ++ oxp_dba739c1-76e4-4b6a-a173-89c938fa13ef/crucible c6f0a0c8-8410-47ef-adb8-86e9edc688e5 none none off ++ oxp_dba739c1-76e4-4b6a-a173-89c938fa13ef/crypt/zone/oxz_crucible_b3a4d434-aaee-4752-8c99-69d88fbcb8c5 7a364d04-c4a2-4e2c-8081-c24a276621c5 none none off ++ oxp_e6f289fe-142e-4778-8629-dc87adb53f06/crucible 9aab84cf-3764-4611-892b-76e0570a1699 none none off ++ oxp_e6f289fe-142e-4778-8629-dc87adb53f06/crypt/zone/oxz_crucible_1a20ee3c-f66e-4fca-ab85-2a248aa3d79d 9bbbccf0-a3e1-4d6c-becd-c74a91eef9e8 none none off + + omicron zones generation 2 -> 3: ------------------------------------------------------------------------------------------ zone type zone id disposition underlay IP diff --git a/nexus/reconfigurator/planning/tests/output/planner_dataset_settings_modified_in_place_1_2.txt b/nexus/reconfigurator/planning/tests/output/planner_dataset_settings_modified_in_place_1_2.txt new file mode 100644 index 0000000000..99c67e7e4c --- /dev/null +++ b/nexus/reconfigurator/planning/tests/output/planner_dataset_settings_modified_in_place_1_2.txt @@ -0,0 +1,116 @@ +from: blueprint 67e80d21-3436-4473-8f3d-920e0d386b12 +to: blueprint fe13be30-94c2-4fa6-aad5-ae3c5028f6bb + + MODIFIED SLEDS: + + sled c52410de-5fea-4e77-b162-756d103523b3 (active): + + physical disks at generation 1: + ---------------------------------------------------------------------- + vendor model serial + ---------------------------------------------------------------------- + fake-vendor fake-model serial-3b6e2ade-57fc-4f9d-85c3-38fca27f1df6 + fake-vendor fake-model serial-5192ef62-5a12-4a0c-829d-a409da87909c + fake-vendor fake-model serial-8778bcc5-dddf-4345-9fdf-5c46a36497b0 + fake-vendor fake-model serial-9134de8d-9ba8-4ddc-9e84-eb00ec616b53 + fake-vendor fake-model serial-96569b61-9e0c-4ee7-bd11-a5e0c541ca99 + fake-vendor fake-model serial-ba90170e-7399-4260-910a-376254a8a9bf + fake-vendor fake-model serial-bc649720-926b-48f2-a62a-efdcff96b49e + fake-vendor fake-model serial-d55da288-4f35-4e92-97b0-29a5e6009109 + fake-vendor fake-model serial-f83302fc-785c-4ab3-bcca-0d040b3c3062 + fake-vendor fake-model serial-f843fb62-0f04-4c7d-a56f-62531104dc77 + + + datasets generation 1 -> 2: + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crucible 37ca00f9-8c48-49dd-a511-1844366b9fc6 none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/clickhouse 379a5c76-adde-40c7-a0b0-b7837ed4b1bf none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/debug fe7f66a8-7605-4001-87f8-2357e95acb2a 100 GiB none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/internal_dns 7ba6b643-5113-43e9-ae33-de52c2b1d7c2 none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/zone 4300bbd9-56b4-4735-9e6a-5c12ecd431eb none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/zone/oxz_clickhouse_5d62c22a-7ad0-439c-963b-a30ba8ff31bb 997e8452-11b8-4478-98fb-89fbd6abe9b4 none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/zone/oxz_crucible_449deb40-b01b-41ae-8167-7b7b47e2692e e8fc9f62-9c90-4ec5-9a04-59d3dc4970cf none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/zone/oxz_crucible_pantry_197067bc-9a21-444e-9794-6051d9f78a00 19736dbd-1d01-41e9-a800-ffc450464c2d none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/zone/oxz_crucible_pantry_350fba7f-b754-429e-a21d-e91d139713f2 8be4aa2f-1612-4bdf-a0f6-7458b151308f none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/zone/oxz_crucible_pantry_504963cb-3077-477c-b4e5-2d69bf9caa0c 7fd439f9-dcef-4cfb-b1a1-d298be9d2e3b none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/zone/oxz_internal_dns_1e9422ca-a3d9-4435-bb17-39d5ad22b4ba 5651c4fb-d146-4270-8794-6ed7ceb6f130 none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/zone/oxz_nexus_f9f52984-e6ad-4280-bf92-c88da12e8fdc a0b438d3-b77b-4bb6-90c0-8fdf27ca6ea1 none none off + oxp_3b6e2ade-57fc-4f9d-85c3-38fca27f1df6/crypt/zone/oxz_ntp_f7420bc3-7916-44fe-a66e-515ce09ff63f 530104ab-48b9-47fe-a9d8-00cc945d701a none none off + oxp_5192ef62-5a12-4a0c-829d-a409da87909c/crucible f73617d5-cb8c-40d2-8079-6b9b17011b70 none none off + oxp_5192ef62-5a12-4a0c-829d-a409da87909c/crypt/debug 7cf63429-3af0-4e74-a007-c24024a4c6db 100 GiB none off + oxp_5192ef62-5a12-4a0c-829d-a409da87909c/crypt/internal_dns 4c4183bc-ed9f-4a8e-9f9f-73cabb6830d1 none none off + oxp_5192ef62-5a12-4a0c-829d-a409da87909c/crypt/zone 931a0291-5b7c-453b-8bd0-0b835ca8d879 none none off + oxp_5192ef62-5a12-4a0c-829d-a409da87909c/crypt/zone/oxz_crucible_92476a4a-7a95-4141-acc6-e0a42066edbd 426cd017-82e0-4078-a5dd-05c5b88c1e66 none none off + oxp_5192ef62-5a12-4a0c-829d-a409da87909c/crypt/zone/oxz_internal_dns_efecb8a2-ce0b-416f-958b-de1fad1bef02 158e226c-e44e-427f-93af-ee96d2cfb9be none none off + oxp_8778bcc5-dddf-4345-9fdf-5c46a36497b0/crucible d11c234f-c617-4cfe-b61a-38ea70de8325 none none off + oxp_8778bcc5-dddf-4345-9fdf-5c46a36497b0/crypt/debug 38570a1f-a96b-45dc-8cdd-91ec150657bf 100 GiB none off + oxp_8778bcc5-dddf-4345-9fdf-5c46a36497b0/crypt/internal_dns 50cbcefa-0500-4cd2-b077-f4cda0ffce81 none none off + oxp_8778bcc5-dddf-4345-9fdf-5c46a36497b0/crypt/zone 83954f81-3607-45e5-b380-6c19c0eafcb2 none none off + oxp_8778bcc5-dddf-4345-9fdf-5c46a36497b0/crypt/zone/oxz_crucible_d0e39a63-1310-42a3-ba54-6624006c0d24 80d8fd68-b3e3-49ba-b758-d58271057bb7 none none off + oxp_8778bcc5-dddf-4345-9fdf-5c46a36497b0/crypt/zone/oxz_internal_dns_4a0ec9f6-6ce6-4456-831e-5f8df7b57332 d2b9f103-8bf1-4603-873d-cec130430ba7 none none off + oxp_9134de8d-9ba8-4ddc-9e84-eb00ec616b53/crucible 5dc63025-10bf-4e78-80d8-aec0a884c931 none none off + oxp_9134de8d-9ba8-4ddc-9e84-eb00ec616b53/crypt/debug f46e90fa-6f1c-40d4-813b-0b2dfbc8295d 100 GiB none off + oxp_9134de8d-9ba8-4ddc-9e84-eb00ec616b53/crypt/zone 1d57c1bc-f3dc-4799-848e-ffab7e1a9704 none none off + oxp_9134de8d-9ba8-4ddc-9e84-eb00ec616b53/crypt/zone/oxz_crucible_729e375b-31a4-4cfc-b56c-afeef8d8adfc 476cc49e-29d1-4c3d-b4c0-c5ec01206b26 none none off + oxp_96569b61-9e0c-4ee7-bd11-a5e0c541ca99/crucible 572474f0-3630-40d5-814f-9fc58261e8cd none none off + oxp_96569b61-9e0c-4ee7-bd11-a5e0c541ca99/crypt/debug 72893a49-bd26-425f-a56e-ee09d6f634b1 100 GiB none off + oxp_96569b61-9e0c-4ee7-bd11-a5e0c541ca99/crypt/zone 269d597d-795f-4675-9210-3796379f082e none none off + oxp_96569b61-9e0c-4ee7-bd11-a5e0c541ca99/crypt/zone/oxz_crucible_fff71a84-09c2-4dab-bc18-8f4570f278bb 00abfe99-288d-4a63-abea-adfa62e74524 none none off + oxp_ba90170e-7399-4260-910a-376254a8a9bf/crucible a527de5a-e39c-4991-98fd-75fd5e567f91 none none off + oxp_ba90170e-7399-4260-910a-376254a8a9bf/crypt/debug 2280f30e-4f1b-45f2-a6fe-83098487637b 100 GiB none off + oxp_ba90170e-7399-4260-910a-376254a8a9bf/crypt/zone a7b90e18-788d-47cc-91d8-4eb427c9c041 none none off + oxp_ba90170e-7399-4260-910a-376254a8a9bf/crypt/zone/oxz_crucible_1fcb5e9b-85f1-426d-ae88-6159804063fd 39a39559-055c-4c18-a02b-c963276b2171 none none off + oxp_bc649720-926b-48f2-a62a-efdcff96b49e/crucible 0821d5cd-e706-4e90-ba27-7820efeb3d6c none none off + oxp_bc649720-926b-48f2-a62a-efdcff96b49e/crypt/debug 56542c75-0603-4fd4-af13-8af63a364e7c 100 GiB none off + oxp_bc649720-926b-48f2-a62a-efdcff96b49e/crypt/zone 6901d663-862d-4893-8aa2-d75d94f78530 none none off + oxp_bc649720-926b-48f2-a62a-efdcff96b49e/crypt/zone/oxz_crucible_b9d0d20d-5ccf-4570-ad00-b5bf33a5a63e 634b6dfa-d1b4-4f90-808e-eba7cd093a1e none none off + oxp_d55da288-4f35-4e92-97b0-29a5e6009109/crucible 67211a77-5b63-4487-aec8-087864f06a6a none none off + oxp_d55da288-4f35-4e92-97b0-29a5e6009109/crypt/debug 855e6f57-c6c8-408d-bda0-7fdd220565a1 100 GiB none off + oxp_d55da288-4f35-4e92-97b0-29a5e6009109/crypt/zone 2e7f7b63-681f-490a-8a31-40bb196aa927 none none off + oxp_d55da288-4f35-4e92-97b0-29a5e6009109/crypt/zone/oxz_crucible_adb88e8f-1299-4c8b-992b-2a54dbdd51ef d1a2bb7d-1916-43ce-bad8-7dc78c86b89e none none off + oxp_f83302fc-785c-4ab3-bcca-0d040b3c3062/crucible 9e0bd909-0d35-4f03-8246-66a67b6cfbe4 none none off + oxp_f83302fc-785c-4ab3-bcca-0d040b3c3062/crypt/debug bf3ff0d1-497e-4411-b70d-d6faca5c8970 100 GiB none off + oxp_f83302fc-785c-4ab3-bcca-0d040b3c3062/crypt/zone c56b51d9-6573-40fe-98f6-12b760a6f136 none none off + oxp_f83302fc-785c-4ab3-bcca-0d040b3c3062/crypt/zone/oxz_crucible_b4e83ee5-a40e-4202-89cd-f2c1ede124d8 14ce9d7e-95ca-42e3-894a-2cec314959cb none none off + oxp_f843fb62-0f04-4c7d-a56f-62531104dc77/crucible 9bf6d6ee-e7cd-4aad-a66c-343af08bea16 none none off + oxp_f843fb62-0f04-4c7d-a56f-62531104dc77/crypt/zone e35ef3d9-5e22-42c7-8623-9f24c00b0677 none none off + oxp_f843fb62-0f04-4c7d-a56f-62531104dc77/crypt/zone/oxz_crucible_fc4f1769-9611-42d3-b8c1-f2be9b5359f6 35fa6ec8-6b58-4fcc-a5a2-36e66736e9c1 none none off +* oxp_f843fb62-0f04-4c7d-a56f-62531104dc77/crypt/debug 2011121d-b454-41c5-9062-18fa04ee1d52 - none - 1 GiB off + └─ + 100 GiB + none + + + omicron zones at generation 2: + --------------------------------------------------------------------------------------------- + zone type zone id disposition underlay IP + --------------------------------------------------------------------------------------------- + clickhouse 5d62c22a-7ad0-439c-963b-a30ba8ff31bb in service fd00:1122:3344:101::23 + crucible 1fcb5e9b-85f1-426d-ae88-6159804063fd in service fd00:1122:3344:101::2c + crucible 449deb40-b01b-41ae-8167-7b7b47e2692e in service fd00:1122:3344:101::27 + crucible 729e375b-31a4-4cfc-b56c-afeef8d8adfc in service fd00:1122:3344:101::2a + crucible 92476a4a-7a95-4141-acc6-e0a42066edbd in service fd00:1122:3344:101::28 + crucible adb88e8f-1299-4c8b-992b-2a54dbdd51ef in service fd00:1122:3344:101::2e + crucible b4e83ee5-a40e-4202-89cd-f2c1ede124d8 in service fd00:1122:3344:101::2f + crucible b9d0d20d-5ccf-4570-ad00-b5bf33a5a63e in service fd00:1122:3344:101::2d + crucible d0e39a63-1310-42a3-ba54-6624006c0d24 in service fd00:1122:3344:101::29 + crucible fc4f1769-9611-42d3-b8c1-f2be9b5359f6 in service fd00:1122:3344:101::30 + crucible fff71a84-09c2-4dab-bc18-8f4570f278bb in service fd00:1122:3344:101::2b + crucible_pantry 197067bc-9a21-444e-9794-6051d9f78a00 in service fd00:1122:3344:101::24 + crucible_pantry 350fba7f-b754-429e-a21d-e91d139713f2 in service fd00:1122:3344:101::25 + crucible_pantry 504963cb-3077-477c-b4e5-2d69bf9caa0c in service fd00:1122:3344:101::26 + internal_dns 1e9422ca-a3d9-4435-bb17-39d5ad22b4ba in service fd00:1122:3344:1::1 + internal_dns 4a0ec9f6-6ce6-4456-831e-5f8df7b57332 in service fd00:1122:3344:3::1 + internal_dns efecb8a2-ce0b-416f-958b-de1fad1bef02 in service fd00:1122:3344:2::1 + internal_ntp f7420bc3-7916-44fe-a66e-515ce09ff63f in service fd00:1122:3344:101::21 + nexus f9f52984-e6ad-4280-bf92-c88da12e8fdc in service fd00:1122:3344:101::22 + + + COCKROACHDB SETTINGS: + state fingerprint::::::::::::::::: (none) (unchanged) + cluster.preserve_downgrade_option: (do not modify) (unchanged) + + METADATA: + internal DNS version: 1 (unchanged) + external DNS version: 1 (unchanged) + diff --git a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_1_2.txt b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_1_2.txt index ee0dc4bbe2..7287b03947 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_1_2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_1_2.txt @@ -21,6 +21,59 @@ to: blueprint 1ac2d88f-27dd-4506-8585-6b2be832528e - fake-vendor fake-model serial-ffea118f-7715-4e21-8fc5-bb23cd0f59e8 + datasets from generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crucible a9524f87-1bd8-4a3c-b7bd-e3c19a14fb50 none none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/clickhouse 3cc40210-6bf9-45fb-ac4b-8cae1c1529af none none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/debug d01cbfd4-5f98-4f95-b362-7145429d3228 100 GiB none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/internal_dns cf3c6633-2c48-4963-a40d-acac89939915 none none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/zone bf59eede-81b0-4acb-b923-33681a92a14f none none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/zone/oxz_clickhouse_4e36b7ef-5684-4304-b7c3-3c31aaf83d4f 418538c2-112c-4bd6-9d02-054b08a79438 none none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/zone/oxz_crucible_e29998e7-9ed2-46b6-bb70-4118159fe07f da2dd11a-9022-4c2b-ad8b-f5650f223449 none none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/zone/oxz_crucible_pantry_f11f5c60-1ac7-4630-9a3a-a9bc85c75203 2efbbe4f-8548-4890-9f40-1bf099cf795e none none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/zone/oxz_internal_dns_f231e4eb-3fc9-4964-9d71-2c41644852d9 b181a265-ccea-42ca-9259-aa405fb48d6f none none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/zone/oxz_nexus_6a70a233-1900-43c0-9c00-aa9d1f7adfbc 8ac37626-6bbc-4fe7-9c95-8dbbc91c0dbe none none off +- oxp_069446b4-7881-49dc-838a-63a782d4896d/crypt/zone/oxz_ntp_c62b87b6-b98d-4d22-ba4f-cee4499e2ba8 a351cc88-3b88-4408-ba57-098c1b610ffb none none off +- oxp_20eba316-dffe-4516-9703-af561da19b0b/crucible 5743004a-db46-4e84-b826-d860619dc063 none none off +- oxp_20eba316-dffe-4516-9703-af561da19b0b/crypt/debug 2cd129ba-90d2-4e0a-8de9-8779430bfd52 100 GiB none off +- oxp_20eba316-dffe-4516-9703-af561da19b0b/crypt/zone 4e2be862-e2c1-4d37-ac57-34ceea106a0c none none off +- oxp_20eba316-dffe-4516-9703-af561da19b0b/crypt/zone/oxz_crucible_1e1ed0cc-1adc-410f-943a-d1a3107de619 88c2e536-57af-4402-99dd-cfaca1862b41 none none off +- oxp_426f4b6d-4a82-4106-bf4b-64ee86a2a5a4/crucible ed196541-0958-4fc0-8cf8-6da4c522e620 none none off +- oxp_426f4b6d-4a82-4106-bf4b-64ee86a2a5a4/crypt/debug 15227fcb-319a-491f-bb0c-8d245bec4e58 100 GiB none off +- oxp_426f4b6d-4a82-4106-bf4b-64ee86a2a5a4/crypt/zone 07f06a1f-bb55-4e72-a84f-bb1cfd5d18a6 none none off +- oxp_426f4b6d-4a82-4106-bf4b-64ee86a2a5a4/crypt/zone/oxz_crucible_2307bbed-02ba-493b-89e3-46585c74c8fc d3e92497-a1c6-4aae-a192-21762cb35207 none none off +- oxp_82daeef2-8641-4bf5-ac66-f7b5f62c48b6/crucible ff266774-5999-4631-a516-3f2f9a05f688 none none off +- oxp_82daeef2-8641-4bf5-ac66-f7b5f62c48b6/crypt/debug 9dc58815-aa5b-409a-98e7-7cbf92b17819 100 GiB none off +- oxp_82daeef2-8641-4bf5-ac66-f7b5f62c48b6/crypt/zone 3dda6519-98a6-4943-b4e0-daf82b36526a none none off +- oxp_82daeef2-8641-4bf5-ac66-f7b5f62c48b6/crypt/zone/oxz_crucible_c28d7b4b-a259-45ad-945d-f19ca3c6964c 6f1e99c9-4934-48f5-8b63-e0836d784bdc none none off +- oxp_8e5feeb2-14f1-440f-a909-3c34aa8e129b/crucible db626d31-b4a3-4b81-970d-cfa469c5d2ff none none off +- oxp_8e5feeb2-14f1-440f-a909-3c34aa8e129b/crypt/debug 56e40972-948d-4900-9d19-9d62b1983f43 100 GiB none off +- oxp_8e5feeb2-14f1-440f-a909-3c34aa8e129b/crypt/zone 064e7ade-7e0a-471c-9743-752a215d7f5c none none off +- oxp_8e5feeb2-14f1-440f-a909-3c34aa8e129b/crypt/zone/oxz_crucible_9179d6dc-387d-424e-8d62-ed59b2c728f6 5d1bf2c0-daab-4ec6-91c1-cd405da49ab9 none none off +- oxp_942e2123-7c4e-4f6b-9317-1341fe212647/crucible 33c2c71f-d4d7-4357-ba2a-eb9d5d8aa0f5 none none off +- oxp_942e2123-7c4e-4f6b-9317-1341fe212647/crypt/debug 81489861-36ab-4513-a5bb-953d3980974d 100 GiB none off +- oxp_942e2123-7c4e-4f6b-9317-1341fe212647/crypt/zone 5d9ec6b2-c3ad-4588-a8e3-4ad1c0d83643 none none off +- oxp_942e2123-7c4e-4f6b-9317-1341fe212647/crypt/zone/oxz_crucible_f06e91a1-0c17-4cca-adbc-1c9b67bdb11d 1cebd581-aab7-42d5-a8a2-15b3e2f55e23 none none off +- oxp_97a5ce17-df5b-47e7-baf8-80ae710ce18e/crucible 040dc13c-0267-430a-b20d-89fd0f000f0b none none off +- oxp_97a5ce17-df5b-47e7-baf8-80ae710ce18e/crypt/debug 59d4773a-d49c-4c28-acc6-92d80beb18f6 100 GiB none off +- oxp_97a5ce17-df5b-47e7-baf8-80ae710ce18e/crypt/zone 0fe8f233-160a-421b-aba6-1274f95ba79c none none off +- oxp_97a5ce17-df5b-47e7-baf8-80ae710ce18e/crypt/zone/oxz_crucible_603e629d-2599-400e-b879-4134d4cc426e 5c06fe50-9fe1-44e1-85c2-38ba11e4495b none none off +- oxp_debc9fb6-bd58-4e4f-b8b8-6a9a07fcf25d/crucible 454c71e8-ba8d-4afd-af1f-0ceb403e6b6b none none off +- oxp_debc9fb6-bd58-4e4f-b8b8-6a9a07fcf25d/crypt/debug 433a21ac-1830-42ed-991b-18976ebf312f 100 GiB none off +- oxp_debc9fb6-bd58-4e4f-b8b8-6a9a07fcf25d/crypt/zone d8d25341-34f7-4b98-b6e9-252c06a079fc none none off +- oxp_debc9fb6-bd58-4e4f-b8b8-6a9a07fcf25d/crypt/zone/oxz_crucible_ad76d200-5675-444b-b19c-684689ff421f ed7d0891-d558-4c30-8d73-9594ae70b061 none none off +- oxp_f63a32a9-0659-43cf-8efc-8f34e7af9d45/crucible e97a59e3-8f57-42f3-95f5-316f966c5efc none none off +- oxp_f63a32a9-0659-43cf-8efc-8f34e7af9d45/crypt/debug 3568368a-2a70-4300-b689-3ec8d0bc8f71 100 GiB none off +- oxp_f63a32a9-0659-43cf-8efc-8f34e7af9d45/crypt/zone 5073b1d2-6dcb-4a42-afb8-c6dd74f50201 none none off +- oxp_f63a32a9-0659-43cf-8efc-8f34e7af9d45/crypt/zone/oxz_crucible_e9bf2525-5fa0-4c1b-b52d-481225083845 cbece778-757f-435a-86b1-97a6f4ad5a70 none none off +- oxp_ffea118f-7715-4e21-8fc5-bb23cd0f59e8/crucible 7f4605b3-1caf-4751-9485-2e9554d9b3b5 none none off +- oxp_ffea118f-7715-4e21-8fc5-bb23cd0f59e8/crypt/debug c84f4ae2-e449-4980-acdd-b3a5ac7e506a 100 GiB none off +- oxp_ffea118f-7715-4e21-8fc5-bb23cd0f59e8/crypt/zone af81321c-23c6-4491-88a8-8eb5534aa8d8 none none off +- oxp_ffea118f-7715-4e21-8fc5-bb23cd0f59e8/crypt/zone/oxz_crucible_2e65b765-5c41-4519-bf4e-e2a68569afc1 a0459348-1ea1-4a5f-a821-677ed1c46aa6 none none off + + omicron zones generation 2 -> 3: ---------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -75,6 +128,59 @@ to: blueprint 1ac2d88f-27dd-4506-8585-6b2be832528e fake-vendor fake-model serial-ff7e002b-3ad8-4d45-b03a-c46ef0ac8e59 + datasets generation 1 -> 2: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crucible 3629f248-3fc1-4073-b21b-6a1529fa204e none none off + oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/debug 64793588-5ff8-42d8-af9d-f7b58a2d1431 100 GiB none off + oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/internal_dns 1114a853-37f6-4933-8ab1-85ad855145ea none none off + oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/zone 7627711f-c877-4892-8bb9-2ab494673bab none none off + oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/zone/oxz_crucible_70232a6d-6c9d-4fa6-a34d-9c73d940db33 b860f342-824e-4f30-960d-7e2fe519b72b none none off + oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/zone/oxz_crucible_pantry_15dbaa30-1539-49d6-970d-ba5962960f33 6d4bc7b7-5f22-44e5-8578-fba855718b56 none none off + oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/zone/oxz_internal_dns_eac6c0a0-baa5-4490-9cee-65198b7fbd9c 13ef9dfc-1881-4517-b518-b94d8b8f196e none none off + oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/zone/oxz_nexus_3d4143df-e212-4774-9258-7d9b421fac2e aba6813b-e60b-44cb-b22f-934bd6e28cf9 none none off + oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/zone/oxz_ntp_1ec4cc7b-2f00-4d13-8176-3b9815533ae9 67169d16-1b64-4236-8cff-6d06a7ab84cd none none off + oxp_440ae69d-5e2e-4539-91d0-e2930bdd7203/crucible 0cf030b7-7c47-4304-8247-1a20e6048554 none none off + oxp_440ae69d-5e2e-4539-91d0-e2930bdd7203/crypt/debug 54954ab8-0874-41f7-9382-02aae6831f4c 100 GiB none off + oxp_440ae69d-5e2e-4539-91d0-e2930bdd7203/crypt/zone fb4ac064-d11e-4afb-bfc8-b87c58b71977 none none off + oxp_440ae69d-5e2e-4539-91d0-e2930bdd7203/crypt/zone/oxz_crucible_8b0b8623-930a-41af-9f9b-ca28b1b11139 0d862983-16aa-4d85-a6f3-78de73b063bb none none off + oxp_4e91d4a3-bb6c-44bb-bd4e-bf8913c1ba2b/crucible 8ee49248-8d40-4637-8aad-049c9b312bf2 none none off + oxp_4e91d4a3-bb6c-44bb-bd4e-bf8913c1ba2b/crypt/debug 112a7b3e-f24f-4a14-94f2-32a1ad8cdbe3 100 GiB none off + oxp_4e91d4a3-bb6c-44bb-bd4e-bf8913c1ba2b/crypt/zone d48badf3-3993-44a5-809c-4eacad8fb929 none none off + oxp_4e91d4a3-bb6c-44bb-bd4e-bf8913c1ba2b/crypt/zone/oxz_crucible_5d9d8fa7-8379-470b-90ba-fe84a3c45512 07f839aa-beb9-4730-b8eb-7b52a2e40872 none none off + oxp_67de3a80-29cb-4066-b743-e285a2ca1f4e/crucible fb826e91-9b48-433e-9fc4-dbe56982f94a none none off + oxp_67de3a80-29cb-4066-b743-e285a2ca1f4e/crypt/debug 73542884-3a70-4acc-a474-b7d361e79909 100 GiB none off + oxp_67de3a80-29cb-4066-b743-e285a2ca1f4e/crypt/zone 84488b5f-690c-4a21-b14a-9288eaa54488 none none off + oxp_67de3a80-29cb-4066-b743-e285a2ca1f4e/crypt/zone/oxz_crucible_cf87d2a3-d323-44a3-a87e-adc4ef6c75f4 f39b842a-f73c-4aae-a5ac-fc707fa49dca none none off + oxp_9139b70f-c1d3-475d-8f02-7c9acba52b2b/crucible cbd086a5-8730-49ce-983f-a870416589e5 none none off + oxp_9139b70f-c1d3-475d-8f02-7c9acba52b2b/crypt/debug d86839e1-e253-4ae2-b1ac-03b7020614cd 100 GiB none off + oxp_9139b70f-c1d3-475d-8f02-7c9acba52b2b/crypt/zone b1144b57-1e79-41a6-ae1b-9676d777f628 none none off + oxp_9139b70f-c1d3-475d-8f02-7c9acba52b2b/crypt/zone/oxz_crucible_8567a616-a709-4c8c-a323-4474675dad5c 67c894bc-e684-42e7-8bc8-1fe86356165e none none off + oxp_95fbb110-5272-4646-ab50-21b31b7cde23/crucible cf4285e6-acd6-4bd6-80a7-4d28c1f1d543 none none off + oxp_95fbb110-5272-4646-ab50-21b31b7cde23/crypt/debug fe27d4c5-cb2b-461c-a03b-3cd163682098 100 GiB none off + oxp_95fbb110-5272-4646-ab50-21b31b7cde23/crypt/zone 141811b6-c055-46b9-9a20-7c6265d507ee none none off + oxp_95fbb110-5272-4646-ab50-21b31b7cde23/crypt/zone/oxz_crucible_f68846ad-4619-4747-8293-a2b4aeeafc5b dcc8dea5-c5a2-4559-8629-0015a3b4820e none none off + oxp_9bf35cd7-4938-4c34-8189-288b3195cb64/crucible 5159978d-1d63-4c7a-935f-819c39c4b15b none none off + oxp_9bf35cd7-4938-4c34-8189-288b3195cb64/crypt/debug 96883b04-1e79-4c38-a0e9-6eaa806df51e 100 GiB none off + oxp_9bf35cd7-4938-4c34-8189-288b3195cb64/crypt/zone b55242c9-3e0c-4bb9-b702-0d94893388a3 none none off + oxp_9bf35cd7-4938-4c34-8189-288b3195cb64/crypt/zone/oxz_crucible_99c6401d-9796-4ae1-bf0c-9a097cf21c33 6dfbbad9-2860-4547-8962-35224ff3251a none none off + oxp_9d833141-18a1-4f24-8a34-6076c026aa87/crucible c358c3ef-daa8-4b2d-8c5d-fd760798fcd8 none none off + oxp_9d833141-18a1-4f24-8a34-6076c026aa87/crypt/debug f631d6a1-9db5-4fc7-978b-9ace485dfe16 100 GiB none off + oxp_9d833141-18a1-4f24-8a34-6076c026aa87/crypt/zone 2a72fbf4-ff92-47a2-a4b9-2a702215c946 none none off + oxp_9d833141-18a1-4f24-8a34-6076c026aa87/crypt/zone/oxz_crucible_4f8ce495-21dd-48a1-859c-80d34ce394ed eed07a8f-8a64-46fc-9ee8-dff2e610b655 none none off + oxp_a279461f-a7b9-413f-a79f-cb4dab4c3fce/crucible fbc90cb1-5616-48cc-8b13-89acd5209f4f none none off + oxp_a279461f-a7b9-413f-a79f-cb4dab4c3fce/crypt/debug 1b9c97d6-c90d-4109-b99c-9ab799b3c3b9 100 GiB none off + oxp_a279461f-a7b9-413f-a79f-cb4dab4c3fce/crypt/zone 813c6cd2-b100-4481-aaf4-c008f2e81d6f none none off + oxp_a279461f-a7b9-413f-a79f-cb4dab4c3fce/crypt/zone/oxz_crucible_a1ae92ac-e1f1-4654-ab54-5b75ba7c44d6 de262cbb-4a71-4f24-a483-1d190bb1fc49 none none off + oxp_ff7e002b-3ad8-4d45-b03a-c46ef0ac8e59/crucible 0aa0ec68-c208-4d91-beec-cfb7fdc33895 none none off + oxp_ff7e002b-3ad8-4d45-b03a-c46ef0ac8e59/crypt/debug 9427caff-29ec-4cd1-981b-26d4a7900052 100 GiB none off + oxp_ff7e002b-3ad8-4d45-b03a-c46ef0ac8e59/crypt/zone 4ed5d6be-14cc-4bb5-b0d3-88a48b539751 none none off + oxp_ff7e002b-3ad8-4d45-b03a-c46ef0ac8e59/crypt/zone/oxz_crucible_a308d3e1-118c-440a-947a-8b6ab7d833ab 1c597048-bc3f-46d5-94cb-3507ada774e3 none none off ++ oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/zone/oxz_crucible_pantry_ff9ce09c-afbf-425b-bbfa-3d8fb254f98e 60d5c18d-6940-48eb-b36c-7f0b6ef55463 none none off ++ oxp_1e2ec79e-9c11-4133-ac77-e0b994a507d5/crypt/zone/oxz_nexus_845869e9-ecb2-4ec3-b6b8-2a836e459243 b8054c80-65c3-4a44-ba95-f65e37fd2678 none none off + + omicron zones generation 2 -> 3: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -115,6 +221,61 @@ to: blueprint 1ac2d88f-27dd-4506-8585-6b2be832528e fake-vendor fake-model serial-99e926d6-bd42-4cde-9f63-5ecc7ea14322 + datasets generation 1 -> 2: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_07068f19-1ff2-48da-8e72-874780df2339/crucible ea21aba9-6e79-4e20-8e35-92cd9fbba41c none none off + oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/debug 9ed7b9ab-dc13-4179-b867-08d23156253a 100 GiB none off + oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/internal_dns af7470be-9bc4-4d4b-8634-66fb0771fecc none none off + oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/zone 0a1e8f41-ffe6-4a4e-98de-a84217fddd4a none none off + oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/zone/oxz_crucible_0e2b035e-1de1-48af-8ac0-5316418e3de1 67788d73-161d-4331-b68b-5799dc103acb none none off + oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/zone/oxz_crucible_pantry_b7402110-d88f-4ca4-8391-4a2fda6ad271 f8eb44eb-00bd-416b-99e9-2539a7469963 none none off + oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/zone/oxz_internal_dns_5c78756d-6182-4c27-a507-3419e8dbe76b 9428edca-e834-4512-b888-e399103343db none none off + oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/zone/oxz_nexus_e6d0df1f-9f98-4c5a-9540-8444d1185c7d 5f868bf8-8770-4f07-a620-af7dee8062d6 none none off + oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/zone/oxz_ntp_c552280f-ba02-4f8d-9049-bd269e6b7845 a26ebbdb-38b4-4921-9186-3b1e1fb7cbc2 none none off + oxp_0f12e6ee-41d2-4eb0-813f-ba5240900ded/crucible b350b7d8-66cc-4d54-b480-1884c791a6d9 none none off + oxp_0f12e6ee-41d2-4eb0-813f-ba5240900ded/crypt/debug 3dad2cc1-25cc-401e-9763-47001a1acc17 100 GiB none off + oxp_0f12e6ee-41d2-4eb0-813f-ba5240900ded/crypt/zone 7a3d49fb-56fd-4e30-b1ca-7c9d6a1fdbfc none none off + oxp_0f12e6ee-41d2-4eb0-813f-ba5240900ded/crypt/zone/oxz_crucible_b7ae596e-0c85-40b2-bb47-df9f76db3cca 66fd473e-6927-45fa-93e0-6bff82f9c3df none none off + oxp_0fdb4a39-3cd5-47a0-9064-e7f3c285af61/crucible 9020f7d3-2bfc-4af4-a339-c60d233266cd none none off + oxp_0fdb4a39-3cd5-47a0-9064-e7f3c285af61/crypt/debug 55da940b-ae9a-4c93-87ea-4845c66dbe1c 100 GiB none off + oxp_0fdb4a39-3cd5-47a0-9064-e7f3c285af61/crypt/zone caa4bf8a-ccd5-4ebf-b5c0-8c3664c69738 none none off + oxp_0fdb4a39-3cd5-47a0-9064-e7f3c285af61/crypt/zone/oxz_crucible_cf13b878-47f1-4ba0-b8c2-9f3e15f2ee87 e45274bc-8d11-4ffc-89bd-3d17a9bb629e none none off + oxp_13572832-83ad-40d6-896a-751f7e53f4f6/crucible 87d29998-7d01-420b-97c6-f02067fb96ba none none off + oxp_13572832-83ad-40d6-896a-751f7e53f4f6/crypt/debug f37b9916-d47a-4fda-9385-2031a73b7a0e 100 GiB none off + oxp_13572832-83ad-40d6-896a-751f7e53f4f6/crypt/zone 2a95cee2-ff4f-4400-81ac-46df69443c68 none none off + oxp_13572832-83ad-40d6-896a-751f7e53f4f6/crypt/zone/oxz_crucible_eb034526-1767-4cc4-8225-ec962265710b b0ff583d-a6fd-4298-8093-ab54f1eaac30 none none off + oxp_3602bdd9-f7bb-4490-87a6-8f061f7712f5/crucible 3b096c27-0a84-4335-be9f-e6104d709162 none none off + oxp_3602bdd9-f7bb-4490-87a6-8f061f7712f5/crypt/debug aa333d9a-908b-4e2e-a476-a000e6f2fe25 100 GiB none off + oxp_3602bdd9-f7bb-4490-87a6-8f061f7712f5/crypt/zone 6dcb997c-6e19-4c31-9c0e-a5d41f66963f none none off + oxp_3602bdd9-f7bb-4490-87a6-8f061f7712f5/crypt/zone/oxz_crucible_2bf9ee97-90e1-48a7-bb06-a35cec63b7fe 85bee553-3b86-489f-9b90-d45a26de1a8b none none off + oxp_65707837-95a4-45d7-84e6-8b9a4da215f1/crucible 3409e636-fb7e-4699-967a-d7faad2fee27 none none off + oxp_65707837-95a4-45d7-84e6-8b9a4da215f1/crypt/debug eb939b53-6b86-44ae-b3bb-f4fc3111278d 100 GiB none off + oxp_65707837-95a4-45d7-84e6-8b9a4da215f1/crypt/zone f92429b2-b8fe-4a95-9a29-ca8ffe0d51f8 none none off + oxp_65707837-95a4-45d7-84e6-8b9a4da215f1/crypt/zone/oxz_crucible_e3bfcb1e-3708-45e7-a45a-2a2cab7ad829 47d54397-a793-404b-8293-3fc11184b525 none none off + oxp_7a43b2b0-3846-401c-8317-d555715a00f7/crucible b89f0c5b-bb1f-43f1-8473-b697a63c565f none none off + oxp_7a43b2b0-3846-401c-8317-d555715a00f7/crypt/debug 5a3f0c96-a125-4cc0-9e84-fb5551fe557e 100 GiB none off + oxp_7a43b2b0-3846-401c-8317-d555715a00f7/crypt/zone 4dc29bec-f01c-4998-b755-46226bce0aa5 none none off + oxp_7a43b2b0-3846-401c-8317-d555715a00f7/crypt/zone/oxz_crucible_15f29557-d4da-45ef-b435-a0a1cd586e0c 83759687-8f02-47ab-925e-82cc75e77819 none none off + oxp_855e3ef1-6929-4e21-8451-0e62bd93c7c9/crucible 0daab3dc-5ef4-44dc-abf6-04b03560f726 none none off + oxp_855e3ef1-6929-4e21-8451-0e62bd93c7c9/crypt/debug b5203528-903b-4e16-bd00-13147a83a712 100 GiB none off + oxp_855e3ef1-6929-4e21-8451-0e62bd93c7c9/crypt/zone 8d86f556-b74e-43f9-87ab-13f0b8d38a66 none none off + oxp_855e3ef1-6929-4e21-8451-0e62bd93c7c9/crypt/zone/oxz_crucible_751bc6fe-22ad-4ce1-bc51-cf31fdf02bfa c059bd54-6bd4-4a2c-a2d7-cb530f18d016 none none off + oxp_8adcf329-4cee-4075-b798-28b5add1edf5/crucible c6074d19-cb72-4fd1-b6f7-c2a017518ede none none off + oxp_8adcf329-4cee-4075-b798-28b5add1edf5/crypt/debug 23c24e8b-87ac-4462-a27d-1bbdb74b7ba3 100 GiB none off + oxp_8adcf329-4cee-4075-b798-28b5add1edf5/crypt/zone bbd3ec95-304c-4e3a-af36-e30990c697a5 none none off + oxp_8adcf329-4cee-4075-b798-28b5add1edf5/crypt/zone/oxz_crucible_e5121f83-faf2-4928-b5a8-94a1da99e8eb 8b12688c-61f9-4ecb-b17f-00df7b60105c none none off + oxp_99e926d6-bd42-4cde-9f63-5ecc7ea14322/crucible d5ad4cb2-723a-4ea4-8345-67438d9f1857 none none off + oxp_99e926d6-bd42-4cde-9f63-5ecc7ea14322/crypt/debug e9ae4c7d-4721-49de-bf6d-8ecec30c3ebb 100 GiB none off + oxp_99e926d6-bd42-4cde-9f63-5ecc7ea14322/crypt/zone 9bdf76dc-8c49-4a8c-85e9-23b70d9d090e none none off + oxp_99e926d6-bd42-4cde-9f63-5ecc7ea14322/crypt/zone/oxz_crucible_5cf79919-b28e-4064-b6f8-8906c471b5ce 36996d69-9e96-4b35-b38c-ce4a3ec71634 none none off ++ oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/clickhouse 158cd75c-abe9-4891-af66-3c8d5e6d65f4 none none off ++ oxp_07068f19-1ff2-48da-8e72-874780df2339/crypt/zone/oxz_clickhouse_c8851a11-a4f7-4b21-9281-6182fd15dc8d 7d47e5d6-a1a5-451a-b4b4-3a9747f8154a none none off ++ oxp_0f12e6ee-41d2-4eb0-813f-ba5240900ded/crypt/internal_dns a759d2f3-003c-4fb8-b06b-f985e213b273 none none off ++ oxp_0f12e6ee-41d2-4eb0-813f-ba5240900ded/crypt/zone/oxz_internal_dns_e639b672-27c4-4ecb-82c1-d672eb1ccf4e 4a5db72b-3b8d-4032-9507-524ba3843ed2 none none off + + omicron zones generation 2 -> 3: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt index 72832a0b9c..bf6d180eea 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt @@ -21,6 +21,59 @@ to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 fake-vendor fake-model serial-d1ebfd7b-3842-4ad7-be31-2b9c031209a9 + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crucible 79a16369-70ab-4f63-af6d-1c7f088eeee3 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/clickhouse d577ac11-62ac-4a71-bed7-e7327148bd33 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/debug d6d36f6b-35ff-4766-99de-44abe46932d1 100 GiB none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/internal_dns b9aa1175-2640-4923-81b3-1e1469b15abf none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone 18e2a336-9b65-421e-8409-04d9abce8cd6 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_clickhouse_93b137a1-a1d6-4b5b-b2cb-21a9f11e2883 d1a755ac-dafc-4087-a0ce-ee8b3f882ac1 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_crucible_19fbc4f8-a683-4f22-8f5a-e74782b935be 68724637-d228-4faa-a19f-b5df857ff4ab none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_crucible_pantry_9f0abbad-dbd3-4d43-9675-78092217ffd9 da62be58-643f-4497-a9d7-e259de6c7a12 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_internal_dns_c406da50-34b9-4bb4-a460-8f49875d2a6a f47f67f6-e315-4272-b93a-b467cfdfbb7a none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_nexus_6dff7633-66bb-4924-a6ff-2c896e66964b 9dce1e52-8e3b-4641-9982-69deb049f647 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_ntp_7f4e9f9f-08f8-4d14-885d-e977c05525ad 7773ffbd-1842-4425-aff5-88f577cd8955 none none off + oxp_44cdb6f2-fa6c-4b69-bab2-3ae4e1ec4b34/crucible ac31a04c-1f3a-43f4-b45a-cf4b5176e7f6 none none off + oxp_44cdb6f2-fa6c-4b69-bab2-3ae4e1ec4b34/crypt/debug 26154290-91f7-4f35-bcd0-ec7a8f398d82 100 GiB none off + oxp_44cdb6f2-fa6c-4b69-bab2-3ae4e1ec4b34/crypt/zone c37902cf-d1fe-4370-baef-b7de8731dffe none none off + oxp_44cdb6f2-fa6c-4b69-bab2-3ae4e1ec4b34/crypt/zone/oxz_crucible_6b53ab2e-d98c-485f-87a3-4d5df595390f 51229ad8-cda3-40c4-b0f3-b1af669abcd4 none none off + oxp_4de5fc8e-0e41-4ab9-ba12-2dc63882c96a/crucible b92bba84-9437-4f93-8ad0-80bf7ecdae5f none none off + oxp_4de5fc8e-0e41-4ab9-ba12-2dc63882c96a/crypt/debug 6dc5df73-7e44-4065-9f14-70a0d965a853 100 GiB none off + oxp_4de5fc8e-0e41-4ab9-ba12-2dc63882c96a/crypt/zone 00c2d1d6-94da-4985-a144-2194588afecf none none off + oxp_4de5fc8e-0e41-4ab9-ba12-2dc63882c96a/crypt/zone/oxz_crucible_b0c63f48-01ea-4aae-bb26-fb0dd59d1662 5d8788c0-13df-4c07-a8b2-b9365fec9796 none none off + oxp_51564e7a-d69f-4942-bcfe-330224633ca6/crucible dedbf4e7-32fb-406e-a5cc-a2f5281aa6d8 none none off + oxp_51564e7a-d69f-4942-bcfe-330224633ca6/crypt/debug 3063bdcc-4363-495b-a81f-67c6cc437a75 100 GiB none off + oxp_51564e7a-d69f-4942-bcfe-330224633ca6/crypt/zone 3527b52f-f1d4-4e2f-b41e-559f58657839 none none off + oxp_51564e7a-d69f-4942-bcfe-330224633ca6/crypt/zone/oxz_crucible_f55e6aaf-e8fc-4913-9e3c-8cd1bd4bdad3 82fc150e-d6f5-4bb6-8eee-17a0728aadb5 none none off + oxp_5ca23cb3-cc90-41c5-a474-01898cdd0796/crucible b2c9f282-e767-4df1-941e-b7cea21b1a02 none none off + oxp_5ca23cb3-cc90-41c5-a474-01898cdd0796/crypt/debug 53deebeb-3952-483a-afd2-2202cee9c33b 100 GiB none off + oxp_5ca23cb3-cc90-41c5-a474-01898cdd0796/crypt/zone 22581703-157c-474f-bbf3-34f076dd7bca none none off + oxp_5ca23cb3-cc90-41c5-a474-01898cdd0796/crypt/zone/oxz_crucible_d660d7ed-28c0-45ae-9ace-dc3ecf7e8786 68217627-4519-4cfa-85b9-25efb4dad71a none none off + oxp_6a23a532-0712-4a8d-be9b-e8c17e97aa4b/crucible 47cf951d-ca5c-4ae0-baa3-94472efbcf03 none none off + oxp_6a23a532-0712-4a8d-be9b-e8c17e97aa4b/crypt/debug e315743d-53e3-4505-9364-657c2486a1bb 100 GiB none off + oxp_6a23a532-0712-4a8d-be9b-e8c17e97aa4b/crypt/zone 4d6e70b1-ff91-4133-b65f-6fcd5bda2568 none none off + oxp_6a23a532-0712-4a8d-be9b-e8c17e97aa4b/crypt/zone/oxz_crucible_e98cc0de-abf6-4da4-a20d-d05c7a9bb1d7 a602c9d4-16a9-4e55-b5de-76fb33cc4ca9 none none off + oxp_6f1a330e-e8d4-4c09-97fc-8918b69b2a3c/crucible 9390bde4-2073-44b4-a496-5129c7e5ca40 none none off + oxp_6f1a330e-e8d4-4c09-97fc-8918b69b2a3c/crypt/debug 2e901ffa-895b-4405-b59a-2bcdfabda681 100 GiB none off + oxp_6f1a330e-e8d4-4c09-97fc-8918b69b2a3c/crypt/zone 41e55e8a-ca4b-4c27-8337-57d42cc36fb5 none none off + oxp_6f1a330e-e8d4-4c09-97fc-8918b69b2a3c/crypt/zone/oxz_crucible_4f1ce8a2-d3a5-4a38-be4c-9817de52db37 ef14767b-6be7-44cb-833d-27d9f523dcdb none none off + oxp_7113d104-fb55-4299-bf53-b3c59d258e44/crucible 5ffb2b84-4a3f-444a-aebf-381f8c58b7ae none none off + oxp_7113d104-fb55-4299-bf53-b3c59d258e44/crypt/debug ee20f8cb-7d24-422b-9dff-ca4c9596191f 100 GiB none off + oxp_7113d104-fb55-4299-bf53-b3c59d258e44/crypt/zone ae6c87ea-dc50-4a15-855a-224300f20c74 none none off + oxp_7113d104-fb55-4299-bf53-b3c59d258e44/crypt/zone/oxz_crucible_67d913e0-0005-4599-9b28-0abbf6cc2916 d63be5b2-5471-4f4d-accc-82e2a0d7276f none none off + oxp_8c10be49-3a66-40d4-a082-64d09d916f14/crucible d72ffc3a-4665-40bc-a099-ebe628622636 none none off + oxp_8c10be49-3a66-40d4-a082-64d09d916f14/crypt/debug 84bc6d3f-2d9c-42b3-9982-7394630a0928 100 GiB none off + oxp_8c10be49-3a66-40d4-a082-64d09d916f14/crypt/zone 3fc37e5a-3bf3-476f-8a8f-6d32a533cfc0 none none off + oxp_8c10be49-3a66-40d4-a082-64d09d916f14/crypt/zone/oxz_crucible_2aa0ea4f-3561-4989-a98c-9ab7d9a240fb 05a77359-0a07-4976-8fbb-5f1eee688e47 none none off + oxp_d1ebfd7b-3842-4ad7-be31-2b9c031209a9/crucible 39185a78-064c-49d5-b716-93f1a312c0c1 none none off + oxp_d1ebfd7b-3842-4ad7-be31-2b9c031209a9/crypt/debug 0c11d380-038c-4b68-b9f4-1775f6fec1e8 100 GiB none off + oxp_d1ebfd7b-3842-4ad7-be31-2b9c031209a9/crypt/zone 64a64a7b-0d29-4470-bc96-4796ca357507 none none off + oxp_d1ebfd7b-3842-4ad7-be31-2b9c031209a9/crypt/zone/oxz_crucible_67622d61-2df4-414d-aa0e-d1277265f405 6dbd0e11-89af-4464-bd84-c20cb818a9d5 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -62,6 +115,57 @@ to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 - fake-vendor fake-model serial-ef64ff6d-250d-47ac-8686-e696cfb46966 + datasets from generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +- oxp_22930645-144a-415c-bceb-2dbfafb9c29e/crucible 8dead13f-23ec-47db-95bf-8ac7c15ec0bf none none off +- oxp_22930645-144a-415c-bceb-2dbfafb9c29e/crypt/debug 2277b21f-9c43-4537-b63b-82b9c66bc022 100 GiB none off +- oxp_22930645-144a-415c-bceb-2dbfafb9c29e/crypt/internal_dns 6f1c7df6-6a3b-433e-b376-d1b8b5cf4d5e none none off +- oxp_22930645-144a-415c-bceb-2dbfafb9c29e/crypt/zone 5884d02b-a463-45ba-8e99-660ec63d2779 none none off +- oxp_22930645-144a-415c-bceb-2dbfafb9c29e/crypt/zone/oxz_crucible_4a9a0a9d-87f0-4f1d-9181-27f6b435e637 c36945af-cdba-45d8-941f-09c5bea2658e none none off +- oxp_22930645-144a-415c-bceb-2dbfafb9c29e/crypt/zone/oxz_crucible_pantry_2f5e8010-a94d-43a4-9c5c-3f52832f5f7f 8d1d1315-00a3-4cb0-b3c2-7f3f224d63ba none none off +- oxp_22930645-144a-415c-bceb-2dbfafb9c29e/crypt/zone/oxz_internal_dns_d6ee1338-3127-43ec-9aaa-b973ccf05496 b79f5a18-1ea5-46cd-b002-3d7f860bd444 none none off +- oxp_22930645-144a-415c-bceb-2dbfafb9c29e/crypt/zone/oxz_nexus_0dcfdfc5-481e-4153-b97c-11cf02b648ea 23d48cd2-e829-496c-b4df-a05d30d4fcad none none off +- oxp_22930645-144a-415c-bceb-2dbfafb9c29e/crypt/zone/oxz_ntp_56ac1706-9e2a-49ba-bd6f-a99c44cb2ccb 11a058db-758a-4fda-beb8-be18fcf2df25 none none off +- oxp_24155070-8a43-4244-a3ba-853d8c71972d/crucible 8a70b0f8-2021-46ac-b57e-62ed57442c2e none none off +- oxp_24155070-8a43-4244-a3ba-853d8c71972d/crypt/debug cadb7c95-f233-4033-b0fe-a7b997245722 100 GiB none off +- oxp_24155070-8a43-4244-a3ba-853d8c71972d/crypt/zone 2b630e70-1cd8-4142-aee9-7067b6fe3ef3 none none off +- oxp_24155070-8a43-4244-a3ba-853d8c71972d/crypt/zone/oxz_crucible_e39d7c9e-182b-48af-af87-58079d723583 f49fa95b-4d2d-4ea3-a9ff-5ed73bf29a7f none none off +- oxp_494782c7-3821-4f49-918b-ce42cc4d18ad/crucible 0c7a0040-a420-4c46-a43f-cf531b30218b none none off +- oxp_494782c7-3821-4f49-918b-ce42cc4d18ad/crypt/debug 988e1f23-ecca-487a-aec7-089593a043cc 100 GiB none off +- oxp_494782c7-3821-4f49-918b-ce42cc4d18ad/crypt/zone 04c9bd23-0d9f-4a36-a442-42b9e3c54179 none none off +- oxp_494782c7-3821-4f49-918b-ce42cc4d18ad/crypt/zone/oxz_crucible_b91b271d-8d80-4f49-99a0-34006ae86063 73830784-d10d-489b-9e08-41f96e8ae130 none none off +- oxp_6ea8a67f-d27d-472b-844c-6c8245b00e2b/crucible b47d4afb-fa04-4f3a-9816-fa83714b211f none none off +- oxp_6ea8a67f-d27d-472b-844c-6c8245b00e2b/crypt/debug b26bab43-3760-4418-a6ab-47e01b267a1d 100 GiB none off +- oxp_6ea8a67f-d27d-472b-844c-6c8245b00e2b/crypt/zone 38e45596-3b96-4ab9-bf80-274dded9157a none none off +- oxp_6ea8a67f-d27d-472b-844c-6c8245b00e2b/crypt/zone/oxz_crucible_f69f92a1-5007-4bb0-a85b-604dc217154b 4dc23347-b0aa-4705-a309-1baf57f222f2 none none off +- oxp_77565d57-c235-4905-b3c7-32d1c2ca2c44/crucible 56f99204-06fc-41fb-8f0c-456c7e97b034 none none off +- oxp_77565d57-c235-4905-b3c7-32d1c2ca2c44/crypt/debug f1299161-15fd-424f-a739-6263c314ba90 100 GiB none off +- oxp_77565d57-c235-4905-b3c7-32d1c2ca2c44/crypt/zone dc86fcab-2838-4b6c-bea3-3cb0fcec846a none none off +- oxp_77565d57-c235-4905-b3c7-32d1c2ca2c44/crypt/zone/oxz_crucible_094f27af-1acb-4d1e-ba97-1fc1377d4bf2 035d0068-1470-4491-900a-e8812e10376f none none off +- oxp_8746874c-dc3b-4454-93cd-2a8fc13720fe/crucible c310b273-8e72-40e1-8f07-a52d2b7532f3 none none off +- oxp_8746874c-dc3b-4454-93cd-2a8fc13720fe/crypt/debug 86573b1f-e557-4da1-b7e2-3f3b42dba8de 100 GiB none off +- oxp_8746874c-dc3b-4454-93cd-2a8fc13720fe/crypt/zone 97735f9a-4a36-4f9a-97f5-900204167d44 none none off +- oxp_8746874c-dc3b-4454-93cd-2a8fc13720fe/crypt/zone/oxz_crucible_f3f2e4f3-0985-4ef6-8336-ce479382d05d a916c527-eca0-4df7-b38e-76f4f7915656 none none off +- oxp_a42c5a67-6e10-4586-a56e-48bb8260e75f/crucible b5844aab-20fe-47c8-8b1d-d2e4b2e75702 none none off +- oxp_a42c5a67-6e10-4586-a56e-48bb8260e75f/crypt/debug 86a8f677-e4d5-466a-bffb-60d653e91ce5 100 GiB none off +- oxp_a42c5a67-6e10-4586-a56e-48bb8260e75f/crypt/zone 619c6140-1d63-495b-a5e4-be8f1fefe5ed none none off +- oxp_a42c5a67-6e10-4586-a56e-48bb8260e75f/crypt/zone/oxz_crucible_01d58626-e1b0-480f-96be-ac784863c7dc ff5950ed-1acc-477f-a9c1-f660161943f4 none none off +- oxp_ca89b120-7bcd-4eeb-baa7-71031fbd103b/crucible f6d614eb-b40d-431b-8a15-f29b7434e702 none none off +- oxp_ca89b120-7bcd-4eeb-baa7-71031fbd103b/crypt/debug 0043d0b0-c24e-4858-afa3-2e3ae2a99a78 100 GiB none off +- oxp_ca89b120-7bcd-4eeb-baa7-71031fbd103b/crypt/zone b916b8aa-73b5-433d-ab69-fe3166cc6574 none none off +- oxp_ca89b120-7bcd-4eeb-baa7-71031fbd103b/crypt/zone/oxz_crucible_47a87c6e-ef45-4d52-9a3e-69cdd96737cc 51c0f16b-1ca5-48e6-b2e9-0069cbe9c48b none none off +- oxp_ef61aa97-c862-428c-82f3-0a69a50d6155/crucible 4ee918f4-7f38-4759-9cea-00208b404c09 none none off +- oxp_ef61aa97-c862-428c-82f3-0a69a50d6155/crypt/debug 941ef258-4015-4176-bbc0-5fa323fe2802 100 GiB none off +- oxp_ef61aa97-c862-428c-82f3-0a69a50d6155/crypt/zone 6551d496-8da9-4d11-9be2-7fc3a49f4759 none none off +- oxp_ef61aa97-c862-428c-82f3-0a69a50d6155/crypt/zone/oxz_crucible_6464d025-4652-4948-919e-740bec5699b1 9f67b263-7a55-44a2-b0a0-e738b89fb472 none none off +- oxp_ef64ff6d-250d-47ac-8686-e696cfb46966/crucible 50faf412-62e6-4cb9-a965-d07b7b035da7 none none off +- oxp_ef64ff6d-250d-47ac-8686-e696cfb46966/crypt/debug 98a2efcf-db1a-46a1-a4b9-01cf83b48e56 100 GiB none off +- oxp_ef64ff6d-250d-47ac-8686-e696cfb46966/crypt/zone db457b0d-72f5-47c9-a86c-f9c2c4d3064b none none off +- oxp_ef64ff6d-250d-47ac-8686-e696cfb46966/crypt/zone/oxz_crucible_878dfddd-3113-4197-a3ea-e0d4dbe9b476 f95cc7aa-db18-4277-ad08-528c02d267e4 none none off + + omicron zones generation 2 -> 3: ---------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -114,6 +218,57 @@ to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 - fake-vendor fake-model serial-d56b0c9f-0e57-43d8-a1ac-8b4d2c303c29 + datasets from generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +- oxp_09a5de95-c15f-486e-b776-fca62bf5e179/crucible a68daa64-5586-4806-8aaf-3b87601d7439 none none off +- oxp_09a5de95-c15f-486e-b776-fca62bf5e179/crypt/debug c0485a6d-fe97-47b0-bf93-eb58308fc3c1 100 GiB none off +- oxp_09a5de95-c15f-486e-b776-fca62bf5e179/crypt/internal_dns fa80f286-3de6-4042-907a-398973c726ef none none off +- oxp_09a5de95-c15f-486e-b776-fca62bf5e179/crypt/zone bec5341c-bcb2-4e7c-92e1-d6c47f435ea2 none none off +- oxp_09a5de95-c15f-486e-b776-fca62bf5e179/crypt/zone/oxz_crucible_c407795c-6c8b-428e-8ab8-b962913c447f 99ad09ef-da25-4187-b83a-3e95fc8898f7 none none off +- oxp_09a5de95-c15f-486e-b776-fca62bf5e179/crypt/zone/oxz_crucible_pantry_6939ce48-b17c-4616-b176-8a419a7697be 35fda90e-04f2-4a3e-89ee-92f9b6047956 none none off +- oxp_09a5de95-c15f-486e-b776-fca62bf5e179/crypt/zone/oxz_internal_dns_b6b759d0-f60d-42b7-bbbc-9d61c9e895a9 577d3d90-d609-418b-9278-0cbf605294c6 none none off +- oxp_09a5de95-c15f-486e-b776-fca62bf5e179/crypt/zone/oxz_nexus_b44cdbc0-0ce0-46eb-8b21-a09e113aa1d0 c9208413-484e-47bf-9d54-ec7977cccd81 none none off +- oxp_09a5de95-c15f-486e-b776-fca62bf5e179/crypt/zone/oxz_ntp_9fd52961-426f-4e62-a644-b70871103fca 2f54bd95-c1d7-4aaa-95e2-7ecacb4be6e3 none none off +- oxp_11b8eccf-7c78-4bde-8639-b35a83082a95/crucible 703b5340-55ff-4053-af0b-fd5d20f8c47f none none off +- oxp_11b8eccf-7c78-4bde-8639-b35a83082a95/crypt/debug 571edc71-daed-4b47-b4ba-0dc0e29e5e9d 100 GiB none off +- oxp_11b8eccf-7c78-4bde-8639-b35a83082a95/crypt/zone 34c338eb-b4ea-4c8f-b60e-d3ec612f662c none none off +- oxp_11b8eccf-7c78-4bde-8639-b35a83082a95/crypt/zone/oxz_crucible_8d4d2b28-82bb-4e36-80da-1408d8c35d82 458ce5af-7d1b-4484-a11d-3c5f8e01bc9a none none off +- oxp_1931c422-4c6a-4597-8ae7-ecb44718462c/crucible c674cb78-2509-4a6a-86a9-57dcd8b59dfb none none off +- oxp_1931c422-4c6a-4597-8ae7-ecb44718462c/crypt/debug 5c8d8475-6f8a-4a4a-bc30-d6fdb6d057f2 100 GiB none off +- oxp_1931c422-4c6a-4597-8ae7-ecb44718462c/crypt/zone c575e10d-30ff-4201-9e3a-892fc8f2fc51 none none off +- oxp_1931c422-4c6a-4597-8ae7-ecb44718462c/crypt/zone/oxz_crucible_3b3c14b6-a8e2-4054-a577-8d96cb576230 93d7d978-cfe4-4637-8809-1b234ca7784d none none off +- oxp_21a8a87e-73a4-42d4-a426-f6eec94004e3/crucible a0f3bb4d-67e5-4384-9d17-fbe562f8605a none none off +- oxp_21a8a87e-73a4-42d4-a426-f6eec94004e3/crypt/debug 21e32545-8f60-412f-9f0f-384c00a8c3c7 100 GiB none off +- oxp_21a8a87e-73a4-42d4-a426-f6eec94004e3/crypt/zone c32b8e04-6296-49d8-bf08-16a249890867 none none off +- oxp_21a8a87e-73a4-42d4-a426-f6eec94004e3/crypt/zone/oxz_crucible_57b96d5c-b71e-43e4-8869-7d514003d00d 07a1540e-5775-4768-9938-e8a9031ace4f none none off +- oxp_222c0b55-2966-46b6-816c-9063a7587806/crucible f4d87d35-e308-41f9-af74-6f7169295160 none none off +- oxp_222c0b55-2966-46b6-816c-9063a7587806/crypt/debug d9c04df5-0f09-4f6f-883b-29137611d89b 100 GiB none off +- oxp_222c0b55-2966-46b6-816c-9063a7587806/crypt/zone a08d5c26-4524-4b70-888e-a8db7f2883cb none none off +- oxp_222c0b55-2966-46b6-816c-9063a7587806/crypt/zone/oxz_crucible_b4947d31-f70e-4ee0-8817-0ca6cea9b16b b4ec6b4d-cac2-44ca-8592-b2951398bd9d none none off +- oxp_3676f688-f41c-4f89-936a-6b04c3011b2a/crucible 6439fca6-e8ae-4eff-b9a5-04dd674b1743 none none off +- oxp_3676f688-f41c-4f89-936a-6b04c3011b2a/crypt/debug 222b4387-2804-4d53-ba2f-4a32777d0b72 100 GiB none off +- oxp_3676f688-f41c-4f89-936a-6b04c3011b2a/crypt/zone 5b3537cb-5cd4-48f8-bb9b-222f8287b990 none none off +- oxp_3676f688-f41c-4f89-936a-6b04c3011b2a/crypt/zone/oxz_crucible_e4b3e159-3dbe-48cb-8497-e3da92a90e5a 231ab7ea-48cd-45f4-b359-d9ff54732688 none none off +- oxp_5e9e14c4-d60d-4b5c-a11c-bba54eb24c9f/crucible f89c2279-d18a-4f57-98cc-b48267835008 none none off +- oxp_5e9e14c4-d60d-4b5c-a11c-bba54eb24c9f/crypt/debug d5497445-1e12-4497-94e5-087fc82cce67 100 GiB none off +- oxp_5e9e14c4-d60d-4b5c-a11c-bba54eb24c9f/crypt/zone 1ed89b99-e4e5-4873-8ab3-f68285365c8c none none off +- oxp_5e9e14c4-d60d-4b5c-a11c-bba54eb24c9f/crypt/zone/oxz_crucible_b1783e95-9598-451d-b6ba-c50b52b428c3 0bad1d7b-9fa4-4f29-9374-0b2aa2ebcda3 none none off +- oxp_74f7b89e-88f5-4336-ba8b-22283a6966c5/crucible ff77f150-ae89-4483-872a-d269e06c807c none none off +- oxp_74f7b89e-88f5-4336-ba8b-22283a6966c5/crypt/debug ac76ddfb-dafd-47fb-87c7-abee8b82f6bd 100 GiB none off +- oxp_74f7b89e-88f5-4336-ba8b-22283a6966c5/crypt/zone b7dab9eb-f509-4e87-976a-aba0d11578a8 none none off +- oxp_74f7b89e-88f5-4336-ba8b-22283a6966c5/crypt/zone/oxz_crucible_15bb9def-69b8-4d2e-b04f-9fee1143387c d9b8254c-75fa-45b6-8816-e3e6da18f49f none none off +- oxp_a787cac8-b5e3-49e3-aaab-20d8eadd8a63/crucible 3648ccea-b4ff-4087-9cf4-8291096ef1b3 none none off +- oxp_a787cac8-b5e3-49e3-aaab-20d8eadd8a63/crypt/debug a3152a55-6fe7-4b34-90ee-e2370860b197 100 GiB none off +- oxp_a787cac8-b5e3-49e3-aaab-20d8eadd8a63/crypt/zone bf566572-3446-46c4-ba77-88b57f59dc99 none none off +- oxp_a787cac8-b5e3-49e3-aaab-20d8eadd8a63/crypt/zone/oxz_crucible_996d7570-b0df-46d5-aaa4-0c97697cf484 c5d41993-88ce-471d-bd89-6ffd35dc8f61 none none off +- oxp_d56b0c9f-0e57-43d8-a1ac-8b4d2c303c29/crucible a073286c-c538-421f-855b-ba7bca328900 none none off +- oxp_d56b0c9f-0e57-43d8-a1ac-8b4d2c303c29/crypt/debug 5d5eedcd-8193-40ed-9018-7faab9df80c0 100 GiB none off +- oxp_d56b0c9f-0e57-43d8-a1ac-8b4d2c303c29/crypt/zone 422f8e78-ab9d-4a60-b859-a036db2979a7 none none off +- oxp_d56b0c9f-0e57-43d8-a1ac-8b4d2c303c29/crypt/zone/oxz_crucible_c6dd531e-2d1d-423b-acc8-358533dab78c e2105178-7bec-4e0f-95a9-91f0e698cca5 none none off + + omicron zones at generation 2: --------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -152,6 +307,57 @@ to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 fake-vendor fake-model serial-f4d7f914-ec73-4b65-8696-5068591d9065 + datasets generation 1 -> 2: + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crucible 5c9ef84c-434c-4406-b68a-70e9af65b5a5 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/debug bf9b39db-5a6a-4b45-b2da-c37425271014 100 GiB none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone fc0b9a2c-4002-4fff-92f4-b541b7dd18c4 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_crucible_85b8c68a-160d-461d-94dd-1baf175fa75c 7b00d896-de30-48f8-bcb7-b140ffab2781 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_nexus_a732c489-d29a-4f75-b900-5966385943af db6c139b-9028-4d8e-92c7-6cc1e9aa0131 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_ntp_621509d6-3772-4009-aca1-35eefd1098fb 3b5822d2-9918-4bd6-8b75-2f52bdd73189 none none off + oxp_5248a306-4a03-449e-a8a3-6f86d26da755/crucible 182f7cbb-ea53-4057-b85c-667e5d949db5 none none off + oxp_5248a306-4a03-449e-a8a3-6f86d26da755/crypt/debug 1b4e8d9e-e447-4df1-8e0b-57edc318e8ad 100 GiB none off + oxp_5248a306-4a03-449e-a8a3-6f86d26da755/crypt/zone 5f097047-8290-438a-b85a-80bc9450b26c none none off + oxp_5248a306-4a03-449e-a8a3-6f86d26da755/crypt/zone/oxz_crucible_f0ff59e8-4105-4980-a4bb-a1f4c58de1e3 c596346d-4040-4103-b036-8fafdbaada00 none none off + oxp_55196665-ed61-4b23-9a74-0711bf2eaf90/crucible 931c4336-5ff9-45ce-b71b-9b6e81e16e53 none none off + oxp_55196665-ed61-4b23-9a74-0711bf2eaf90/crypt/debug ae4f4e83-0bd1-48dc-bfe3-f1082e8f357a 100 GiB none off + oxp_55196665-ed61-4b23-9a74-0711bf2eaf90/crypt/zone b19e4e13-79e3-481d-ad80-db076a26b7eb none none off + oxp_55196665-ed61-4b23-9a74-0711bf2eaf90/crypt/zone/oxz_crucible_23a8fa2b-ef3e-4017-a43f-f7a83953bd7c 3c7c5190-92dd-4d3b-9222-95d82a530d9e none none off + oxp_6b2a719a-35eb-469f-aa54-114a1f21f37d/crucible 11e2e73b-93a1-47f1-aff3-c4dc7667f4f1 none none off + oxp_6b2a719a-35eb-469f-aa54-114a1f21f37d/crypt/debug be833d4e-3439-41ac-b3ad-3e4d14a66360 100 GiB none off + oxp_6b2a719a-35eb-469f-aa54-114a1f21f37d/crypt/zone 7ab2c8a5-3135-4a51-a232-7af8c72b9d3c none none off + oxp_6b2a719a-35eb-469f-aa54-114a1f21f37d/crypt/zone/oxz_crucible_f1a7b9a7-fc6a-4b23-b829-045ff33117ff c864de0d-9859-4ad1-a30b-f5ac45ba03ed none none off + oxp_7ed4296a-66d1-4fb2-bc56-9b23b8f27d7e/crucible bdc3e42b-b28f-42c9-99fa-3f92e8b30a3c none none off + oxp_7ed4296a-66d1-4fb2-bc56-9b23b8f27d7e/crypt/debug a02b70bf-b069-4fec-9f53-1976ba462c45 100 GiB none off + oxp_7ed4296a-66d1-4fb2-bc56-9b23b8f27d7e/crypt/zone 4b69a9a9-2994-433c-9733-05de50d9c2a1 none none off + oxp_7ed4296a-66d1-4fb2-bc56-9b23b8f27d7e/crypt/zone/oxz_crucible_15c103f0-ac63-423b-ba5d-1b5fcd563ba3 02deac75-eb1a-423e-9b64-d20ca921fc25 none none off + oxp_984e2389-e7fd-4af9-ab02-e3caf77f95b5/crucible 5c956b09-323e-476c-9a71-352154b1f841 none none off + oxp_984e2389-e7fd-4af9-ab02-e3caf77f95b5/crypt/debug 5c79ad9d-1aef-407d-804c-ace1d0e069a4 100 GiB none off + oxp_984e2389-e7fd-4af9-ab02-e3caf77f95b5/crypt/zone b27f6972-47f0-43e9-a7cb-3a2fcd93798b none none off + oxp_984e2389-e7fd-4af9-ab02-e3caf77f95b5/crypt/zone/oxz_crucible_95482c25-1e7f-43e8-adf1-e3548a1b3ae0 5c5d822f-d696-4b6a-a075-286c437deba1 none none off + oxp_a5f75431-3795-426c-8f80-176f658281a5/crucible 7b4f5c33-6a5a-42f9-8199-69f72761b24d none none off + oxp_a5f75431-3795-426c-8f80-176f658281a5/crypt/debug dbc27904-c6dd-4cdc-96c8-32a24bdba0a1 100 GiB none off + oxp_a5f75431-3795-426c-8f80-176f658281a5/crypt/zone 4d913be5-fa91-4b22-b714-53babe093654 none none off + oxp_a5f75431-3795-426c-8f80-176f658281a5/crypt/zone/oxz_crucible_3aa07966-5899-4789-ace5-f8eeb375c6c3 23ff87ad-3d52-4c7e-a9a3-be288d08835c none none off + oxp_cf32a1ce-2c9e-49f5-b1cf-4af7f2a28901/crucible 77279948-9fe2-46f2-af39-0d5b692f5984 none none off + oxp_cf32a1ce-2c9e-49f5-b1cf-4af7f2a28901/crypt/debug 2dfc5c53-6618-4352-b754-86ef6463c20a 100 GiB none off + oxp_cf32a1ce-2c9e-49f5-b1cf-4af7f2a28901/crypt/zone e79fbff1-f7fb-4912-9f5c-faca57a6a9c4 none none off + oxp_cf32a1ce-2c9e-49f5-b1cf-4af7f2a28901/crypt/zone/oxz_crucible_c60379ba-4e30-4628-a79a-0ae509aef4c5 a6fcf496-70a1-49bf-a951-62fcec8dd5e2 none none off + oxp_e405da11-cb6b-4ebc-bac1-9bc997352e10/crucible 0f6d5b5f-674d-465e-9b40-d09c6865416a none none off + oxp_e405da11-cb6b-4ebc-bac1-9bc997352e10/crypt/debug 61a653cf-44a6-43c0-90e1-bec539511703 100 GiB none off + oxp_e405da11-cb6b-4ebc-bac1-9bc997352e10/crypt/zone ffc0cb27-54e5-4d28-8f3d-5f7730fed34a none none off + oxp_e405da11-cb6b-4ebc-bac1-9bc997352e10/crypt/zone/oxz_crucible_72c5a909-077d-4ec1-a9d5-ae64ef9d716e 4f3e0a2b-43df-43f1-9244-a67fe65a4856 none none off + oxp_f4d7f914-ec73-4b65-8696-5068591d9065/crucible e7ca75fe-d59c-4324-9053-a6a1566958e2 none none off + oxp_f4d7f914-ec73-4b65-8696-5068591d9065/crypt/debug b803d901-7e43-42fa-8372-43c3c5b3c1a9 100 GiB none off + oxp_f4d7f914-ec73-4b65-8696-5068591d9065/crypt/zone ccf69d3f-87be-4f8c-9191-9dde56547b21 none none off + oxp_f4d7f914-ec73-4b65-8696-5068591d9065/crypt/zone/oxz_crucible_0dfbf374-9ef9-430f-b06d-f271bf7f84c4 f72e1f0d-0acd-4cde-9acd-f25663592558 none none off ++ oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_nexus_2ec75441-3d7d-4b4b-9614-af03de5a3666 cd15e9c9-0238-493a-8b32-926d1cd1bce6 none none off ++ oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_nexus_508abd03-cbfe-4654-9a6d-7f15a1ad32e5 b781d032-3149-4c44-a7d3-5f8d80e4a607 none none off ++ oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_nexus_59950bc8-1497-44dd-8cbf-b6502ba921b2 63ec1a21-2c77-41b5-ad3e-e7bf39207107 none none off + + omicron zones generation 2 -> 3: ------------------------------------------------------------------------------------------ zone type zone id disposition underlay IP @@ -191,6 +397,57 @@ to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 fake-vendor fake-model serial-fe4fdfba-3b6d-47d3-8612-1fb2390b650a + datasets generation 1 -> 2: + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crucible cefe1de4-bebb-4ac1-8871-09585baf593d none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/debug 755e24a8-67cc-44b1-8c25-2dcb3acd988f 100 GiB none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone 29246ffd-11d0-4afd-8324-4727380185a3 none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_crucible_a1c03689-fc62-4ea5-bb72-4d01f5138614 951bc0b6-8136-4ec3-870b-ffaa4d2ff2f9 none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_nexus_4ad0e9da-08f8-4d40-b4d3-d17e711b5bbf 45d32c13-cbbb-4382-a0ed-dc6574b827b7 none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_ntp_bf79a56a-97af-4cc4-94a5-8b20d64c2cda a410308c-e2cb-4e4d-9da6-1879336f93f2 none none off + oxp_39ca2e23-4c38-4743-afe0-26b0380b27db/crucible 3bf64d72-e015-4377-b02a-a094f8f96d57 none none off + oxp_39ca2e23-4c38-4743-afe0-26b0380b27db/crypt/debug c834f8cd-25ee-4c62-af03-49cef53fc4c1 100 GiB none off + oxp_39ca2e23-4c38-4743-afe0-26b0380b27db/crypt/zone 212dad15-885a-45f6-b7ea-5e2ed8d9186f none none off + oxp_39ca2e23-4c38-4743-afe0-26b0380b27db/crypt/zone/oxz_crucible_d47f4996-fac0-4657-bcea-01b1fee6404d c1af262a-2595-4236-98c8-21c5b63c80c3 none none off + oxp_4fbd2fe0-2eac-41b8-8e8d-4fa46c3e8b6c/crucible c35cac80-eb26-4d90-ac3e-879b9d80c04c none none off + oxp_4fbd2fe0-2eac-41b8-8e8d-4fa46c3e8b6c/crypt/debug 5a288f52-a84e-45a6-873a-3d9c81d67380 100 GiB none off + oxp_4fbd2fe0-2eac-41b8-8e8d-4fa46c3e8b6c/crypt/zone e1e73d4f-0138-42dc-9e10-3432b26098f4 none none off + oxp_4fbd2fe0-2eac-41b8-8e8d-4fa46c3e8b6c/crypt/zone/oxz_crucible_a568e92e-4fbd-4b69-acd8-f16277073031 b8868d0c-960d-4ae4-b340-2c5970c8d530 none none off + oxp_60131a33-1f12-4dbb-9435-bdd368db1f51/crucible 30d7609e-1c74-4002-8558-b62246ee9600 none none off + oxp_60131a33-1f12-4dbb-9435-bdd368db1f51/crypt/debug 078134ea-f776-4283-ae17-116869f304b4 100 GiB none off + oxp_60131a33-1f12-4dbb-9435-bdd368db1f51/crypt/zone 48e191ca-1498-4381-b2dd-675b022d9a86 none none off + oxp_60131a33-1f12-4dbb-9435-bdd368db1f51/crypt/zone/oxz_crucible_772cbcbd-58be-4158-be85-be744871fa22 a7228ebc-840f-4393-94d7-b338dab3d459 none none off + oxp_77e45b5b-869f-4e78-8ce3-28bbe8cf37e9/crucible eff3d661-6ab1-4966-ab0a-6f94fda0bd39 none none off + oxp_77e45b5b-869f-4e78-8ce3-28bbe8cf37e9/crypt/debug 60a98875-ee39-49d8-b4b8-1f5d168e39e2 100 GiB none off + oxp_77e45b5b-869f-4e78-8ce3-28bbe8cf37e9/crypt/zone 1ce1d218-9f8d-4c13-a1bf-e085c569a4b8 none none off + oxp_77e45b5b-869f-4e78-8ce3-28bbe8cf37e9/crypt/zone/oxz_crucible_be75764a-491b-4aec-992e-1c39e25de975 09d557a8-4a28-4434-bcca-8fa593cc2fec none none off + oxp_789d607d-d196-428e-a988-f7886a327859/crucible 7253d7b0-8954-41f0-b788-9efc41f42742 none none off + oxp_789d607d-d196-428e-a988-f7886a327859/crypt/debug 1f7f6932-ed14-482a-816e-b0f76d96603d 100 GiB none off + oxp_789d607d-d196-428e-a988-f7886a327859/crypt/zone d3ae6bfa-bdfd-4a36-9009-4e061be33c0b none none off + oxp_789d607d-d196-428e-a988-f7886a327859/crypt/zone/oxz_crucible_e001fea0-6594-4ece-97e3-6198c293e931 5e27b9bc-e69f-4258-83f2-5f9a1109a625 none none off + oxp_b104b94c-2197-4e76-bfbd-6f966bd5af66/crucible 70b8bba8-802a-462a-9aa7-3dcd20a1fbbf none none off + oxp_b104b94c-2197-4e76-bfbd-6f966bd5af66/crypt/debug c2eba705-dd3c-49f1-9d7d-abb951cbc722 100 GiB none off + oxp_b104b94c-2197-4e76-bfbd-6f966bd5af66/crypt/zone 474256d1-bab1-499a-9679-566accf12f3f none none off + oxp_b104b94c-2197-4e76-bfbd-6f966bd5af66/crypt/zone/oxz_crucible_414830dc-c8c1-4748-9e9e-bc3a6435a93c a9351df9-851e-4c85-a7f2-74490471e876 none none off + oxp_cd62306a-aedf-47e8-93d5-92a358d64c7b/crucible 3acdbfe5-1226-4752-a14e-9f1e468ade71 none none off + oxp_cd62306a-aedf-47e8-93d5-92a358d64c7b/crypt/debug 73674f4b-1d93-404a-bc9c-8395efac97fd 100 GiB none off + oxp_cd62306a-aedf-47e8-93d5-92a358d64c7b/crypt/zone 6e415c23-e85d-4f5b-b993-43a8f1c56763 none none off + oxp_cd62306a-aedf-47e8-93d5-92a358d64c7b/crypt/zone/oxz_crucible_be920398-024a-4655-8c49-69b5ac48dfff 87f757d6-fa4c-4423-995c-1eab5e7d09a2 none none off + oxp_f1693454-aac1-4265-b8a0-4e9f3f41c7b3/crucible f45ef5d4-14b2-4346-8297-20919d6ee9da none none off + oxp_f1693454-aac1-4265-b8a0-4e9f3f41c7b3/crypt/debug 938737fb-b72f-4727-8833-9697c518ca37 100 GiB none off + oxp_f1693454-aac1-4265-b8a0-4e9f3f41c7b3/crypt/zone 35a43943-68f5-44f8-83a9-c69423b1ab04 none none off + oxp_f1693454-aac1-4265-b8a0-4e9f3f41c7b3/crypt/zone/oxz_crucible_a73f322a-9463-4d18-8f60-7ddf6f59f231 c4fa8f96-497e-47ad-9953-6ca3c9a90d25 none none off + oxp_fe4fdfba-3b6d-47d3-8612-1fb2390b650a/crucible fa262c74-8870-4038-8b71-b8dfa4adda12 none none off + oxp_fe4fdfba-3b6d-47d3-8612-1fb2390b650a/crypt/debug 8e58b91f-9ce2-4256-8dec-5f90f31a73fa 100 GiB none off + oxp_fe4fdfba-3b6d-47d3-8612-1fb2390b650a/crypt/zone 7ec7d64c-c0f8-48fc-905d-fdd9de487672 none none off + oxp_fe4fdfba-3b6d-47d3-8612-1fb2390b650a/crypt/zone/oxz_crucible_66ecd4a6-73a7-4e26-9711-17abdd67a66e db8c9c71-bad4-4a12-8ec7-4fe5d63b13ff none none off ++ oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_nexus_3ca5292f-8a59-4475-bb72-0f43714d0fff 871b35e6-d234-4a96-bab4-d07314bc6ba2 none none off ++ oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_nexus_99f6d544-8599-4e2b-a55a-82d9e0034662 8a39677a-fbcf-4884-b000-63be3247fb63 none none off ++ oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_nexus_c26b3bda-5561-44a1-a69f-22103fe209a1 c9c1a582-1fe0-4001-9301-97230387563a none none off + + omicron zones generation 2 -> 3: ------------------------------------------------------------------------------------------ zone type zone id disposition underlay IP diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt index e3d677d697..13a58a2955 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt @@ -21,6 +21,57 @@ to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 fake-vendor fake-model serial-f4d7f914-ec73-4b65-8696-5068591d9065 + datasets at generation 2: + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crucible 5c9ef84c-434c-4406-b68a-70e9af65b5a5 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/debug bf9b39db-5a6a-4b45-b2da-c37425271014 100 GiB none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone fc0b9a2c-4002-4fff-92f4-b541b7dd18c4 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_crucible_85b8c68a-160d-461d-94dd-1baf175fa75c 7b00d896-de30-48f8-bcb7-b140ffab2781 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_nexus_2ec75441-3d7d-4b4b-9614-af03de5a3666 cd15e9c9-0238-493a-8b32-926d1cd1bce6 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_nexus_508abd03-cbfe-4654-9a6d-7f15a1ad32e5 b781d032-3149-4c44-a7d3-5f8d80e4a607 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_nexus_59950bc8-1497-44dd-8cbf-b6502ba921b2 63ec1a21-2c77-41b5-ad3e-e7bf39207107 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_nexus_a732c489-d29a-4f75-b900-5966385943af db6c139b-9028-4d8e-92c7-6cc1e9aa0131 none none off + oxp_4069c804-c51a-4adc-8822-3cbbab56ed3f/crypt/zone/oxz_ntp_621509d6-3772-4009-aca1-35eefd1098fb 3b5822d2-9918-4bd6-8b75-2f52bdd73189 none none off + oxp_5248a306-4a03-449e-a8a3-6f86d26da755/crucible 182f7cbb-ea53-4057-b85c-667e5d949db5 none none off + oxp_5248a306-4a03-449e-a8a3-6f86d26da755/crypt/debug 1b4e8d9e-e447-4df1-8e0b-57edc318e8ad 100 GiB none off + oxp_5248a306-4a03-449e-a8a3-6f86d26da755/crypt/zone 5f097047-8290-438a-b85a-80bc9450b26c none none off + oxp_5248a306-4a03-449e-a8a3-6f86d26da755/crypt/zone/oxz_crucible_f0ff59e8-4105-4980-a4bb-a1f4c58de1e3 c596346d-4040-4103-b036-8fafdbaada00 none none off + oxp_55196665-ed61-4b23-9a74-0711bf2eaf90/crucible 931c4336-5ff9-45ce-b71b-9b6e81e16e53 none none off + oxp_55196665-ed61-4b23-9a74-0711bf2eaf90/crypt/debug ae4f4e83-0bd1-48dc-bfe3-f1082e8f357a 100 GiB none off + oxp_55196665-ed61-4b23-9a74-0711bf2eaf90/crypt/zone b19e4e13-79e3-481d-ad80-db076a26b7eb none none off + oxp_55196665-ed61-4b23-9a74-0711bf2eaf90/crypt/zone/oxz_crucible_23a8fa2b-ef3e-4017-a43f-f7a83953bd7c 3c7c5190-92dd-4d3b-9222-95d82a530d9e none none off + oxp_6b2a719a-35eb-469f-aa54-114a1f21f37d/crucible 11e2e73b-93a1-47f1-aff3-c4dc7667f4f1 none none off + oxp_6b2a719a-35eb-469f-aa54-114a1f21f37d/crypt/debug be833d4e-3439-41ac-b3ad-3e4d14a66360 100 GiB none off + oxp_6b2a719a-35eb-469f-aa54-114a1f21f37d/crypt/zone 7ab2c8a5-3135-4a51-a232-7af8c72b9d3c none none off + oxp_6b2a719a-35eb-469f-aa54-114a1f21f37d/crypt/zone/oxz_crucible_f1a7b9a7-fc6a-4b23-b829-045ff33117ff c864de0d-9859-4ad1-a30b-f5ac45ba03ed none none off + oxp_7ed4296a-66d1-4fb2-bc56-9b23b8f27d7e/crucible bdc3e42b-b28f-42c9-99fa-3f92e8b30a3c none none off + oxp_7ed4296a-66d1-4fb2-bc56-9b23b8f27d7e/crypt/debug a02b70bf-b069-4fec-9f53-1976ba462c45 100 GiB none off + oxp_7ed4296a-66d1-4fb2-bc56-9b23b8f27d7e/crypt/zone 4b69a9a9-2994-433c-9733-05de50d9c2a1 none none off + oxp_7ed4296a-66d1-4fb2-bc56-9b23b8f27d7e/crypt/zone/oxz_crucible_15c103f0-ac63-423b-ba5d-1b5fcd563ba3 02deac75-eb1a-423e-9b64-d20ca921fc25 none none off + oxp_984e2389-e7fd-4af9-ab02-e3caf77f95b5/crucible 5c956b09-323e-476c-9a71-352154b1f841 none none off + oxp_984e2389-e7fd-4af9-ab02-e3caf77f95b5/crypt/debug 5c79ad9d-1aef-407d-804c-ace1d0e069a4 100 GiB none off + oxp_984e2389-e7fd-4af9-ab02-e3caf77f95b5/crypt/zone b27f6972-47f0-43e9-a7cb-3a2fcd93798b none none off + oxp_984e2389-e7fd-4af9-ab02-e3caf77f95b5/crypt/zone/oxz_crucible_95482c25-1e7f-43e8-adf1-e3548a1b3ae0 5c5d822f-d696-4b6a-a075-286c437deba1 none none off + oxp_a5f75431-3795-426c-8f80-176f658281a5/crucible 7b4f5c33-6a5a-42f9-8199-69f72761b24d none none off + oxp_a5f75431-3795-426c-8f80-176f658281a5/crypt/debug dbc27904-c6dd-4cdc-96c8-32a24bdba0a1 100 GiB none off + oxp_a5f75431-3795-426c-8f80-176f658281a5/crypt/zone 4d913be5-fa91-4b22-b714-53babe093654 none none off + oxp_a5f75431-3795-426c-8f80-176f658281a5/crypt/zone/oxz_crucible_3aa07966-5899-4789-ace5-f8eeb375c6c3 23ff87ad-3d52-4c7e-a9a3-be288d08835c none none off + oxp_cf32a1ce-2c9e-49f5-b1cf-4af7f2a28901/crucible 77279948-9fe2-46f2-af39-0d5b692f5984 none none off + oxp_cf32a1ce-2c9e-49f5-b1cf-4af7f2a28901/crypt/debug 2dfc5c53-6618-4352-b754-86ef6463c20a 100 GiB none off + oxp_cf32a1ce-2c9e-49f5-b1cf-4af7f2a28901/crypt/zone e79fbff1-f7fb-4912-9f5c-faca57a6a9c4 none none off + oxp_cf32a1ce-2c9e-49f5-b1cf-4af7f2a28901/crypt/zone/oxz_crucible_c60379ba-4e30-4628-a79a-0ae509aef4c5 a6fcf496-70a1-49bf-a951-62fcec8dd5e2 none none off + oxp_e405da11-cb6b-4ebc-bac1-9bc997352e10/crucible 0f6d5b5f-674d-465e-9b40-d09c6865416a none none off + oxp_e405da11-cb6b-4ebc-bac1-9bc997352e10/crypt/debug 61a653cf-44a6-43c0-90e1-bec539511703 100 GiB none off + oxp_e405da11-cb6b-4ebc-bac1-9bc997352e10/crypt/zone ffc0cb27-54e5-4d28-8f3d-5f7730fed34a none none off + oxp_e405da11-cb6b-4ebc-bac1-9bc997352e10/crypt/zone/oxz_crucible_72c5a909-077d-4ec1-a9d5-ae64ef9d716e 4f3e0a2b-43df-43f1-9244-a67fe65a4856 none none off + oxp_f4d7f914-ec73-4b65-8696-5068591d9065/crucible e7ca75fe-d59c-4324-9053-a6a1566958e2 none none off + oxp_f4d7f914-ec73-4b65-8696-5068591d9065/crypt/debug b803d901-7e43-42fa-8372-43c3c5b3c1a9 100 GiB none off + oxp_f4d7f914-ec73-4b65-8696-5068591d9065/crypt/zone ccf69d3f-87be-4f8c-9191-9dde56547b21 none none off + oxp_f4d7f914-ec73-4b65-8696-5068591d9065/crypt/zone/oxz_crucible_0dfbf374-9ef9-430f-b06d-f271bf7f84c4 f72e1f0d-0acd-4cde-9acd-f25663592558 none none off + + omicron zones at generation 3: ------------------------------------------------------------------------------------------ zone type zone id disposition underlay IP @@ -60,6 +111,57 @@ to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 fake-vendor fake-model serial-fe4fdfba-3b6d-47d3-8612-1fb2390b650a + datasets at generation 2: + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crucible cefe1de4-bebb-4ac1-8871-09585baf593d none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/debug 755e24a8-67cc-44b1-8c25-2dcb3acd988f 100 GiB none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone 29246ffd-11d0-4afd-8324-4727380185a3 none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_crucible_a1c03689-fc62-4ea5-bb72-4d01f5138614 951bc0b6-8136-4ec3-870b-ffaa4d2ff2f9 none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_nexus_3ca5292f-8a59-4475-bb72-0f43714d0fff 871b35e6-d234-4a96-bab4-d07314bc6ba2 none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_nexus_4ad0e9da-08f8-4d40-b4d3-d17e711b5bbf 45d32c13-cbbb-4382-a0ed-dc6574b827b7 none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_nexus_99f6d544-8599-4e2b-a55a-82d9e0034662 8a39677a-fbcf-4884-b000-63be3247fb63 none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_nexus_c26b3bda-5561-44a1-a69f-22103fe209a1 c9c1a582-1fe0-4001-9301-97230387563a none none off + oxp_33d48d85-751e-4982-b738-eae4d9a05f01/crypt/zone/oxz_ntp_bf79a56a-97af-4cc4-94a5-8b20d64c2cda a410308c-e2cb-4e4d-9da6-1879336f93f2 none none off + oxp_39ca2e23-4c38-4743-afe0-26b0380b27db/crucible 3bf64d72-e015-4377-b02a-a094f8f96d57 none none off + oxp_39ca2e23-4c38-4743-afe0-26b0380b27db/crypt/debug c834f8cd-25ee-4c62-af03-49cef53fc4c1 100 GiB none off + oxp_39ca2e23-4c38-4743-afe0-26b0380b27db/crypt/zone 212dad15-885a-45f6-b7ea-5e2ed8d9186f none none off + oxp_39ca2e23-4c38-4743-afe0-26b0380b27db/crypt/zone/oxz_crucible_d47f4996-fac0-4657-bcea-01b1fee6404d c1af262a-2595-4236-98c8-21c5b63c80c3 none none off + oxp_4fbd2fe0-2eac-41b8-8e8d-4fa46c3e8b6c/crucible c35cac80-eb26-4d90-ac3e-879b9d80c04c none none off + oxp_4fbd2fe0-2eac-41b8-8e8d-4fa46c3e8b6c/crypt/debug 5a288f52-a84e-45a6-873a-3d9c81d67380 100 GiB none off + oxp_4fbd2fe0-2eac-41b8-8e8d-4fa46c3e8b6c/crypt/zone e1e73d4f-0138-42dc-9e10-3432b26098f4 none none off + oxp_4fbd2fe0-2eac-41b8-8e8d-4fa46c3e8b6c/crypt/zone/oxz_crucible_a568e92e-4fbd-4b69-acd8-f16277073031 b8868d0c-960d-4ae4-b340-2c5970c8d530 none none off + oxp_60131a33-1f12-4dbb-9435-bdd368db1f51/crucible 30d7609e-1c74-4002-8558-b62246ee9600 none none off + oxp_60131a33-1f12-4dbb-9435-bdd368db1f51/crypt/debug 078134ea-f776-4283-ae17-116869f304b4 100 GiB none off + oxp_60131a33-1f12-4dbb-9435-bdd368db1f51/crypt/zone 48e191ca-1498-4381-b2dd-675b022d9a86 none none off + oxp_60131a33-1f12-4dbb-9435-bdd368db1f51/crypt/zone/oxz_crucible_772cbcbd-58be-4158-be85-be744871fa22 a7228ebc-840f-4393-94d7-b338dab3d459 none none off + oxp_77e45b5b-869f-4e78-8ce3-28bbe8cf37e9/crucible eff3d661-6ab1-4966-ab0a-6f94fda0bd39 none none off + oxp_77e45b5b-869f-4e78-8ce3-28bbe8cf37e9/crypt/debug 60a98875-ee39-49d8-b4b8-1f5d168e39e2 100 GiB none off + oxp_77e45b5b-869f-4e78-8ce3-28bbe8cf37e9/crypt/zone 1ce1d218-9f8d-4c13-a1bf-e085c569a4b8 none none off + oxp_77e45b5b-869f-4e78-8ce3-28bbe8cf37e9/crypt/zone/oxz_crucible_be75764a-491b-4aec-992e-1c39e25de975 09d557a8-4a28-4434-bcca-8fa593cc2fec none none off + oxp_789d607d-d196-428e-a988-f7886a327859/crucible 7253d7b0-8954-41f0-b788-9efc41f42742 none none off + oxp_789d607d-d196-428e-a988-f7886a327859/crypt/debug 1f7f6932-ed14-482a-816e-b0f76d96603d 100 GiB none off + oxp_789d607d-d196-428e-a988-f7886a327859/crypt/zone d3ae6bfa-bdfd-4a36-9009-4e061be33c0b none none off + oxp_789d607d-d196-428e-a988-f7886a327859/crypt/zone/oxz_crucible_e001fea0-6594-4ece-97e3-6198c293e931 5e27b9bc-e69f-4258-83f2-5f9a1109a625 none none off + oxp_b104b94c-2197-4e76-bfbd-6f966bd5af66/crucible 70b8bba8-802a-462a-9aa7-3dcd20a1fbbf none none off + oxp_b104b94c-2197-4e76-bfbd-6f966bd5af66/crypt/debug c2eba705-dd3c-49f1-9d7d-abb951cbc722 100 GiB none off + oxp_b104b94c-2197-4e76-bfbd-6f966bd5af66/crypt/zone 474256d1-bab1-499a-9679-566accf12f3f none none off + oxp_b104b94c-2197-4e76-bfbd-6f966bd5af66/crypt/zone/oxz_crucible_414830dc-c8c1-4748-9e9e-bc3a6435a93c a9351df9-851e-4c85-a7f2-74490471e876 none none off + oxp_cd62306a-aedf-47e8-93d5-92a358d64c7b/crucible 3acdbfe5-1226-4752-a14e-9f1e468ade71 none none off + oxp_cd62306a-aedf-47e8-93d5-92a358d64c7b/crypt/debug 73674f4b-1d93-404a-bc9c-8395efac97fd 100 GiB none off + oxp_cd62306a-aedf-47e8-93d5-92a358d64c7b/crypt/zone 6e415c23-e85d-4f5b-b993-43a8f1c56763 none none off + oxp_cd62306a-aedf-47e8-93d5-92a358d64c7b/crypt/zone/oxz_crucible_be920398-024a-4655-8c49-69b5ac48dfff 87f757d6-fa4c-4423-995c-1eab5e7d09a2 none none off + oxp_f1693454-aac1-4265-b8a0-4e9f3f41c7b3/crucible f45ef5d4-14b2-4346-8297-20919d6ee9da none none off + oxp_f1693454-aac1-4265-b8a0-4e9f3f41c7b3/crypt/debug 938737fb-b72f-4727-8833-9697c518ca37 100 GiB none off + oxp_f1693454-aac1-4265-b8a0-4e9f3f41c7b3/crypt/zone 35a43943-68f5-44f8-83a9-c69423b1ab04 none none off + oxp_f1693454-aac1-4265-b8a0-4e9f3f41c7b3/crypt/zone/oxz_crucible_a73f322a-9463-4d18-8f60-7ddf6f59f231 c4fa8f96-497e-47ad-9953-6ca3c9a90d25 none none off + oxp_fe4fdfba-3b6d-47d3-8612-1fb2390b650a/crucible fa262c74-8870-4038-8b71-b8dfa4adda12 none none off + oxp_fe4fdfba-3b6d-47d3-8612-1fb2390b650a/crypt/debug 8e58b91f-9ce2-4256-8dec-5f90f31a73fa 100 GiB none off + oxp_fe4fdfba-3b6d-47d3-8612-1fb2390b650a/crypt/zone 7ec7d64c-c0f8-48fc-905d-fdd9de487672 none none off + oxp_fe4fdfba-3b6d-47d3-8612-1fb2390b650a/crypt/zone/oxz_crucible_66ecd4a6-73a7-4e26-9711-17abdd67a66e db8c9c71-bad4-4a12-8ec7-4fe5d63b13ff none none off + + omicron zones at generation 3: ------------------------------------------------------------------------------------------ zone type zone id disposition underlay IP @@ -125,6 +227,59 @@ to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 fake-vendor fake-model serial-d1ebfd7b-3842-4ad7-be31-2b9c031209a9 + datasets at generation 1: + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset uuid quota reservation compression + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crucible 79a16369-70ab-4f63-af6d-1c7f088eeee3 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/clickhouse d577ac11-62ac-4a71-bed7-e7327148bd33 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/debug d6d36f6b-35ff-4766-99de-44abe46932d1 100 GiB none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/internal_dns b9aa1175-2640-4923-81b3-1e1469b15abf none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone 18e2a336-9b65-421e-8409-04d9abce8cd6 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_clickhouse_93b137a1-a1d6-4b5b-b2cb-21a9f11e2883 d1a755ac-dafc-4087-a0ce-ee8b3f882ac1 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_crucible_19fbc4f8-a683-4f22-8f5a-e74782b935be 68724637-d228-4faa-a19f-b5df857ff4ab none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_crucible_pantry_9f0abbad-dbd3-4d43-9675-78092217ffd9 da62be58-643f-4497-a9d7-e259de6c7a12 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_internal_dns_c406da50-34b9-4bb4-a460-8f49875d2a6a f47f67f6-e315-4272-b93a-b467cfdfbb7a none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_nexus_6dff7633-66bb-4924-a6ff-2c896e66964b 9dce1e52-8e3b-4641-9982-69deb049f647 none none off + oxp_13e6503b-5300-4ccd-abc4-c1512b435929/crypt/zone/oxz_ntp_7f4e9f9f-08f8-4d14-885d-e977c05525ad 7773ffbd-1842-4425-aff5-88f577cd8955 none none off + oxp_44cdb6f2-fa6c-4b69-bab2-3ae4e1ec4b34/crucible ac31a04c-1f3a-43f4-b45a-cf4b5176e7f6 none none off + oxp_44cdb6f2-fa6c-4b69-bab2-3ae4e1ec4b34/crypt/debug 26154290-91f7-4f35-bcd0-ec7a8f398d82 100 GiB none off + oxp_44cdb6f2-fa6c-4b69-bab2-3ae4e1ec4b34/crypt/zone c37902cf-d1fe-4370-baef-b7de8731dffe none none off + oxp_44cdb6f2-fa6c-4b69-bab2-3ae4e1ec4b34/crypt/zone/oxz_crucible_6b53ab2e-d98c-485f-87a3-4d5df595390f 51229ad8-cda3-40c4-b0f3-b1af669abcd4 none none off + oxp_4de5fc8e-0e41-4ab9-ba12-2dc63882c96a/crucible b92bba84-9437-4f93-8ad0-80bf7ecdae5f none none off + oxp_4de5fc8e-0e41-4ab9-ba12-2dc63882c96a/crypt/debug 6dc5df73-7e44-4065-9f14-70a0d965a853 100 GiB none off + oxp_4de5fc8e-0e41-4ab9-ba12-2dc63882c96a/crypt/zone 00c2d1d6-94da-4985-a144-2194588afecf none none off + oxp_4de5fc8e-0e41-4ab9-ba12-2dc63882c96a/crypt/zone/oxz_crucible_b0c63f48-01ea-4aae-bb26-fb0dd59d1662 5d8788c0-13df-4c07-a8b2-b9365fec9796 none none off + oxp_51564e7a-d69f-4942-bcfe-330224633ca6/crucible dedbf4e7-32fb-406e-a5cc-a2f5281aa6d8 none none off + oxp_51564e7a-d69f-4942-bcfe-330224633ca6/crypt/debug 3063bdcc-4363-495b-a81f-67c6cc437a75 100 GiB none off + oxp_51564e7a-d69f-4942-bcfe-330224633ca6/crypt/zone 3527b52f-f1d4-4e2f-b41e-559f58657839 none none off + oxp_51564e7a-d69f-4942-bcfe-330224633ca6/crypt/zone/oxz_crucible_f55e6aaf-e8fc-4913-9e3c-8cd1bd4bdad3 82fc150e-d6f5-4bb6-8eee-17a0728aadb5 none none off + oxp_5ca23cb3-cc90-41c5-a474-01898cdd0796/crucible b2c9f282-e767-4df1-941e-b7cea21b1a02 none none off + oxp_5ca23cb3-cc90-41c5-a474-01898cdd0796/crypt/debug 53deebeb-3952-483a-afd2-2202cee9c33b 100 GiB none off + oxp_5ca23cb3-cc90-41c5-a474-01898cdd0796/crypt/zone 22581703-157c-474f-bbf3-34f076dd7bca none none off + oxp_5ca23cb3-cc90-41c5-a474-01898cdd0796/crypt/zone/oxz_crucible_d660d7ed-28c0-45ae-9ace-dc3ecf7e8786 68217627-4519-4cfa-85b9-25efb4dad71a none none off + oxp_6a23a532-0712-4a8d-be9b-e8c17e97aa4b/crucible 47cf951d-ca5c-4ae0-baa3-94472efbcf03 none none off + oxp_6a23a532-0712-4a8d-be9b-e8c17e97aa4b/crypt/debug e315743d-53e3-4505-9364-657c2486a1bb 100 GiB none off + oxp_6a23a532-0712-4a8d-be9b-e8c17e97aa4b/crypt/zone 4d6e70b1-ff91-4133-b65f-6fcd5bda2568 none none off + oxp_6a23a532-0712-4a8d-be9b-e8c17e97aa4b/crypt/zone/oxz_crucible_e98cc0de-abf6-4da4-a20d-d05c7a9bb1d7 a602c9d4-16a9-4e55-b5de-76fb33cc4ca9 none none off + oxp_6f1a330e-e8d4-4c09-97fc-8918b69b2a3c/crucible 9390bde4-2073-44b4-a496-5129c7e5ca40 none none off + oxp_6f1a330e-e8d4-4c09-97fc-8918b69b2a3c/crypt/debug 2e901ffa-895b-4405-b59a-2bcdfabda681 100 GiB none off + oxp_6f1a330e-e8d4-4c09-97fc-8918b69b2a3c/crypt/zone 41e55e8a-ca4b-4c27-8337-57d42cc36fb5 none none off + oxp_6f1a330e-e8d4-4c09-97fc-8918b69b2a3c/crypt/zone/oxz_crucible_4f1ce8a2-d3a5-4a38-be4c-9817de52db37 ef14767b-6be7-44cb-833d-27d9f523dcdb none none off + oxp_7113d104-fb55-4299-bf53-b3c59d258e44/crucible 5ffb2b84-4a3f-444a-aebf-381f8c58b7ae none none off + oxp_7113d104-fb55-4299-bf53-b3c59d258e44/crypt/debug ee20f8cb-7d24-422b-9dff-ca4c9596191f 100 GiB none off + oxp_7113d104-fb55-4299-bf53-b3c59d258e44/crypt/zone ae6c87ea-dc50-4a15-855a-224300f20c74 none none off + oxp_7113d104-fb55-4299-bf53-b3c59d258e44/crypt/zone/oxz_crucible_67d913e0-0005-4599-9b28-0abbf6cc2916 d63be5b2-5471-4f4d-accc-82e2a0d7276f none none off + oxp_8c10be49-3a66-40d4-a082-64d09d916f14/crucible d72ffc3a-4665-40bc-a099-ebe628622636 none none off + oxp_8c10be49-3a66-40d4-a082-64d09d916f14/crypt/debug 84bc6d3f-2d9c-42b3-9982-7394630a0928 100 GiB none off + oxp_8c10be49-3a66-40d4-a082-64d09d916f14/crypt/zone 3fc37e5a-3bf3-476f-8a8f-6d32a533cfc0 none none off + oxp_8c10be49-3a66-40d4-a082-64d09d916f14/crypt/zone/oxz_crucible_2aa0ea4f-3561-4989-a98c-9ab7d9a240fb 05a77359-0a07-4976-8fbb-5f1eee688e47 none none off + oxp_d1ebfd7b-3842-4ad7-be31-2b9c031209a9/crucible 39185a78-064c-49d5-b716-93f1a312c0c1 none none off + oxp_d1ebfd7b-3842-4ad7-be31-2b9c031209a9/crypt/debug 0c11d380-038c-4b68-b9f4-1775f6fec1e8 100 GiB none off + oxp_d1ebfd7b-3842-4ad7-be31-2b9c031209a9/crypt/zone 64a64a7b-0d29-4470-bc96-4796ca357507 none none off + oxp_d1ebfd7b-3842-4ad7-be31-2b9c031209a9/crypt/zone/oxz_crucible_67622d61-2df4-414d-aa0e-d1277265f405 6dbd0e11-89af-4464-bd84-c20cb818a9d5 none none off + + omicron zones at generation 2: ---------------------------------------------------------------------------------------------- zone type zone id disposition underlay IP @@ -179,11 +334,11 @@ mismatched zone type: after: Nexus( Nexus { internal_address: [fd01:1122:3344:105::22]:12221, external_ip: OmicronZoneExternalFloatingIp { - id: cd63774a-2e2f-49ce-a3df-33e3b5d02650 (external_ip), + id: a6c185b0-b470-4631-b6c8-373349430abb (external_ip), ip: 192.0.2.2, }, nic: NetworkInterface { - id: 99402426-92dd-4975-9347-907e130d6b79, + id: 4e187c77-f123-4738-80d5-410b6a416438, kind: Service { id: 6dff7633-66bb-4924-a6ff-2c896e66964b, }, diff --git a/nexus/src/app/background/tasks/inventory_collection.rs b/nexus/src/app/background/tasks/inventory_collection.rs index c4271c58d8..0b361b2014 100644 --- a/nexus/src/app/background/tasks/inventory_collection.rs +++ b/nexus/src/app/background/tasks/inventory_collection.rs @@ -9,6 +9,7 @@ use anyhow::ensure; use anyhow::Context; use futures::future::BoxFuture; use futures::FutureExt; +use internal_dns_resolver::ResolveError; use internal_dns_types::names::ServiceName; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; @@ -132,19 +133,55 @@ async fn inventory_activate( }) .collect::>(); - // Find clickhouse-admin-keeper clients - let keeper_admin_clients = resolver - .lookup_socket_v6(ServiceName::ClickhouseAdminKeeper) + // Find clickhouse-admin-keeper servers if there are any. + let keeper_admin_clients = match resolver + .lookup_all_socket_v6(ServiceName::ClickhouseAdminKeeper) .await - .context("looking up ClickhouseAdminKeeper addresses") - .into_iter() - .map(|sockaddr| { - let url = format!("http://{}", sockaddr); - let log = - opctx.log.new(o!("clickhouse_admin_keeper_url" => url.clone())); - clickhouse_admin_keeper_client::Client::new(&url, log) - }) - .collect::>(); + { + Ok(sockaddrs) => sockaddrs + .into_iter() + .map(|sockaddr| { + let url = format!("http://{}", sockaddr); + let log = opctx + .log + .new(o!("clickhouse_admin_keeper_url" => url.clone())); + clickhouse_admin_keeper_client::Client::new(&url, log) + }) + .collect::>(), + Err(err) => match err { + // When DNS resolution fails because no clickhouse-keeper-admin + // servers have been found, we allow this and move on. This is + // because multi-node clickhouse may not be enabled, and therefore + // there will not be any clickhouse-keeper-admin servers to find. + // + // In the long term, we expect multi-node clickhouse to always + // be enabled, and therefore we may want to bubble up any error + // we find, including `NotFound`. However, since we must enable + // multi-node clickhouse via reconfigurator, and not RSS, we may + // find ourselves with a small gap early on where the names don't + // yet exist. This would block the rest of inventory collection if + // we early return. We may be able to resolve this problem at rack + // handoff time, but it's worth considering whether we want to error + // here in case a gap remains. + // + // See https://github.com/oxidecomputer/omicron/issues/7005 + ResolveError::NotFound(_) | ResolveError::NotFoundByString(_) => { + vec![] + } + ResolveError::Resolve(hickory_err) + if matches!( + hickory_err.kind(), + hickory_resolver::error::ResolveErrorKind::NoRecordsFound { .. } + ) => + { + vec![] + } + _ => { + return Err(err) + .context("looking up clickhouse-admin-keeper addresses"); + } + }, + }; // Create an enumerator to find sled agents. let sled_enum = DbSledAgentEnumerator { opctx, datastore }; diff --git a/nexus/src/app/background/tasks/networking.rs b/nexus/src/app/background/tasks/networking.rs index 95005d07cd..ed27409c9b 100644 --- a/nexus/src/app/background/tasks/networking.rs +++ b/nexus/src/app/background/tasks/networking.rs @@ -4,7 +4,7 @@ use db::datastore::SwitchPortSettingsCombinedResult; use dpd_client::types::{ - LinkCreate, LinkId, LinkSettings, PortFec, PortSettings, PortSpeed, + LinkCreate, LinkId, LinkSettings, PortFec, PortSettings, PortSpeed, TxEq, }; use nexus_db_model::{SwitchLinkFec, SwitchLinkSpeed}; use nexus_db_queries::db; @@ -63,7 +63,17 @@ pub(crate) fn api_to_dpd_port_settings( //TODO breakouts let link_id = LinkId(0); - + let tx_eq = if let Some(Some(t)) = settings.tx_eq.get(0) { + Some(TxEq { + pre1: t.pre1.map(Into::into), + pre2: t.pre2.map(Into::into), + main: t.main.map(Into::into), + post2: t.post2.map(Into::into), + post1: t.post2.map(Into::into), + }) + } else { + None + }; for l in settings.links.iter() { dpd_port_settings.links.insert( link_id.to_string(), @@ -72,6 +82,7 @@ pub(crate) fn api_to_dpd_port_settings( autoneg: l.autoneg, lane: Some(LinkId(0)), kr: false, + tx_eq: tx_eq.clone(), fec: match l.fec { SwitchLinkFec::Firecode => PortFec::Firecode, SwitchLinkFec::Rs => PortFec::Rs, diff --git a/nexus/src/app/background/tasks/sync_switch_configuration.rs b/nexus/src/app/background/tasks/sync_switch_configuration.rs index 49dbfb2e52..ceacae7645 100644 --- a/nexus/src/app/background/tasks/sync_switch_configuration.rs +++ b/nexus/src/app/background/tasks/sync_switch_configuration.rs @@ -53,7 +53,7 @@ use sled_agent_client::types::{ BgpConfig as SledBgpConfig, BgpPeerConfig as SledBgpPeerConfig, EarlyNetworkConfig, EarlyNetworkConfigBody, HostPortConfig, LldpAdminStatus, LldpPortConfig, PortConfigV2, RackNetworkConfigV2, - RouteConfig as SledRouteConfig, UplinkAddressConfig, + RouteConfig as SledRouteConfig, TxEqConfig, UplinkAddressConfig, }; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, @@ -935,6 +935,19 @@ impl BackgroundTask for SwitchPortSettingsManager { }, }; + // TODO https://github.com/oxidecomputer/omicron/issues/3062 + let tx_eq = if let Some(Some(c)) = info.tx_eq.get(0) { + Some(TxEqConfig { + pre1: c.pre1.map(Into::into), + pre2: c.pre2.map(Into::into), + main: c.main.map(Into::into), + post2: c.post2.map(Into::into), + post1: c.post1.map(Into::into), + }) + } else { + None + }; + let mut port_config = PortConfigV2 { addresses: info.addresses.iter().map(|a| UplinkAddressConfig { @@ -1012,7 +1025,8 @@ impl BackgroundTask for SwitchPortSettingsManager { system_name: c.system_name.clone(), system_description: c.system_description.clone(), management_addrs:c.management_ip.map(|a| vec![a.ip()]), - }) + }), + tx_eq, } ; @@ -1456,6 +1470,18 @@ fn uplinks( }) }; + let tx_eq = if let Some(Some(c)) = config.tx_eq.get(0) { + Some(TxEqConfig { + pre1: c.pre1, + pre2: c.pre2, + main: c.main, + post2: c.post2, + post1: c.post1, + }) + } else { + None + }; + let config = HostPortConfig { port: port.port_name.clone(), addrs: config @@ -1467,6 +1493,7 @@ fn uplinks( }) .collect(), lldp, + tx_eq, }; match uplinks.entry(*location) { diff --git a/nexus/src/app/metrics.rs b/nexus/src/app/metrics.rs index ba76f87392..40f7882281 100644 --- a/nexus/src/app/metrics.rs +++ b/nexus/src/app/metrics.rs @@ -115,7 +115,8 @@ impl super::Nexus { .timeseries_schema_list(&pagination.page, limit) .await .map_err(|e| match e { - oximeter_db::Error::DatabaseUnavailable(_) => { + oximeter_db::Error::DatabaseUnavailable(_) + | oximeter_db::Error::Connection(_) => { Error::ServiceUnavailable { internal_message: e.to_string(), } @@ -150,7 +151,8 @@ impl super::Nexus { result.tables }) .map_err(|e| match e { - oximeter_db::Error::DatabaseUnavailable(_) => { + oximeter_db::Error::DatabaseUnavailable(_) + | oximeter_db::Error::Connection(_) => { Error::ServiceUnavailable { internal_message: e.to_string(), } diff --git a/nexus/src/app/mod.rs b/nexus/src/app/mod.rs index fa9c2c69cf..e451119bfc 100644 --- a/nexus/src/app/mod.rs +++ b/nexus/src/app/mod.rs @@ -24,6 +24,7 @@ use nexus_db_queries::authn; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; +use omicron_common::address::CLICKHOUSE_HTTP_PORT; use omicron_common::address::CLICKHOUSE_TCP_PORT; use omicron_common::address::DENDRITE_PORT; use omicron_common::address::MGD_PORT; @@ -411,13 +412,12 @@ impl Nexus { .map_err(|e| e.to_string())?; // Client to the ClickHouse database. - let timeseries_client = - if let Some(http_address) = &config.pkg.timeseries_db.address { - let native_address = - SocketAddr::new(http_address.ip(), CLICKHOUSE_TCP_PORT); - oximeter_db::Client::new(*http_address, native_address, &log) - } else { - // TODO-cleanup: Remove this when we remove the HTTP client. + // TODO-cleanup: Simplify this when we remove the HTTP client. + let timeseries_client = match ( + &config.pkg.timeseries_db.address, + &config.pkg.timeseries_db.native_address, + ) { + (None, None) => { let http_resolver = qorb_resolver.for_service(ServiceName::Clickhouse); let native_resolver = @@ -427,7 +427,24 @@ impl Nexus { native_resolver, &log, ) - }; + } + (maybe_http, maybe_native) => { + let (http_address, native_address) = + match (maybe_http, maybe_native) { + (None, None) => unreachable!("handled above"), + (None, Some(native)) => ( + SocketAddr::new(native.ip(), CLICKHOUSE_HTTP_PORT), + *native, + ), + (Some(http), None) => ( + *http, + SocketAddr::new(http.ip(), CLICKHOUSE_TCP_PORT), + ), + (Some(http), Some(native)) => (*http, *native), + }; + oximeter_db::Client::new(http_address, native_address, &log) + } + }; // TODO-cleanup We may want to make the populator a first-class // background task. diff --git a/nexus/src/app/oximeter.rs b/nexus/src/app/oximeter.rs index 6a4a81a47a..708b0828c9 100644 --- a/nexus/src/app/oximeter.rs +++ b/nexus/src/app/oximeter.rs @@ -241,7 +241,8 @@ pub(crate) async fn unassign_producer( fn map_oximeter_err(error: oximeter_db::Error) -> Error { match error { - oximeter_db::Error::DatabaseUnavailable(_) => { + oximeter_db::Error::DatabaseUnavailable(_) + | oximeter_db::Error::Connection(_) => { Error::ServiceUnavailable { internal_message: error.to_string() } } _ => Error::InternalError { internal_message: error.to_string() }, diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index 9a361d564d..a6e38e8dcf 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -650,12 +650,15 @@ impl super::Nexus { }, }, }; + let link = LinkConfigCreate { - mtu: 1500, //TODO https://github.com/oxidecomputer/omicron/issues/2274 + //TODO https://github.com/oxidecomputer/omicron/issues/2274 + mtu: 1500, fec: uplink_config.uplink_port_fec.into(), speed: uplink_config.uplink_port_speed.into(), autoneg: uplink_config.autoneg, lldp, + tx_eq: uplink_config.tx_eq.map(|t| t.into()), }; port_settings_params.links.insert("phy".to_string(), link); diff --git a/nexus/src/app/sagas/common_storage/pantry_pool.rs b/nexus/src/app/sagas/common_storage/pantry_pool.rs index 9d1e76d27d..ae0ee3ce2a 100644 --- a/nexus/src/app/sagas/common_storage/pantry_pool.rs +++ b/nexus/src/app/sagas/common_storage/pantry_pool.rs @@ -84,9 +84,12 @@ impl backend::Connector for PantryConnector { pub(crate) fn make_pantry_connection_pool( qorb_resolver: &QorbResolver, ) -> pool::Pool { - pool::Pool::new( + match pool::Pool::new( qorb_resolver.for_service(ServiceName::CruciblePantry), Arc::new(PantryConnector), qorb::policy::Policy::default(), - ) + ) { + Ok(pool) => pool, + Err(e) => e.into_inner(), + } } diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index 22fa61685d..0c5df8c844 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -486,6 +486,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { let dataset_id = Uuid::new_v4(); let http_address = clickhouse.http_address(); let http_port = http_address.port(); + let native_address = clickhouse.native_address(); self.rack_init_builder.add_clickhouse_dataset( zpool_id, dataset_id, @@ -504,6 +505,8 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { .as_mut() .expect("Tests expect to set a port of Clickhouse") .set_port(http_port); + self.config.pkg.timeseries_db.native_address = + Some(native_address.into()); let pool_name = illumos_utils::zpool::ZpoolName::new_external(zpool_id) .to_string() diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index 6500d7249a..9ce686db56 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -130,6 +130,7 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { fec: LinkFec::None, speed: LinkSpeed::Speed100G, autoneg: false, + tx_eq: None, }, ); // interfaces diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index 884f80f15e..1c23041f91 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -289,6 +289,11 @@ impl Blueprint { .iter() .map(|(sled_id, disks)| (*sled_id, disks.clone().into())) .collect(), + before + .blueprint_datasets + .iter() + .map(|(sled_id, datasets)| (*sled_id, datasets.clone().into())) + .collect(), &self, ) } @@ -334,12 +339,31 @@ impl Blueprint { }) .collect(); + let before_datasets = before + .sled_agents + .iter() + .map(|(sled_id, sa)| { + ( + *sled_id, + CollectionDatasetsConfig { + datasets: sa + .datasets + .iter() + .map(|d| (CollectionDatasetIdentifier::from(d), d.clone().into())) + .collect::>(), + } + .into(), + ) + }) + .collect(); + BlueprintDiff::new( DiffBeforeMetadata::Collection { id: before.id }, DiffBeforeClickhouseClusterConfig::from(before), before_state, before_zones, before_disks, + before_datasets, &self, ) } @@ -984,6 +1008,59 @@ impl From for DatasetConfig { } } +/// Information about a dataset as used for diffing collections and blueprints. +/// +/// This struct acts as a "lowest common denominator" between the +/// inventory and blueprint types, for the purposes of comparison. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct BlueprintDatasetConfigForDiff { + pub name: String, + pub id: Option, + pub quota: Option, + pub reservation: Option, + pub compression: String, +} + +fn unwrap_or_none(opt: &Option) -> String { + opt.as_ref().map(|v| v.to_string()).unwrap_or_else(|| "none".to_string()) +} + +impl BlueprintDatasetConfigForDiff { + fn as_strings(&self) -> Vec { + vec![ + self.name.clone(), + unwrap_or_none(&self.id), + unwrap_or_none(&self.quota), + unwrap_or_none(&self.reservation), + self.compression.clone(), + ] + } +} + +impl From for BlueprintDatasetConfigForDiff { + fn from(dataset: crate::inventory::Dataset) -> Self { + Self { + name: dataset.name, + id: dataset.id, + quota: dataset.quota, + reservation: dataset.reservation, + compression: dataset.compression, + } + } +} + +impl From for BlueprintDatasetConfigForDiff { + fn from(dataset: BlueprintDatasetConfig) -> Self { + Self { + name: DatasetName::new(dataset.pool, dataset.kind).full_name(), + id: Some(dataset.id), + quota: dataset.quota, + reservation: dataset.reservation, + compression: dataset.compression.to_string(), + } + } +} + /// Describe high-level metadata about a blueprint // These fields are a subset of [`Blueprint`], and include only the data we can // quickly fetch from the main blueprint table (e.g., when listing all @@ -1271,6 +1348,77 @@ pub struct CollectionPhysicalDisksConfig { disks: BTreeSet, } +/// Single sled's datasets config for "before" version within a [`BlueprintDiff`]. +#[derive(Clone, Debug, From)] +pub enum BlueprintOrCollectionDatasetsConfig { + /// The diff was made from a collection. + Collection(CollectionDatasetsConfig), + /// The diff was made from a blueprint. + Blueprint(BlueprintDatasetsConfig), +} + +impl BlueprintOrCollectionDatasetsConfig { + pub fn generation(&self) -> Option { + match self { + BlueprintOrCollectionDatasetsConfig::Collection(_) => None, + BlueprintOrCollectionDatasetsConfig::Blueprint(c) => { + Some(c.generation) + } + } + } + + pub fn datasets( + &self, + ) -> BTreeMap + { + match self { + BlueprintOrCollectionDatasetsConfig::Collection(c) => { + c.datasets.clone() + } + BlueprintOrCollectionDatasetsConfig::Blueprint(c) => c + .datasets + .values() + .map(|d| { + (CollectionDatasetIdentifier::from(d), d.clone().into()) + }) + .collect(), + } + } +} + +/// A unique identifier for a dataset within a collection. +/// +/// If a UUID is known for the dataset, it should be used. +/// However, some datasets exist without UUIDs, and should still +/// be reported by the inventory collection subsystem. +#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] +pub struct CollectionDatasetIdentifier { + name: String, + id: Option, +} + +impl From<&BlueprintDatasetConfig> for CollectionDatasetIdentifier { + fn from(d: &BlueprintDatasetConfig) -> Self { + Self { + id: Some(d.id), + name: DatasetName::new(d.pool.clone(), d.kind.clone()).full_name(), + } + } +} + +impl From<&crate::inventory::Dataset> for CollectionDatasetIdentifier { + fn from(d: &crate::inventory::Dataset) -> Self { + Self { id: d.id, name: d.name.clone() } + } +} + +/// Single sled's dataset config for "before" version within a [`BlueprintDiff`]. +#[derive(Clone, Debug, From)] +pub struct CollectionDatasetsConfig { + datasets: + BTreeMap, +} + /// Encapsulates Reconfigurator state /// /// This serialized from is intended for saving state from hand-constructed or diff --git a/nexus/types/src/deployment/blueprint_diff.rs b/nexus/types/src/deployment/blueprint_diff.rs index 713699e5c1..5b0eb301e1 100644 --- a/nexus/types/src/deployment/blueprint_diff.rs +++ b/nexus/types/src/deployment/blueprint_diff.rs @@ -6,8 +6,8 @@ use super::blueprint_display::{ constants::*, linear_table_modified, linear_table_unchanged, - BpClickhouseServersSubtableSchema, BpDiffState, BpGeneration, - BpOmicronZonesSubtableSchema, BpPhysicalDisksSubtableSchema, + BpClickhouseServersSubtableSchema, BpDatasetsSubtableSchema, BpDiffState, + BpGeneration, BpOmicronZonesSubtableSchema, BpPhysicalDisksSubtableSchema, BpSledSubtable, BpSledSubtableColumn, BpSledSubtableData, BpSledSubtableRow, KvListWithHeading, KvPair, }; @@ -25,11 +25,12 @@ use std::fmt; use crate::deployment::blueprint_display::BpClickhouseKeepersSubtableSchema; use crate::deployment::{ - BlueprintMetadata, BlueprintOrCollectionDisksConfig, + BlueprintDatasetConfigForDiff, BlueprintDatasetsConfig, BlueprintMetadata, + BlueprintOrCollectionDatasetsConfig, BlueprintOrCollectionDisksConfig, BlueprintOrCollectionZoneConfig, BlueprintOrCollectionZonesConfig, BlueprintPhysicalDisksConfig, BlueprintZoneConfig, - BlueprintZoneDisposition, BlueprintZonesConfig, DiffBeforeMetadata, - ZoneSortKey, + BlueprintZoneDisposition, BlueprintZonesConfig, + CollectionDatasetIdentifier, DiffBeforeMetadata, ZoneSortKey, }; use crate::external_api::views::SledState; @@ -558,6 +559,263 @@ impl BpDiffPhysicalDisks { } } +#[derive(Debug)] +pub struct DiffDatasetsDetails { + // Datasets that come from inventory don't have generation numbers + pub before_generation: Option, + + // Datasets that are removed don't have "after" generation numbers + pub after_generation: Option, + + // Datasets added, removed, modified, or unmodified + pub datasets: + BTreeMap, +} + +impl BpSledSubtableData for DiffDatasetsDetails { + fn bp_generation(&self) -> BpGeneration { + BpGeneration::Diff { + before: self.before_generation, + after: self.after_generation, + } + } + + fn rows( + &self, + state: BpDiffState, + ) -> impl Iterator { + self.datasets.values().map(move |dataset| { + BpSledSubtableRow::from_strings(state, dataset.as_strings()) + }) + } +} + +#[derive(Debug)] +pub struct ModifiedDataset { + pub before: BlueprintDatasetConfigForDiff, + pub after: BlueprintDatasetConfigForDiff, +} + +#[derive(Debug)] +pub struct BpDiffDatasetsModified { + pub generation_before: Option, + pub generation_after: Option, + pub datasets: Vec, +} + +impl BpSledSubtableData for BpDiffDatasetsModified { + fn bp_generation(&self) -> BpGeneration { + BpGeneration::Diff { + before: self.generation_before, + after: self.generation_after, + } + } + + fn rows( + &self, + state: BpDiffState, + ) -> impl Iterator { + self.datasets.iter().map(move |dataset| { + let before_strings = dataset.before.as_strings(); + let after_strings = dataset.after.as_strings(); + + let mut columns = vec![]; + for (before, after) in std::iter::zip(before_strings, after_strings) + { + let column = if before != after { + BpSledSubtableColumn::diff(before, after) + } else { + BpSledSubtableColumn::value(before) + }; + columns.push(column); + } + + BpSledSubtableRow::new(state, columns) + }) + } +} + +#[derive(Debug, Default)] +pub struct BpDiffDatasets { + pub added: BTreeMap, + pub removed: BTreeMap, + pub modified: BTreeMap, + pub unchanged: BTreeMap, +} + +impl BpDiffDatasets { + pub fn new( + before: BTreeMap, + mut after: BTreeMap, + ) -> Self { + let mut diffs = BpDiffDatasets::default(); + + // Observe the set of old sleds first + for (sled_id, before_datasets) in before { + let before_generation = before_datasets.generation(); + + // If the sled exists in both the old and new set, compare + // the set of datasets to identify which "grouping" they should + // land in. + if let Some(after_datasets) = after.remove(&sled_id) { + let after_generation = Some(after_datasets.generation); + + let mut unchanged = BTreeMap::new(); + let mut modified = BTreeMap::new(); + let mut removed = BTreeMap::new(); + + // Normalize the "before" and "after" data to compare individual + // datasets. + + let b = before_datasets.datasets(); + let mut added: BTreeMap< + CollectionDatasetIdentifier, + BlueprintDatasetConfigForDiff, + > = after_datasets + .datasets + .values() + .map(|d| { + (CollectionDatasetIdentifier::from(d), d.clone().into()) + }) + .collect(); + + for (id, dataset_before) in b { + if let Some(dataset_after) = added.remove(&id) { + if dataset_before == dataset_after { + unchanged.insert(id, dataset_after); + } else { + modified + .insert(id, (dataset_before, dataset_after)); + } + } else { + removed.insert(id, dataset_before); + } + } + + if !added.is_empty() { + diffs.added.insert( + sled_id, + DiffDatasetsDetails { + before_generation, + after_generation, + datasets: added, + }, + ); + } + if !removed.is_empty() { + diffs.removed.insert( + sled_id, + DiffDatasetsDetails { + before_generation, + after_generation, + datasets: removed, + }, + ); + } + if !modified.is_empty() { + diffs.modified.insert( + sled_id, + BpDiffDatasetsModified { + generation_before: before_generation, + generation_after: after_generation, + datasets: modified + .into_values() + .map(|(before, after)| ModifiedDataset { + before, + after, + }) + .collect(), + }, + ); + } + if !unchanged.is_empty() { + diffs.unchanged.insert( + sled_id, + DiffDatasetsDetails { + before_generation, + after_generation, + datasets: unchanged, + }, + ); + } + } else { + diffs.removed.insert( + sled_id, + DiffDatasetsDetails { + before_generation, + after_generation: None, + datasets: before_datasets + .datasets() + .into_iter() + .collect(), + }, + ); + } + } + + // Any sleds remaining in `after` have just been added, since we remove + // sleds from `after`, that were also in `before`, in the above loop. + for (sled_id, after_datasets) in after { + let added: BTreeMap = + after_datasets + .datasets + .into_values() + .map(|d| (CollectionDatasetIdentifier::from(&d), d.into())) + .collect(); + if !added.is_empty() { + diffs.added.insert( + sled_id, + DiffDatasetsDetails { + before_generation: None, + after_generation: Some(after_datasets.generation), + datasets: added, + }, + ); + } + } + + diffs + } + + /// Return a [`BpSledSubtable`] for the given `sled_id` + pub fn to_bp_sled_subtable( + &self, + sled_id: &SledUuid, + ) -> Option { + let mut generation = BpGeneration::Diff { before: None, after: None }; + let mut rows = vec![]; + if let Some(diff) = self.unchanged.get(sled_id) { + generation = diff.bp_generation(); + rows.extend(diff.rows(BpDiffState::Unchanged)); + } + if let Some(diff) = self.removed.get(sled_id) { + // Generations never vary for the same sled, so this is harmless + // + // (Same below, where we overwrite the "generation") + generation = diff.bp_generation(); + rows.extend(diff.rows(BpDiffState::Removed)); + } + if let Some(diff) = self.modified.get(sled_id) { + generation = diff.bp_generation(); + rows.extend(diff.rows(BpDiffState::Modified)); + } + if let Some(diff) = self.added.get(sled_id) { + generation = diff.bp_generation(); + rows.extend(diff.rows(BpDiffState::Added)); + } + + if rows.is_empty() { + None + } else { + Some(BpSledSubtable::new( + BpDatasetsSubtableSchema {}, + generation, + rows, + )) + } + } +} + /// Summarizes the differences between two blueprints #[derive(Debug)] pub struct BlueprintDiff { @@ -567,6 +825,7 @@ pub struct BlueprintDiff { pub after_state: BTreeMap, pub zones: BpDiffZones, pub physical_disks: BpDiffPhysicalDisks, + pub datasets: BpDiffDatasets, pub sleds_added: BTreeSet, pub sleds_removed: BTreeSet, pub sleds_unchanged: BTreeSet, @@ -584,11 +843,16 @@ impl BlueprintDiff { before_state: BTreeMap, before_zones: BTreeMap, before_disks: BTreeMap, + before_datasets: BTreeMap< + SledUuid, + BlueprintOrCollectionDatasetsConfig, + >, after_blueprint: &Blueprint, ) -> Self { let mut after_state = after_blueprint.sled_state.clone(); let after_zones = after_blueprint.blueprint_zones.clone(); let after_disks = after_blueprint.blueprint_disks.clone(); + let after_datasets = after_blueprint.blueprint_datasets.clone(); // Work around a quirk of sled decommissioning. If a sled has a before // state of `decommissioned`, it may or may not be present in @@ -618,23 +882,25 @@ impl BlueprintDiff { .keys() .chain(before_zones.keys()) .chain(before_disks.keys()) + .chain(before_datasets.keys()) .collect(); let after_sleds: BTreeSet<_> = after_state .keys() .chain(after_zones.keys()) .chain(after_disks.keys()) + .chain(after_datasets.keys()) .collect(); let all_sleds: BTreeSet<_> = before_sleds.union(&after_sleds).map(|&sled_id| *sled_id).collect(); - // All sleds that have state, zones, or disks in `after_*`, but not + // All sleds that have state, zones, disks or datasets in `after_*`, but not // `before_*` have been added. let sleds_added: BTreeSet<_> = after_sleds .difference(&before_sleds) .map(|&sled_id| *sled_id) .collect(); - // All sleds that have state, zones, or disks in `before_*`, but not + // All sleds that have state, zones, disks or datasets in `before_*`, but not // `after_*` have been removed. let sleds_removed: BTreeSet<_> = before_sleds .difference(&after_sleds) @@ -644,6 +910,7 @@ impl BlueprintDiff { let zones = BpDiffZones::new(before_zones, after_zones); let physical_disks = BpDiffPhysicalDisks::new(before_disks, after_disks); + let datasets = BpDiffDatasets::new(before_datasets, after_datasets); // Sleds that haven't been added or removed are either unchanged or // modified. @@ -663,6 +930,9 @@ impl BlueprintDiff { before_state.get(sled_id) != after_state.get(sled_id) || physical_disks.added.contains_key(sled_id) || physical_disks.removed.contains_key(sled_id) + || datasets.added.contains_key(sled_id) + || datasets.modified.contains_key(sled_id) + || datasets.removed.contains_key(sled_id) || zones.added.contains_key(sled_id) || zones.removed.contains_key(sled_id) || zones.modified.contains_key(sled_id) @@ -682,6 +952,7 @@ impl BlueprintDiff { after_state, zones, physical_disks, + datasets, sleds_added, sleds_removed, sleds_unchanged: unchanged_sleds, @@ -1227,6 +1498,11 @@ impl<'diff> BlueprintDiffDisplay<'diff> { writeln!(f, "{table}\n")?; } + // Write the datasets table if it exists + if let Some(table) = self.diff.datasets.to_bp_sled_subtable(sled_id) { + writeln!(f, "{table}\n")?; + } + // Write the zones table if it exists if let Some(table) = self.diff.zones.to_bp_sled_subtable(sled_id) { writeln!(f, "{table}\n")?; diff --git a/nexus/types/src/deployment/blueprint_display.rs b/nexus/types/src/deployment/blueprint_display.rs index 19abbb7d1e..0f20d82345 100644 --- a/nexus/types/src/deployment/blueprint_display.rs +++ b/nexus/types/src/deployment/blueprint_display.rs @@ -342,6 +342,18 @@ impl BpSledSubtableSchema for BpPhysicalDisksSubtableSchema { } } +/// The [`BpSledSubtable`] schema for datasets +pub struct BpDatasetsSubtableSchema {} +impl BpSledSubtableSchema for BpDatasetsSubtableSchema { + fn table_name(&self) -> &'static str { + "datasets" + } + + fn column_names(&self) -> &'static [&'static str] { + &["dataset name", "dataset uuid", "quota", "reservation", "compression"] + } +} + /// The [`BpSledSubtable`] schema for omicron zones pub struct BpOmicronZonesSubtableSchema {} impl BpSledSubtableSchema for BpOmicronZonesSubtableSchema { diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index bb1845efe6..9af9d3be1e 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -14,7 +14,7 @@ use omicron_common::api::external::{ IdentityMetadataCreateParams, IdentityMetadataUpdateParams, InstanceAutoRestartPolicy, InstanceCpuCount, LinkFec, LinkSpeed, Name, NameOrId, PaginationOrder, RouteDestination, RouteTarget, SemverVersion, - UserId, + TxEqConfig, UserId, }; use omicron_common::disk::DiskVariant; use oxnet::{IpNet, Ipv4Net, Ipv6Net}; @@ -1723,6 +1723,9 @@ pub struct LinkConfigCreate { /// Whether or not to set autonegotiation pub autoneg: bool, + + /// Optional tx_eq settings + pub tx_eq: Option, } /// The LLDP configuration associated with a port. diff --git a/nexus/types/src/inventory.rs b/nexus/types/src/inventory.rs index 85958f518a..b38e8f3077 100644 --- a/nexus/types/src/inventory.rs +++ b/nexus/types/src/inventory.rs @@ -484,6 +484,7 @@ pub struct Dataset { pub compression: String, } +// TODO: Rather than converting, I think these types can be de-duplicated impl From for Dataset { fn from(disk: InventoryDataset) -> Self { Self { diff --git a/openapi/bootstrap-agent.json b/openapi/bootstrap-agent.json index 54feaa7325..2f3058cd46 100644 --- a/openapi/bootstrap-agent.json +++ b/openapi/bootstrap-agent.json @@ -856,6 +856,15 @@ } ] }, + "tx_eq": { + "nullable": true, + "description": "TX-EQ configuration for this port", + "allOf": [ + { + "$ref": "#/components/schemas/TxEqConfig" + } + ] + }, "uplink_port_fec": { "description": "Port forward error correction type.", "allOf": [ @@ -1536,6 +1545,42 @@ } ] }, + "TxEqConfig": { + "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", + "type": "object", + "properties": { + "main": { + "nullable": true, + "description": "Main tap", + "type": "integer", + "format": "int32" + }, + "post1": { + "nullable": true, + "description": "Post-cursor tap1", + "type": "integer", + "format": "int32" + }, + "post2": { + "nullable": true, + "description": "Post-cursor tap2", + "type": "integer", + "format": "int32" + }, + "pre1": { + "nullable": true, + "description": "Pre-cursor tap1", + "type": "integer", + "format": "int32" + }, + "pre2": { + "nullable": true, + "description": "Pre-cursor tap2", + "type": "integer", + "format": "int32" + } + } + }, "TypedUuidForRackInitKind": { "type": "string", "format": "uuid" diff --git a/openapi/clickhouse-admin-server.json b/openapi/clickhouse-admin-server.json index 9dc2506eb1..a15090dd2d 100644 --- a/openapi/clickhouse-admin-server.json +++ b/openapi/clickhouse-admin-server.json @@ -44,6 +44,35 @@ } } } + }, + "/distributed-ddl-queue": { + "get": { + "summary": "Contains information about distributed ddl queries (ON CLUSTER clause)", + "description": "that were executed on a cluster.", + "operationId": "distributed_ddl_queue", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_DistributedDdlQueue", + "type": "array", + "items": { + "$ref": "#/components/schemas/DistributedDdlQueue" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } } }, "components": { @@ -90,6 +119,105 @@ } ] }, + "DistributedDdlQueue": { + "description": "Contains information about distributed ddl queries (ON CLUSTER clause) that were executed on a cluster.", + "type": "object", + "properties": { + "cluster": { + "description": "Cluster name", + "type": "string" + }, + "entry": { + "description": "Query id", + "type": "string" + }, + "entry_version": { + "description": "Version of the entry", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "exception_code": { + "description": "Exception code", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "exception_text": { + "description": "Exception message", + "type": "string" + }, + "host": { + "description": "Hostname", + "type": "string", + "format": "ipv6" + }, + "initiator_host": { + "description": "Host that initiated the DDL operation", + "type": "string" + }, + "initiator_port": { + "description": "Port used by the initiator", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "port": { + "description": "Host Port", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "query": { + "description": "Query executed", + "type": "string" + }, + "query_create_time": { + "description": "Query created time", + "type": "string", + "format": "date-time" + }, + "query_duration_ms": { + "description": "Duration of query execution (in milliseconds)", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "query_finish_time": { + "description": "Query finish time", + "type": "string", + "format": "date-time" + }, + "settings": { + "description": "Settings used in the DDL operation", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "status": { + "description": "Status of the query", + "type": "string" + } + }, + "required": [ + "cluster", + "entry", + "entry_version", + "exception_code", + "exception_text", + "host", + "initiator_host", + "initiator_port", + "port", + "query", + "query_create_time", + "query_duration_ms", + "query_finish_time", + "settings", + "status" + ] + }, "Error": { "description": "Error information from a response.", "type": "object", diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 8df32aed4e..4392de318d 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -4677,6 +4677,15 @@ } ] }, + "tx_eq": { + "nullable": true, + "description": "TX-EQ configuration for this port", + "allOf": [ + { + "$ref": "#/components/schemas/TxEqConfig" + } + ] + }, "uplink_port_fec": { "description": "Port forward error correction type.", "allOf": [ @@ -5746,6 +5755,42 @@ "SwitchPutResponse": { "type": "object" }, + "TxEqConfig": { + "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", + "type": "object", + "properties": { + "main": { + "nullable": true, + "description": "Main tap", + "type": "integer", + "format": "int32" + }, + "post1": { + "nullable": true, + "description": "Post-cursor tap1", + "type": "integer", + "format": "int32" + }, + "post2": { + "nullable": true, + "description": "Post-cursor tap2", + "type": "integer", + "format": "int32" + }, + "pre1": { + "nullable": true, + "description": "Pre-cursor tap1", + "type": "integer", + "format": "int32" + }, + "pre2": { + "nullable": true, + "description": "Pre-cursor tap2", + "type": "integer", + "format": "int32" + } + } + }, "TypedUuidForDatasetKind": { "type": "string", "format": "uuid" diff --git a/openapi/nexus.json b/openapi/nexus.json index c44f21bb39..f12ff4730c 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -17194,6 +17194,15 @@ "$ref": "#/components/schemas/LinkSpeed" } ] + }, + "tx_eq": { + "nullable": true, + "description": "Optional tx_eq settings", + "allOf": [ + { + "$ref": "#/components/schemas/TxEqConfig" + } + ] } }, "required": [ @@ -20436,6 +20445,12 @@ "$ref": "#/components/schemas/LinkSpeed" } ] + }, + "tx_eq_config_id": { + "nullable": true, + "description": "The tx_eq configuration id for this link.", + "type": "string", + "format": "uuid" } }, "required": [ @@ -20736,6 +20751,18 @@ } ] }, + "tx_eq": { + "description": "TX equalization settings. These are optional, and most links will not need them.", + "type": "array", + "items": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TxEqConfig" + } + ] + } + }, "vlan_interfaces": { "description": "Vlan interface settings.", "type": "array", @@ -20754,6 +20781,7 @@ "port", "routes", "settings", + "tx_eq", "vlan_interfaces" ] }, @@ -20940,6 +20968,42 @@ "items" ] }, + "TxEqConfig": { + "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", + "type": "object", + "properties": { + "main": { + "nullable": true, + "description": "Main tap", + "type": "integer", + "format": "int32" + }, + "post1": { + "nullable": true, + "description": "Post-cursor tap1", + "type": "integer", + "format": "int32" + }, + "post2": { + "nullable": true, + "description": "Post-cursor tap2", + "type": "integer", + "format": "int32" + }, + "pre1": { + "nullable": true, + "description": "Pre-cursor tap1", + "type": "integer", + "format": "int32" + }, + "pre2": { + "nullable": true, + "description": "Pre-cursor tap2", + "type": "integer", + "format": "int32" + } + } + }, "UninitializedSled": { "description": "A sled that has not been added to an initialized rack yet", "type": "object", diff --git a/openapi/repo-depot.json b/openapi/repo-depot.json new file mode 100644 index 0000000000..0c0019cf8d --- /dev/null +++ b/openapi/repo-depot.json @@ -0,0 +1,82 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Oxide TUF Repo Depot API", + "description": "API for fetching update artifacts", + "contact": { + "url": "https://oxide.computer", + "email": "api@oxide.computer" + }, + "version": "0.0.1" + }, + "paths": { + "/artifact/sha256/{sha256}": { + "get": { + "summary": "Fetch an artifact from the depot.", + "operationId": "artifact_get_by_sha256", + "parameters": [ + { + "in": "path", + "name": "sha256", + "required": true, + "schema": { + "type": "string", + "format": "hex string (32 bytes)" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "*/*": { + "schema": {} + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + } + }, + "components": { + "schemas": { + "Error": { + "description": "Error information from a response.", + "type": "object", + "properties": { + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" + } + }, + "required": [ + "message", + "request_id" + ] + } + }, + "responses": { + "Error": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } +} diff --git a/openapi/sled-agent.json b/openapi/sled-agent.json index 0345bbf9e8..9963526ef8 100644 --- a/openapi/sled-agent.json +++ b/openapi/sled-agent.json @@ -10,6 +10,149 @@ "version": "0.0.1" }, "paths": { + "/artifacts": { + "get": { + "operationId": "artifact_list", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Map_of_uint", + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "uint", + "minimum": 0 + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/artifacts/{sha256}": { + "put": { + "operationId": "artifact_put", + "parameters": [ + { + "in": "path", + "name": "sha256", + "required": true, + "schema": { + "type": "string", + "format": "hex string (32 bytes)" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArtifactPutResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "operationId": "artifact_delete", + "parameters": [ + { + "in": "path", + "name": "sha256", + "required": true, + "schema": { + "type": "string", + "format": "hex string (32 bytes)" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/artifacts/{sha256}/copy-from-depot": { + "post": { + "operationId": "artifact_copy_from_depot", + "parameters": [ + { + "in": "path", + "name": "sha256", + "required": true, + "schema": { + "type": "string", + "format": "hex string (32 bytes)" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArtifactCopyFromDepotBody" + } + } + }, + "required": true + }, + "responses": { + "202": { + "description": "successfully enqueued operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArtifactCopyFromDepotResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/boot-disk/{boot_disk}/os/write": { "post": { "summary": "Write a new host OS image to the specified boot disk", @@ -1417,6 +1560,41 @@ "start_request" ] }, + "ArtifactCopyFromDepotBody": { + "type": "object", + "properties": { + "depot_base_url": { + "type": "string" + } + }, + "required": [ + "depot_base_url" + ] + }, + "ArtifactCopyFromDepotResponse": { + "type": "object" + }, + "ArtifactPutResponse": { + "type": "object", + "properties": { + "datasets": { + "description": "The number of valid M.2 artifact datasets we found on the sled. There is typically one of these datasets for each functional M.2.", + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "successful_writes": { + "description": "The number of valid writes to the M.2 artifact datasets. This should be less than or equal to the number of artifact datasets.", + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "datasets", + "successful_writes" + ] + }, "Baseboard": { "description": "Describes properties that should uniquely identify a Gimlet.", "oneOf": [ @@ -3114,6 +3292,14 @@ "port": { "description": "Switchport to use for external connectivity", "type": "string" + }, + "tx_eq": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TxEqConfig" + } + ] } }, "required": [ @@ -4448,6 +4634,15 @@ } ] }, + "tx_eq": { + "nullable": true, + "description": "TX-EQ configuration for this port", + "allOf": [ + { + "$ref": "#/components/schemas/TxEqConfig" + } + ] + }, "uplink_port_fec": { "description": "Port forward error correction type.", "allOf": [ @@ -5159,6 +5354,42 @@ "sync" ] }, + "TxEqConfig": { + "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", + "type": "object", + "properties": { + "main": { + "nullable": true, + "description": "Main tap", + "type": "integer", + "format": "int32" + }, + "post1": { + "nullable": true, + "description": "Post-cursor tap1", + "type": "integer", + "format": "int32" + }, + "post2": { + "nullable": true, + "description": "Post-cursor tap2", + "type": "integer", + "format": "int32" + }, + "pre1": { + "nullable": true, + "description": "Pre-cursor tap1", + "type": "integer", + "format": "int32" + }, + "pre2": { + "nullable": true, + "description": "Pre-cursor tap2", + "type": "integer", + "format": "int32" + } + } + }, "TypedUuidForDatasetKind": { "type": "string", "format": "uuid" diff --git a/openapi/wicketd.json b/openapi/wicketd.json index 55db469210..8e20f23d06 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -5991,6 +5991,42 @@ } ] }, + "TxEqConfig": { + "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", + "type": "object", + "properties": { + "main": { + "nullable": true, + "description": "Main tap", + "type": "integer", + "format": "int32" + }, + "post1": { + "nullable": true, + "description": "Post-cursor tap1", + "type": "integer", + "format": "int32" + }, + "post2": { + "nullable": true, + "description": "Post-cursor tap2", + "type": "integer", + "format": "int32" + }, + "pre1": { + "nullable": true, + "description": "Pre-cursor tap1", + "type": "integer", + "format": "int32" + }, + "pre2": { + "nullable": true, + "description": "Pre-cursor tap2", + "type": "integer", + "format": "int32" + } + } + }, "TypedUuidForRackInitKind": { "type": "string", "format": "uuid" @@ -6630,6 +6666,15 @@ "$ref": "#/components/schemas/RouteConfig" } }, + "tx_eq": { + "nullable": true, + "default": null, + "allOf": [ + { + "$ref": "#/components/schemas/TxEqConfig" + } + ] + }, "uplink_port_fec": { "$ref": "#/components/schemas/PortFec" }, diff --git a/oximeter/collector/src/lib.rs b/oximeter/collector/src/lib.rs index 6b10cf31cd..68fff4cbf0 100644 --- a/oximeter/collector/src/lib.rs +++ b/oximeter/collector/src/lib.rs @@ -344,11 +344,20 @@ impl Oximeter { )) }; - qorb::pool::Pool::new( + match qorb::pool::Pool::new( nexus_resolver, Arc::new(NexusConnector { log: log.clone() }), qorb::policy::Policy::default(), - ) + ) { + Ok(pool) => { + debug!(log, "registered USDT probes"); + pool + } + Err(err) => { + error!(log, "failed to register USDT probes"); + err.into_inner() + } + } }; let notify_nexus = || async { diff --git a/oximeter/db/Cargo.toml b/oximeter/db/Cargo.toml index b0afdfbb07..df4bc7d703 100644 --- a/oximeter/db/Cargo.toml +++ b/oximeter/db/Cargo.toml @@ -17,6 +17,7 @@ camino.workspace = true chrono.workspace = true chrono-tz.workspace = true clap.workspace = true +const_format.workspace = true clickward.workspace = true debug-ignore.workspace = true dropshot.workspace = true @@ -32,6 +33,7 @@ omicron-common.workspace = true omicron-workspace-hack.workspace = true oximeter.workspace = true oxql-types.workspace = true +parse-display.workspace = true qorb.workspace = true regex.workspace = true serde.workspace = true @@ -93,6 +95,11 @@ optional = true workspace = true features = [ "rt-multi-thread", "macros" ] +[build-dependencies] +anyhow.workspace = true +nom.workspace = true +quote.workspace = true + [dev-dependencies] camino-tempfile.workspace = true criterion = { workspace = true, features = [ "async_tokio" ] } diff --git a/oximeter/db/build.rs b/oximeter/db/build.rs new file mode 100644 index 0000000000..398dfb7f69 --- /dev/null +++ b/oximeter/db/build.rs @@ -0,0 +1,103 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright 2024 Oxide Computer Company + +use anyhow::Context as _; +use nom::IResult; + +/// Build script for generating native type representations from the +/// ground-truth SQL definitions. +fn main() -> anyhow::Result<()> { + // We're only parsing data types, specifically the enum variant-to-name + // mappings. These are the same for single-node and replicated databases, so + // use the former for simplicity. + const INIT_FILE: &str = + concat!(env!("CARGO_MANIFEST_DIR"), "/schema/single-node/db-init.sql"); + let contents = std::fs::read_to_string(INIT_FILE) + .with_context(|| format!("Failed to read SQL file: '{INIT_FILE}'"))?; + let field_type_enum = + find_enum(&contents, "type").context("failed to find column 'type'")?; + let field_source_enum = find_enum(&contents, "source") + .context("failed to find column 'source'")?; + let datum_type_enum = find_enum(&contents, "datum_type") + .context("failed to find column 'datum_type'")?; + std::fs::write( + format!("{}/enum_defs.rs", std::env::var("OUT_DIR")?), + [field_type_enum, field_source_enum, datum_type_enum].join("\n"), + ) + .context("writing output file")?; + Ok(()) +} + +// Find an enum in the `timeseries_schema` table definition for the named +// column, and return the corresponding `DataType::Enum8()` definition for it. +fn find_enum(contents: &str, column: &str) -> Option { + let needle = format!("{column} Enum(\n"); + let start = contents.find(&needle)? + needle.len(); + let s = &contents[start..].trim(); + let (variants, names): (Vec, Vec) = + variant_list(s).ok()?.1.into_iter().unzip(); + let enum_map = quote::format_ident!("{}_ENUM_MAP", column.to_uppercase()); + let enum_rev_map = + quote::format_ident!("{}_ENUM_REV_MAP", column.to_uppercase()); + let enum_type = + quote::format_ident!("{}_ENUM_DATA_TYPE", column.to_uppercase()); + let parsed_type = if column == "type" { + quote::quote! { ::oximeter::FieldType } + } else if column == "source" { + quote::quote! { ::oximeter::FieldSource } + } else if column == "datum_type" { + quote::quote! { ::oximeter::DatumType } + } else { + unreachable!(); + }; + Some(quote::quote! { + /// Mapping from the variant index to the string form. + #[allow(dead_code)] + static #enum_map: ::std::sync::LazyLock<::indexmap::IndexMap> = ::std::sync::LazyLock::new(|| { + ::indexmap::IndexMap::from([ + #((#variants, String::from(#names))),* + ]) + }); + /// Reverse mapping, from the _parsed_ form to the variant index. + #[allow(dead_code)] + static #enum_rev_map: ::std::sync::LazyLock<::indexmap::IndexMap<#parsed_type, i8>> = ::std::sync::LazyLock::new(|| { + ::indexmap::IndexMap::from([ + #((<#parsed_type as ::std::str::FromStr>::from_str(#names).unwrap(), #variants)),* + ]) + }); + /// Actual DataType::Enum8(_) with the contained variant-to-name mapping. + #[allow(dead_code)] + static #enum_type: ::std::sync::LazyLock = ::std::sync::LazyLock::new(|| { + crate::native::block::DataType::Enum8( + ::indexmap::IndexMap::from([ + #((#variants, String::from(#names))),* + ]) + ) + }); + }.to_string()) +} + +fn variant_list(s: &str) -> IResult<&str, Vec<(i8, String)>> { + nom::multi::separated_list1( + nom::bytes::complete::is_a(" ,\n"), + single_variant, + )(s) +} + +fn single_variant(s: &str) -> IResult<&str, (i8, String)> { + nom::combinator::map( + nom::sequence::separated_pair( + nom::sequence::delimited( + nom::bytes::complete::tag("'"), + nom::character::complete::alphanumeric1, + nom::bytes::complete::tag("'"), + ), + nom::bytes::complete::tag(" = "), + nom::character::complete::i8, + ), + |(name, variant): (&str, i8)| (variant, name.to_string()), + )(s) +} diff --git a/oximeter/db/schema/replicated/13/timeseries-to-delete.txt b/oximeter/db/schema/replicated/13/timeseries-to-delete.txt new file mode 100644 index 0000000000..da3f1d6376 --- /dev/null +++ b/oximeter/db/schema/replicated/13/timeseries-to-delete.txt @@ -0,0 +1,25 @@ +switch_data_link:bytes_sent +switch_data_link:bytes_received +switch_data_link:errors_sent +switch_data_link:errors_received +switch_data_link:receive_crc_error_drops +switch_data_link:receive_buffer_full_drops +switch_data_link:packets_sent +switch_data_link:packets_received +switch_data_link:link_up +switch_data_link:link_enabled +switch_data_link:link_fsm +switch_data_link:pcs_bad_sync_headers +switch_data_link:pcs_errored_blocks +switch_data_link:pcs_block_lock_loss +switch_data_link:pcs_high_ber +switch_data_link:pcs_valid_errors +switch_data_link:pcs_invalid_errors +switch_data_link:pcs_unknown_errors +switch_data_link:pcs_sync_loss +switch_data_link:fec_high_symbol_errors +switch_data_link:fec_sync_aligned +switch_data_link:fec_corrected_blocks +switch_data_link:fec_uncorrected_blocks +switch_data_link:fec_symbol_errors +dendrite:sample_collection_duration diff --git a/oximeter/db/schema/replicated/13/up1.sql b/oximeter/db/schema/replicated/13/up1.sql new file mode 100644 index 0000000000..ac13ea6d01 --- /dev/null +++ b/oximeter/db/schema/replicated/13/up1.sql @@ -0,0 +1,4 @@ +ALTER TABLE oximeter.fields_bool_local +ON CLUSTER oximeter_cluster +ALTER COLUMN IF EXISTS +field_value TYPE Bool; diff --git a/oximeter/db/schema/replicated/13/up2.sql b/oximeter/db/schema/replicated/13/up2.sql new file mode 100644 index 0000000000..11f42784c5 --- /dev/null +++ b/oximeter/db/schema/replicated/13/up2.sql @@ -0,0 +1,4 @@ +ALTER TABLE oximeter.measurements_bool_local +ON CLUSTER oximeter_cluster +ALTER COLUMN IF EXISTS +datum TYPE Nullable(Bool); diff --git a/oximeter/db/schema/replicated/13/up3.sql b/oximeter/db/schema/replicated/13/up3.sql new file mode 100644 index 0000000000..e39f7d1c73 --- /dev/null +++ b/oximeter/db/schema/replicated/13/up3.sql @@ -0,0 +1,4 @@ +ALTER TABLE oximeter.fields_bool +ON CLUSTER oximeter_cluster +ALTER COLUMN IF EXISTS +field_value TYPE Bool; diff --git a/oximeter/db/schema/replicated/13/up4.sql b/oximeter/db/schema/replicated/13/up4.sql new file mode 100644 index 0000000000..03aec4da41 --- /dev/null +++ b/oximeter/db/schema/replicated/13/up4.sql @@ -0,0 +1,4 @@ +ALTER TABLE oximeter.measurements_bool +ON CLUSTER oximeter_cluster +ALTER COLUMN IF EXISTS +datum TYPE Nullable(Bool); diff --git a/oximeter/db/schema/replicated/db-init-2.sql b/oximeter/db/schema/replicated/db-init-2.sql index 51e64e20e0..7507041f40 100644 --- a/oximeter/db/schema/replicated/db-init-2.sql +++ b/oximeter/db/schema/replicated/db-init-2.sql @@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_bool_local ON CLUSTER oximeter_ timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Nullable(UInt8) + datum Nullable(Bool) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_bool_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -595,7 +595,7 @@ CREATE TABLE IF NOT EXISTS oximeter.fields_bool_local ON CLUSTER oximeter_cluste timeseries_name String, timeseries_key UInt64, field_name String, - field_value UInt8, + field_value Bool, last_updated_at DateTime MATERIALIZED now() ) ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{shard}/fields_bool_local', '{replica}') diff --git a/oximeter/db/schema/single-node/13/timeseries-to-delete.txt b/oximeter/db/schema/single-node/13/timeseries-to-delete.txt new file mode 100644 index 0000000000..da3f1d6376 --- /dev/null +++ b/oximeter/db/schema/single-node/13/timeseries-to-delete.txt @@ -0,0 +1,25 @@ +switch_data_link:bytes_sent +switch_data_link:bytes_received +switch_data_link:errors_sent +switch_data_link:errors_received +switch_data_link:receive_crc_error_drops +switch_data_link:receive_buffer_full_drops +switch_data_link:packets_sent +switch_data_link:packets_received +switch_data_link:link_up +switch_data_link:link_enabled +switch_data_link:link_fsm +switch_data_link:pcs_bad_sync_headers +switch_data_link:pcs_errored_blocks +switch_data_link:pcs_block_lock_loss +switch_data_link:pcs_high_ber +switch_data_link:pcs_valid_errors +switch_data_link:pcs_invalid_errors +switch_data_link:pcs_unknown_errors +switch_data_link:pcs_sync_loss +switch_data_link:fec_high_symbol_errors +switch_data_link:fec_sync_aligned +switch_data_link:fec_corrected_blocks +switch_data_link:fec_uncorrected_blocks +switch_data_link:fec_symbol_errors +dendrite:sample_collection_duration diff --git a/oximeter/db/schema/single-node/13/up1.sql b/oximeter/db/schema/single-node/13/up1.sql new file mode 100644 index 0000000000..36ffaad12b --- /dev/null +++ b/oximeter/db/schema/single-node/13/up1.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.fields_bool ALTER COLUMN IF EXISTS field_value TYPE Bool; diff --git a/oximeter/db/schema/single-node/13/up2.sql b/oximeter/db/schema/single-node/13/up2.sql new file mode 100644 index 0000000000..b7b2c9b299 --- /dev/null +++ b/oximeter/db/schema/single-node/13/up2.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_bool ALTER COLUMN IF EXISTS datum TYPE Nullable(Bool); diff --git a/oximeter/db/schema/single-node/db-init.sql b/oximeter/db/schema/single-node/db-init.sql index 184951feeb..d505a21414 100644 --- a/oximeter/db/schema/single-node/db-init.sql +++ b/oximeter/db/schema/single-node/db-init.sql @@ -24,7 +24,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_bool timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Nullable(UInt8) + datum Nullable(Bool) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -518,7 +518,7 @@ CREATE TABLE IF NOT EXISTS oximeter.fields_bool timeseries_name String, timeseries_key UInt64, field_name String, - field_value UInt8, + field_value Bool, last_updated_at DateTime MATERIALIZED now() ) ENGINE = ReplacingMergeTree() diff --git a/oximeter/db/src/bin/oxdb/main.rs b/oximeter/db/src/bin/oxdb/main.rs index ad5018eee5..e7d49f6707 100644 --- a/oximeter/db/src/bin/oxdb/main.rs +++ b/oximeter/db/src/bin/oxdb/main.rs @@ -311,8 +311,6 @@ async fn query( #[tokio::main] async fn main() -> anyhow::Result<()> { - usdt::register_probes().context("Failed to register USDT probes")?; - let args = OxDb::parse(); let decorator = slog_term::TermDecorator::new().build(); let drain = slog_term::FullFormat::new(decorator) diff --git a/oximeter/db/src/client/dbwrite.rs b/oximeter/db/src/client/dbwrite.rs index 3559374e0d..d37d95deb9 100644 --- a/oximeter/db/src/client/dbwrite.rs +++ b/oximeter/db/src/client/dbwrite.rs @@ -8,18 +8,19 @@ use crate::client::Client; use crate::model; +use crate::model::to_block::ToBlock as _; use crate::Error; use camino::Utf8PathBuf; use oximeter::Sample; -use oximeter::TimeseriesName; +use oximeter::TimeseriesSchema; use slog::debug; use std::collections::BTreeMap; use std::collections::BTreeSet; #[derive(Debug)] pub(super) struct UnrolledSampleRows { - /// The timeseries schema rows, keyed by timeseries name. - pub new_schema: BTreeMap, + /// The timeseries schema rows. + pub new_schema: Vec, /// The rows to insert in all the other tables, keyed by the table name. pub rows: BTreeMap>, } @@ -182,14 +183,14 @@ impl Client { continue; } Ok(None) => {} - Ok(Some((name, schema))) => { + Ok(Some(schema)) => { debug!( self.log, "new timeseries schema"; - "timeseries_name" => %name, - "schema" => %schema + "timeseries_name" => %schema.timeseries_name, + "schema" => ?schema, ); - new_schema.insert(name, schema); + new_schema.insert(schema.timeseries_name.clone(), schema); } } @@ -217,6 +218,7 @@ impl Client { seen_timeseries.insert(key); } + let new_schema = new_schema.into_values().collect(); UnrolledSampleRows { new_schema, rows } } @@ -268,7 +270,7 @@ impl Client { // receive a sample with a new schema, and both would then try to insert that schema. pub(super) async fn save_new_schema_or_remove( &self, - new_schema: BTreeMap, + new_schema: Vec, ) -> Result<(), Error> { if !new_schema.is_empty() { debug!( @@ -276,17 +278,11 @@ impl Client { "inserting {} new timeseries schema", new_schema.len() ); - const APPROX_ROW_SIZE: usize = 64; - let mut body = String::with_capacity( - APPROX_ROW_SIZE + APPROX_ROW_SIZE * new_schema.len(), + let body = const_format::formatcp!( + "INSERT INTO {}.timeseries_schema FORMAT Native", + crate::DATABASE_NAME ); - body.push_str("INSERT INTO "); - body.push_str(crate::DATABASE_NAME); - body.push_str(".timeseries_schema FORMAT JSONEachRow\n"); - for row_data in new_schema.values() { - body.push_str(row_data); - body.push('\n'); - } + let block = TimeseriesSchema::to_block(&new_schema)?; // Try to insert the schema. // @@ -294,16 +290,16 @@ impl Client { // internal cache. Since we check the internal cache first for // schema, if we fail here but _don't_ remove the schema, we'll // never end up inserting the schema, but we will insert samples. - if let Err(e) = self.execute(body).await { + if let Err(e) = self.insert_native(&body, block).await { debug!( self.log, "failed to insert new schema, removing from cache"; "error" => ?e, ); let mut schema = self.schema.lock().await; - for name in new_schema.keys() { + for schema_to_remove in new_schema.iter() { schema - .remove(name) + .remove(&schema_to_remove.timeseries_name) .expect("New schema should have been cached"); } return Err(e); diff --git a/oximeter/db/src/client/mod.rs b/oximeter/db/src/client/mod.rs index 04caa13bd9..3fdee90858 100644 --- a/oximeter/db/src/client/mod.rs +++ b/oximeter/db/src/client/mod.rs @@ -17,7 +17,9 @@ pub use self::dbwrite::DbWrite; pub use self::dbwrite::TestDbWrite; use crate::client::query_summary::QuerySummary; use crate::model; +use crate::model::from_block::FromBlock; use crate::native; +use crate::native::block::Block; use crate::native::block::ValueArray; use crate::native::QueryResult; use crate::query; @@ -193,21 +195,39 @@ impl Client { )); let schema = Mutex::new(BTreeMap::new()); let request_timeout = DEFAULT_REQUEST_TIMEOUT; + let pool = match Pool::new( + http_resolver, + Arc::new(ReqwestConnector {}), + qorb::policy::Policy::default(), + ) { + Ok(pool) => { + debug!(log, "registered USDT probes"); + pool + } + Err(err) => { + error!(log, "failed to register USDT probes"); + err.into_inner() + } + }; + let native_pool = match Pool::new( + native_resolver, + Arc::new(native::connection::Connector), + qorb::policy::Policy::default(), + ) { + Ok(pool) => { + debug!(log, "registered USDT probes"); + pool + } + Err(err) => { + error!(log, "failed to register USDT probes"); + err.into_inner() + } + }; Self { _id: id, log, - source: ClientSource::Pool { - pool: DebugIgnore(Pool::new( - http_resolver, - Arc::new(ReqwestConnector {}), - qorb::policy::Policy::default(), - )), - }, - native_pool: DebugIgnore(Pool::new( - native_resolver, - Arc::new(native::connection::Connector), - Default::default(), - )), + source: ClientSource::Pool { pool: DebugIgnore(pool) }, + native_pool: DebugIgnore(native_pool), schema, request_timeout, } @@ -243,15 +263,25 @@ impl Client { let client = reqwest::Client::new(); let url = format!("http://{}", http_address); let schema = Mutex::new(BTreeMap::new()); + let native_pool = match Pool::new( + Box::new(SingleHostResolver::new(native_address)), + Arc::new(native::connection::Connector), + Default::default(), + ) { + Ok(pool) => { + debug!(log, "registered USDT probes"); + pool + } + Err(err) => { + error!(log, "failed to register USDT probes"); + err.into_inner() + } + }; Self { _id: id, log, source: ClientSource::Static(ReqwestClient { url, client }), - native_pool: DebugIgnore(Pool::new( - Box::new(SingleHostResolver::new(native_address)), - Arc::new(native::connection::Connector), - Default::default(), - )), + native_pool: DebugIgnore(native_pool), schema, request_timeout, } @@ -428,7 +458,7 @@ impl Client { "FROM {}.timeseries_schema ", "ORDER BY timeseries_name ", "LIMIT {} ", - "FORMAT JSONEachRow;", + "FORMAT Native;", ), crate::DATABASE_NAME, limit.get(), @@ -441,7 +471,7 @@ impl Client { "WHERE timeseries_name > '{}' ", "ORDER BY timeseries_name ", "LIMIT {} ", - "FORMAT JSONEachRow;", + "FORMAT Native;", ), crate::DATABASE_NAME, last_timeseries, @@ -449,18 +479,19 @@ impl Client { ) } }; - let body = self.execute_with_body(sql).await?.1; - let schema = body - .lines() - .map(|line| { - TimeseriesSchema::from( - serde_json::from_str::(line) - .expect( - "Failed to deserialize TimeseriesSchema from database", - ), - ) - }) - .collect::>(); + let result = self.execute_with_block(&sql).await?; + let Some(block) = result.data.as_ref() else { + error!( + self.log, + "query listing timeseries schema did not contain \ + a data block" + ); + return Err(Error::Database(String::from( + "Query listing timeseries schema did not contain \ + any data from the database", + ))); + }; + let schema = TimeseriesSchema::from_block(block)?; ResultsPage::new(schema, &dropshot::EmptyScanParams {}, |schema, _| { schema.timeseries_name.clone() }) @@ -649,7 +680,7 @@ impl Client { "path" => path.display(), "filename" => &name, ); - match self.execute_native(sql).await { + match self.execute_native(&sql).await { Ok(_) => debug!( self.log, "successfully applied schema upgrade file"; @@ -857,11 +888,11 @@ impl Client { /// Read the latest version applied in the database. pub async fn read_latest_version(&self) -> Result { const ALIAS: &str = "max_version"; - let sql = format!( + const QUERY: &str = const_format::formatcp!( "SELECT MAX(value) AS {ALIAS} FROM {db_name}.version;", db_name = crate::DATABASE_NAME, ); - match self.execute_with_result_native(sql).await { + match self.execute_with_block(QUERY).await { Ok(result) => { let Some(data) = &result.data else { error!( @@ -957,13 +988,14 @@ impl Client { "INSERT INTO {db_name}.version (*) VALUES ({version}, now());", db_name = crate::DATABASE_NAME, ); - self.execute_native(sql).await + self.execute_native(&sql).await } /// Verifies if instance is part of oximeter_cluster pub async fn is_oximeter_cluster(&self) -> Result { - let sql = format!("SHOW CLUSTER {}", crate::CLUSTER_NAME); - self.execute_with_result_native(sql).await.and_then(|result| { + const QUERY: &str = + const_format::formatcp!("SHOW CLUSTER {}", crate::CLUSTER_NAME); + self.execute_with_block(QUERY).await.and_then(|result| { result .data .ok_or_else(|| { @@ -989,7 +1021,7 @@ impl Client { async fn verify_or_cache_sample_schema( &self, sample: &Sample, - ) -> Result, Error> { + ) -> Result, Error> { let sample_schema = TimeseriesSchema::from(sample); let name = sample_schema.timeseries_name.clone(); let mut schema = self.schema.lock().await; @@ -1023,15 +1055,8 @@ impl Client { } } Entry::Vacant(entry) => { - let name = entry.key().clone(); entry.insert(sample_schema.clone()); - Ok(Some(( - name, - serde_json::to_string(&model::DbTimeseriesSchema::from( - sample_schema, - )) - .expect("Failed to convert schema to DB model"), - ))) + Ok(Some(sample_schema)) } } } @@ -1090,36 +1115,48 @@ impl Client { Ok(timeseries_by_key.into_values().collect()) } + // Insert data using the native TCP interface. + async fn insert_native( + &self, + sql: &str, + block: Block, + ) -> Result { + trace!( + self.log, + "executing SQL query"; + "sql" => sql, + ); + let mut handle = self.native_pool.claim().await?; + let id = usdt::UniqueId::new(); + probes::sql__query__start!(|| (&id, &sql)); + let result = handle.insert(sql, block).await.map_err(Error::from); + probes::sql__query__done!(|| (&id)); + result + } + // Execute a generic SQL statement, using the native TCP interface. - async fn execute_native(&self, sql: S) -> Result<(), Error> - where - S: Into, - { - self.execute_with_result_native(sql).await.map(|_| ()) + async fn execute_native(&self, sql: &str) -> Result<(), Error> { + self.execute_with_block(sql).await.map(|_| ()) } // Execute a generic SQL statement, returning the query result as a data // block. // // TODO-robustness This currently does no validation of the statement. - async fn execute_with_result_native( + async fn execute_with_block( &self, - sql: S, - ) -> Result - where - S: Into, - { - let sql = sql.into(); + sql: &str, + ) -> Result { trace!( self.log, "executing SQL query"; - "sql" => &sql, + "sql" => sql, ); let mut handle = self.native_pool.claim().await?; let id = usdt::UniqueId::new(); probes::sql__query__start!(|| (&id, &sql)); - let result = handle.query(sql.as_str()).await.map_err(Error::from); + let result = handle.query(sql).await.map_err(Error::from); probes::sql__query__done!(|| (&id)); result } @@ -1217,7 +1254,7 @@ impl Client { let sql = { if schema.is_empty() { format!( - "SELECT * FROM {db_name}.timeseries_schema FORMAT JSONEachRow;", + "SELECT * FROM {db_name}.timeseries_schema FORMAT Native;", db_name = crate::DATABASE_NAME, ) } else { @@ -1228,7 +1265,7 @@ impl Client { "FROM {db_name}.timeseries_schema ", "WHERE timeseries_name NOT IN ", "({current_keys}) ", - "FORMAT JSONEachRow;", + "FORMAT Native;", ), db_name = crate::DATABASE_NAME, current_keys = schema @@ -1239,21 +1276,22 @@ impl Client { ) } }; - let body = self.execute_with_body(sql).await?.1; - if body.is_empty() { + let body = self.execute_with_block(&sql).await?; + let Some(data) = body.data.as_ref() else { trace!(self.log, "no new timeseries schema in database"); - } else { - trace!(self.log, "extracting new timeseries schema"); - let new = body.lines().map(|line| { - let schema = TimeseriesSchema::from( - serde_json::from_str::(line) - .expect( - "Failed to deserialize TimeseriesSchema from database", - ), - ); - (schema.timeseries_name.clone(), schema) - }); - schema.extend(new); + return Ok(()); + }; + if data.n_rows == 0 { + trace!(self.log, "no new timeseries schema in database"); + return Ok(()); + } + trace!( + self.log, + "retrieved new timeseries schema"; + "n_schema" => data.n_rows, + ); + for new_schema in TimeseriesSchema::from_block(data)?.into_iter() { + schema.insert(new_schema.timeseries_name.clone(), new_schema); } Ok(()) } @@ -1347,7 +1385,7 @@ impl Client { "table_name" => table, "n_timeseries" => chunk.len(), ); - self.execute_native(sql).await?; + self.execute_native(&sql).await?; } } Ok(()) @@ -1787,7 +1825,7 @@ mod tests { .ping() .await .expect_err("Should fail to ping non-existent server"); - let Error::Connection(qorb::pool::Error::TimedOut) = &e else { + let Error::Connection(_) = &e else { panic!("Expected connection error, found {e:?}"); }; logctx.cleanup_successful(); @@ -3011,7 +3049,7 @@ mod tests { client: &Client, ) -> Result<(), Error> { let field = FieldValue::Bool(true); - let as_json = serde_json::Value::from(1_u64); + let as_json = serde_json::Value::from(true); test_recall_field_value_impl(field, as_json, client).await?; Ok(()) } @@ -3125,7 +3163,7 @@ mod tests { "INSERT INTO oximeter.{field_table} FORMAT JSONEachRow {row}" ); client - .execute_native(insert_sql) + .execute_native(&insert_sql) .await .expect("Failed to insert field row"); @@ -3136,7 +3174,7 @@ mod tests { crate::DATABASE_SELECT_FORMAT, ); let body = client - .execute_with_body(select_sql) + .execute_with_body(&select_sql) .await .expect("Failed to select field row") .1; @@ -3465,7 +3503,7 @@ mod tests { ); println!("Inserted row: {}", inserted_row); client - .execute_native(insert_sql) + .execute_native(&insert_sql) .await .expect("Failed to insert measurement row"); @@ -3718,7 +3756,6 @@ mod tests { _: &ClickHouseDeployment, client: Client, ) { - usdt::register_probes().unwrap(); let samples = [oximeter_test_utils::make_sample()]; client.insert_samples(&samples).await.unwrap(); @@ -3778,7 +3815,6 @@ mod tests { client: Client, ) { use strum::IntoEnumIterator; - usdt::register_probes().unwrap(); // Attempt to select all schema with each datum type. for ty in oximeter::DatumType::iter() { let sql = format!( @@ -3819,7 +3855,6 @@ mod tests { db: &ClickHouseDeployment, client: Client, ) { - usdt::register_probes().unwrap(); let samples = [oximeter_test_utils::make_sample()]; // We're using the components of the `insert_samples()` method here, @@ -4036,11 +4071,11 @@ mod tests { // table, to version 2, which adds two columns to that table in // different SQL files. client - .execute_native(format!("CREATE DATABASE {test_name};")) + .execute_native(&format!("CREATE DATABASE {test_name};")) .await .unwrap(); client - .execute_native(format!( + .execute_native(&format!( "\ CREATE TABLE {test_name}.tbl (\ `col0` UInt8 \ @@ -4085,7 +4120,7 @@ mod tests { // Check that it actually worked! let body = client - .execute_with_body(format!( + .execute_with_body(&format!( "\ SELECT name, type FROM system.columns \ WHERE database = '{test_name}' AND table = 'tbl' \ @@ -4248,11 +4283,11 @@ mod tests { // modifications over two versions, rather than as multiple schema // upgrades in one version bump. client - .execute_native(format!("CREATE DATABASE {test_name};")) + .execute_native(&format!("CREATE DATABASE {test_name};")) .await .unwrap(); client - .execute_native(format!( + .execute_native(&format!( "\ CREATE TABLE {test_name}.tbl (\ `col0` UInt8 \ @@ -4425,7 +4460,6 @@ mod tests { #[tokio::test] async fn test_select_all_field_types() { use strum::IntoEnumIterator; - usdt::register_probes().unwrap(); let logctx = test_setup_log("test_select_all_field_types"); let log = &logctx.log; @@ -4844,7 +4878,6 @@ mod tests { native_address: SocketAddr, replicated: bool, ) { - usdt::register_probes().unwrap(); let client = Client::new(http_address, native_address, &log); const STARTING_VERSION: u64 = 1; diff --git a/oximeter/db/src/client/oxql.rs b/oximeter/db/src/client/oxql.rs index d9f3295375..184e70cd44 100644 --- a/oximeter/db/src/client/oxql.rs +++ b/oximeter/db/src/client/oxql.rs @@ -1385,7 +1385,6 @@ mod tests { // fetching different sets of fields at different times. #[tokio::test] async fn test_get_entire_timeseries_and_part_of_another() { - usdt::register_probes().unwrap(); let ctx = setup_oxql_test("test_get_entire_timeseries_and_part_of_another") .await; diff --git a/oximeter/db/src/configs/replica_config.xml b/oximeter/db/src/configs/replica_config.xml index bf424185b6..7d27b51ab4 100644 --- a/oximeter/db/src/configs/replica_config.xml +++ b/oximeter/db/src/configs/replica_config.xml @@ -484,4 +484,9 @@ false false + + + + 1.0 + diff --git a/oximeter/db/src/lib.rs b/oximeter/db/src/lib.rs index 36fff6056f..3712771595 100644 --- a/oximeter/db/src/lib.rs +++ b/oximeter/db/src/lib.rs @@ -20,6 +20,8 @@ pub use oximeter::Field; pub use oximeter::FieldType; pub use oximeter::Measurement; pub use oximeter::Sample; +use parse_display::Display; +use parse_display::FromStr; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -224,7 +226,17 @@ pub struct Timeseries { } #[derive( - Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + FromStr, + Display, )] pub enum DbFieldSource { Target, diff --git a/oximeter/db/src/model/from_block.rs b/oximeter/db/src/model/from_block.rs new file mode 100644 index 0000000000..83e0f0a0e7 --- /dev/null +++ b/oximeter/db/src/model/from_block.rs @@ -0,0 +1,142 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright 2024 Oxide Computer Company + +//! Trait for deserializing an array of values from a `Block`. + +use crate::native::block::Block; +use crate::native::block::DataType; +use crate::native::block::ValueArray; +use crate::native::Error; +use oximeter::AuthzScope; +use oximeter::FieldSchema; +use oximeter::TimeseriesDescription; +use oximeter::TimeseriesSchema; +use oximeter::Units; +use std::collections::BTreeSet; +use std::num::NonZeroU8; + +/// Trait for deserializing an array of items from a ClickHouse data block. +pub trait FromBlock: Sized { + /// Deserialize an array of `Self`s from a block. + fn from_block(block: &Block) -> Result, Error>; +} + +// TODO-cleanup: This is probably a good candidate for a derive-macro, which +// expands to the code that checks that names / types in the block match those +// of the fields in the struct itself. +impl FromBlock for TimeseriesSchema { + fn from_block(block: &Block) -> Result, Error> { + if block.is_empty() { + return Ok(vec![]); + } + let n_rows = + usize::try_from(block.n_rows).map_err(|_| Error::BlockTooLarge)?; + let mut out = Vec::with_capacity(n_rows); + let ValueArray::String(timeseries_names) = + block.column_values("timeseries_name")? + else { + return Err(Error::UnexpectedColumnType); + }; + let ValueArray::Array { + values: field_names, + inner_type: DataType::String, + } = block.column_values("fields.name")? + else { + return Err(Error::UnexpectedColumnType); + }; + let ValueArray::Array { + values: field_types, + inner_type: DataType::Enum8(field_type_variants), + } = block.column_values("fields.type")? + else { + return Err(Error::UnexpectedColumnType); + }; + let ValueArray::Array { + values: field_sources, + inner_type: DataType::Enum8(field_source_variants), + } = block.column_values("fields.source")? + else { + return Err(Error::UnexpectedColumnType); + }; + let ValueArray::Enum8 { + variants: datum_type_variants, + values: datum_types, + } = block.column_values("datum_type")? + else { + return Err(Error::UnexpectedColumnType); + }; + let ValueArray::DateTime64 { values: created, .. } = + block.column_values("created")? + else { + return Err(Error::UnexpectedColumnType); + }; + + for row in 0..n_rows { + let ValueArray::String(names) = &field_names[row] else { + unreachable!(); + }; + let ValueArray::Enum8 { values: row_field_types, .. } = + &field_types[row] + else { + unreachable!(); + }; + let ValueArray::Enum8 { values: row_field_sources, .. } = + &field_sources[row] + else { + unreachable!(); + }; + let mut field_schema = BTreeSet::new(); + let n_fields = names.len(); + for field in 0..n_fields { + let schema = FieldSchema { + name: names[field].clone(), + field_type: field_type_variants[&row_field_types[field]] + .parse() + .map_err(|_| { + Error::Serde(format!( + "Failed to deserialize field type from database: {:?}", + field_type_variants[&row_field_types[field]] + )) + })?, + source: field_source_variants[&row_field_sources[field]] + .parse() + .map_err(|_| { + Error::Serde(format!( + "Failed to deserialize field source from database: {:?}", + field_source_variants[&row_field_sources[field]])) + })?, + description: String::new(), + }; + field_schema.insert(schema); + } + let schema = TimeseriesSchema { + timeseries_name: + timeseries_names[row].clone().parse().map_err(|_| { + Error::Serde(format!( + "Failed to deserialize timeseries name from database: {:?}", + ×eries_names[row] + )) + })?, + description: TimeseriesDescription::default(), + field_schema, + datum_type: datum_type_variants[&datum_types[row]] + .parse() + .map_err(|_| { + Error::Serde(format!( + "Failed to deserialize datum type from database: {:?}", + &datum_type_variants[&datum_types[row]] + )) + })?, + version: unsafe { NonZeroU8::new_unchecked(1) }, + authz_scope: AuthzScope::Fleet, + units: Units::None, + created: created[row].to_utc(), + }; + out.push(schema); + } + Ok(out) + } +} diff --git a/oximeter/db/src/model.rs b/oximeter/db/src/model/mod.rs similarity index 99% rename from oximeter/db/src/model.rs rename to oximeter/db/src/model/mod.rs index d57819b0d0..93b857f34f 100644 --- a/oximeter/db/src/model.rs +++ b/oximeter/db/src/model/mod.rs @@ -29,6 +29,8 @@ use oximeter::types::Measurement; use oximeter::types::MissingDatum; use oximeter::types::Sample; use oximeter::Quantile; +use parse_display::Display; +use parse_display::FromStr; use serde::Deserialize; use serde::Serialize; use std::collections::BTreeMap; @@ -38,6 +40,9 @@ use std::net::IpAddr; use std::net::Ipv6Addr; use uuid::Uuid; +pub mod from_block; +pub mod to_block; + /// Describes the version of the Oximeter database. /// /// For usage and details see: @@ -45,7 +50,7 @@ use uuid::Uuid; /// - [`crate::Client::initialize_db_with_version`] /// - [`crate::Client::ensure_schema`] /// - The `clickhouse-schema-updater` binary in this crate -pub const OXIMETER_VERSION: u64 = 12; +pub const OXIMETER_VERSION: u64 = 13; // Wrapper type to represent a boolean in the database. // @@ -170,7 +175,9 @@ impl From for DbTimeseriesSchema { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] +#[derive( + Clone, Copy, Debug, Serialize, Deserialize, PartialEq, FromStr, Display, +)] pub enum DbFieldType { String, I8, @@ -223,7 +230,9 @@ impl From for DbFieldType { } } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] +#[derive( + Clone, Copy, Debug, Serialize, Deserialize, PartialEq, FromStr, Display, +)] pub enum DbDatumType { Bool, I8, @@ -392,7 +401,7 @@ macro_rules! declare_field_row { } } -declare_field_row! {BoolFieldRow, DbBool, "bool"} +declare_field_row! {BoolFieldRow, bool, "bool"} declare_field_row! {I8FieldRow, i8, "i8"} declare_field_row! {U8FieldRow, u8, "u8"} declare_field_row! {I16FieldRow, i16, "i16"} @@ -630,7 +639,7 @@ fn unroll_from_source(sample: &Sample) -> BTreeMap> { timeseries_name, timeseries_key, field_name, - field_value: DbBool::from(*inner), + field_value: *inner, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } @@ -1468,7 +1477,7 @@ trait FromDbScalar { const DATUM_TYPE: DatumType; } -impl FromDbScalar for DbBool { +impl FromDbScalar for bool { const DATUM_TYPE: DatumType = DatumType::Bool; } @@ -1633,7 +1642,7 @@ pub(crate) fn parse_measurement_from_row( ) -> (TimeseriesKey, Measurement) { match datum_type { DatumType::Bool => { - parse_timeseries_scalar_gauge_measurement::(line) + parse_timeseries_scalar_gauge_measurement::(line) } DatumType::I8 => parse_timeseries_scalar_gauge_measurement::(line), DatumType::U8 => parse_timeseries_scalar_gauge_measurement::(line), @@ -1757,11 +1766,11 @@ pub(crate) fn parse_field_select_row( // Parse the field value as the expected type let value = match expected_field.field_type { FieldType::Bool => { - FieldValue::Bool(bool::from(DbBool::from( + FieldValue::Bool( actual_field_value - .as_u64() - .expect("Expected a u64 for a boolean field from the database") - ))) + .as_bool() + .expect("Expected a boolean field from the database") + ) } FieldType::I8 => { let wide = actual_field_value @@ -2071,7 +2080,7 @@ mod tests { assert_eq!(measurement.datum(), datum); } - let line = r#"{"timeseries_key": 12, "timestamp": "2021-01-01 00:00:00.123456789", "datum": 1 }"#; + let line = r#"{"timeseries_key": 12, "timestamp": "2021-01-01 00:00:00.123456789", "datum": true }"#; let datum = Datum::from(true); run_test(line, &datum, timestamp); diff --git a/oximeter/db/src/model/to_block.rs b/oximeter/db/src/model/to_block.rs new file mode 100644 index 0000000000..5a6bb91766 --- /dev/null +++ b/oximeter/db/src/model/to_block.rs @@ -0,0 +1,115 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright 2024 Oxide Computer Company + +//! Trait for serializing an array of values into a `Block`. + +use crate::native::block::Block; +use crate::native::block::Column; +use crate::native::block::DataType; +use crate::native::block::Precision; +use crate::native::block::ValueArray; +use crate::native::Error; +use chrono::TimeZone as _; +use chrono_tz::Tz; +use indexmap::IndexMap; +use oximeter::TimeseriesSchema; + +include!(concat!(env!("OUT_DIR"), "/enum_defs.rs")); + +/// Trait for serializing an array of items to a ClickHouse data block. +pub trait ToBlock: Sized { + /// Serialize an array of `Self`s to a block. + fn to_block(items: &[Self]) -> Result; +} + +// TODO-cleanup: This is probably a good candidate for a derive-macro, which +// expands to the code that checks that names / types in the block match those +// of the fields in the struct itself. +impl ToBlock for TimeseriesSchema { + fn to_block(items: &[Self]) -> Result { + let n_items = items.len(); + let mut timeseries_names = Vec::with_capacity(n_items); + let mut field_names = Vec::with_capacity(n_items); + let mut field_types = Vec::with_capacity(n_items); + let mut field_sources = Vec::with_capacity(n_items); + let mut datum_types = Vec::with_capacity(n_items); + let mut created = Vec::with_capacity(n_items); + for item in items.iter() { + timeseries_names.push(item.timeseries_name.to_string()); + let n_fields = item.field_schema.len(); + let mut row_field_names = Vec::with_capacity(n_fields); + let mut row_field_types = Vec::with_capacity(n_fields); + let mut row_field_sources = Vec::with_capacity(n_fields); + for field in item.field_schema.iter() { + row_field_names.push(field.name.clone()); + let ty = TYPE_ENUM_REV_MAP.get(&field.field_type).unwrap(); + row_field_types.push(*ty); + let src = SOURCE_ENUM_REV_MAP.get(&field.source).unwrap(); + row_field_sources.push(*src); + } + field_names.push(ValueArray::String(row_field_names)); + field_types.push(ValueArray::Enum8 { + variants: TYPE_ENUM_MAP.clone(), + values: row_field_types, + }); + field_sources.push(ValueArray::Enum8 { + variants: SOURCE_ENUM_MAP.clone(), + values: row_field_sources, + }); + datum_types + .push(*DATUM_TYPE_ENUM_REV_MAP.get(&item.datum_type).unwrap()); + created.push(Tz::UTC.from_utc_datetime(&item.created.naive_utc())); + } + Ok(Block { + name: String::new(), + info: Default::default(), + n_columns: 6, + n_rows: u64::try_from(n_items).map_err(|_| Error::BlockTooLarge)?, + columns: IndexMap::from([ + ( + String::from("timeseries_name"), + Column::from(ValueArray::String(timeseries_names)), + ), + ( + String::from("fields.name"), + Column::from(ValueArray::Array { + inner_type: DataType::String, + values: field_names, + }), + ), + ( + String::from("fields.type"), + Column::from(ValueArray::Array { + inner_type: TYPE_ENUM_DATA_TYPE.clone(), + values: field_types, + }), + ), + ( + String::from("fields.source"), + Column::from(ValueArray::Array { + inner_type: SOURCE_ENUM_DATA_TYPE.clone(), + values: field_sources, + }), + ), + ( + String::from("datum_type"), + Column::from(ValueArray::Enum8 { + variants: DATUM_TYPE_ENUM_MAP.clone(), + values: datum_types, + }), + ), + ( + String::from("created"), + Column::from(ValueArray::DateTime64 { + values: created, + precision: Precision::new(9).unwrap(), + tz: Tz::UTC, + }), + ), + ]), + }) + } +} diff --git a/oximeter/db/src/native/block.rs b/oximeter/db/src/native/block.rs index 10727b6532..e9e2dd0056 100644 --- a/oximeter/db/src/native/block.rs +++ b/oximeter/db/src/native/block.rs @@ -6,20 +6,28 @@ //! Types for working with actual blocks and columns of data. +use super::packets::server::ColumnDescription; use super::Error; use chrono::DateTime; use chrono::NaiveDate; use chrono_tz::Tz; use indexmap::IndexMap; +use nom::branch::alt; use nom::bytes::complete::tag; use nom::bytes::complete::take_while1; +use nom::character::complete::alphanumeric1; +use nom::character::complete::i8 as nom_i8; use nom::character::complete::u8 as nom_u8; +use nom::combinator::all_consuming; use nom::combinator::eof; use nom::combinator::map; use nom::combinator::map_opt; use nom::combinator::opt; +use nom::combinator::value; +use nom::multi::separated_list1; use nom::sequence::delimited; use nom::sequence::preceded; +use nom::sequence::separated_pair; use nom::sequence::tuple; use nom::IResult; use std::fmt; @@ -67,26 +75,15 @@ impl Block { self.n_columns == 0 && self.n_rows == 0 } - /// Create an empty block with the provided column names and types - pub fn empty<'a>( - types: impl IntoIterator, - ) -> Result { - let mut columns = IndexMap::new(); - let mut n_columns = 0; - for (name, type_) in types.into_iter() { - if !type_.is_supported() { - return Err(Error::UnsupportedDataType(type_.to_string())); - } - n_columns += 1; - columns.insert(name.to_string(), Column::empty(type_)); - } - Ok(Self { + /// Create an empty block. + pub fn empty() -> Self { + Self { name: String::new(), info: BlockInfo::default(), - n_columns, + n_columns: 0, n_rows: 0, - columns, - }) + columns: IndexMap::new(), + } } /// Concatenate this data block with another. @@ -106,7 +103,10 @@ impl Block { Ok(()) } - fn matches_structure(&self, block: &Block) -> bool { + /// Return true if this block matches the structure of the other. + /// + /// This means it has the same column names and types. + pub fn matches_structure(&self, block: &Block) -> bool { if self.n_columns != block.n_columns { return false; } @@ -120,6 +120,32 @@ impl Block { } true } + + /// Return the values of the named column, if it exists. + pub fn column_values(&self, name: &str) -> Result<&ValueArray, Error> { + self.columns + .get(name) + .map(|col| &col.values) + .ok_or_else(|| Error::NoSuchColumn(name.to_string())) + } + + pub(crate) fn matches_table_description( + &self, + columns: &[ColumnDescription], + ) -> bool { + if self.n_columns != columns.len() as u64 { + return false; + } + for (our_col, their_col) in self.columns.iter().zip(columns.iter()) { + if our_col.0 != &their_col.name { + return false; + } + if our_col.1.data_type != their_col.data_type { + return false; + } + } + true + } } /// Details about the block. @@ -177,6 +203,13 @@ pub struct Column { pub data_type: DataType, } +impl From for Column { + fn from(values: ValueArray) -> Self { + let data_type = values.data_type(); + Self { values, data_type } + } +} + impl Column { /// Create an empty column of the provided type. pub fn empty(data_type: DataType) -> Self { @@ -194,6 +227,16 @@ impl Column { self.values.concat(rhs.values); Ok(()) } + + /// Return true if the column is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Return the number of elements in the column. + pub fn len(&self) -> usize { + self.values.len() + } } /// An array of singly-typed data values from the server. @@ -252,7 +295,7 @@ impl ValueArray { } /// Return an empty value array of the provided type. - fn empty(data_type: &DataType) -> ValueArray { + pub fn empty(data_type: &DataType) -> ValueArray { match data_type { DataType::UInt8 => ValueArray::UInt8(vec![]), DataType::UInt16 => ValueArray::UInt16(vec![]), @@ -589,6 +632,39 @@ impl DataType { pub(crate) fn is_nullable(&self) -> bool { matches!(self, DataType::Nullable(_)) } + + /// Parse out a data type from a string. + /// + /// This is a `nom`-based function, so that the method can be used in other + /// contexts. The `DataType::from_str()` implementation is a thin wrapper + /// around this. + pub(super) fn nom_parse(s: &str) -> IResult<&str, Self> { + alt(( + value(DataType::UInt8, tag("UInt8")), + value(DataType::UInt16, tag("UInt16")), + value(DataType::UInt32, tag("UInt32")), + value(DataType::UInt64, tag("UInt64")), + value(DataType::UInt128, tag("UInt128")), + value(DataType::Int8, tag("Int8")), + value(DataType::Int16, tag("Int16")), + value(DataType::Int32, tag("Int32")), + value(DataType::Int64, tag("Int64")), + value(DataType::Int128, tag("Int128")), + value(DataType::Float32, tag("Float32")), + value(DataType::Float64, tag("Float64")), + value(DataType::String, tag("String")), + value(DataType::Uuid, tag("UUID")), + value(DataType::Ipv4, tag("IPv4")), + value(DataType::Ipv6, tag("IPv6")), + // IMPORTANT: This needs to consume all its input, otherwise we may + // parse something like `DateTime(UTC)` as `Date`, which is + // incorrect. + value(DataType::Date, all_consuming(tag("Date"))), + // These need to be nested because `alt` supports a max of 21 + // parsers, and we have 22 data types. + alt((datetime, datetime64, enum8, nullable, array)), + ))(s) + } } impl fmt::Display for DataType { @@ -632,9 +708,23 @@ impl fmt::Display for DataType { } // Parse a quoted timezone, like `'UTC'` or `'America/Los_Angeles'` +// +// Note that the quotes may optionally be escaped, like `\'UTC\'`, which is +// needed to support deserializing table descriptions, where the types for each +// column are serialized as an escaped string. fn quoted_timezone(s: &str) -> IResult<&str, Tz> { map( - delimited(tag("'"), take_while1(|c| c != '\''), tag("'")), + delimited( + preceded(opt(tag("\\")), tag("'")), + take_while1(|c: char| { + c.is_ascii_alphanumeric() + || c == '/' + || c == '+' + || c == '-' + || c == '_' + }), + preceded(opt(tag("\\")), tag("'")), + ), parse_timezone, )(s) } @@ -687,104 +777,68 @@ fn parse_timezone(s: &str) -> Tz { s.parse().unwrap_or_else(|_| *DEFAULT_TIMEZONE) } -impl std::str::FromStr for DataType { - type Err = Error; - - fn from_str(s: &str) -> Result { - // Simple scalar types. - if s == "UInt8" { - return Ok(DataType::UInt8); - } else if s == "UInt16" { - return Ok(DataType::UInt16); - } else if s == "UInt32" { - return Ok(DataType::UInt32); - } else if s == "UInt64" { - return Ok(DataType::UInt64); - } else if s == "UInt128" { - return Ok(DataType::UInt128); - } else if s == "Int8" { - return Ok(DataType::Int8); - } else if s == "Int16" { - return Ok(DataType::Int16); - } else if s == "Int32" { - return Ok(DataType::Int32); - } else if s == "Int64" { - return Ok(DataType::Int64); - } else if s == "Int128" { - return Ok(DataType::Int128); - } else if s == "Float32" { - return Ok(DataType::Float32); - } else if s == "Float64" { - return Ok(DataType::Float64); - } else if s == "String" { - return Ok(DataType::String); - } else if s == "UUID" { - return Ok(DataType::Uuid); - } else if s == "IPv4" { - return Ok(DataType::Ipv4); - } else if s == "IPv6" { - return Ok(DataType::Ipv6); - } else if s == "Date" { - return Ok(DataType::Date); - } - - // Check for datetime, possibly with a timezone. - if let Ok((_, dt)) = datetime(s) { - return Ok(dt); - }; +/// Parse an enum variant name. +fn variant_name(s: &str) -> IResult<&str, &str> { + delimited( + preceded(opt(tag("\\")), tag("'")), + alphanumeric1, + preceded(opt(tag("\\")), tag("'")), + )(s) +} - // Check for DateTime64 with precision, and possibly a timezone. - if let Ok((_, dt)) = datetime64(s) { - return Ok(dt); - }; +/// Parse a single enum variant, like `'Foo' = 1`. +/// +/// Note that the single-quotes may be escaped, which is required for parsing +/// the `ColumnDescription` type from a `TableColumns` server packet. +fn enum_variant(s: &str) -> IResult<&str, (i8, &str)> { + map(separated_pair(variant_name, tag(" = "), nom_i8), |(name, variant)| { + (variant, name) + })(s) +} - // Check for Enum8s. - // - // These are written like "Enum8('foo' = 1, 'bar' = 2)" - if let Some(suffix) = s.strip_prefix("Enum8(") { - let Some(inner) = suffix.strip_suffix(")") else { - return Err(Error::UnsupportedDataType(s.to_string())); - }; +/// Parse an `Enum8` data type from a string. +pub(super) fn enum8(s: &str) -> IResult<&str, DataType> { + map( + delimited( + tag("Enum8("), + separated_list1(tag(", "), enum_variant), + tag(")"), + ), + |variants| { let mut map = IndexMap::new(); - for each in inner.split(',') { - let Some((name, value)) = each.split_once(" = ") else { - return Err(Error::UnsupportedDataType(s.to_string())); - }; - let Ok(value) = value.parse() else { - return Err(Error::UnsupportedDataType(s.to_string())); - }; - // Trim whitespace from the name and strip any single-quotes. - let name = name.trim().trim_matches('\'').to_string(); - map.insert(value, name.to_string()); - } - return Ok(DataType::Enum8(map)); - } + for (variant, name) in variants.into_iter() { + map.insert(variant, name.to_string()); + } + DataType::Enum8(map) + }, + )(s) +} - // Recurse for nullable types. - if let Some(suffix) = s.strip_prefix("Nullable(") { - let Some(inner) = suffix.strip_suffix(')') else { - return Err(Error::UnsupportedDataType(s.to_string())); - }; - return inner - .parse() - .map(|inner| DataType::Nullable(Box::new(inner))); - } +fn nullable(s: &str) -> IResult<&str, DataType> { + map(delimited(tag("Nullable("), DataType::nom_parse, tag(")")), |inner| { + DataType::Nullable(Box::new(inner)) + })(s) +} - // And for arrays. - if let Some(suffix) = s.strip_prefix("Array(") { - let Some(inner) = suffix.strip_suffix(')') else { - return Err(Error::UnsupportedDataType(s.to_string())); - }; - return inner.parse().map(|inner| DataType::Array(Box::new(inner))); - } +fn array(s: &str) -> IResult<&str, DataType> { + map(delimited(tag("Array("), DataType::nom_parse, tag(")")), |inner| { + DataType::Array(Box::new(inner)) + })(s) +} - // Anything else is unsupported for now. - Err(Error::UnsupportedDataType(s.to_string())) +impl std::str::FromStr for DataType { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::nom_parse(s) + .map(|(_, parsed)| parsed) + .map_err(|_| Error::UnsupportedDataType(s.to_string())) } } #[cfg(test)] mod tests { + use super::enum8; use super::Block; use super::BlockInfo; use super::Column; @@ -794,6 +848,8 @@ mod tests { use super::DEFAULT_TIMEZONE; use crate::native::block::datetime; use crate::native::block::datetime64; + use crate::native::block::enum_variant; + use crate::native::block::quoted_timezone; use chrono::SubsecRound as _; use chrono::Utc; use chrono_tz::Tz; @@ -933,6 +989,14 @@ mod tests { assert!(datetime64("DateTime64(1,'UTC')").is_err()); } + #[test] + fn parse_escaped_date_time64() { + assert_eq!( + DataType::DateTime64(Precision(1), Tz::UTC), + datetime64(r#"DateTime64(1, \'UTC\')"#).unwrap().1 + ); + } + #[test] fn concat_blocks() { let data = vec![0, 1]; @@ -955,4 +1019,73 @@ mod tests { ValueArray::UInt64([data.as_slice(), data.as_slice()].concat()) ); } + + #[test] + fn test_parse_enum_variant() { + assert_eq!(enum_variant("'Foo' = 1'").unwrap().1, (1, "Foo"),); + assert_eq!(enum_variant("\\'Foo\\' = 1'").unwrap().1, (1, "Foo"),); + + enum_variant("'Foo'").unwrap_err(); + enum_variant("'Foo' = ").unwrap_err(); + enum_variant("'Foo' = x").unwrap_err(); + enum_variant("\"Foo\" = 1").unwrap_err(); + } + + #[test] + fn test_parse_enum8() { + let parsed = enum8("Enum8('Foo' = 1, 'Bar' = 2)").unwrap().1; + let DataType::Enum8(map) = parsed else { + panic!("Expected DataType::Enum8, found {parsed:#?}"); + }; + assert_eq!(map.len(), 2); + assert_eq!(map.get(&1).unwrap(), "Foo"); + assert_eq!(map.get(&2).unwrap(), "Bar"); + } + + #[test] + fn test_parse_array_enum8_with_escapes() { + const INPUT: &str = r#"Array(Enum8(\'Bool\' = 1, \'I64\' = 2))"#; + let parsed = DataType::nom_parse(INPUT).unwrap().1; + let DataType::Array(inner) = parsed else { + panic!("Expected a `DataType::Array(_)`, found {parsed:#?}"); + }; + let DataType::Enum8(map) = &*inner else { + panic!("Expected a `DataType::Enum8(_)`, found {inner:#?}"); + }; + assert_eq!(map.len(), 2); + assert_eq!(map.get(&1).unwrap(), "Bool"); + assert_eq!(map.get(&2).unwrap(), "I64"); + } + + #[test] + fn test_parse_array_enum8_with_bad_escapes() { + DataType::nom_parse(r#"Array(Enum8(\\'Bool\' = 1, \'I64\' = 2))"#) + .expect_err("Should fail to parse data type with bad escape"); + DataType::nom_parse(r#"Array(Enum8(\t\'Bool\' = 1, \'I64\' = 2))"#) + .expect_err("Should fail to parse data type with bad escape"); + DataType::nom_parse(r#"Array(Enum8(\"Bool\' = 1, \'I64\' = 2))"#) + .expect_err("Should fail to parse data type with bad escape"); + } + + #[test] + fn test_parse_all_known_timezones() { + for tz in chrono_tz::TZ_VARIANTS.iter() { + let quoted = format!("'{}'", tz); + let Ok(out) = quoted_timezone("ed) else { + panic!("Failed to parse quoted timezone: {quoted}"); + }; + assert_eq!(&out.1, tz, "Failed to parse quoted timezone: {quoted}"); + + let escape_quoted = format!("\\'{}\\'", tz); + let Ok(out) = quoted_timezone(&escape_quoted) else { + panic!( + "Failed to parse escaped quoted timezone: {escape_quoted}" + ); + }; + assert_eq!( + &out.1, tz, + "Failed to parse escaped quoted timezone: {escape_quoted}" + ); + } + } } diff --git a/oximeter/db/src/native/connection.rs b/oximeter/db/src/native/connection.rs index 911788a91f..706dbf3331 100644 --- a/oximeter/db/src/native/connection.rs +++ b/oximeter/db/src/native/connection.rs @@ -6,6 +6,7 @@ //! A connection and pool for talking to the ClickHouse server. +use super::block::Block; use super::io::packet::client::Encoder; use super::io::packet::server::Decoder; use super::packets::client::Packet as ClientPacket; @@ -208,8 +209,34 @@ impl Connection { Ok(false) } - /// Send a SQL query, possibly with data. + /// Send a SQL query that inserts data. + pub async fn insert( + &mut self, + query: &str, + block: Block, + ) -> Result { + self.query_inner(query, Some(block)).await + } + + /// Send a SQL query, without any data. pub async fn query(&mut self, query: &str) -> Result { + self.query_inner(query, None).await + } + + // Send a SQL query, possibly with data. + // + // If data is present, it is sent after the SQL query itself. An error is + // returned if the server indicates that the block structure required by the + // insert query doesn't match that of the provided block. + // + // IMPORTANT: We do not currently validate that data is provided iff the + // query is an INSERT statement! Callers are required to ensure that they + // provide data if and only if the query requires it. + async fn query_inner( + &mut self, + query: &str, + maybe_data: Option, + ) -> Result { let mut query_result = QueryResult { id: Uuid::new_v4(), progress: Progress::default(), @@ -221,12 +248,109 @@ impl Connection { self.writer.send(ClientPacket::Query(query)).await?; probes::packet__sent!(|| "Query"); self.outstanding_query = true; + + // If we have data to send, wait for the server to send an empty block + // that describes its structure. + if let Some(block_to_insert) = maybe_data { + let res: Result<(), Error> = loop { + match self.reader.next().await { + Some(Ok(packet)) => match packet { + ServerPacket::Hello(_) + | ServerPacket::Pong + // The server should only send this after we've + // inserted our data. + | ServerPacket::EndOfStream => + { + let kind = packet.kind(); + probes::unexpected__server__packet!(|| kind); + break Err(Error::UnexpectedPacket(kind)); + } + ServerPacket::Data(block) => { + probes::data__packet__received!(|| { + ( + block.n_columns, + block.n_rows, + block + .columns + .iter() + .map(|(name, col)| { + ( + name.clone(), + col.data_type.to_string(), + ) + }) + .collect::>(), + ) + }); + + // Similar to when selecting data, the server sends + // a block with zero rows that describes the table + // structure, so any block with a non-zero number of + // rows is an error here. + if block.n_rows != 0 { + break Err(Error::ExpectedEmptyDataBlock); + } + + // Don't concatenate the block, but check that its + // structure matches what we're about to insert. + if !block.matches_structure(&block_to_insert) { + break Err(Error::MismatchedBlockStructure); + } + + // Finally, send the actual data block and an empty + // block to tell the server we're finished. + if let Err(e) = self + .writer + .send(ClientPacket::Data(block_to_insert)) + .await + { + break Err(e); + } + break self + .writer + .send(ClientPacket::Data(Block::empty())) + .await; + } + ServerPacket::Exception(exceptions) => { + break Err(Error::Exception { exceptions }) + } + ServerPacket::Progress(progress) => { + query_result.progress += progress + } + ServerPacket::ProfileInfo(info) => { + let _ = query_result.profile_info.replace(info); + } + ServerPacket::TableColumns(columns) => { + if !block_to_insert + .matches_table_description(&columns) + { + break Err(Error::MismatchedBlockStructure); + } + } + ServerPacket::ProfileEvents(block) => { + let _ = query_result.profile_events.replace(block); + } + }, + Some(Err(e)) => break Err(e), + None => break Err(Error::Disconnected), + } + }; + if let Err(e) = res { + self.outstanding_query = false; + return Err(e); + } + } + + // Now wait for the remainder of the query to execute. let res = loop { match self.reader.next().await { Some(Ok(packet)) => match packet { - ServerPacket::Hello(_) => { - probes::unexpected__server__packet!(|| "Hello"); - break Err(Error::UnexpectedPacket("Hello")); + ServerPacket::Hello(_) + | ServerPacket::Pong + | ServerPacket::TableColumns(_) => { + let kind = packet.kind(); + probes::unexpected__server__packet!(|| kind); + break Err(Error::UnexpectedPacket(kind)); } ServerPacket::Data(block) => { probes::data__packet__received!(|| { @@ -263,10 +387,6 @@ impl Connection { ServerPacket::Progress(progress) => { query_result.progress += progress } - ServerPacket::Pong => { - probes::unexpected__server__packet!(|| "Hello"); - break Err(Error::UnexpectedPacket("Pong")); - } ServerPacket::EndOfStream => break Ok(query_result), ServerPacket::ProfileInfo(info) => { let _ = query_result.profile_info.replace(info); diff --git a/oximeter/db/src/native/io/mod.rs b/oximeter/db/src/native/io/mod.rs index 999e90f4f6..1d54b13969 100644 --- a/oximeter/db/src/native/io/mod.rs +++ b/oximeter/db/src/native/io/mod.rs @@ -13,4 +13,5 @@ pub mod packet; pub mod profile_info; pub mod progress; pub mod string; +pub mod table_columns; pub mod varuint; diff --git a/oximeter/db/src/native/io/packet/client.rs b/oximeter/db/src/native/io/packet/client.rs index c8397d68a2..78e9fd09df 100644 --- a/oximeter/db/src/native/io/packet/client.rs +++ b/oximeter/db/src/native/io/packet/client.rs @@ -67,8 +67,7 @@ impl Encoder { io::string::encode("", &mut dst); // Send an empty block to signal the end of data transfer. - self.encode_block(Block::empty(std::iter::empty()).unwrap(), &mut dst) - .unwrap(); + self.encode_block(Block::empty(), &mut dst).unwrap(); } /// Encode a ClientInfo into the buffer. diff --git a/oximeter/db/src/native/io/packet/server.rs b/oximeter/db/src/native/io/packet/server.rs index 0ef6d96d4b..1fd9e2cac4 100644 --- a/oximeter/db/src/native/io/packet/server.rs +++ b/oximeter/db/src/native/io/packet/server.rs @@ -161,6 +161,20 @@ impl tokio_util::codec::Decoder for Decoder { }; Packet::ProfileInfo(info) } + Packet::TABLE_COLUMNS => { + match io::table_columns::decode(&mut buf) { + Ok(Some(columns)) => Packet::TableColumns(columns), + Ok(None) => return Ok(None), + Err(e) => { + probes::invalid__packet!(|| ( + "TableColumns", + src.len() + )); + src.clear(); + return Err(e); + } + } + } Packet::PROFILE_EVENTS => match io::block::decode(&mut buf) { // Profile events are encoded as a data block. Ok(Some(block)) => Packet::ProfileEvents(block), diff --git a/oximeter/db/src/native/io/table_columns.rs b/oximeter/db/src/native/io/table_columns.rs new file mode 100644 index 0000000000..c25b2c88a9 --- /dev/null +++ b/oximeter/db/src/native/io/table_columns.rs @@ -0,0 +1,213 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright 2024 Oxide Computer Company + +//! Decoding table column descriptions from the database. +//! +//! As is helpfully noted in the ClickHouse sources at +//! , +//! +//! > NOTE: Serialization format is insane. +//! +//! The server serializes an array of `ColumnDescription`s to help the client +//! validate data before inserting it. This is a pure-text format that includes +//! column names and types, but also comments, codecs, and TTL values. The +//! format is indeed pretty wonky, so we're using `nom` to help parse it +//! robustly. For now, we only care about the names and data types; the +//! remainder is collected as-is, without interpretation, into a generic +//! `details` field. + +use super::string; +use crate::native::block::DataType; +use crate::native::packets::server::ColumnDescription; +use crate::native::Error; +use nom::bytes::streaming::is_not; +use nom::bytes::streaming::tag; +use nom::character::streaming::u64 as nom_u64; +use nom::error::ErrorKind; +use nom::sequence::delimited; +use nom::sequence::separated_pair; +use nom::sequence::terminated; +use nom::Err as NomErr; +use nom::IResult; + +/// Decode an array of `ColumnDescription`s from a buffer. +pub fn decode( + src: &mut &[u8], +) -> Result>, Error> { + // See `src/Storages/ColumnDescription.cpp` for details of the encoding + // here, but briefly: + // + // - The packet starts with a "header" describing the table name (usually + // empty), the version, the number of columns. + // - Each column is serialized as a string, with its name; type; comment; + // compression codec; settings; statistics; and TTL + // - These are all newline delimited, with tabs prefixing each of the items + // listed above. + // + // See https://github.com/ClickHouse/ClickHouse/blob/c82cf25b3e5864bcc153cbe45adb8c6527e1ec6e/src/Server/TCPHandler.cpp#L2433 + // for more details, which is where the encoding of these values starts. + let Some(_table_name) = string::decode(src)? else { + return Ok(None); + }; + + // The column description itself is serialized as a ClickHouse + // varuint-prefixed string. Deserialize this, and then parse out the pieces + // of that parsed string. + let Some(text) = string::decode(src)? else { + return Ok(None); + }; + column_descriptions(&text) +} + +fn column_descriptions( + text: &str, +) -> Result>, Error> { + // Try to match the version "header" + let text = match tag::<_, _, (_, ErrorKind)>("columns format version: 1\n")( + text, + ) { + Ok((text, _match)) => text, + Err(NomErr::Incomplete(_)) => return Ok(None), + Err(e) => { + return Err(Error::InvalidPacket { + kind: "TableColumns", + msg: format!("failed to parse header: {e}"), + }) + } + }; + + // Match the number of columns. + let (text, n_columns) = match column_count(text) { + Ok(input) => input, + Err(NomErr::Incomplete(_)) => return Ok(None), + Err(e) => { + return Err(Error::InvalidPacket { + kind: "TableColumns", + msg: format!("failed to parse column count: {e}"), + }); + } + }; + + // Extract each column, each of which is on a separate line. + let mut out = Vec::with_capacity( + usize::try_from(n_columns).map_err(|_| Error::BlockTooLarge)?, + ); + for line in text.lines() { + let col = match column_description(line) { + Ok(out) => out.1, + Err(NomErr::Incomplete(_)) => return Ok(None), + Err(e) => { + return Err(Error::InvalidPacket { + kind: "TableColumns", + msg: format!("failed to parse description: {e}"), + }) + } + }; + out.push(col); + } + assert_eq!(out.len(), n_columns as usize); + Ok(Some(out)) +} + +// Parse out the column count from a header line like: "3 columns:\n". +fn column_count(s: &str) -> IResult<&str, u64> { + terminated(nom_u64, tag(" columns:\n"))(s) +} + +// Parse a backtick-quoted column name like: "`foo.bar`". +fn backtick_quoted_column_name(s: &str) -> IResult<&str, &str> { + delimited(tag("`"), is_not("`"), tag("`"))(s) +} + +// Parse a full column description from one line. +// +// Note that this must _not_ end in a newline, so one should use something like +// `str::lines()` and pass the result to this method. +fn column_description(s: &str) -> IResult<&str, ColumnDescription> { + let (s, (name, data_type)) = separated_pair( + backtick_quoted_column_name, + tag(" "), + DataType::nom_parse, + )(s)?; + + // At this point, we take any remaining output as the details, which may be + // empty. + Ok(( + "", + ColumnDescription { + name: name.to_string(), + data_type, + details: s.to_string(), + }, + )) +} + +#[cfg(test)] +mod tests { + use super::backtick_quoted_column_name; + use super::column_count; + use super::column_description; + use super::column_descriptions; + use super::NomErr; + use crate::native::block::DataType; + + #[test] + fn test_backtick_quoted_column_name() { + assert_eq!(backtick_quoted_column_name("`foo`").unwrap().1, "foo"); + assert_eq!( + backtick_quoted_column_name("`foo.bar`").unwrap().1, + "foo.bar" + ); + assert!(matches!( + backtick_quoted_column_name("`foo").unwrap_err(), + NomErr::Incomplete(_) + )); + } + + #[test] + fn test_column_count() { + assert_eq!(column_count("4 columns:\n").unwrap().1, 4,); + } + + #[test] + fn test_column_description_only_required_parts() { + let desc = column_description("`timeseries_name` String").unwrap().1; + assert_eq!(desc.name, "timeseries_name"); + assert_eq!(desc.data_type, DataType::String); + assert!(desc.details.is_empty()); + } + + #[test] + fn test_column_descriptions() { + static INPUT: &str = r#"columns format version: 1 +2 columns: +`timeseries_name` String +`fields.name` Array(String) +"#; + let columns = column_descriptions(&mut &*INPUT) + .expect("failed to decode column descriptions") + .expect("expected Some(_) column description"); + assert_eq!(columns.len(), 2); + assert_eq!(columns[0].name, "timeseries_name"); + assert_eq!(columns[0].data_type, DataType::String); + assert_eq!(columns[1].name, "fields.name"); + assert_eq!( + columns[1].data_type, + DataType::Array(Box::new(DataType::String)) + ); + } + + #[test] + fn test_column_description_with_default() { + static INPUT: &str = r#"`timeseries_name` String\tDEFAULT "foo""#; + let column = column_description(&mut &*INPUT) + .expect("failed to decode column description") + .1; + assert_eq!(column.name, "timeseries_name"); + assert_eq!(column.data_type, DataType::String); + assert_eq!(column.details, "\\tDEFAULT \"foo\""); + } +} diff --git a/oximeter/db/src/native/mod.rs b/oximeter/db/src/native/mod.rs index 02cd485569..6c14988a86 100644 --- a/oximeter/db/src/native/mod.rs +++ b/oximeter/db/src/native/mod.rs @@ -171,6 +171,9 @@ pub enum Error { #[error("Unrecognized server packet, kind = {0}")] UnrecognizedServerPacket(u8), + #[error("Invalid data packet kind = '{kind}', msg = {msg}")] + InvalidPacket { kind: &'static str, msg: String }, + #[error("Encountered non-UTF8 string")] NonUtf8String, @@ -205,11 +208,32 @@ pub enum Error { )] Exception { exceptions: Vec }, - #[error("Cannot concatenate blocks with mismatched structure")] + #[error( + "Mismatched data block structure when concatenating blocks or \ + inserting data blocks into the database" + )] MismatchedBlockStructure, #[error("Value out of range for corresponding ClickHouse type")] OutOfRange { type_name: String, min: String, max: String, value: String }, + + #[error("Failed to serialize / deserialize value from the database")] + Serde(String), + + #[error("No column with name '{0}'")] + NoSuchColumn(String), + + #[error("Too many rows to create block")] + TooManyRows, + + #[error("Column has unexpected type")] + UnexpectedColumnType, + + #[error("Data block is too large")] + BlockTooLarge, + + #[error("Expected an empty data block")] + ExpectedEmptyDataBlock, } /// Error codes and related constants. diff --git a/oximeter/db/src/native/packets/server.rs b/oximeter/db/src/native/packets/server.rs index 2e6be64a9d..0acf614496 100644 --- a/oximeter/db/src/native/packets/server.rs +++ b/oximeter/db/src/native/packets/server.rs @@ -6,10 +6,30 @@ //! Packets sent from the server. +use crate::native::block::Block; +use crate::native::block::DataType; use std::fmt; use std::time::Duration; -use crate::native::block::Block; +/// Description of a single column in a table. +/// +/// This is used during insertion queries. When the client sends a request to +/// insert data, the server responds with a description of all the columns in +/// the table, which the client is supposed to use to verify the data block +/// being inserted. +#[derive(Clone, Debug, PartialEq)] +pub struct ColumnDescription { + /// The name of the column. + pub name: String, + /// The type of the column. + pub data_type: DataType, + /// Other details for the column. + /// + /// This is collected as a string, but otherwise unparsed or processed. We + /// don't care about these details at this point, and do nothing with them + /// for now. + pub details: String, +} /// A packet sent from the ClickHouse server to the client. #[derive(Clone, Debug, PartialEq)] @@ -30,6 +50,8 @@ pub enum Packet { EndOfStream, /// Profiling data for a query. ProfileInfo(ProfileInfo), + /// Metadata about the columns in a table. + TableColumns(Vec), /// A data block containing profiling events during a query. ProfileEvents(Block), } @@ -42,6 +64,7 @@ impl Packet { pub const PONG: u8 = 4; pub const END_OF_STREAM: u8 = 5; pub const PROFILE_INFO: u8 = 6; + pub const TABLE_COLUMNS: u8 = 11; pub const PROFILE_EVENTS: u8 = 14; /// Return the kind of the packet as a string. @@ -54,6 +77,7 @@ impl Packet { Packet::Pong => "Pong", Packet::EndOfStream => "EndOfStream", Packet::ProfileInfo(_) => "ProfileInfo", + Packet::TableColumns(_) => "TableColumns", Packet::ProfileEvents(_) => "ProfileEvents", } } diff --git a/oximeter/db/src/shells/native.rs b/oximeter/db/src/shells/native.rs index f513435275..ae075aa02f 100644 --- a/oximeter/db/src/shells/native.rs +++ b/oximeter/db/src/shells/native.rs @@ -16,7 +16,6 @@ use tabled::{builder::Builder, settings::Style}; /// Run the native SQL shell. pub async fn shell(addr: IpAddr, port: u16) -> anyhow::Result<()> { - usdt::register_probes()?; let addr = SocketAddr::new(addr, port); let mut conn = native::Connection::new(addr) .await diff --git a/oximeter/db/tests/integration_test.rs b/oximeter/db/tests/integration_test.rs index 3a1649959e..b34c962881 100644 --- a/oximeter/db/tests/integration_test.rs +++ b/oximeter/db/tests/integration_test.rs @@ -123,7 +123,7 @@ async fn test_schemas_disjoint() -> anyhow::Result<()> { /// doesn't make much sense in an integration test. #[tokio::test] async fn test_cluster() -> anyhow::Result<()> { - usdt::register_probes().unwrap(); + usdt::register_probes().expect("Failed to register USDT probes"); let request_timeout = Duration::from_secs(15); let start = tokio::time::Instant::now(); let logctx = test_setup_log("test_cluster"); diff --git a/oximeter/oximeter/schema/dendrite.toml b/oximeter/oximeter/schema/dendrite.toml index e822069a2f..ca1497b447 100644 --- a/oximeter/oximeter/schema/dendrite.toml +++ b/oximeter/oximeter/schema/dendrite.toml @@ -18,7 +18,7 @@ versions = [ # switch for its statistics, which is why these fields are included. # Dendrite may eventually report statistics about itself, or other aspects # not related to the switch, so they belong here, not the target. - { added_in = 1, fields = [ "switch_model", "switch_revision", "switch_id", "switch_serial" ] } + { added_in = 1, fields = [ "switch_id", "switch_fab", "switch_lot", "switch_wafer", "switch_wafer_loc_x", "switch_wafer_loc_y", "switch_model", "switch_revision", "switch_serial", "switch_slot" ] } ] [fields.rack_id] @@ -53,6 +53,30 @@ description = "Revision number of the switch being managed" type = "uuid" description = "ID of the switch being managed" +[fields.switch_fab] +type = "string" +description = "Fabrication plant identifier of the switch the link is on" + +[fields.switch_lot] +type = "string" +description = "Lot number of the switch the link is on" + +[fields.switch_wafer] +type = "u8" +description = "Wafer number of the switch the link is on" + +[fields.switch_wafer_loc_x] +type = "i16" +description = "X-coordinate wafer location of the switch the link is on" + +[fields.switch_wafer_loc_y] +type = "i16" +description = "Y-coordinate wafer location of the switch the link is on" + [fields.switch_serial] type = "string" description = "Serial number of the switch being managed" + +[fields.switch_slot] +type = "u16" +description = "Slot number of the switch the link is on" diff --git a/oximeter/oximeter/schema/switch-data-link.toml b/oximeter/oximeter/schema/switch-data-link.toml index d6744e8c7f..dce4841a09 100644 --- a/oximeter/oximeter/schema/switch-data-link.toml +++ b/oximeter/oximeter/schema/switch-data-link.toml @@ -4,8 +4,9 @@ format_version = 1 name = "switch_data_link" description = "A network data link on an Oxide switch" authz_scope = "fleet" + versions = [ - { version = 1, fields = [ "rack_id", "sled_id", "sled_model", "sled_revision", "sled_serial", "switch_id", "switch_model", "switch_revision", "switch_serial" ] }, + { version = 1, fields = [ "rack_id", "sled_id", "sled_model", "sled_revision", "sled_serial", "switch_id", "switch_fab", "switch_lot", "switch_wafer", "switch_wafer_loc_x", "switch_wafer_loc_y", "switch_model", "switch_revision", "switch_serial", "switch_slot" ] }, ] [[metrics]] @@ -251,6 +252,26 @@ description = "Serial number of the sled managing the link's switch" type = "uuid" description = "ID of the switch the link is on" +[fields.switch_fab] +type = "string" +description = "Fabrication plant identifier of the switch the link is on" + +[fields.switch_lot] +type = "string" +description = "Lot number of the switch the link is on" + +[fields.switch_wafer] +type = "u8" +description = "Wafer number of the switch the link is on" + +[fields.switch_wafer_loc_x] +type = "i16" +description = "X-coordinate wafer location of the switch the link is on" + +[fields.switch_wafer_loc_y] +type = "i16" +description = "Y-coordinate wafer location of the switch the link is on" + [fields.switch_model] type = "string" description = "The model number switch the link is on" @@ -263,6 +284,10 @@ description = "Revision number of the switch the link is on" type = "string" description = "Serial number of the switch the link is on" +[fields.switch_slot] +type = "u16" +description = "Slot number of the switch the link is on" + [fields.port_id] type = "string" description = "Physical switch port the link is on" diff --git a/oximeter/types/Cargo.toml b/oximeter/types/Cargo.toml index 6d6bbc07e6..3f4ab66c2e 100644 --- a/oximeter/types/Cargo.toml +++ b/oximeter/types/Cargo.toml @@ -14,6 +14,7 @@ float-ord.workspace = true num.workspace = true omicron-common.workspace = true omicron-workspace-hack.workspace = true +parse-display.workspace = true regex.workspace = true schemars = { workspace = true, features = [ "uuid1", "bytes", "chrono" ] } serde.workspace = true diff --git a/oximeter/types/src/schema.rs b/oximeter/types/src/schema.rs index 135c77462a..3f0e8beb5c 100644 --- a/oximeter/types/src/schema.rs +++ b/oximeter/types/src/schema.rs @@ -14,6 +14,8 @@ use crate::Metric; use crate::Target; use chrono::DateTime; use chrono::Utc; +use parse_display::Display; +use parse_display::FromStr; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -63,11 +65,14 @@ impl FieldSchema { Debug, PartialEq, Eq, + Hash, PartialOrd, Ord, Deserialize, Serialize, JsonSchema, + FromStr, + Display, )] #[serde(rename_all = "snake_case")] pub enum FieldSource { diff --git a/oximeter/types/src/types.rs b/oximeter/types/src/types.rs index 60260e3649..cea48f4477 100644 --- a/oximeter/types/src/types.rs +++ b/oximeter/types/src/types.rs @@ -15,6 +15,8 @@ use chrono::DateTime; use chrono::Utc; use num::traits::One; use num::traits::Zero; +use parse_display::Display; +use parse_display::FromStr; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -40,12 +42,15 @@ use uuid::Uuid; Debug, PartialEq, Eq, + Hash, PartialOrd, Ord, JsonSchema, Serialize, Deserialize, strum::EnumIter, + FromStr, + Display, )] #[serde(rename_all = "snake_case")] pub enum FieldType { @@ -86,12 +91,6 @@ impl FieldType { } } -impl std::fmt::Display for FieldType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - macro_rules! impl_field_type_from { ($ty:ty, $variant:path) => { impl From<&$ty> for FieldType { @@ -313,6 +312,8 @@ pub struct Field { Serialize, Deserialize, strum::EnumIter, + FromStr, + Display, )] #[serde(rename_all = "snake_case")] pub enum DatumType { @@ -390,12 +391,6 @@ impl DatumType { } } -impl std::fmt::Display for DatumType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - /// A `Datum` is a single sampled data point from a metric. #[derive(Clone, Debug, PartialEq, JsonSchema, Serialize, Deserialize)] #[serde(tag = "type", content = "datum", rename_all = "snake_case")] diff --git a/package-manifest.toml b/package-manifest.toml index 4937f13362..535f1e2150 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -621,10 +621,10 @@ service_name = "propolis-server" only_for_targets.image = "standard" source.type = "prebuilt" source.repo = "propolis" -source.commit = "e0c83fd0e0760eec1af306286c50081689d11a51" +source.commit = "86101eaf80b55e7f405b5cafe9b0de0e9f331656" # The SHA256 digest is automatically posted to: # https://buildomat.eng.oxide.computer/public/file/oxidecomputer/propolis/image//propolis-server.sha256.txt -source.sha256 = "05e3b3497d340f6a6a1eda6aebdca81979c1b2e0b99411b9a20af9e35bdf07de" +source.sha256 = "8dd411d6f2db23f93c2340cce11aa194da8dcb8cfd20081a614a5722ffbfe255" output.type = "zone" [package.mg-ddm-gz] @@ -719,8 +719,8 @@ only_for_targets.image = "standard" # the other `source.*` keys. source.type = "prebuilt" source.repo = "dendrite" -source.commit = "f3810e7bc1f0d746b5e95b3aaff32e52b02dfdfa" -source.sha256 = "c1506f6f818327523e6ff3102432a2038d319338b883235664b34f9132ff676a" +source.commit = "5b1d590426971e481d927d3978e918ec25f3ad21" +source.sha256 = "b2662975bd704edfa393549d90c42c8f363037d630cab66cd9d6891bdb64f470" output.type = "zone" output.intermediate_only = true @@ -746,8 +746,8 @@ only_for_targets.image = "standard" # the other `source.*` keys. source.type = "prebuilt" source.repo = "dendrite" -source.commit = "f3810e7bc1f0d746b5e95b3aaff32e52b02dfdfa" -source.sha256 = "061d40085e733e60d7c53ebfd2a4cf64f54a856e7eb5fd4b82ac65ec6a5b847b" +source.commit = "5b1d590426971e481d927d3978e918ec25f3ad21" +source.sha256 = "7dee2af3126b35c6b7e0e22cb45c3e47f5ea8a6d98b991304c0d3e08bc341437" output.type = "zone" output.intermediate_only = true @@ -766,8 +766,8 @@ only_for_targets.image = "standard" # the other `source.*` keys. source.type = "prebuilt" source.repo = "dendrite" -source.commit = "f3810e7bc1f0d746b5e95b3aaff32e52b02dfdfa" -source.sha256 = "c6cb4c077f0ddfc78ab06e07316d1312657f95526ced60c2b8e7baf1c73ae24a" +source.commit = "5b1d590426971e481d927d3978e918ec25f3ad21" +source.sha256 = "1310877ed0a0f2fb8b764f2c7b4b45c31200c6b899e9ae8ae1e548c9ab6c8bbf" output.type = "zone" output.intermediate_only = true diff --git a/schema/crdb/README.adoc b/schema/crdb/README.adoc index 3567821ea6..a9c437bc9a 100644 --- a/schema/crdb/README.adoc +++ b/schema/crdb/README.adoc @@ -168,6 +168,34 @@ If you cannot write the data migration in SQL, you would need to figure out a different way to backfill the data before you can apply the step that adds the `NOT NULL` constraint. This is likely a substantial project +==== Changing enum variants + +Adding a new variant to an enum is straightforward: `ALTER TYPE your_type ADD VALUE IF NOT EXISTS your_new_value AFTER some_existing_value` +(or `... BEFORE some_existing_value`); for an example, see the +link:https://github.com/oxidecomputer/omicron/tree/main/schema/crdb/add-management-gateway-producer-kind[`add-management-gateway-producer-kind`] migration. + +Removing or renaming variants is more burdensome. `ALTER TYPE DROP VALUE ...` +and `ALTER TYPE RENAME VALUE ...` both exist, but they do not have clauses to +support idempotent operation, making them unsuitable for migrations. Instead, +you can use the following sequence of migration steps: + +. Create a new temporary enum with the new variants, and a different name as the old type. +. Create a new temporary column with the temporary enum type. (Adding a column supports `IF NOT EXISTS`). +. Set the values of the temporary column based on the value of the old column. +. Drop the old column. +. Drop the old type. +. Create a new enum with the new variants, and the same name as the original enum type (which we can now do, as the old type has been dropped). +. Create a new column with the same name as the original column, and the new type --- again, we can do this now as the original column has been dropped. +. Set the values of the new column based on the temporary column. +. Drop the temporary column. +. Drop the temporary type. + +For an example, see the +link:https://github.com/oxidecomputer/omicron/tree/main/schema/crdb/auto-restart-policy-v2[`auto-restart-policy-v2`] migration (whose README is the +source of this list!). The steps can be simplified some if the enum itself is +being renamed, in which case you may not need the temporary enum; see the +link:https://github.com/oxidecomputer/omicron/tree/main/schema/crdb/separate-instance-and-vmm-states[`separate-instance-and-vmm-states`] migration for an example. + ==== Renaming columns Idempotently renaming existing columns is unfortunately not possible in our diff --git a/schema/crdb/add-tx-eq/up1.sql b/schema/crdb/add-tx-eq/up1.sql new file mode 100644 index 0000000000..07be4dd657 --- /dev/null +++ b/schema/crdb/add-tx-eq/up1.sql @@ -0,0 +1,4 @@ +/* + * Add a pointer to this link's transceiver equalization config settings. + */ +ALTER TABLE omicron.public.switch_port_settings_link_config ADD COLUMN IF NOT EXISTS tx_eq_config_id UUID; diff --git a/schema/crdb/add-tx-eq/up2.sql b/schema/crdb/add-tx-eq/up2.sql new file mode 100644 index 0000000000..76c8083eaa --- /dev/null +++ b/schema/crdb/add-tx-eq/up2.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS omicron.public.tx_eq_config ( + id UUID PRIMARY KEY, + pre1 INT4, + pre2 INT4, + main INT4, + post2 INT4, + post1 INT4 +); diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index a5a36156b4..b095c4c89a 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -2847,6 +2847,7 @@ CREATE TABLE IF NOT EXISTS omicron.public.switch_port_settings_link_config ( speed omicron.public.switch_link_speed, autoneg BOOL NOT NULL DEFAULT false, lldp_link_config_id UUID, + tx_eq_config_id UUID, PRIMARY KEY (port_settings_id, link_name) ); @@ -2865,6 +2866,15 @@ CREATE TABLE IF NOT EXISTS omicron.public.lldp_link_config ( time_deleted TIMESTAMPTZ ); +CREATE TABLE IF NOT EXISTS omicron.public.tx_eq_config ( + id UUID PRIMARY KEY, + pre1 INT4, + pre2 INT4, + main INT4, + post2 INT4, + post1 INT4 +); + CREATE TYPE IF NOT EXISTS omicron.public.switch_interface_kind AS ENUM ( 'primary', 'vlan', @@ -4591,7 +4601,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - (TRUE, NOW(), NOW(), '112.0.0', NULL) + (TRUE, NOW(), NOW(), '113.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; diff --git a/schema/rss-sled-plan.json b/schema/rss-sled-plan.json index 7e8513e1e3..56fe35bcde 100644 --- a/schema/rss-sled-plan.json +++ b/schema/rss-sled-plan.json @@ -751,6 +751,17 @@ } ] }, + "tx_eq": { + "description": "TX-EQ configuration for this port", + "anyOf": [ + { + "$ref": "#/definitions/TxEqConfig" + }, + { + "type": "null" + } + ] + }, "uplink_port_fec": { "description": "Port forward error correction type.", "allOf": [ @@ -1091,6 +1102,52 @@ } ] }, + "TxEqConfig": { + "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", + "type": "object", + "properties": { + "main": { + "description": "Main tap", + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "post1": { + "description": "Post-cursor tap1", + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "post2": { + "description": "Post-cursor tap2", + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "pre1": { + "description": "Pre-cursor tap1", + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "pre2": { + "description": "Pre-cursor tap2", + "type": [ + "integer", + "null" + ], + "format": "int32" + } + } + }, "TypedUuidForSledKind": { "type": "string", "format": "uuid" diff --git a/sled-agent/Cargo.toml b/sled-agent/Cargo.toml index 360ba7f499..557dcbcb4e 100644 --- a/sled-agent/Cargo.toml +++ b/sled-agent/Cargo.toml @@ -64,12 +64,15 @@ propolis_api_types.workspace = true propolis-client.workspace = true propolis-mock-server.workspace = true # Only used by the simulated sled agent rand = { workspace = true, features = ["getrandom"] } +repo-depot-api.workspace = true +repo-depot-client.workspace = true reqwest = { workspace = true, features = ["rustls-tls", "stream"] } schemars = { workspace = true, features = ["chrono", "uuid1"] } semver.workspace = true serde.workspace = true serde_human_bytes.workspace = true serde_json = { workspace = true, features = ["raw_value"] } +sha2.workspace = true sha3.workspace = true sled-agent-api.workspace = true sled-agent-client.workspace = true @@ -105,6 +108,7 @@ opte-ioctl.workspace = true assert_matches.workspace = true expectorate.workspace = true guppy.workspace = true +hex-literal.workspace = true http.workspace = true hyper.workspace = true omicron-test-utils.workspace = true diff --git a/sled-agent/api/src/lib.rs b/sled-agent/api/src/lib.rs index e0d76a857b..b5608602f2 100644 --- a/sled-agent/api/src/lib.rs +++ b/sled-agent/api/src/lib.rs @@ -2,13 +2,15 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use std::{collections::BTreeMap, time::Duration}; +use std::collections::BTreeMap; +use std::time::Duration; use camino::Utf8PathBuf; use dropshot::{ - FreeformBody, HttpError, HttpResponseCreated, HttpResponseDeleted, - HttpResponseHeaders, HttpResponseOk, HttpResponseUpdatedNoContent, Path, - Query, RequestContext, StreamingBody, TypedBody, + FreeformBody, HttpError, HttpResponseAccepted, HttpResponseCreated, + HttpResponseDeleted, HttpResponseHeaders, HttpResponseOk, + HttpResponseUpdatedNoContent, Path, Query, RequestContext, StreamingBody, + TypedBody, }; use nexus_sled_agent_shared::inventory::{ Inventory, OmicronZonesConfig, SledRole, @@ -25,6 +27,7 @@ use omicron_common::{ DatasetsConfig, DatasetsManagementResult, DiskVariant, DisksManagementResult, OmicronPhysicalDisksConfig, }, + update::ArtifactHash, }; use omicron_uuid_kinds::{PropolisUuid, ZpoolUuid}; use schemars::JsonSchema; @@ -301,6 +304,43 @@ pub trait SledAgentApi { artifact: TypedBody, ) -> Result; + #[endpoint { + method = GET, + path = "/artifacts" + }] + async fn artifact_list( + rqctx: RequestContext, + ) -> Result>, HttpError>; + + #[endpoint { + method = POST, + path = "/artifacts/{sha256}/copy-from-depot" + }] + async fn artifact_copy_from_depot( + rqctx: RequestContext, + path_params: Path, + body: TypedBody, + ) -> Result, HttpError>; + + #[endpoint { + method = PUT, + path = "/artifacts/{sha256}" + }] + async fn artifact_put( + rqctx: RequestContext, + path_params: Path, + body: StreamingBody, + ) -> Result, HttpError>; + + #[endpoint { + method = DELETE, + path = "/artifacts/{sha256}" + }] + async fn artifact_delete( + rqctx: RequestContext, + path_params: Path, + ) -> Result; + /// Take a snapshot of a disk that is attached to an instance #[endpoint { method = POST, @@ -547,6 +587,30 @@ pub struct DiskPathParam { pub disk_id: Uuid, } +#[derive(Deserialize, JsonSchema)] +pub struct ArtifactPathParam { + pub sha256: ArtifactHash, +} + +#[derive(Deserialize, JsonSchema)] +pub struct ArtifactCopyFromDepotBody { + pub depot_base_url: String, +} + +#[derive(Serialize, JsonSchema)] +pub struct ArtifactCopyFromDepotResponse {} + +#[derive(Debug, Serialize, JsonSchema)] +pub struct ArtifactPutResponse { + /// The number of valid M.2 artifact datasets we found on the sled. There is + /// typically one of these datasets for each functional M.2. + pub datasets: usize, + + /// The number of valid writes to the M.2 artifact datasets. This should be + /// less than or equal to the number of artifact datasets. + pub successful_writes: usize, +} + #[derive(Deserialize, JsonSchema)] pub struct VmmIssueDiskSnapshotRequestPathParam { pub propolis_id: PropolisUuid, diff --git a/sled-agent/repo-depot-api/Cargo.toml b/sled-agent/repo-depot-api/Cargo.toml new file mode 100644 index 0000000000..f9fa60ad8b --- /dev/null +++ b/sled-agent/repo-depot-api/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "repo-depot-api" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[lints] +workspace = true + +[dependencies] +dropshot.workspace = true +omicron-common.workspace = true +omicron-workspace-hack.workspace = true +schemars.workspace = true +serde.workspace = true diff --git a/sled-agent/repo-depot-api/src/lib.rs b/sled-agent/repo-depot-api/src/lib.rs new file mode 100644 index 0000000000..236b9c8e7a --- /dev/null +++ b/sled-agent/repo-depot-api/src/lib.rs @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use dropshot::{FreeformBody, HttpError, HttpResponseOk, Path, RequestContext}; +use omicron_common::update::ArtifactHash; +use schemars::JsonSchema; +use serde::Deserialize; + +#[dropshot::api_description] +pub trait RepoDepotApi { + type Context; + + /// Fetch an artifact from the depot. + #[endpoint { + method = GET, + path = "/artifact/sha256/{sha256}", + }] + async fn artifact_get_by_sha256( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; +} + +#[derive(Clone, Debug, Deserialize, JsonSchema)] +pub struct ArtifactPathParams { + pub sha256: ArtifactHash, +} diff --git a/sled-agent/src/artifact_store.rs b/sled-agent/src/artifact_store.rs new file mode 100644 index 0000000000..fc0dc4a20a --- /dev/null +++ b/sled-agent/src/artifact_store.rs @@ -0,0 +1,910 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Manages TUF artifacts stored on this sled. The implementation is a very +//! basic content-addressed object store. +//! +//! GET operations are handled by the "Repo Depot" API, which is deliberately +//! a separate Dropshot service from the rest of Sled Agent. This is to avoid a +//! circular logical dependency, because we expect Sled Agent to fetch artifacts +//! it does not have from another Repo Depot that does have them (at Nexus's +//! direction). This API's implementation is also part of this module. +//! +//! POST, PUT, and DELETE operations are called by Nexus and handled by the Sled +//! Agent API. + +use std::collections::BTreeMap; +use std::io::ErrorKind; +use std::net::SocketAddrV6; +use std::str::FromStr; +use std::time::Duration; + +use camino::{Utf8Path, Utf8PathBuf}; +use camino_tempfile::{NamedUtf8TempFile, Utf8TempPath}; +use dropshot::{ + Body, ConfigDropshot, FreeformBody, HttpError, HttpResponseOk, + HttpServerStarter, Path, RequestContext, StreamingBody, +}; +use futures::{Stream, TryStreamExt}; +use http::StatusCode; +use omicron_common::address::REPO_DEPOT_PORT; +use omicron_common::disk::{DatasetKind, DatasetsConfig}; +use omicron_common::update::ArtifactHash; +use repo_depot_api::*; +use sha2::{Digest, Sha256}; +use sled_agent_api::ArtifactPutResponse; +use sled_storage::dataset::M2_ARTIFACT_DATASET; +use sled_storage::error::Error as StorageError; +use sled_storage::manager::StorageHandle; +use slog::{error, info, Logger}; +use slog_error_chain::SlogInlineError; +use tokio::fs::{File, OpenOptions}; +use tokio::io::AsyncWriteExt; + +const TEMP_SUBDIR: &str = "tmp"; + +/// Content-addressable local storage for software artifacts. +/// +/// Storage for artifacts is backed by datasets that are explicitly designated +/// for this purpose. The `T: DatasetsManager` parameter, which varies between +/// the real sled agent, the simulated sled agent, and unit tests, specifies +/// exactly which datasets are available for artifact storage. That's the only +/// thing `T` is used for. The behavior of storing artifacts as files under +/// one or more paths is identical for all callers (i.e., both the real and +/// simulated sled agents). +/// +/// A given artifact is generally stored on both datasets designated for +/// artifact storage across both M.2 devices, but we attempt to be resilient to +/// a failing or missing M.2 device. This means: +/// +/// - for PUT, we try to write to all datasets, logging errors as we go; if we +/// successfully write the artifact to at least one, we return OK. +/// - for GET, we look in each dataset until we find it. +/// - for DELETE, we attempt to delete it from each dataset, logging errors as +/// we go, and failing if we saw any errors. +#[derive(Clone)] +pub(crate) struct ArtifactStore { + log: Logger, + reqwest_client: reqwest::Client, + storage: T, +} + +impl ArtifactStore { + pub(crate) fn new(log: &Logger, storage: T) -> ArtifactStore { + ArtifactStore { + log: log.new(slog::o!("component" => "ArtifactStore")), + reqwest_client: reqwest::ClientBuilder::new() + .connect_timeout(Duration::from_secs(15)) + .read_timeout(Duration::from_secs(15)) + .build() + .unwrap(), + storage, + } + } +} + +impl ArtifactStore { + pub(crate) async fn start( + self, + sled_address: SocketAddrV6, + dropshot_config: &ConfigDropshot, + ) -> Result>, StartError> + { + // In the real sled agent, the update datasets are durable and may + // retain temporary files leaked during a crash. Upon startup, we + // attempt to remove the subdirectory we store temporary files in, + // logging an error if that fails. + // + // (This function is part of `start` instead of `new` out of + // convenience: this function already needs to be async and fallible, + // but `new` doesn't; and all the sled agent implementations that don't + // call this function also don't need to run cleanup.) + for mountpoint in self + .storage + .artifact_storage_paths() + .await + .map_err(StartError::DatasetConfig)? + { + let path = mountpoint.join(TEMP_SUBDIR); + if let Err(err) = tokio::fs::remove_dir_all(&path).await { + if err.kind() != ErrorKind::NotFound { + // We log an error here because we expect that if we are + // having disk I/O errors, something else (fmd?) will + // identify those issues and bubble them up to the operator. + // (As of writing this comment that is not true but we + // expect this to exist in the limit, and refusing to start + // Sled Agent because of a problem with a single FRU seems + // inappropriate.) + error!( + &self.log, + "Failed to remove stale temporary artifacts"; + "error" => &err, + "path" => path.as_str(), + ); + } + } + } + + let mut depot_address = sled_address; + depot_address.set_port(REPO_DEPOT_PORT); + + let log = self.log.new(o!("component" => "dropshot (Repo Depot)")); + Ok(HttpServerStarter::new( + &ConfigDropshot { + bind_address: depot_address.into(), + ..dropshot_config.clone() + }, + repo_depot_api_mod::api_description::() + .expect("registered entrypoints"), + self, + &log, + ) + .map_err(StartError::Dropshot)? + .start()) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum StartError { + #[error("Error retrieving dataset configuration")] + DatasetConfig(#[source] sled_storage::error::Error), + + #[error("Dropshot error while starting Repo Depot service")] + Dropshot(#[source] Box), +} + +macro_rules! log_and_store { + ($last_error:expr, $log:expr, $verb:literal, $path:expr, $err:expr) => {{ + error!( + $log, + concat!("Failed to ", $verb, " path"); + "error" => &$err, + "path" => $path.as_str(), + ); + $last_error = Some(Error::File { verb: $verb, path: $path, err: $err }); + }}; +} + +impl ArtifactStore { + /// GET operation (served by Repo Depot API) + /// + /// We try all datasets, returning early if we find the artifact, logging + /// errors as we go. If we don't find it we return the most recent error we + /// logged or a NotFound. + pub(crate) async fn get( + &self, + sha256: ArtifactHash, + ) -> Result { + let sha256_str = sha256.to_string(); + let mut last_error = None; + for mountpoint in self.storage.artifact_storage_paths().await? { + let path = mountpoint.join(&sha256_str); + match File::open(&path).await { + Ok(file) => { + info!( + &self.log, + "Retrieved artifact"; + "sha256" => &sha256_str, + "path" => path.as_str(), + ); + return Ok(file); + } + Err(err) if err.kind() == ErrorKind::NotFound => {} + Err(err) => { + log_and_store!(last_error, &self.log, "open", path, err); + } + } + } + Err(last_error.unwrap_or(Error::NotFound { sha256 })) + } + + /// List operation (served by Sled Agent API) + /// + /// We try all datasets, logging errors as we go; if we're experiencing I/O + /// errors, Nexus should still be aware of the artifacts we think we have. + pub(crate) async fn list( + &self, + ) -> Result, Error> { + let mut map = BTreeMap::new(); + let mut any_datasets = false; + for mountpoint in self.storage.artifact_storage_paths().await? { + any_datasets = true; + let mut read_dir = match tokio::fs::read_dir(&mountpoint).await { + Ok(read_dir) => read_dir, + Err(err) => { + error!( + &self.log, + "Failed to read dir"; + "error" => &err, + "path" => mountpoint.as_str(), + ); + continue; + } + }; + // The semantics of tokio::fs::ReadDir are weird. At least with + // `std::fs::ReadDir`, we know when the end of the iterator is, + // because `.next()` returns `Option>`; we could + // theoretically log the error and continue trying to retrieve + // elements from the iterator (but whether this makes sense to do + // is not documented and likely system-dependent). + // + // The Tokio version returns `Result>`, which + // has no indication of whether there might be more items in + // the stream! (The stream adapter in tokio-stream simply calls + // `Result::transpose()`, so in theory an error is not the end of + // the stream.) + // + // For lack of any direction we stop reading entries from the stream + // on the first error. That way we at least don't get stuck retrying + // an operation that will always fail. + loop { + match read_dir.next_entry().await { + Ok(Some(entry)) => { + if let Ok(file_name) = entry.file_name().into_string() { + if let Ok(hash) = ArtifactHash::from_str(&file_name) + { + *map.entry(hash).or_default() += 1; + } + } + } + Ok(None) => break, + Err(err) => { + error!( + &self.log, + "Failed to read dir"; + "error" => &err, + "path" => mountpoint.as_str(), + ); + break; + } + } + } + } + if any_datasets { + Ok(map) + } else { + Err(Error::NoUpdateDataset) + } + } + + /// Common implementation for all artifact write operations that creates + /// a temporary file on all datasets. Returns an [`ArtifactWriter`] that + /// can be used to write the artifact to all temporary files, then move all + /// temporary files to their final paths. + /// + /// Most errors during the write process are considered non-fatal errors, + /// which are logged instead of immediately returned. + /// + /// In this method, possible fatal errors are: + /// - No temporary files could be created. + /// - A temporary file already exists (another task is writing to this + /// artifact). + async fn writer( + &self, + sha256: ArtifactHash, + ) -> Result { + let mut files = Vec::new(); + let mut last_error = None; + let mut datasets = 0; + for mountpoint in self.storage.artifact_storage_paths().await? { + datasets += 1; + let temp_dir = mountpoint.join(TEMP_SUBDIR); + if let Err(err) = tokio::fs::create_dir(&temp_dir).await { + if err.kind() != ErrorKind::AlreadyExists { + log_and_store!( + last_error, &self.log, "create", temp_dir, err + ); + continue; + } + } + + let temp_path = + Utf8TempPath::from_path(temp_dir.join(sha256.to_string())); + let file = match OpenOptions::new() + .write(true) + .create_new(true) + .open(&temp_path) + .await + { + Ok(file) => file, + Err(err) => { + if err.kind() == ErrorKind::AlreadyExists { + return Err(Error::AlreadyInProgress { sha256 }); + } else { + let path = temp_path.to_path_buf(); + log_and_store!( + last_error, &self.log, "create", path, err + ); + continue; + } + } + }; + let file = NamedUtf8TempFile::from_parts(file, temp_path); + + files.push(Some((file, mountpoint))); + } + if files.is_empty() { + Err(last_error.unwrap_or(Error::NoUpdateDataset)) + } else { + Ok(ArtifactWriter { + datasets, + hasher: Sha256::new(), + files, + log: self.log.clone(), + sha256, + }) + } + } + + /// PUT operation (served by Sled Agent API) which takes a [`StreamingBody`] + pub(crate) async fn put_body( + &self, + sha256: ArtifactHash, + body: StreamingBody, + ) -> Result { + self.writer(sha256) + .await? + .write_stream(body.into_stream().map_err(Error::Body)) + .await + } + + /// POST operation (served by Sled Agent API) + pub(crate) async fn copy_from_depot( + &self, + sha256: ArtifactHash, + depot_base_url: &str, + ) -> Result<(), Error> { + let client = repo_depot_client::Client::new_with_client( + depot_base_url, + self.reqwest_client.clone(), + self.log.new(slog::o!( + "component" => "Repo Depot client (ArtifactStore)", + "base_url" => depot_base_url.to_owned(), + )), + ); + // Check that there's no conflict before we send the upstream request. + let writer = self.writer(sha256).await?; + let response = client + .artifact_get_by_sha256(&sha256.to_string()) + .await + .map_err(|err| Error::DepotCopy { + sha256, + base_url: depot_base_url.to_owned(), + err, + })?; + // Copy from the stream on its own task and immediately return. + let log = self.log.clone(); + let base_url = depot_base_url.to_owned(); + tokio::task::spawn(async move { + let stream = response.into_inner().into_inner().map_err(|err| { + Error::DepotCopy { + sha256, + base_url: base_url.clone(), + err: repo_depot_client::ClientError::ResponseBodyError(err), + } + }); + if let Err(err) = writer.write_stream(stream).await { + error!( + &log, + "Failed to write artifact"; + "err" => &err, + ); + } + }); + Ok(()) + } + + /// DELETE operation (served by Sled Agent API) + /// + /// We attempt to delete the artifact in all datasets, logging errors as we + /// go. If any errors occurred we return the most recent error we logged. + pub(crate) async fn delete( + &self, + sha256: ArtifactHash, + ) -> Result<(), Error> { + let sha256 = sha256.to_string(); + let mut any_datasets = false; + let mut last_error = None; + for mountpoint in self.storage.artifact_storage_paths().await? { + any_datasets = true; + let path = mountpoint.join(&sha256); + match tokio::fs::remove_file(&path).await { + Ok(()) => { + info!( + &self.log, + "Removed artifact"; + "sha256" => &sha256, + "path" => path.as_str(), + ); + } + Err(err) if err.kind() == ErrorKind::NotFound => {} + Err(err) => { + log_and_store!(last_error, &self.log, "remove", path, err); + } + } + } + if let Some(last_error) = last_error { + Err(last_error) + } else if any_datasets { + Ok(()) + } else { + // If we're here because there aren't any update datasets, we should + // report Service Unavailable instead of a successful result. + Err(Error::NoUpdateDataset) + } + } +} + +/// Abstracts over what kind of sled agent we are; each of the real sled agent, +/// simulated sled agent, and this module's unit tests have different ways of +/// keeping track of the datasets on the system. +pub(crate) trait DatasetsManager: Sync { + async fn artifact_storage_paths( + &self, + ) -> Result + '_, StorageError>; +} + +/// Iterator `.filter().map()` common to `DatasetsManager` implementations. +pub(crate) fn filter_dataset_mountpoints( + config: DatasetsConfig, + root: &Utf8Path, +) -> impl Iterator + '_ { + config + .datasets + .into_values() + .filter(|dataset| *dataset.name.dataset() == DatasetKind::Update) + .map(|dataset| dataset.name.mountpoint(root)) +} + +impl DatasetsManager for StorageHandle { + async fn artifact_storage_paths( + &self, + ) -> Result + '_, StorageError> { + // TODO: When datasets are managed by Reconfigurator (#6229), + // this should be changed to use `self.datasets_config_list()` and + // `filter_dataset_mountpoints`. + Ok(self + .get_latest_disks() + .await + .all_m2_mountpoints(M2_ARTIFACT_DATASET) + .into_iter()) + } +} + +/// Abstraction that handles writing to several temporary files. +struct ArtifactWriter { + datasets: usize, + files: Vec, Utf8PathBuf)>>, + hasher: Sha256, + log: Logger, + sha256: ArtifactHash, +} + +impl ArtifactWriter { + /// Calls [`ArtifactWriter::write`] for each chunk in the stream, then + /// [`ArtifactWriter::finalize`]. See the documentation for these functions + /// for error handling information. + async fn write_stream( + self, + stream: impl Stream, Error>>, + ) -> Result { + let writer = stream + .try_fold(self, |mut writer, chunk| async { + writer.write(chunk).await?; + Ok(writer) + }) + .await?; + writer.finalize().await + } + + /// Write `chunk` to all temporary files. + /// + /// Errors in this method are considered non-fatal errors. All errors + /// are logged. If all files have failed, this method returns the most + /// recently-seen non-fatal error as a fatal error. + async fn write(&mut self, chunk: impl AsRef<[u8]>) -> Result<(), Error> { + self.hasher.update(&chunk); + + let mut last_error = None; + for option in &mut self.files { + if let Some((mut file, mountpoint)) = option.take() { + match file.as_file_mut().write_all(chunk.as_ref()).await { + Ok(()) => { + *option = Some((file, mountpoint)); + } + Err(err) => { + let path = file.path().to_owned(); + log_and_store!( + last_error, &self.log, "write to", path, err + ); + // `file` and `final_path` are dropped here, cleaning up + // the file + } + } + } + } + + self.files.retain(Option::is_some); + if self.files.is_empty() { + Err(last_error.unwrap_or(Error::NoUpdateDataset)) + } else { + Ok(()) + } + } + + /// Rename all files to their final paths. + /// + /// Errors in this method are considered non-fatal errors. If all files have + /// failed in some way, the most recently-seen error is returned as a fatal + /// error. + async fn finalize(self) -> Result { + let digest = self.hasher.finalize(); + if digest.as_slice() != self.sha256.as_ref() { + return Err(Error::HashMismatch { + expected: self.sha256, + actual: ArtifactHash(digest.into()), + }); + } + + let mut last_error = None; + let mut successful_writes = 0; + for (mut file, mountpoint) in self.files.into_iter().flatten() { + // 1. fsync the temporary file. + if let Err(err) = file.as_file_mut().sync_all().await { + let path = file.path().to_owned(); + log_and_store!(last_error, &self.log, "sync", path, err); + continue; + } + // 2. Open the parent directory so we can fsync it. + let parent_dir = match File::open(&mountpoint).await { + Ok(dir) => dir, + Err(err) => { + log_and_store!( + last_error, &self.log, "open", mountpoint, err + ); + continue; + } + }; + // 3. Rename the temporary file. + let final_path = mountpoint.join(self.sha256.to_string()); + let moved_final_path = final_path.clone(); + if let Err(err) = tokio::task::spawn_blocking(move || { + file.persist(&moved_final_path) + }) + .await? + { + error!( + &self.log, + "Failed to rename temporary file"; + "error" => &err.error, + "from" => err.file.path().as_str(), + "to" => final_path.as_str(), + ); + last_error = Some(Error::FileRename { + from: err.file.path().to_owned(), + to: final_path, + err: err.error, + }); + continue; + } + // 4. fsync the parent directory. + if let Err(err) = parent_dir.sync_all().await { + log_and_store!(last_error, &self.log, "sync", mountpoint, err); + continue; + } + + successful_writes += 1; + } + + if successful_writes > 0 { + info!( + &self.log, + "Wrote artifact"; + "sha256" => &self.sha256.to_string(), + "datasets" => self.datasets, + "successful_writes" => successful_writes, + ); + Ok(ArtifactPutResponse { + datasets: self.datasets, + successful_writes, + }) + } else { + Err(last_error.unwrap_or(Error::NoUpdateDataset)) + } + } +} + +/// Implementation of the Repo Depot API backed by an +/// `ArtifactStore`. +enum RepoDepotImpl {} + +impl RepoDepotApi for RepoDepotImpl { + type Context = ArtifactStore; + + async fn artifact_get_by_sha256( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let sha256 = path_params.into_inner().sha256; + let file = rqctx.context().get(sha256).await?; + let file_access = hyper_staticfile::vfs::TokioFileAccess::new(file); + let file_stream = + hyper_staticfile::util::FileBytesStream::new(file_access); + let body = Body::wrap(hyper_staticfile::Body::Full(file_stream)); + Ok(HttpResponseOk(FreeformBody(body))) + } +} + +#[derive(Debug, thiserror::Error, SlogInlineError)] +pub(crate) enum Error { + #[error("Another task is already writing artifact {sha256}")] + AlreadyInProgress { sha256: ArtifactHash }, + + #[error("Error while reading request body")] + Body(dropshot::HttpError), + + #[error("Error retrieving dataset configuration")] + DatasetConfig(#[from] sled_storage::error::Error), + + #[error("Error fetching artifact {sha256} from depot at {base_url}")] + DepotCopy { + sha256: ArtifactHash, + base_url: String, + #[source] + err: repo_depot_client::ClientError, + }, + + #[error("Failed to {verb} `{path}`")] + File { + verb: &'static str, + path: Utf8PathBuf, + #[source] + err: std::io::Error, + }, + + #[error("Failed to rename `{from}` to `{to}`")] + FileRename { + from: Utf8PathBuf, + to: Utf8PathBuf, + #[source] + err: std::io::Error, + }, + + #[error("Digest mismatch: expected {expected}, actual {actual}")] + HashMismatch { expected: ArtifactHash, actual: ArtifactHash }, + + #[error("Blocking task failed")] + Join(#[from] tokio::task::JoinError), + + #[error("Artifact {sha256} not found")] + NotFound { sha256: ArtifactHash }, + + #[error("No update datasets present")] + NoUpdateDataset, +} + +impl From for HttpError { + fn from(err: Error) -> HttpError { + match err { + Error::AlreadyInProgress { .. } => HttpError::for_client_error( + None, + StatusCode::CONFLICT, + err.to_string(), + ), + Error::Body(inner) => inner, + Error::DatasetConfig(_) | Error::NoUpdateDataset => { + HttpError::for_unavail(None, err.to_string()) + } + Error::DepotCopy { .. } + | Error::File { .. } + | Error::FileRename { .. } + | Error::Join(_) => HttpError::for_internal_error(err.to_string()), + Error::HashMismatch { .. } => { + HttpError::for_bad_request(None, err.to_string()) + } + Error::NotFound { .. } => { + HttpError::for_not_found(None, err.to_string()) + } + } + } +} + +#[cfg(test)] +mod test { + use camino_tempfile::Utf8TempDir; + use futures::stream; + use hex_literal::hex; + use omicron_common::disk::{ + DatasetConfig, DatasetKind, DatasetName, DatasetsConfig, + }; + use omicron_common::update::ArtifactHash; + use omicron_common::zpool_name::ZpoolName; + use omicron_test_utils::dev::test_setup_log; + use omicron_uuid_kinds::{DatasetUuid, ZpoolUuid}; + use sled_storage::error::Error as StorageError; + use tokio::io::AsyncReadExt; + + use super::{ArtifactStore, DatasetsManager, Error}; + + struct TestBackend { + datasets: DatasetsConfig, + mountpoint_root: Utf8TempDir, + } + + impl TestBackend { + fn new(len: usize) -> TestBackend { + let mountpoint_root = camino_tempfile::tempdir().unwrap(); + + let mut datasets = DatasetsConfig::default(); + if len > 0 { + datasets.generation = datasets.generation.next(); + } + for _ in 0..len { + let dataset = DatasetConfig { + id: DatasetUuid::new_v4(), + name: DatasetName::new( + ZpoolName::new_external(ZpoolUuid::new_v4()), + DatasetKind::Update, + ), + compression: Default::default(), + quota: None, + reservation: None, + }; + let mountpoint = + dataset.name.mountpoint(mountpoint_root.path()); + std::fs::create_dir_all(mountpoint).unwrap(); + datasets.datasets.insert(dataset.id, dataset); + } + + TestBackend { datasets, mountpoint_root } + } + } + + impl DatasetsManager for TestBackend { + async fn artifact_storage_paths( + &self, + ) -> Result + '_, StorageError> + { + Ok(super::filter_dataset_mountpoints( + self.datasets.clone(), + self.mountpoint_root.path(), + )) + } + } + + const TEST_ARTIFACT: &[u8] = b"I'm an artifact!\n"; + const TEST_HASH: ArtifactHash = ArtifactHash(hex!( + "ab3581cd62f6645518f61a8e4391af6c062d5d60111edb0e51b37bd84827f5b4" + )); + + #[tokio::test] + async fn list_get_put_delete() { + let log = test_setup_log("get_put_delete"); + let backend = TestBackend::new(2); + let store = ArtifactStore::new(&log.log, backend); + + // list succeeds with an empty result + assert!(store.list().await.unwrap().is_empty()); + // get fails, because it doesn't exist yet + assert!(matches!( + store.get(TEST_HASH).await, + Err(Error::NotFound { .. }) + )); + // delete does not fail because we don't fail if the artifact is not + // present + assert!(matches!(store.delete(TEST_HASH).await, Ok(()))); + + // test several things here: + // 1. put succeeds + // 2. put is idempotent (we don't care if it clobbers a file as long as + // the hash is okay) + // 3. we don't fail trying to create TEMP_SUBDIR twice + for _ in 0..2 { + store + .writer(TEST_HASH) + .await + .unwrap() + .write_stream(stream::once(async { Ok(TEST_ARTIFACT) })) + .await + .unwrap(); + // list lists the file + assert!(store + .list() + .await + .unwrap() + .into_iter() + .eq([(TEST_HASH, 2)])); + // get succeeds, file reads back OK + let mut file = store.get(TEST_HASH).await.unwrap(); + let mut vec = Vec::new(); + file.read_to_end(&mut vec).await.unwrap(); + assert_eq!(vec, TEST_ARTIFACT); + } + + // all datasets should have the artifact + for mountpoint in store.storage.artifact_storage_paths().await.unwrap() + { + assert_eq!( + tokio::fs::read(mountpoint.join(TEST_HASH.to_string())) + .await + .unwrap(), + TEST_ARTIFACT + ); + } + + // delete succeeds and is idempotent + for _ in 0..2 { + store.delete(TEST_HASH).await.unwrap(); + // list succeeds with an empty result + assert!(store.list().await.unwrap().is_empty()); + // get now fails because it no longer exists + assert!(matches!( + store.get(TEST_HASH).await, + Err(Error::NotFound { .. }) + )); + } + + log.cleanup_successful(); + } + + #[tokio::test] + async fn no_dataset() { + // If there are no update datasets, all gets should fail with + // `Error::NotFound`, and all other operations should fail with + // `Error::NoUpdateDataset`. + + let log = test_setup_log("no_dataset"); + let backend = TestBackend::new(0); + let store = ArtifactStore::new(&log.log, backend); + + assert!(matches!( + store.writer(TEST_HASH).await, + Err(Error::NoUpdateDataset) + )); + assert!(matches!( + store.get(TEST_HASH).await, + Err(Error::NotFound { .. }) + )); + assert!(matches!(store.list().await, Err(Error::NoUpdateDataset))); + assert!(matches!( + store.delete(TEST_HASH).await, + Err(Error::NoUpdateDataset) + )); + + log.cleanup_successful(); + } + + #[tokio::test] + async fn wrong_hash() { + const ACTUAL: ArtifactHash = ArtifactHash(hex!( + "4d27a9d1ddb65e0f2350a400cf73157e42ae2ca687a4220aa0a73b9bb2d211f7" + )); + + let log = test_setup_log("wrong_hash"); + let backend = TestBackend::new(2); + let store = ArtifactStore::new(&log.log, backend); + let err = store + .writer(TEST_HASH) + .await + .unwrap() + .write_stream(stream::once(async { + Ok(b"This isn't right at all.") + })) + .await + .unwrap_err(); + match err { + Error::HashMismatch { expected, actual } => { + assert_eq!(expected, TEST_HASH); + assert_eq!(actual, ACTUAL); + } + err => panic!("wrong error: {err}"), + } + assert!(matches!( + store.get(TEST_HASH).await, + Err(Error::NotFound { .. }) + )); + + log.cleanup_successful(); + } +} diff --git a/sled-agent/src/bootstrap/early_networking.rs b/sled-agent/src/bootstrap/early_networking.rs index 9685780a0e..acfbafe61c 100644 --- a/sled-agent/src/bootstrap/early_networking.rs +++ b/sled-agent/src/bootstrap/early_networking.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, Context}; use dpd_client::types::{ - LinkCreate, LinkId, LinkSettings, PortId, PortSettings, + LinkCreate, LinkId, LinkSettings, PortId, PortSettings, TxEq, }; use dpd_client::Client as DpdClient; use futures::future; @@ -702,6 +702,13 @@ impl<'a> EarlyNetworkSetup<'a> { fec: convert_fec(&port_config.uplink_port_fec), speed: convert_speed(&port_config.uplink_port_speed), lane: Some(LinkId(0)), + tx_eq: port_config.tx_eq.map(|x| TxEq { + pre1: x.pre1, + pre2: x.pre2, + main: x.main, + post2: x.post2, + post1: x.post1, + }), }, addrs, }; diff --git a/sled-agent/src/common/disk.rs b/sled-agent/src/common/disk.rs index 7bef28ac7c..ba50d0f7c1 100644 --- a/sled-agent/src/common/disk.rs +++ b/sled-agent/src/common/disk.rs @@ -8,7 +8,6 @@ use chrono::Utc; use omicron_common::api::external::DiskState; use omicron_common::api::external::Error; use omicron_common::api::internal::nexus::DiskRuntimeState; -use propolis_client::types::DiskAttachmentState as PropolisDiskState; use sled_agent_types::disk::DiskStateRequested; use uuid::Uuid; @@ -47,15 +46,9 @@ impl DiskStates { /// Propolis. pub fn observe_transition( &mut self, - observed: &PropolisDiskState, + observed: &DiskState, ) -> Option { - let next = match observed { - PropolisDiskState::Attached(uuid) => DiskState::Attached(*uuid), - PropolisDiskState::Detached => DiskState::Detached, - PropolisDiskState::Destroyed => DiskState::Destroyed, - PropolisDiskState::Faulted => DiskState::Faulted, - }; - self.transition(next, None); + self.transition(observed.clone(), None); None } diff --git a/sled-agent/src/http_entrypoints.rs b/sled-agent/src/http_entrypoints.rs index 489fc9ab0d..cd3afc44ac 100644 --- a/sled-agent/src/http_entrypoints.rs +++ b/sled-agent/src/http_entrypoints.rs @@ -11,10 +11,10 @@ use bootstore::schemes::v0::NetworkConfig; use camino::Utf8PathBuf; use display_error_chain::DisplayErrorChain; use dropshot::{ - ApiDescription, Body, FreeformBody, HttpError, HttpResponseCreated, - HttpResponseDeleted, HttpResponseHeaders, HttpResponseOk, - HttpResponseUpdatedNoContent, Path, Query, RequestContext, StreamingBody, - TypedBody, + ApiDescription, Body, FreeformBody, HttpError, HttpResponseAccepted, + HttpResponseCreated, HttpResponseDeleted, HttpResponseHeaders, + HttpResponseOk, HttpResponseUpdatedNoContent, Path, Query, RequestContext, + StreamingBody, TypedBody, }; use nexus_sled_agent_shared::inventory::{ Inventory, OmicronZonesConfig, SledRole, @@ -31,6 +31,7 @@ use omicron_common::disk::{ DatasetsConfig, DatasetsManagementResult, DiskVariant, DisksManagementResult, M2Slot, OmicronPhysicalDisksConfig, }; +use omicron_common::update::ArtifactHash; use sled_agent_api::*; use sled_agent_types::boot_disk::{ BootDiskOsWriteStatus, BootDiskPathParams, BootDiskUpdatePathParams, @@ -401,6 +402,48 @@ impl SledAgentApi for SledAgentImpl { Ok(HttpResponseUpdatedNoContent()) } + async fn artifact_list( + rqctx: RequestContext, + ) -> Result>, HttpError> { + Ok(HttpResponseOk(rqctx.context().artifact_store().list().await?)) + } + + async fn artifact_copy_from_depot( + rqctx: RequestContext, + path_params: Path, + body: TypedBody, + ) -> Result, HttpError> + { + let sha256 = path_params.into_inner().sha256; + let depot_base_url = body.into_inner().depot_base_url; + rqctx + .context() + .artifact_store() + .copy_from_depot(sha256, &depot_base_url) + .await?; + Ok(HttpResponseAccepted(ArtifactCopyFromDepotResponse {})) + } + + async fn artifact_put( + rqctx: RequestContext, + path_params: Path, + body: StreamingBody, + ) -> Result, HttpError> { + let sha256 = path_params.into_inner().sha256; + Ok(HttpResponseOk( + rqctx.context().artifact_store().put_body(sha256, body).await?, + )) + } + + async fn artifact_delete( + rqctx: RequestContext, + path_params: Path, + ) -> Result { + let sha256 = path_params.into_inner().sha256; + rqctx.context().artifact_store().delete(sha256).await?; + Ok(HttpResponseDeleted()) + } + async fn vmm_issue_disk_snapshot_request( rqctx: RequestContext, path_params: Path, diff --git a/sled-agent/src/instance.rs b/sled-agent/src/instance.rs index be348cdbc3..4883918c36 100644 --- a/sled-agent/src/instance.rs +++ b/sled-agent/src/instance.rs @@ -418,6 +418,8 @@ struct InstanceRunner { // Properties visible to Propolis properties: propolis_client::types::InstanceProperties, + vcpus: u8, + memory_mib: u64, // The ID of the Propolis server (and zone) running this instance propolis_id: PropolisUuid, @@ -911,6 +913,8 @@ impl InstanceRunner { let request = propolis_client::types::InstanceEnsureRequest { properties: self.properties.clone(), + vcpus: self.vcpus, + memory: self.memory_mib, nics, disks: self .requested_disks @@ -1366,15 +1370,13 @@ impl Instance { id: id.into_untyped_uuid(), name: hardware.properties.hostname.to_string(), description: "Test description".to_string(), - image_id: Uuid::nil(), - bootrom_id: Uuid::nil(), - // TODO: Align the byte type w/propolis. - memory: hardware.properties.memory.to_whole_mebibytes(), - // TODO: we should probably make propolis aligned with - // InstanceCpuCount here, to avoid any casting... - vcpus: hardware.properties.ncpus.0 as u8, metadata, }, + // TODO: we should probably make propolis aligned with + // InstanceCpuCount here, to avoid any casting... + vcpus: hardware.properties.ncpus.0 as u8, + // TODO: Align the byte type w/propolis. + memory_mib: hardware.properties.memory.to_whole_mebibytes(), propolis_id, propolis_addr, vnic_allocator, diff --git a/sled-agent/src/lib.rs b/sled-agent/src/lib.rs index a2421528e2..b2d78c4a5e 100644 --- a/sled-agent/src/lib.rs +++ b/sled-agent/src/lib.rs @@ -15,6 +15,7 @@ pub mod sim; pub mod common; // Modules for the non-simulated sled agent. +pub mod artifact_store; mod backing_fs; mod boot_disk_os_writer; pub mod bootstrap; diff --git a/sled-agent/src/rack_setup/service.rs b/sled-agent/src/rack_setup/service.rs index c1e29b0e0f..2795422ee2 100644 --- a/sled-agent/src/rack_setup/service.rs +++ b/sled-agent/src/rack_setup/service.rs @@ -844,6 +844,15 @@ impl ServiceInner { management_addrs: lp.management_addrs.clone(), } }), + tx_eq: config.tx_eq.as_ref().map(|tx_eq| { + NexusTypes::TxEqConfig { + pre1: tx_eq.pre1, + pre2: tx_eq.pre2, + main: tx_eq.main, + post2: tx_eq.post2, + post1: tx_eq.post1, + } + }), }) .collect(), bgp: config diff --git a/sled-agent/src/services.rs b/sled-agent/src/services.rs index 7c4fb55151..a088627ee4 100644 --- a/sled-agent/src/services.rs +++ b/sled-agent/src/services.rs @@ -81,7 +81,7 @@ use omicron_common::address::{Ipv6Subnet, NEXUS_TECHPORT_EXTERNAL_PORT}; use omicron_common::address::{BOOTSTRAP_ARTIFACT_PORT, COCKROACH_ADMIN_PORT}; use omicron_common::api::external::Generation; use omicron_common::api::internal::shared::{ - HostPortConfig, RackNetworkConfig, + HostPortConfig, RackNetworkConfig, SledIdentifiers, }; use omicron_common::backoff::{ retry_notify, retry_policy_internal_service_aggressive, BackoffError, @@ -373,15 +373,18 @@ fn display_zone_init_errors(errors: &[(String, Box)]) -> String { /// Configuration parameters which modify the [`ServiceManager`]'s behavior. pub struct Config { /// Identifies the sled being configured - pub sled_id: Uuid, + pub sled_identifiers: SledIdentifiers, /// Identifies the revision of the sidecar to be used. pub sidecar_revision: SidecarRevision, } impl Config { - pub fn new(sled_id: Uuid, sidecar_revision: SidecarRevision) -> Self { - Self { sled_id, sidecar_revision } + pub fn new( + sled_identifiers: SledIdentifiers, + sidecar_revision: SidecarRevision, + ) -> Self { + Self { sled_identifiers, sidecar_revision } } } @@ -967,6 +970,7 @@ impl ServiceManager { .get() .expect("sled agent not started") .config + .sled_identifiers .sled_id } @@ -2670,15 +2674,42 @@ impl ServiceManager { if let Some(i) = info { dendrite_config = dendrite_config + .add_property( + "rack_id", + "astring", + &i.rack_id.to_string(), + ) .add_property( "sled_id", "astring", - &i.config.sled_id.to_string(), + &i.config + .sled_identifiers + .sled_id + .to_string(), ) .add_property( - "rack_id", + "sled_model", "astring", - &i.rack_id.to_string(), + &i.config + .sled_identifiers + .model + .to_string(), + ) + .add_property( + "sled_serial", + "astring", + &i.config + .sled_identifiers + .serial + .to_string(), + ) + .add_property( + "sled_revision", + "astring", + &i.config + .sled_identifiers + .revision + .to_string(), ); } @@ -2970,7 +3001,10 @@ impl ServiceManager { .add_property( "sled_uuid", "astring", - &i.config.sled_id.to_string(), + &i.config + .sled_identifiers + .sled_id + .to_string(), ) .add_property( "rack_uuid", @@ -3016,7 +3050,10 @@ impl ServiceManager { .add_property( "sled_uuid", "astring", - &i.config.sled_id.to_string(), + &i.config + .sled_identifiers + .sled_id + .to_string(), ) .add_property( "rack_uuid", @@ -3619,6 +3656,7 @@ impl ServiceManager { illumos_utils::zfs::Zfs::get_values( &dataset.full_name(), &["zoned", "canmount", "encryption"], + None, ) .map_err(|err| Error::GetZfsValue { zone: zone.zone_name(), @@ -4351,7 +4389,25 @@ impl ServiceManager { )?; smfh.setprop_default_instance( "config/sled_id", - info.config.sled_id, + info.config.sled_identifiers.sled_id, + )?; + smfh.setprop_default_instance( + "config/sled_model", + info.config + .sled_identifiers + .model + .to_string(), + )?; + smfh.setprop_default_instance( + "config/sled_revision", + info.config.sled_identifiers.revision, + )?; + smfh.setprop_default_instance( + "config/sled_serial", + info.config + .sled_identifiers + .serial + .to_string(), )?; } else { info!( @@ -4460,7 +4516,7 @@ impl ServiceManager { )?; smfh.setprop_default_instance( "config/sled_uuid", - info.config.sled_id, + info.config.sled_identifiers.sled_id, )?; } for address in &request.addresses { @@ -4503,7 +4559,7 @@ impl ServiceManager { )?; smfh.setprop_default_instance( "config/sled_uuid", - info.config.sled_id, + info.config.sled_identifiers.sled_id, )?; } smfh.delpropvalue_default_instance( @@ -4944,7 +5000,13 @@ mod illumos_tests { fn make_config(&self) -> Config { Config { - sled_id: Uuid::new_v4(), + sled_identifiers: SledIdentifiers { + rack_id: Uuid::new_v4(), + sled_id: Uuid::new_v4(), + model: "fake-gimlet".to_string(), + revision: 1, + serial: "fake-serial".to_string(), + }, sidecar_revision: SidecarRevision::Physical( "rev_whatever_its_a_test".to_string(), ), diff --git a/sled-agent/src/sim/artifact_store.rs b/sled-agent/src/sim/artifact_store.rs new file mode 100644 index 0000000000..d73f5a2880 --- /dev/null +++ b/sled-agent/src/sim/artifact_store.rs @@ -0,0 +1,48 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Implementation of `crate::artifact_store::StorageBackend` for our simulated +//! storage. + +use std::sync::Arc; + +use camino_tempfile::Utf8TempDir; +use futures::lock::Mutex; +use sled_storage::error::Error as StorageError; + +use super::storage::Storage; +use crate::artifact_store::DatasetsManager; + +pub(super) struct SimArtifactStorage { + root: Utf8TempDir, + backend: Arc>, +} + +impl SimArtifactStorage { + pub(super) fn new(backend: Arc>) -> SimArtifactStorage { + SimArtifactStorage { + root: camino_tempfile::tempdir().unwrap(), + backend, + } + } +} + +impl DatasetsManager for SimArtifactStorage { + async fn artifact_storage_paths( + &self, + ) -> Result + '_, StorageError> + { + let config = self + .backend + .lock() + .await + .datasets_config_list() + .await + .map_err(|_| StorageError::LedgerNotFound)?; + Ok(crate::artifact_store::filter_dataset_mountpoints( + config, + self.root.path(), + )) + } +} diff --git a/sled-agent/src/sim/disk.rs b/sled-agent/src/sim/disk.rs index 9661b1949b..f08d010ae6 100644 --- a/sled-agent/src/sim/disk.rs +++ b/sled-agent/src/sim/disk.rs @@ -18,7 +18,6 @@ use omicron_common::api::internal::nexus::ProducerEndpoint; use omicron_common::api::internal::nexus::ProducerKind; use oximeter_producer::LogConfig; use oximeter_producer::Server as ProducerServer; -use propolis_client::types::DiskAttachmentState as PropolisDiskState; use sled_agent_types::disk::DiskStateRequested; use std::net::{Ipv6Addr, SocketAddr}; use std::sync::Arc; @@ -222,18 +221,13 @@ impl Simulatable for SimDisk { fn execute_desired_transition(&mut self) -> Option { if let Some(desired) = self.state.desired() { - // These operations would typically be triggered via responses from - // Propolis, but for a simulated sled agent, this does not exist. - // - // Instead, we make transitions to new states based entirely on the - // value of "desired". let observed = match desired { DiskStateRequested::Attached(uuid) => { - PropolisDiskState::Attached(*uuid) + DiskState::Attached(*uuid) } - DiskStateRequested::Detached => PropolisDiskState::Detached, - DiskStateRequested::Destroyed => PropolisDiskState::Destroyed, - DiskStateRequested::Faulted => PropolisDiskState::Faulted, + DiskStateRequested::Detached => DiskState::Detached, + DiskStateRequested::Destroyed => DiskState::Destroyed, + DiskStateRequested::Faulted => DiskState::Faulted, }; self.state.observe_transition(&observed) } else { diff --git a/sled-agent/src/sim/http_entrypoints.rs b/sled-agent/src/sim/http_entrypoints.rs index af9b016370..fdffb249cf 100644 --- a/sled-agent/src/sim/http_entrypoints.rs +++ b/sled-agent/src/sim/http_entrypoints.rs @@ -10,6 +10,7 @@ use dropshot::endpoint; use dropshot::ApiDescription; use dropshot::FreeformBody; use dropshot::HttpError; +use dropshot::HttpResponseAccepted; use dropshot::HttpResponseCreated; use dropshot::HttpResponseDeleted; use dropshot::HttpResponseHeaders; @@ -35,6 +36,7 @@ use omicron_common::disk::DatasetsConfig; use omicron_common::disk::DatasetsManagementResult; use omicron_common::disk::DisksManagementResult; use omicron_common::disk::OmicronPhysicalDisksConfig; +use omicron_common::update::ArtifactHash; use sled_agent_api::*; use sled_agent_types::boot_disk::BootDiskOsWriteStatus; use sled_agent_types::boot_disk::BootDiskPathParams; @@ -181,6 +183,48 @@ impl SledAgentApi for SledAgentSimImpl { Ok(HttpResponseUpdatedNoContent()) } + async fn artifact_list( + rqctx: RequestContext, + ) -> Result>, HttpError> { + Ok(HttpResponseOk(rqctx.context().artifact_store().list().await?)) + } + + async fn artifact_copy_from_depot( + rqctx: RequestContext, + path_params: Path, + body: TypedBody, + ) -> Result, HttpError> + { + let sha256 = path_params.into_inner().sha256; + let depot_base_url = body.into_inner().depot_base_url; + rqctx + .context() + .artifact_store() + .copy_from_depot(sha256, &depot_base_url) + .await?; + Ok(HttpResponseAccepted(ArtifactCopyFromDepotResponse {})) + } + + async fn artifact_put( + rqctx: RequestContext, + path_params: Path, + body: StreamingBody, + ) -> Result, HttpError> { + let sha256 = path_params.into_inner().sha256; + Ok(HttpResponseOk( + rqctx.context().artifact_store().put_body(sha256, body).await?, + )) + } + + async fn artifact_delete( + rqctx: RequestContext, + path_params: Path, + ) -> Result { + let sha256 = path_params.into_inner().sha256; + rqctx.context().artifact_store().delete(sha256).await?; + Ok(HttpResponseDeleted()) + } + async fn vmm_issue_disk_snapshot_request( rqctx: RequestContext, path_params: Path, diff --git a/sled-agent/src/sim/mod.rs b/sled-agent/src/sim/mod.rs index 14d980cf79..ab3b155b36 100644 --- a/sled-agent/src/sim/mod.rs +++ b/sled-agent/src/sim/mod.rs @@ -4,6 +4,7 @@ //! Simulated sled agent implementation +mod artifact_store; mod collection; mod config; mod disk; diff --git a/sled-agent/src/sim/sled_agent.rs b/sled-agent/src/sim/sled_agent.rs index 321e9cc34f..4403116bab 100644 --- a/sled-agent/src/sim/sled_agent.rs +++ b/sled-agent/src/sim/sled_agent.rs @@ -4,12 +4,14 @@ //! Simulated sled agent implementation +use super::artifact_store::SimArtifactStorage; use super::collection::{PokeMode, SimCollection}; use super::config::Config; use super::disk::SimDisk; use super::instance::{self, SimInstance}; use super::storage::CrucibleData; use super::storage::Storage; +use crate::artifact_store::ArtifactStore; use crate::nexus::NexusClient; use crate::sim::simulatable::Simulatable; use crate::updates::UpdateManager; @@ -72,7 +74,7 @@ pub struct SledAgent { vmms: Arc>, /// collection of simulated disks, indexed by disk uuid disks: Arc>, - storage: Mutex, + storage: Arc>, updates: UpdateManager, nexus_address: SocketAddr, pub nexus_client: Arc, @@ -88,6 +90,7 @@ pub struct SledAgent { fake_zones: Mutex, instance_ensure_state_error: Mutex>, pub bootstore_network_config: Mutex, + artifacts: ArtifactStore, pub log: Logger, } @@ -165,6 +168,14 @@ impl SledAgent { }, }); + let storage = Arc::new(Mutex::new(Storage::new( + id.into_untyped_uuid(), + config.storage.ip, + storage_log, + ))); + let artifacts = + ArtifactStore::new(&log, SimArtifactStorage::new(storage.clone())); + Arc::new(SledAgent { id, ip: config.dropshot.bind_address.ip(), @@ -178,11 +189,7 @@ impl SledAgent { disk_log, sim_mode, )), - storage: Mutex::new(Storage::new( - id.into_untyped_uuid(), - config.storage.ip, - storage_log, - )), + storage, updates: UpdateManager::new(config.updates.clone()), nexus_address, nexus_client, @@ -197,6 +204,7 @@ impl SledAgent { zones: vec![], }), instance_ensure_state_error: Mutex::new(None), + artifacts, log, bootstore_network_config, }) @@ -338,14 +346,12 @@ impl SledAgent { id: propolis_id.into_untyped_uuid(), name: hardware.properties.hostname.to_string(), description: "sled-agent-sim created instance".to_string(), - image_id: Uuid::default(), - bootrom_id: Uuid::default(), - memory: hardware.properties.memory.to_whole_mebibytes(), - vcpus: hardware.properties.ncpus.0 as u8, metadata, }; let body = propolis_client::types::InstanceEnsureRequest { properties, + memory: hardware.properties.memory.to_whole_mebibytes(), + vcpus: hardware.properties.ncpus.0 as u8, nics: vec![], disks: vec![], boot_settings: None, @@ -560,6 +566,10 @@ impl SledAgent { &self.updates } + pub(super) fn artifact_store(&self) -> &ArtifactStore { + &self.artifacts + } + pub async fn vmm_count(&self) -> usize { self.vmms.size().await } diff --git a/sled-agent/src/sled_agent.rs b/sled-agent/src/sled_agent.rs index 8a5b15aaaf..953136bb95 100644 --- a/sled-agent/src/sled_agent.rs +++ b/sled-agent/src/sled_agent.rs @@ -4,6 +4,7 @@ //! Sled agent implementation +use crate::artifact_store::ArtifactStore; use crate::boot_disk_os_writer::BootDiskOsWriter; use crate::bootstrap::config::BOOTSTRAP_AGENT_RACK_INIT_PORT; use crate::bootstrap::early_networking::EarlyNetworkSetupError; @@ -167,6 +168,9 @@ pub enum Error { #[error("Expected revision to fit in a u32, but found {0}")] UnexpectedRevision(i64), + + #[error(transparent)] + RepoDepotStart(#[from] crate::artifact_store::StartError), } impl From for omicron_common::api::external::Error { @@ -360,6 +364,9 @@ struct SledAgentInner { // Component of Sled Agent responsible for managing instrumentation probes. probes: ProbeManager, + + // Component of Sled Agent responsible for managing the artifact store. + repo_depot: dropshot::HttpServer>, } impl SledAgentInner { @@ -461,7 +468,7 @@ impl SledAgent { serial: baseboard.identifier().to_string(), }; let metrics_manager = - MetricsManager::new(&log, identifiers, *sled_address.ip())?; + MetricsManager::new(&log, identifiers.clone(), *sled_address.ip())?; // Start tracking the underlay physical links. for link in underlay::find_chelsio_links(&config.data_links)? { @@ -507,10 +514,8 @@ impl SledAgent { }; let updates = UpdateManager::new(update_config); - let svc_config = services::Config::new( - request.body.id.into_untyped_uuid(), - config.sidecar_revision.clone(), - ); + let svc_config = + services::Config::new(identifiers, config.sidecar_revision.clone()); // Get our rack network config from the bootstore; we cannot proceed // until we have this, as we need to know which switches have uplinks to @@ -592,6 +597,10 @@ impl SledAgent { log.new(o!("component" => "ProbeManager")), ); + let repo_depot = ArtifactStore::new(&log, storage_manager.clone()) + .start(sled_address, &config.dropshot) + .await?; + let sled_agent = SledAgent { inner: Arc::new(SledAgentInner { id: request.body.id, @@ -614,6 +623,7 @@ impl SledAgent { bootstore: long_running_task_handles.bootstore.clone(), _metrics_manager: metrics_manager, boot_disk_os_writer: BootDiskOsWriter::new(&parent_log), + repo_depot, }), log: log.clone(), sprockets: config.sprockets.clone(), @@ -1089,6 +1099,8 @@ impl SledAgent { } /// Downloads and applies an artifact. + // TODO: This is being split into "download" (i.e. store an artifact in the + // artifact store) and "apply" (perform an update using an artifact). pub async fn update_artifact( &self, artifact: UpdateArtifactId, @@ -1100,6 +1112,10 @@ impl SledAgent { Ok(()) } + pub fn artifact_store(&self) -> &ArtifactStore { + &self.inner.repo_depot.app_private() + } + /// Issue a snapshot request for a Crucible disk attached to an instance pub async fn vmm_issue_disk_snapshot_request( &self, diff --git a/sled-agent/tests/integration_tests/early_network.rs b/sled-agent/tests/integration_tests/early_network.rs index c0ecc09f12..420ce493d0 100644 --- a/sled-agent/tests/integration_tests/early_network.rs +++ b/sled-agent/tests/integration_tests/early_network.rs @@ -154,6 +154,7 @@ fn current_config_example() -> (&'static str, EarlyNetworkConfig) { vlan_id: None, }], autoneg: true, + tx_eq: None, lldp: None, }], bgp: vec![BgpConfig { diff --git a/sled-agent/tests/output/new-rss-sled-plans/madrid-rss-sled-plan.json b/sled-agent/tests/output/new-rss-sled-plans/madrid-rss-sled-plan.json index 270d926ea8..25e470e389 100644 --- a/sled-agent/tests/output/new-rss-sled-plans/madrid-rss-sled-plan.json +++ b/sled-agent/tests/output/new-rss-sled-plans/madrid-rss-sled-plan.json @@ -144,7 +144,8 @@ "uplink_port_fec": "none", "bgp_peers": [], "autoneg": false, - "lldp": null + "lldp": null, + "tx_eq": null }, { "routes": [ @@ -167,7 +168,8 @@ "uplink_port_fec": "none", "bgp_peers": [], "autoneg": false, - "lldp": null + "lldp": null, + "tx_eq": null } ], "bgp": [], diff --git a/sled-agent/types/src/early_networking.rs b/sled-agent/types/src/early_networking.rs index 46ceb2dbbf..6faead6fe9 100644 --- a/sled-agent/types/src/early_networking.rs +++ b/sled-agent/types/src/early_networking.rs @@ -300,6 +300,7 @@ pub mod back_compat { bgp_peers: v1.bgp_peers.clone(), autoneg: v1.autoneg, lldp: None, + tx_eq: None, } } } @@ -347,6 +348,7 @@ pub mod back_compat { bgp_peers: vec![], autoneg: false, lldp: None, + tx_eq: None, } } } @@ -520,6 +522,7 @@ mod tests { autoneg: false, bgp_peers: vec![], lldp: None, + tx_eq: None, }], bgp: vec![], bfd: vec![], @@ -602,6 +605,7 @@ mod tests { autoneg: false, bgp_peers: vec![], lldp: None, + tx_eq: None, }], bgp: vec![], bfd: vec![], diff --git a/sled-storage/src/dataset.rs b/sled-storage/src/dataset.rs index a715a33a69..0f2b610c42 100644 --- a/sled-storage/src/dataset.rs +++ b/sled-storage/src/dataset.rs @@ -30,6 +30,7 @@ pub const CLUSTER_DATASET: &'static str = "cluster"; pub const CONFIG_DATASET: &'static str = "config"; pub const M2_DEBUG_DATASET: &'static str = "debug"; pub const M2_BACKING_DATASET: &'static str = "backing"; +pub const M2_ARTIFACT_DATASET: &'static str = "update"; pub const DEBUG_DATASET_QUOTA: ByteCount = if cfg!(any(test, feature = "testing")) { @@ -46,6 +47,10 @@ pub const DUMP_DATASET_QUOTA: ByteCount = ByteCount::from_gibibytes_u32(100); // passed to zfs create -o compression= pub const DUMP_DATASET_COMPRESSION: CompressionAlgorithm = CompressionAlgorithm::GzipN { level: GzipLevel::new::<9>() }; +// TODO-correctness: This value of 20 GiB is a wild guess -- given TUF repo +// sizes as of Oct 2024, it would be capable of storing about 10 distinct system +// versions. +pub const ARTIFACT_DATASET_QUOTA: ByteCount = ByteCount::from_gibibytes_u32(20); // U.2 datasets live under the encrypted dataset and inherit encryption pub const ZONE_DATASET: &'static str = "crypt/zone"; @@ -65,7 +70,7 @@ const U2_EXPECTED_DATASETS: [ExpectedDataset; U2_EXPECTED_DATASET_COUNT] = [ .compression(DUMP_DATASET_COMPRESSION), ]; -const M2_EXPECTED_DATASET_COUNT: usize = 6; +const M2_EXPECTED_DATASET_COUNT: usize = 7; const M2_EXPECTED_DATASETS: [ExpectedDataset; M2_EXPECTED_DATASET_COUNT] = [ // Stores software images. // @@ -90,6 +95,9 @@ const M2_EXPECTED_DATASETS: [ExpectedDataset; M2_EXPECTED_DATASET_COUNT] = [ ExpectedDataset::new(CONFIG_DATASET), // Store debugging data, such as service bundles. ExpectedDataset::new(M2_DEBUG_DATASET).quota(DEBUG_DATASET_QUOTA), + // Stores software artifacts (zones, OS images, Hubris images, etc.) + // extracted from TUF repos by Nexus. + ExpectedDataset::new(M2_ARTIFACT_DATASET).quota(ARTIFACT_DATASET_QUOTA), ]; // Helper type for describing expected datasets and their optional quota. diff --git a/smf/clickhouse/config.xml b/smf/clickhouse/config.xml index b5b1f9c17f..58ae5dcaf5 100644 --- a/smf/clickhouse/config.xml +++ b/smf/clickhouse/config.xml @@ -38,4 +38,8 @@ + + + 1.0 + diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index eff73799ee..a7236f5142 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -43,6 +43,7 @@ uuid.workspace = true chrono.workspace = true expectorate.workspace = true gethostname.workspace = true +serde.workspace = true [features] seed-gen = ["dep:filetime"] diff --git a/test-utils/src/dev/clickhouse.rs b/test-utils/src/dev/clickhouse.rs index 8aa7b319c4..8956bb88a7 100644 --- a/test-utils/src/dev/clickhouse.rs +++ b/test-utils/src/dev/clickhouse.rs @@ -387,6 +387,9 @@ pub enum ClickHouseError { Timeout, } +const SINGLE_NODE_CONFIG_FILE: &str = + concat!(env!("CARGO_MANIFEST_DIR"), "/../smf/clickhouse/config.xml"); + impl ClickHouseProcess { /// Start a new single node ClickHouse server listening on the provided /// ports. @@ -395,8 +398,22 @@ impl ClickHouseProcess { ports: ClickHousePorts, ) -> Result { let data_dir = ClickHouseDataDir::new(logctx)?; + + // Copy the configuration file into the test directory, so we don't + // leave the preprocessed config file hanging around. + tokio::fs::copy(SINGLE_NODE_CONFIG_FILE, data_dir.config_file_path()) + .await + .with_context(|| { + format!( + "failed to copy config file from {} to test data path {}", + SINGLE_NODE_CONFIG_FILE, + data_dir.config_file_path(), + ) + })?; let args = vec![ "server".to_string(), + "--config-file".to_string(), + data_dir.config_file_path().to_string(), "--log-file".to_string(), data_dir.log_path().to_string(), "--errorlog-file".to_string(), @@ -731,6 +748,10 @@ impl ClickHouseDataDir { self.dir.path() } + fn config_file_path(&self) -> Utf8PathBuf { + self.root_path().join("config.xml") + } + fn datastore_path(&self) -> Utf8PathBuf { self.dir.path().join("datastore/") } @@ -1299,10 +1320,11 @@ async fn clickhouse_ready_from_log( #[cfg(test)] mod tests { use super::{ - discover_ready, wait_for_ports, ClickHouseError, ClickHousePorts, - CLICKHOUSE_HTTP_PORT_NEEDLE, CLICKHOUSE_READY, + discover_ready, wait_for_ports, ClickHouseDeployment, ClickHouseError, + ClickHousePorts, CLICKHOUSE_HTTP_PORT_NEEDLE, CLICKHOUSE_READY, CLICKHOUSE_TCP_PORT_NEEDLE, CLICKHOUSE_TIMEOUT, }; + use crate::dev::test_setup_log; use camino_tempfile::NamedUtf8TempFile; use std::process::Stdio; use std::{io::Write, sync::Arc, time::Duration}; @@ -1458,4 +1480,39 @@ mod tests { let second = ClickHousePorts { http: 1, native: 1 }; ClickHousePorts { http: 2, native: 2 }.assert_consistent(&second); } + + #[derive(Debug, serde::Deserialize)] + struct Setting { + name: String, + value: String, + changed: u8, + } + + #[tokio::test] + async fn sparse_serialization_is_disabled() { + let logctx = test_setup_log("sparse_serialization_is_disabled"); + let mut db = + ClickHouseDeployment::new_single_node(&logctx).await.unwrap(); + let client = reqwest::Client::new(); + let url = format!("http://{}", db.http_address()); + let setting: Setting = client + .post(url) + .body( + "SELECT name, value, changed \ + FROM system.merge_tree_settings \ + WHERE name == 'ratio_of_defaults_for_sparse_serialization' \ + FORMAT JSONEachRow", + ) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + assert_eq!(setting.name, "ratio_of_defaults_for_sparse_serialization"); + assert_eq!(setting.value, "1"); + assert_eq!(setting.changed, 1); + db.cleanup().await.unwrap(); + logctx.cleanup_successful(); + } } diff --git a/tools/dendrite_openapi_version b/tools/dendrite_openapi_version index 75c57e9d29..22aa28efaa 100755 --- a/tools/dendrite_openapi_version +++ b/tools/dendrite_openapi_version @@ -1,2 +1,2 @@ -COMMIT="f3810e7bc1f0d746b5e95b3aaff32e52b02dfdfa" -SHA2="3a54305ab4b1270c9a5fb0603f481fce199f3767c174a03559ff642f7f44687e" +COMMIT="5b1d590426971e481d927d3978e918ec25f3ad21" +SHA2="bcddcd4d600f5cb9bd73754125b4263312fcabadd2a521d44355633a796c8a71" diff --git a/tools/dendrite_stub_checksums b/tools/dendrite_stub_checksums index fe420e299e..9738f78501 100644 --- a/tools/dendrite_stub_checksums +++ b/tools/dendrite_stub_checksums @@ -1,3 +1,3 @@ -CIDL_SHA256_ILLUMOS="c1506f6f818327523e6ff3102432a2038d319338b883235664b34f9132ff676a" -CIDL_SHA256_LINUX_DPD="fc9ea4dc22e761dce3aa4d252983360f799426a0c23ea8f347653664d3e2b55a" -CIDL_SHA256_LINUX_SWADM="9da0dd6c972206338971a90144b1c35e101d69aaacf26240a45cef45d828b090" +CIDL_SHA256_ILLUMOS="b2662975bd704edfa393549d90c42c8f363037d630cab66cd9d6891bdb64f470" +CIDL_SHA256_LINUX_DPD="10d356231ec5357788c5d01da14bc393be4c79dd5df45c142398ef5674e733fb" +CIDL_SHA256_LINUX_SWADM="1e6cc87d1820a4647c171479a0c0a587a892078e49e8e7485769a33e62516f40" diff --git a/tools/permslip_production b/tools/permslip_production index b985e1280c..ce73c3c6da 100644 --- a/tools/permslip_production +++ b/tools/permslip_production @@ -1,2 +1,2 @@ -926c726edca29f7134df9489fdedbdc3553d7e9ecbaf97d3be6c55dc2634f828 manifest-oxide-rot-1-v1.0.28.toml +a72a5f931bcfd3d931df407fbbba6d851165c4637adf39568a94f755966b6c9c manifest-oxide-rot-1-v1.0.30.toml 610ebce44b1fb622eb56591534fb2569340fdba9b5ba62ca1b02f0b2d2e973dc manifest-bootleby-v1.3.1.toml diff --git a/tools/permslip_staging b/tools/permslip_staging index 2882d76c6d..98d69cc43e 100644 --- a/tools/permslip_staging +++ b/tools/permslip_staging @@ -1,5 +1,5 @@ 3850f287e8f16d4b84ed157761487b64a7cea36c9a52ef5bb22f8cbbe022e1cb manifest-gimlet-v1.0.31.toml -82f68363c5f89acb8f1e9f0fdec00694d84bd69291a63f9c3c3141721a42af9a manifest-oxide-rot-1-v1.0.28.toml +949ec9036a538eb2ffa2e701c6bd6cdae1df4a3e7bd97e70a58f8ae2362620ce manifest-oxide-rot-1-v1.0.30.toml ae9ddf5f9a16a780dd25aa6259f0492ce74a792e2802c8c7c3ef9163e556fb15 manifest-psc-v1.0.30.toml bea28961a57d2cf3d123df05a1eb9ec87fdde2862b197416b0f2531b6db4f06e manifest-sidecar-v1.0.30.toml 6f8459afe22c27d5920356878e4d8d639464f39a15ce7b5b040c2d908d52a570 manifest-bootleby-v1.3.1.toml diff --git a/tufaceous-lib/src/archive.rs b/tufaceous-lib/src/archive.rs index 9440db5a4c..675cba7eee 100644 --- a/tufaceous-lib/src/archive.rs +++ b/tufaceous-lib/src/archive.rs @@ -14,7 +14,10 @@ use std::{ fmt, io::{BufReader, BufWriter, Cursor, Read, Seek}, }; -use zip::{write::FileOptions, CompressionMethod, ZipArchive, ZipWriter}; +use zip::{ + write::{FileOptions, SimpleFileOptions}, + CompressionMethod, ZipArchive, ZipWriter, +}; /// A builder for TUF repo archives. #[derive(Debug)] @@ -65,18 +68,20 @@ impl ArchiveBuilder { Ok(()) } - pub fn finish(mut self) -> Result<()> { - let zip_file = self.writer.finish().with_context(|| { - format!("error finalizing archive at `{}`", self.output_path) + pub fn finish(self) -> Result<()> { + let Self { writer, output_path } = self; + + let zip_file = writer.0.finish().with_context(|| { + format!("error finalizing archive at `{}`", output_path) })?; zip_file.into_inner().with_context(|| { - format!("error writing archive at `{}`", self.output_path) + format!("error writing archive at `{}`", output_path) })?; Ok(()) } - fn file_options() -> FileOptions { + fn file_options() -> SimpleFileOptions { // The main purpose of the zip archive is to transmit archives that are // already compressed, so there's no point trying to re-compress them. FileOptions::default().compression_method(CompressionMethod::Stored) diff --git a/wicket-common/src/example.rs b/wicket-common/src/example.rs index 3951520f01..8572b791cd 100644 --- a/wicket-common/src/example.rs +++ b/wicket-common/src/example.rs @@ -13,7 +13,7 @@ use omicron_common::{ external::AllowedSourceIps, internal::shared::{ BgpConfig, BgpPeerConfig, LldpAdminStatus, LldpPortConfig, PortFec, - PortSpeed, RouteConfig, + PortSpeed, RouteConfig, TxEqConfig, }, }, }; @@ -177,6 +177,14 @@ impl ExampleRackSetupData { management_addrs: None, }); + let tx_eq = Some(TxEqConfig { + pre1: Some(0), + pre2: Some(0), + main: Some(26), + post2: Some(0), + post1: Some(0), + }); + let switch1_port0_lldp = Some(LldpPortConfig { status: LldpAdminStatus::Enabled, chassis_id: Some("chassid id override".to_string()), @@ -190,10 +198,11 @@ impl ExampleRackSetupData { let rack_network_config = UserSpecifiedRackNetworkConfig { infra_ip_first: "172.30.0.1".parse().unwrap(), infra_ip_last: "172.30.0.10".parse().unwrap(), + #[rustfmt::skip] switch0: btreemap! { - "port0".to_owned() => UserSpecifiedPortConfig { - addresses: vec!["172.30.0.1/24".parse().unwrap()], - routes: vec![RouteConfig { + "port0".to_owned() => UserSpecifiedPortConfig { + addresses: vec!["172.30.0.1/24".parse().unwrap()], + routes: vec![RouteConfig { destination: "0.0.0.0/0".parse().unwrap(), nexthop: "172.30.0.10".parse().unwrap(), vlan_id: Some(1), @@ -202,10 +211,12 @@ impl ExampleRackSetupData { bgp_peers: switch0_port0_bgp_peers, uplink_port_speed: PortSpeed::Speed400G, uplink_port_fec: PortFec::Firecode, - lldp: switch0_port0_lldp, - autoneg: true, - }, - }, + lldp: switch0_port0_lldp, + tx_eq, + autoneg: true, + }, + }, + #[rustfmt::skip] switch1: btreemap! { // Use the same port name as in switch0 to test that it doesn't // collide. @@ -221,6 +232,7 @@ impl ExampleRackSetupData { uplink_port_speed: PortSpeed::Speed400G, uplink_port_fec: PortFec::Firecode, lldp: switch1_port0_lldp, + tx_eq, autoneg: true, }, }, diff --git a/wicket-common/src/rack_setup.rs b/wicket-common/src/rack_setup.rs index cb6b13422b..7136bc8d96 100644 --- a/wicket-common/src/rack_setup.rs +++ b/wicket-common/src/rack_setup.rs @@ -15,6 +15,7 @@ use omicron_common::api::internal::shared::LldpPortConfig; use omicron_common::api::internal::shared::PortFec; use omicron_common::api::internal::shared::PortSpeed; use omicron_common::api::internal::shared::RouteConfig; +use omicron_common::api::internal::shared::TxEqConfig; use omicron_common::api::internal::shared::UplinkAddressConfig; use omicron_common::update::ArtifactHash; use owo_colors::OwoColorize; @@ -188,6 +189,8 @@ pub struct UserSpecifiedPortConfig { pub bgp_peers: Vec, #[serde(default)] pub lldp: Option, + #[serde(default)] + pub tx_eq: Option, } /// User-specified version of [`BgpPeerConfig`]. diff --git a/wicket/src/cli/rack_setup/config_toml.rs b/wicket/src/cli/rack_setup/config_toml.rs index 92496e94d5..727f1bbaa8 100644 --- a/wicket/src/cli/rack_setup/config_toml.rs +++ b/wicket/src/cli/rack_setup/config_toml.rs @@ -322,6 +322,7 @@ fn populate_uplink_table(cfg: &UserSpecifiedPortConfig) -> Table { autoneg, bgp_peers, lldp, + tx_eq, } = cfg; let mut uplink = Table::new(); @@ -533,6 +534,26 @@ fn populate_uplink_table(cfg: &UserSpecifiedPortConfig) -> Table { uplink.insert("lldp", Item::Table(lldp)); } + if let Some(t) = tx_eq { + let mut tx_eq = Table::new(); + if let Some(x) = t.pre1 { + tx_eq.insert("pre1", i32_item(x)); + } + if let Some(x) = t.pre2 { + tx_eq.insert("pre2", i32_item(x)); + } + if let Some(x) = t.main { + tx_eq.insert("main", i32_item(x)); + } + if let Some(x) = t.post1 { + tx_eq.insert("post1", i32_item(x)); + } + if let Some(x) = t.post2 { + tx_eq.insert("post2", i32_item(x)); + } + uplink.insert("tx_eq", Item::Table(tx_eq)); + } + uplink } @@ -556,6 +577,14 @@ fn string_item(s: impl ToString) -> Item { Item::Value(string_value(s)) } +fn i32_value(i: i32) -> Value { + Value::Integer(Formatted::new(i.into())) +} + +fn i32_item(i: i32) -> Item { + Item::Value(i32_value(i)) +} + fn i64_value(i: i64) -> Value { Value::Integer(Formatted::new(i)) } diff --git a/wicket/src/ui/panes/rack_setup.rs b/wicket/src/ui/panes/rack_setup.rs index be299ef022..1828049fc3 100644 --- a/wicket/src/ui/panes/rack_setup.rs +++ b/wicket/src/ui/panes/rack_setup.rs @@ -774,6 +774,7 @@ fn rss_config_text<'a>( autoneg, bgp_peers, lldp, + tx_eq, } = uplink; let mut items = vec![ @@ -1136,6 +1137,45 @@ fn rss_config_text<'a>( items.extend(lldp); } + if let Some(t) = tx_eq { + let mut tx_eq = vec![vec![Span::styled( + " • TxEq port settings: ", + label_style, + )]]; + + if let Some(x) = t.pre1 { + tx_eq.push(vec![ + Span::styled(" • Precursor 1: ", label_style), + Span::styled(x.to_string(), ok_style), + ]) + } + if let Some(x) = t.pre2 { + tx_eq.push(vec![ + Span::styled(" • Precursor 2: ", label_style), + Span::styled(x.to_string(), ok_style), + ]) + } + if let Some(x) = t.main { + tx_eq.push(vec![ + Span::styled(" • Main cursor: ", label_style), + Span::styled(x.to_string(), ok_style), + ]) + } + if let Some(x) = t.post2 { + tx_eq.push(vec![ + Span::styled(" • Postcursor 2: ", label_style), + Span::styled(x.to_string(), ok_style), + ]) + } + if let Some(x) = t.post1 { + tx_eq.push(vec![ + Span::styled(" • Postcursor 1: ", label_style), + Span::styled(x.to_string(), ok_style), + ]) + } + items.extend(tx_eq); + } + append_list( &mut spans, Cow::from(format!("Uplink {}: ", i + 1)), diff --git a/wicket/tests/output/example_non_empty.toml b/wicket/tests/output/example_non_empty.toml index fafb31048d..d6f5e7c820 100644 --- a/wicket/tests/output/example_non_empty.toml +++ b/wicket/tests/output/example_non_empty.toml @@ -119,6 +119,13 @@ system_name = "system name override" system_description = "system description override" port_description = "port description override" +[rack_network_config.switch0.port0.tx_eq] +pre1 = 0 +pre2 = 0 +main = 26 +post1 = 0 +post2 = 0 + [rack_network_config.switch1.port0] routes = [{ nexthop = "172.33.0.10", destination = "0.0.0.0/0", vlan_id = 1 }] addresses = [{ address = "172.32.0.1/24" }] @@ -148,6 +155,13 @@ system_description = "system description override" port_description = "port description override" management_addrs = ["172.32.0.4"] +[rack_network_config.switch1.port0.tx_eq] +pre1 = 0 +pre2 = 0 +main = 26 +post1 = 0 +post2 = 0 + [[rack_network_config.bgp]] asn = 47 originate = ["10.0.0.0/16"] diff --git a/wicketd/src/preflight_check/uplink.rs b/wicketd/src/preflight_check/uplink.rs index da30cd0199..e70d9db57b 100644 --- a/wicketd/src/preflight_check/uplink.rs +++ b/wicketd/src/preflight_check/uplink.rs @@ -778,6 +778,7 @@ fn build_port_settings( fec, speed, lane: Some(LinkId(0)), + tx_eq: None, }, }, ); diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index 2be098dbc2..8ee252ef77 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -702,6 +702,7 @@ fn build_port_config( use bootstrap_agent_client::types::PortSpeed as BaPortSpeed; use bootstrap_agent_client::types::RouteConfig as BaRouteConfig; use bootstrap_agent_client::types::SwitchLocation as BaSwitchLocation; + use bootstrap_agent_client::types::TxEqConfig as BaTxEqConfig; use bootstrap_agent_client::types::UplinkAddressConfig as BaUplinkAddressConfig; use omicron_common::api::internal::shared::LldpAdminStatus; use omicron_common::api::internal::shared::PortFec; @@ -807,6 +808,13 @@ fn build_port_config( port_description: c.port_description.clone(), management_addrs: c.management_addrs.clone(), }), + tx_eq: config.tx_eq.as_ref().map(|c| BaTxEqConfig { + pre1: c.pre1, + pre2: c.pre2, + main: c.main, + post2: c.post2, + post1: c.post1, + }), } } diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 24ae59bcc7..c893112051 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -20,7 +20,7 @@ workspace = true [dependencies] ahash = { version = "0.8.11" } aho-corasick = { version = "1.1.3" } -anyhow = { version = "1.0.89", features = ["backtrace"] } +anyhow = { version = "1.0.92", features = ["backtrace"] } base16ct = { version = "0.2.0", default-features = false, features = ["alloc"] } base64 = { version = "0.22.1" } base64ct = { version = "1.6.0", default-features = false, features = ["std"] } @@ -91,7 +91,7 @@ pkcs8 = { version = "0.10.2", default-features = false, features = ["encryption" postgres-types = { version = "0.2.8", default-features = false, features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } predicates = { version = "3.1.2" } proc-macro2 = { version = "1.0.87" } -qorb = { version = "0.1.2", features = ["qtop"] } +qorb = { version = "0.2.0", features = ["qtop"] } quote = { version = "1.0.37" } rand = { version = "0.8.5", features = ["small_rng"] } regex = { version = "1.11.0" } @@ -131,11 +131,12 @@ uuid = { version = "1.10.0", features = ["serde", "v4"] } x509-cert = { version = "0.2.5" } zerocopy = { version = "0.7.35", features = ["derive", "simd"] } zeroize = { version = "1.8.1", features = ["std", "zeroize_derive"] } +zip = { version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] } [build-dependencies] ahash = { version = "0.8.11" } aho-corasick = { version = "1.1.3" } -anyhow = { version = "1.0.89", features = ["backtrace"] } +anyhow = { version = "1.0.92", features = ["backtrace"] } base16ct = { version = "0.2.0", default-features = false, features = ["alloc"] } base64 = { version = "0.22.1" } base64ct = { version = "1.6.0", default-features = false, features = ["std"] } @@ -207,7 +208,7 @@ pkcs8 = { version = "0.10.2", default-features = false, features = ["encryption" postgres-types = { version = "0.2.8", default-features = false, features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } predicates = { version = "3.1.2" } proc-macro2 = { version = "1.0.87" } -qorb = { version = "0.1.2", features = ["qtop"] } +qorb = { version = "0.2.0", features = ["qtop"] } quote = { version = "1.0.37" } rand = { version = "0.8.5", features = ["small_rng"] } regex = { version = "1.11.0" } @@ -250,6 +251,7 @@ uuid = { version = "1.10.0", features = ["serde", "v4"] } x509-cert = { version = "0.2.5" } zerocopy = { version = "0.7.35", features = ["derive", "simd"] } zeroize = { version = "1.8.1", features = ["std", "zeroize_derive"] } +zip = { version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] } [target.x86_64-unknown-linux-gnu.dependencies] cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] }