diff --git a/.github/workflows/hakari.yml b/.github/workflows/hakari.yml index c006a41f35..b5a7504066 100644 --- a/.github/workflows/hakari.yml +++ b/.github/workflows/hakari.yml @@ -24,7 +24,7 @@ jobs: with: toolchain: stable - name: Install cargo-hakari - uses: taiki-e/install-action@c1dd9c9e59427252db32b9ece987f4eebc3a021a # v2 + uses: taiki-e/install-action@21526ba3bb38834e625c185ae4f2f942f1fb8f27 # v2 with: tool: cargo-hakari - name: Check workspace-hack Cargo.toml is up-to-date diff --git a/Cargo.lock b/Cargo.lock index 108c8b182d..28d3015025 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -458,7 +458,7 @@ dependencies = [ [[package]] name = "bhyve_api" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=54398875a2125227d13827d4236dce943c019b1c#54398875a2125227d13827d4236dce943c019b1c" +source = "git+https://github.com/oxidecomputer/propolis?rev=3e1d129151c3621d28ead5c6e5760693ba6e7fec#3e1d129151c3621d28ead5c6e5760693ba6e7fec" dependencies = [ "bhyve_api_sys", "libc", @@ -468,7 +468,7 @@ dependencies = [ [[package]] name = "bhyve_api_sys" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=54398875a2125227d13827d4236dce943c019b1c#54398875a2125227d13827d4236dce943c019b1c" +source = "git+https://github.com/oxidecomputer/propolis?rev=3e1d129151c3621d28ead5c6e5760693ba6e7fec#3e1d129151c3621d28ead5c6e5760693ba6e7fec" dependencies = [ "libc", "strum", @@ -1281,7 +1281,7 @@ dependencies = [ [[package]] name = "crucible-agent-client" version = "0.0.1" -source = "git+https://github.com/oxidecomputer/crucible?rev=51a3121c8318fc7ac97d74f917ce1d37962e785f#51a3121c8318fc7ac97d74f917ce1d37962e785f" +source = "git+https://github.com/oxidecomputer/crucible?rev=945f040d259ca8013d3fb26f510453da7cd7b1a6#945f040d259ca8013d3fb26f510453da7cd7b1a6" dependencies = [ "anyhow", "chrono", @@ -1297,7 +1297,7 @@ dependencies = [ [[package]] name = "crucible-pantry-client" version = "0.0.1" -source = "git+https://github.com/oxidecomputer/crucible?rev=51a3121c8318fc7ac97d74f917ce1d37962e785f#51a3121c8318fc7ac97d74f917ce1d37962e785f" +source = "git+https://github.com/oxidecomputer/crucible?rev=945f040d259ca8013d3fb26f510453da7cd7b1a6#945f040d259ca8013d3fb26f510453da7cd7b1a6" dependencies = [ "anyhow", "chrono", @@ -1314,7 +1314,7 @@ dependencies = [ [[package]] name = "crucible-smf" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/crucible?rev=51a3121c8318fc7ac97d74f917ce1d37962e785f#51a3121c8318fc7ac97d74f917ce1d37962e785f" +source = "git+https://github.com/oxidecomputer/crucible?rev=945f040d259ca8013d3fb26f510453da7cd7b1a6#945f040d259ca8013d3fb26f510453da7cd7b1a6" dependencies = [ "crucible-workspace-hack", "libc", @@ -1671,9 +1671,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e054665eaf6d97d1e7125512bb2d35d07c73ac86cc6920174cb42d1ab697a554" +checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44" dependencies = [ "diesel_table_macro_syntax", "proc-macro2", @@ -1790,8 +1790,8 @@ dependencies = [ "omicron-test-utils", "omicron-workspace-hack", "openapi-lint", - "openapiv3 1.0.3", - "pretty-hex 0.3.0", + "openapiv3", + "pretty-hex 0.4.0", "schemars", "serde", "serde_json", @@ -1895,7 +1895,7 @@ dependencies = [ "hyper", "indexmap 2.1.0", "multer", - "openapiv3 2.0.0-rc.1", + "openapiv3", "paste", "percent-encoding", "proc-macro2", @@ -2276,9 +2276,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -3040,9 +3040,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -3245,7 +3245,7 @@ dependencies = [ "omicron-test-utils", "omicron-workspace-hack", "openapi-lint", - "openapiv3 1.0.3", + "openapiv3", "schemars", "serde", "serde_json", @@ -3993,6 +3993,7 @@ dependencies = [ "sled-agent-client", "steno", "strum", + "thiserror", "uuid", ] @@ -4038,7 +4039,7 @@ dependencies = [ "omicron-sled-agent", "omicron-test-utils", "omicron-workspace-hack", - "openapiv3 1.0.3", + "openapiv3", "openssl", "oso", "oximeter", @@ -4178,6 +4179,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "serde_with", "steno", "strum", "uuid", @@ -4563,7 +4565,7 @@ dependencies = [ "omicron-workspace-hack", "once_cell", "openapi-lint", - "openapiv3 1.0.3", + "openapiv3", "schemars", "serde", "serde_json", @@ -4640,7 +4642,7 @@ dependencies = [ "omicron-workspace-hack", "once_cell", "openapi-lint", - "openapiv3 1.0.3", + "openapiv3", "openssl", "oxide-client", "oximeter", @@ -4838,7 +4840,7 @@ dependencies = [ "omicron-workspace-hack", "once_cell", "openapi-lint", - "openapiv3 1.0.3", + "openapiv3", "opte-ioctl", "oximeter", "oximeter-instruments", @@ -4968,7 +4970,7 @@ dependencies = [ "num-iter", "num-traits", "once_cell", - "openapiv3 2.0.0-rc.1", + "openapiv3", "petgraph", "postgres-types", "ppv-lite86", @@ -5064,26 +5066,15 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openapi-lint" version = "0.4.0" -source = "git+https://github.com/oxidecomputer/openapi-lint?branch=main#bb69a3a4a184d966bac2a0df2be5c9038d9867d0" +source = "git+https://github.com/oxidecomputer/openapi-lint?branch=main#ef442ee4343e97b6d9c217d3e7533962fe7d7236" dependencies = [ "heck 0.4.1", "indexmap 2.1.0", "lazy_static", - "openapiv3 1.0.3", + "openapiv3", "regex", ] -[[package]] -name = "openapiv3" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e56d5c441965b6425165b7e3223cc933ca469834f4a8b4786817a1f9dc4f13" -dependencies = [ - "indexmap 2.1.0", - "serde", - "serde_json", -] - [[package]] name = "openapiv3" version = "2.0.0-rc.1" @@ -5097,9 +5088,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" dependencies = [ "bitflags 2.4.0", "cfg-if 1.0.0", @@ -5129,9 +5120,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" dependencies = [ "cc", "libc", @@ -5270,6 +5261,7 @@ dependencies = [ "rstest", "schemars", "serde", + "serde_json", "strum", "thiserror", "trybuild", @@ -5310,7 +5302,7 @@ dependencies = [ "omicron-test-utils", "omicron-workspace-hack", "openapi-lint", - "openapiv3 1.0.3", + "openapiv3", "oximeter", "oximeter-client", "oximeter-db", @@ -5629,9 +5621,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" @@ -5976,9 +5968,9 @@ checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" [[package]] name = "pretty-hex" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" +checksum = "23c6b968ed37d62e35b4febaba13bfa231b0b7929d68b8a94e65445a17e2d35f" [[package]] name = "pretty_assertions" @@ -6077,7 +6069,7 @@ dependencies = [ "heck 0.4.1", "http", "indexmap 2.1.0", - "openapiv3 2.0.0-rc.1", + "openapiv3", "proc-macro2", "quote", "regex", @@ -6095,7 +6087,7 @@ name = "progenitor-macro" version = "0.4.0" source = "git+https://github.com/oxidecomputer/progenitor?branch=main#9339b57628e1e76b1d7131ef93a6c0db2ab0a762" dependencies = [ - "openapiv3 2.0.0-rc.1", + "openapiv3", "proc-macro2", "progenitor-impl", "quote", @@ -6110,7 +6102,7 @@ dependencies = [ [[package]] name = "propolis-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=54398875a2125227d13827d4236dce943c019b1c#54398875a2125227d13827d4236dce943c019b1c" +source = "git+https://github.com/oxidecomputer/propolis?rev=3e1d129151c3621d28ead5c6e5760693ba6e7fec#3e1d129151c3621d28ead5c6e5760693ba6e7fec" dependencies = [ "async-trait", "base64 0.21.5", @@ -6131,7 +6123,7 @@ dependencies = [ [[package]] name = "propolis-mock-server" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=54398875a2125227d13827d4236dce943c019b1c#54398875a2125227d13827d4236dce943c019b1c" +source = "git+https://github.com/oxidecomputer/propolis?rev=3e1d129151c3621d28ead5c6e5760693ba6e7fec#3e1d129151c3621d28ead5c6e5760693ba6e7fec" dependencies = [ "anyhow", "atty", @@ -6161,7 +6153,7 @@ dependencies = [ [[package]] name = "propolis_types" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=54398875a2125227d13827d4236dce943c019b1c#54398875a2125227d13827d4236dce943c019b1c" +source = "git+https://github.com/oxidecomputer/propolis?rev=3e1d129151c3621d28ead5c6e5760693ba6e7fec#3e1d129151c3621d28ead5c6e5760693ba6e7fec" dependencies = [ "schemars", "serde", @@ -7536,6 +7528,7 @@ dependencies = [ "progenitor", "regress", "reqwest", + "schemars", "serde", "sled-storage", "slog", @@ -9104,12 +9097,12 @@ dependencies = [ [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna 0.5.0", "percent-encoding", ] @@ -9546,7 +9539,7 @@ dependencies = [ "omicron-test-utils", "omicron-workspace-hack", "openapi-lint", - "openapiv3 1.0.3", + "openapiv3", "rand 0.8.5", "reqwest", "schemars", @@ -9893,9 +9886,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 239fb453dc..533e710dc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -171,9 +171,9 @@ cookie = "0.18" criterion = { version = "0.5.1", features = [ "async_tokio" ] } crossbeam = "0.8" crossterm = { version = "0.27.0", features = ["event-stream"] } -crucible-agent-client = { git = "https://github.com/oxidecomputer/crucible", rev = "51a3121c8318fc7ac97d74f917ce1d37962e785f" } -crucible-pantry-client = { git = "https://github.com/oxidecomputer/crucible", rev = "51a3121c8318fc7ac97d74f917ce1d37962e785f" } -crucible-smf = { git = "https://github.com/oxidecomputer/crucible", rev = "51a3121c8318fc7ac97d74f917ce1d37962e785f" } +crucible-agent-client = { git = "https://github.com/oxidecomputer/crucible", rev = "945f040d259ca8013d3fb26f510453da7cd7b1a6" } +crucible-pantry-client = { git = "https://github.com/oxidecomputer/crucible", rev = "945f040d259ca8013d3fb26f510453da7cd7b1a6" } +crucible-smf = { git = "https://github.com/oxidecomputer/crucible", rev = "945f040d259ca8013d3fb26f510453da7cd7b1a6" } curve25519-dalek = "4" datatest-stable = "0.2.3" display-error-chain = "0.2.0" @@ -263,7 +263,7 @@ oxide-client = { path = "clients/oxide-client" } oxide-vpc = { git = "https://github.com/oxidecomputer/opte", rev = "258a8b59902dd36fc7ee5425e6b1fb5fc80d4649", features = [ "api", "std" ] } once_cell = "1.18.0" openapi-lint = { git = "https://github.com/oxidecomputer/openapi-lint", branch = "main" } -openapiv3 = "1.0" +openapiv3 = "2.0.0-rc.1" # must match samael's crate! openssl = "0.10" openssl-sys = "0.9" @@ -288,13 +288,13 @@ petgraph = "0.6.4" postgres-protocol = "0.6.6" predicates = "3.0.4" pretty_assertions = "1.4.0" -pretty-hex = "0.3.0" +pretty-hex = "0.4.0" proc-macro2 = "1.0" progenitor = { git = "https://github.com/oxidecomputer/progenitor", branch = "main" } progenitor-client = { git = "https://github.com/oxidecomputer/progenitor", branch = "main" } -bhyve_api = { git = "https://github.com/oxidecomputer/propolis", rev = "54398875a2125227d13827d4236dce943c019b1c" } -propolis-client = { git = "https://github.com/oxidecomputer/propolis", rev = "54398875a2125227d13827d4236dce943c019b1c" } -propolis-mock-server = { git = "https://github.com/oxidecomputer/propolis", rev = "54398875a2125227d13827d4236dce943c019b1c" } +bhyve_api = { git = "https://github.com/oxidecomputer/propolis", rev = "3e1d129151c3621d28ead5c6e5760693ba6e7fec" } +propolis-client = { git = "https://github.com/oxidecomputer/propolis", rev = "3e1d129151c3621d28ead5c6e5760693ba6e7fec" } +propolis-mock-server = { git = "https://github.com/oxidecomputer/propolis", rev = "3e1d129151c3621d28ead5c6e5760693ba6e7fec" } proptest = "1.4.0" quote = "1.0" rand = "0.8.5" @@ -389,7 +389,7 @@ walkdir = "2.4" wicket = { path = "wicket" } wicket-common = { path = "wicket-common" } wicketd-client = { path = "clients/wicketd-client" } -zeroize = { version = "1.6.0", features = ["zeroize_derive", "std"] } +zeroize = { version = "1.7.0", features = ["zeroize_derive", "std"] } zip = { version = "0.6.6", default-features = false, features = ["deflate","bzip2"] } zone = { version = "0.3", default-features = false, features = ["async"] } diff --git a/certificates/src/lib.rs b/certificates/src/lib.rs index 6bd7fa32de..442a9cfdd5 100644 --- a/certificates/src/lib.rs +++ b/certificates/src/lib.rs @@ -60,14 +60,14 @@ impl From for Error { | InvalidValidationHostname(_) | ErrorValidatingHostname(_) | NoDnsNameMatchingHostname { .. } - | UnsupportedPurpose => Error::InvalidValue { - label: String::from("certificate"), - message: DisplayErrorChain::new(&error).to_string(), - }, - BadPrivateKey(_) => Error::InvalidValue { - label: String::from("private-key"), - message: DisplayErrorChain::new(&error).to_string(), - }, + | UnsupportedPurpose => Error::invalid_value( + "certificate", + DisplayErrorChain::new(&error).to_string(), + ), + BadPrivateKey(_) => Error::invalid_value( + "private-key", + DisplayErrorChain::new(&error).to_string(), + ), Unexpected(_) => Error::InternalError { internal_message: DisplayErrorChain::new(&error).to_string(), }, diff --git a/clients/nexus-client/src/lib.rs b/clients/nexus-client/src/lib.rs index 6667f759e4..3ecba7e710 100644 --- a/clients/nexus-client/src/lib.rs +++ b/clients/nexus-client/src/lib.rs @@ -225,7 +225,7 @@ impl From<&omicron_common::api::internal::nexus::ProducerEndpoint> address: s.address.to_string(), base_route: s.base_route.clone(), id: s.id, - kind: s.kind.map(Into::into), + kind: s.kind.into(), interval: s.interval.into(), } } diff --git a/clients/oximeter-client/src/lib.rs b/clients/oximeter-client/src/lib.rs index 8a03304e06..11aa1452f8 100644 --- a/clients/oximeter-client/src/lib.rs +++ b/clients/oximeter-client/src/lib.rs @@ -43,7 +43,7 @@ impl From<&omicron_common::api::internal::nexus::ProducerEndpoint> address: s.address.to_string(), base_route: s.base_route.clone(), id: s.id, - kind: s.kind.map(Into::into), + kind: s.kind.into(), interval: s.interval.into(), } } diff --git a/clients/sled-agent-client/Cargo.toml b/clients/sled-agent-client/Cargo.toml index e2cc737e70..18ca342a2b 100644 --- a/clients/sled-agent-client/Cargo.toml +++ b/clients/sled-agent-client/Cargo.toml @@ -12,6 +12,7 @@ progenitor.workspace = true ipnetwork.workspace = true regress.workspace = true reqwest = { workspace = true, features = [ "json", "rustls-tls", "stream" ] } +schemars.workspace = true serde.workspace = true slog.workspace = true sled-storage.workspace = true diff --git a/clients/sled-agent-client/src/lib.rs b/clients/sled-agent-client/src/lib.rs index 30b554a021..0bbd27cf3e 100644 --- a/clients/sled-agent-client/src/lib.rs +++ b/clients/sled-agent-client/src/lib.rs @@ -6,11 +6,11 @@ use async_trait::async_trait; use std::convert::TryFrom; -use std::str::FromStr; use uuid::Uuid; progenitor::generate_api!( spec = "../../openapi/sled-agent.json", + derives = [ schemars::JsonSchema ], inner_type = slog::Logger, pre_hook = (|log: &slog::Logger, request: &reqwest::Request| { slog::debug!(log, "client request"; @@ -529,27 +529,3 @@ impl TestInterfaces for Client { .expect("disk_finish_transition() failed unexpectedly"); } } - -impl From for types::DatasetKind { - fn from(k: sled_storage::dataset::DatasetKind) -> Self { - use sled_storage::dataset::DatasetKind::*; - match k { - CockroachDb => Self::CockroachDb, - Crucible => Self::Crucible, - Clickhouse => Self::Clickhouse, - ClickhouseKeeper => Self::ClickhouseKeeper, - ExternalDns => Self::ExternalDns, - InternalDns => Self::InternalDns, - } - } -} - -impl From for types::DatasetName { - fn from(n: sled_storage::dataset::DatasetName) -> Self { - Self { - pool_name: types::ZpoolName::from_str(&n.pool().to_string()) - .unwrap(), - kind: n.dataset().clone().into(), - } - } -} diff --git a/common/src/api/external/error.rs b/common/src/api/external/error.rs index 2eb88dbca9..1e8b1a896a 100644 --- a/common/src/api/external/error.rs +++ b/common/src/api/external/error.rs @@ -35,16 +35,16 @@ pub enum Error { ObjectAlreadyExists { type_name: ResourceType, object_name: String }, /// The request was well-formed, but the operation cannot be completed given /// the current state of the system. - #[error("Invalid Request: {message}")] - InvalidRequest { message: String }, + #[error("Invalid Request: {}", .message.display_internal())] + InvalidRequest { message: MessagePair }, /// Authentication credentials were required but either missing or invalid. /// The HTTP status code is called "Unauthorized", but it's more accurate to /// call it "Unauthenticated". #[error("Missing or invalid credentials")] Unauthenticated { internal_message: String }, /// The specified input field is not valid. - #[error("Invalid Value: {label}, {message}")] - InvalidValue { label: String, message: String }, + #[error("Invalid Value: {label}, {}", .message.display_internal())] + InvalidValue { label: String, message: MessagePair }, /// The request is not authorized to perform the requested operation. #[error("Forbidden")] Forbidden, @@ -53,101 +53,91 @@ pub enum Error { #[error("Internal Error: {internal_message}")] InternalError { internal_message: String }, /// The system (or part of it) is unavailable. - #[error( - "Service Unavailable {}", - message.display_internal() - )] - ServiceUnavailable { message: MessageVariant }, + #[error("Service Unavailable: {internal_message}")] + ServiceUnavailable { internal_message: String }, + + /// There is insufficient capacity to perform the requested operation. + /// + /// This variant is translated to 503 Service Unavailable, and it carries + /// both an external and an internal message. The external message is + /// intended for operator consumption and is intended to not leak any + /// implementation details. + #[error("Insufficient Capacity: {}", .message.display_internal())] + InsufficientCapacity { message: MessagePair }, + /// Method Not Allowed - #[error("Method Not Allowed: {internal_message}")] - MethodNotAllowed { internal_message: String }, + #[error("Method Not Allowed: {}", .message.display_internal())] + MethodNotAllowed { message: MessagePair }, #[error("Type version mismatch! {internal_message}")] TypeVersionMismatch { internal_message: String }, - #[error("Conflict: {internal_message}")] - Conflict { internal_message: String }, + #[error("Conflict: {}", .message.display_internal())] + Conflict { message: MessagePair }, } -/// Represents either a message that is internal to the system or a message that -/// is intended to be returned to the client. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub enum MessageVariant { - Internal(String), - External { - message: String, - /// Internal context, or an empty string if there's no context yet. - internal_context: String, - }, +/// Represents an error message which has an external component, along with +/// some internal context possibly attached to it. +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct MessagePair { + external_message: String, + internal_context: String, } -impl MessageVariant { - fn internal(message: String) -> Self { - MessageVariant::Internal(message) +impl MessagePair { + pub fn new(external_message: String) -> Self { + Self { external_message, internal_context: String::new() } } - fn external(message: String) -> Self { - MessageVariant::External { - message: message, - internal_context: String::new(), - } + pub fn new_full( + external_message: String, + internal_context: String, + ) -> Self { + Self { external_message, internal_context } + } + + pub fn external_message(&self) -> &str { + &self.external_message + } + + pub fn internal_context(&self) -> &str { + &self.internal_context } - fn internal_context(self, context: C) -> Self + fn with_internal_context(self, context: C) -> Self where C: Display + Send + Sync + 'static, { - match self { - MessageVariant::Internal(msg) => { - MessageVariant::Internal(format!("{}: {}", context, msg)) - } - MessageVariant::External { message, internal_context } => { - let internal_context = if internal_context.is_empty() { - context.to_string() - } else { - format!("{}: {}", context, internal_context) - }; - MessageVariant::External { message, internal_context } - } - } + let internal_context = if self.internal_context.is_empty() { + context.to_string() + } else { + format!("{}: {}", context, self.internal_context) + }; + Self { external_message: self.external_message, internal_context } } - fn into_internal_external(self) -> (String, Option) { - match self { - MessageVariant::Internal(msg) => (msg, None), - MessageVariant::External { message, internal_context } => { - let internal = if internal_context.is_empty() { - message.clone() - } else { - format!("{}: {}", message, internal_context) - }; - (internal, Some(message)) - } - } + pub fn into_internal_external(self) -> (String, String) { + let internal = self.display_internal().to_string(); + (internal, self.external_message) } // Do not implement `fmt::Display` for this enum because we don't want users to // accidentally display the internal message to the client. Instead, use a // private formatter. - fn display_internal(&self) -> MessageVariantDisplay<'_> { - MessageVariantDisplay(self) + fn display_internal(&self) -> MessagePairDisplayInternal<'_> { + MessagePairDisplayInternal(self) } } -struct MessageVariantDisplay<'a>(&'a MessageVariant); +struct MessagePairDisplayInternal<'a>(&'a MessagePair); -impl<'a> Display for MessageVariantDisplay<'a> { +impl<'a> Display for MessagePairDisplayInternal<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0 { - MessageVariant::Internal(msg) => write!(f, "(internal: {})", msg), - MessageVariant::External { message, internal_context } => { - write!(f, ": {message}")?; - if !internal_context.is_empty() { - write!(f, " (with internal context: {internal_context})")?; - } - Ok(()) - } + write!(f, "{}", self.0.external_message)?; + if !self.0.internal_context.is_empty() { + write!(f, " (with internal context: {})", self.0.internal_context)?; } + Ok(()) } } @@ -205,6 +195,7 @@ impl Error { | Error::InvalidValue { .. } | Error::Forbidden | Error::MethodNotAllowed { .. } + | Error::InsufficientCapacity { .. } | Error::InternalError { .. } | Error::TypeVersionMismatch { .. } | Error::Conflict { .. } => false, @@ -236,34 +227,59 @@ impl Error { /// /// This should be used for failures due possibly to invalid client input /// or malformed requests. - pub fn invalid_request(message: &str) -> Error { - Error::InvalidRequest { message: message.to_owned() } + pub fn invalid_request(message: impl Into) -> Error { + Error::InvalidRequest { message: MessagePair::new(message.into()) } + } + + /// Generates an [`Error::InvalidValue`] error with the specific label and + /// message. + pub fn invalid_value( + label: impl Into, + message: impl Into, + ) -> Error { + Error::InvalidValue { + label: label.into(), + message: MessagePair::new(message.into()), + } } - /// Generates an [`Error::ServiceUnavailable`] error with a specific - /// message that is sent back to the client. + /// Generates an [`Error::ServiceUnavailable`] error with the specific + /// message /// /// This should be used for transient failures where the caller might be /// expected to retry. Logic errors or other problems indicating that a /// retry would not work should probably be an InternalError (if it's a /// server problem) or InvalidRequest (if it's a client problem) instead. - pub fn unavail_external(external_message: impl Into) -> Error { - Error::ServiceUnavailable { - message: MessageVariant::external(external_message.into()), - } + pub fn unavail(message: &str) -> Error { + Error::ServiceUnavailable { internal_message: message.to_owned() } } - /// Generates an [`Error::ServiceUnavailable`] error with a specific - /// internal-only message. + /// Generates an [`Error::InsufficientCapacity`] error with external and + /// and internal messages. /// - /// This should be used for transient failures where the caller might be - /// expected to retry. - pub fn unavail_internal(internal_message: impl Into) -> Error { - Error::ServiceUnavailable { - message: MessageVariant::internal(internal_message.into()), + /// This should be used for failures where there is insufficient capacity, + /// and where the caller must either take action or wait until capacity is + /// freed. + /// + /// In the future, we may want to provide more help here: e.g. a link to a + /// status or support page. + pub fn insufficient_capacity( + external_message: impl Into, + internal_message: impl Into, + ) -> Error { + Error::InsufficientCapacity { + message: MessagePair::new_full( + external_message.into(), + internal_message.into(), + ), } } + /// Generates an [`Error::MethodNotAllowed`] error with an external message. + pub fn method_not_allowed(message: impl Into) -> Error { + Error::MethodNotAllowed { message: MessagePair::new(message.into()) } + } + /// Generates an [`Error::TypeVersionMismatch`] with a specific message. /// /// TypeVersionMismatch errors are a specific type of error arising from differences @@ -284,8 +300,8 @@ impl Error { /// retried. The internal message should provide more information about the /// source of the conflict and possible actions the caller can take to /// resolve it (if any). - pub fn conflict(message: &str) -> Error { - Error::Conflict { internal_message: message.to_owned() } + pub fn conflict(message: impl Into) -> Error { + Error::Conflict { message: MessagePair::new(message.into()) } } /// Given an [`Error`] with an internal message, return the same error with @@ -299,9 +315,14 @@ impl Error { match self { Error::ObjectNotFound { .. } | Error::ObjectAlreadyExists { .. } - | Error::InvalidRequest { .. } - | Error::InvalidValue { .. } | Error::Forbidden => self, + Error::InvalidRequest { message } => Error::InvalidRequest { + message: message.with_internal_context(context), + }, + Error::InvalidValue { label, message } => Error::InvalidValue { + label, + message: message.with_internal_context(context), + }, Error::Unauthenticated { internal_message } => { Error::Unauthenticated { internal_message: format!( @@ -313,19 +334,22 @@ impl Error { Error::InternalError { internal_message } => Error::InternalError { internal_message: format!("{}: {}", context, internal_message), }, - Error::ServiceUnavailable { message } => { + Error::ServiceUnavailable { internal_message } => { Error::ServiceUnavailable { - message: message.internal_context(context), - } - } - Error::MethodNotAllowed { internal_message } => { - Error::MethodNotAllowed { internal_message: format!( "{}: {}", context, internal_message ), } } + Error::InsufficientCapacity { message } => { + Error::InsufficientCapacity { + message: message.with_internal_context(context), + } + } + Error::MethodNotAllowed { message } => Error::MethodNotAllowed { + message: message.with_internal_context(context), + }, Error::TypeVersionMismatch { internal_message } => { Error::TypeVersionMismatch { internal_message: format!( @@ -334,8 +358,8 @@ impl Error { ), } } - Error::Conflict { internal_message } => Error::Conflict { - internal_message: format!("{}: {}", context, internal_message), + Error::Conflict { message } => Error::Conflict { + message: message.with_internal_context(context), }, } } @@ -387,28 +411,42 @@ impl From for HttpError { internal_message, }, - Error::InvalidRequest { message } => HttpError::for_bad_request( - Some(String::from("InvalidRequest")), - message, - ), + Error::InvalidRequest { message } => { + let (internal_message, external_message) = + message.into_internal_external(); + HttpError { + status_code: http::StatusCode::BAD_REQUEST, + error_code: Some(String::from("InvalidRequest")), + external_message, + internal_message, + } + } Error::InvalidValue { label, message } => { - let message = - format!("unsupported value for \"{}\": {}", label, message); - HttpError::for_bad_request( - Some(String::from("InvalidValue")), - message, - ) + let (internal_message, external_message) = + message.into_internal_external(); + HttpError { + status_code: http::StatusCode::BAD_REQUEST, + error_code: Some(String::from("InvalidValue")), + external_message: format!( + "unsupported value for \"{}\": {}", + label, external_message + ), + internal_message, + } } // TODO: RFC-7231 requires that 405s generate an Accept header to describe // what methods are available in the response - Error::MethodNotAllowed { internal_message } => { - HttpError::for_client_error( - Some(String::from("MethodNotAllowed")), - http::StatusCode::METHOD_NOT_ALLOWED, + Error::MethodNotAllowed { message } => { + let (internal_message, external_message) = + message.into_internal_external(); + HttpError { + status_code: http::StatusCode::METHOD_NOT_ALLOWED, + error_code: Some(String::from("MethodNotAllowed")), + external_message, internal_message, - ) + } } Error::Forbidden => HttpError::for_client_error( @@ -421,14 +459,25 @@ impl From for HttpError { HttpError::for_internal_error(internal_message) } - Error::ServiceUnavailable { message } => { - let (internal_message, external) = + Error::ServiceUnavailable { internal_message } => { + HttpError::for_unavail( + Some(String::from("ServiceNotAvailable")), + internal_message, + ) + } + + Error::InsufficientCapacity { message } => { + // Must create an HttpError explicitly to provide an external + // message for 503. + let (internal_message, external_message) = message.into_internal_external(); HttpError { status_code: http::StatusCode::SERVICE_UNAVAILABLE, - error_code: Some(String::from("ServiceNotAvailable")), - external_message: external - .unwrap_or_else(|| "Service Unavailable".to_owned()), + error_code: Some(String::from("InsufficientCapacity")), + external_message: format!( + "Insufficient capacity: {}", + external_message + ), internal_message, } } @@ -437,12 +486,15 @@ impl From for HttpError { HttpError::for_internal_error(internal_message) } - Error::Conflict { internal_message } => { - HttpError::for_client_error( - Some(String::from("Conflict")), - http::StatusCode::CONFLICT, + Error::Conflict { message } => { + let (internal_message, external_message) = + message.into_internal_external(); + HttpError { + status_code: http::StatusCode::CONFLICT, + error_code: Some(String::from("Conflict")), + external_message, internal_message, - ) + } } } } @@ -481,10 +533,7 @@ impl From> for Error { match rv.status() { http::StatusCode::SERVICE_UNAVAILABLE => { - // TODO: Out of an abundance of caution we treat this - // message as internal, though it's possible some such - // messages should be displayed. - Error::unavail_internal(&message) + Error::unavail(&message) } status if status.is_client_error() => { Error::invalid_request(&message) @@ -596,8 +645,6 @@ impl InternalContext for Result { #[cfg(test)] mod test { - use crate::api::external::error::MessageVariant; - use super::Error; use super::InternalContext; @@ -658,29 +705,11 @@ mod test { }; // test `with_internal_context()` and (separately) `ServiceUnavailable` - // internal variant - let error: Result<(), Error> = Err(Error::unavail_internal("boom")); + // variant + let error: Result<(), Error> = Err(Error::unavail("boom")); match error.with_internal_context(|| format!("uh-oh (#{:2})", 2)) { - Err(Error::ServiceUnavailable { message }) => { - assert_eq!( - message, - MessageVariant::internal("uh-oh (# 2): boom".to_owned()) - ); - } - _ => panic!("returned wrong type"), - }; - - // test `ServiceUnavailable` external variant - let error: Result<(), Error> = Err(Error::unavail_external("boom")); - match error.with_internal_context(|| format!("uh-oh (#{:2})", 2)) { - Err(Error::ServiceUnavailable { message }) => { - assert_eq!( - message, - MessageVariant::External { - message: "boom".to_owned(), - internal_context: "uh-oh (# 2)".to_owned(), - } - ); + Err(Error::ServiceUnavailable { internal_message }) => { + assert_eq!(internal_message, "uh-oh (# 2): boom"); } _ => panic!("returned wrong type"), }; diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 3e58d1d4d4..dfc4d02cde 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -316,10 +316,7 @@ impl Name { /// `Name::try_from(String)` that marshals any error into an appropriate /// `Error`. pub fn from_param(value: String, label: &str) -> Result { - value.parse().map_err(|e| Error::InvalidValue { - label: String::from(label), - message: e, - }) + value.parse().map_err(|e| Error::invalid_value(label, e)) } /// Return the `&str` representing the actual name. @@ -622,6 +619,7 @@ impl From for i64 { Debug, Deserialize, Eq, + Hash, JsonSchema, Ord, PartialEq, @@ -2826,10 +2824,10 @@ mod test { assert!(result.is_err()); assert_eq!( result, - Err(Error::InvalidValue { - label: "the_name".to_string(), - message: "name requires at least one character".to_string() - }) + Err(Error::invalid_value( + "the_name", + "name requires at least one character" + )) ); } diff --git a/common/src/api/internal/nexus.rs b/common/src/api/internal/nexus.rs index 1daa85dbe7..780e60b1a2 100644 --- a/common/src/api/internal/nexus.rs +++ b/common/src/api/internal/nexus.rs @@ -103,7 +103,7 @@ pub struct ProducerEndpoint { /// A unique ID for this producer. pub id: Uuid, /// The kind of producer. - pub kind: Option, + pub kind: ProducerKind, /// The IP address and port at which `oximeter` can collect metrics from the /// producer. pub address: SocketAddr, diff --git a/common/src/api/internal/shared.rs b/common/src/api/internal/shared.rs index 155fbf971b..c8d8b1c786 100644 --- a/common/src/api/internal/shared.rs +++ b/common/src/api/internal/shared.rs @@ -140,6 +140,9 @@ pub struct PortConfigV1 { pub uplink_port_fec: PortFec, /// BGP peers on this port pub bgp_peers: Vec, + /// Whether or not to set autonegotiation + #[serde(default)] + pub autoneg: bool, } impl From for PortConfigV1 { @@ -155,6 +158,7 @@ impl From for PortConfigV1 { uplink_port_speed: value.uplink_port_speed, uplink_port_fec: value.uplink_port_fec, bgp_peers: vec![], + autoneg: false, } } } @@ -260,7 +264,7 @@ pub enum ExternalPortDiscovery { } /// Switchport Speed options -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] +#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum PortSpeed { #[serde(alias = "0G")] @@ -284,7 +288,7 @@ pub enum PortSpeed { } /// Switchport FEC options -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] +#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum PortFec { Firecode, diff --git a/common/src/ledger.rs b/common/src/ledger.rs index ae028998e2..c120ab953c 100644 --- a/common/src/ledger.rs +++ b/common/src/ledger.rs @@ -54,6 +54,7 @@ impl From for crate::api::external::Error { /// /// This structure is intended to help with serialization and deserialization /// of configuration information to both M.2s. +#[derive(Debug)] pub struct Ledger { log: Logger, ledger: T, diff --git a/common/src/nexus_config.rs b/common/src/nexus_config.rs index 94c39b4436..740823e755 100644 --- a/common/src/nexus_config.rs +++ b/common/src/nexus_config.rs @@ -339,6 +339,8 @@ pub struct BackgroundTaskConfig { pub nat_cleanup: NatCleanupConfig, /// configuration for inventory tasks pub inventory: InventoryConfig, + /// configuration for phantom disks task + pub phantom_disks: PhantomDiskConfig, } #[serde_as] @@ -386,7 +388,7 @@ pub struct NatCleanupConfig { pub struct InventoryConfig { /// period (in seconds) for periodic activations of this background task /// - /// Each activation fetches information about all harware and software in + /// Each activation fetches information about all hardware and software in /// the system and inserts it into the database. This generates a moderate /// amount of data. #[serde_as(as = "DurationSeconds")] @@ -405,6 +407,14 @@ pub struct InventoryConfig { pub disable: bool, } +#[serde_as] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct PhantomDiskConfig { + /// period (in seconds) for periodic activations of this background task + #[serde_as(as = "DurationSeconds")] + pub period_secs: Duration, +} + /// Configuration for a nexus server #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct PackageConfig { @@ -508,8 +518,9 @@ mod test { BackgroundTaskConfig, Config, ConfigDropshotWithTls, ConsoleConfig, Database, DeploymentConfig, DnsTasksConfig, DpdConfig, ExternalEndpointsConfig, InternalDns, InventoryConfig, LoadError, - LoadErrorKind, MgdConfig, NatCleanupConfig, PackageConfig, SchemeName, - TimeseriesDbConfig, Tunables, UpdatesConfig, + LoadErrorKind, MgdConfig, NatCleanupConfig, PackageConfig, + PhantomDiskConfig, SchemeName, TimeseriesDbConfig, Tunables, + UpdatesConfig, }; use crate::address::{Ipv6Subnet, RACK_PREFIX}; use crate::api::internal::shared::SwitchLocation; @@ -663,6 +674,7 @@ mod test { inventory.period_secs = 10 inventory.nkeep = 11 inventory.disable = false + phantom_disks.period_secs = 30 [default_region_allocation_strategy] type = "random" seed = 0 @@ -764,7 +776,10 @@ mod test { period_secs: Duration::from_secs(10), nkeep: 11, disable: false, - } + }, + phantom_disks: PhantomDiskConfig { + period_secs: Duration::from_secs(30), + }, }, default_region_allocation_strategy: crate::nexus_config::RegionAllocationStrategy::Random { @@ -822,6 +837,7 @@ mod test { inventory.period_secs = 10 inventory.nkeep = 3 inventory.disable = false + phantom_disks.period_secs = 30 [default_region_allocation_strategy] type = "random" "##, diff --git a/common/src/vlan.rs b/common/src/vlan.rs index 45776e09ac..5e5765ffe2 100644 --- a/common/src/vlan.rs +++ b/common/src/vlan.rs @@ -20,10 +20,10 @@ impl VlanID { /// Creates a new VLAN ID, returning an error if it is out of range. pub fn new(id: u16) -> Result { if VLAN_MAX < id { - return Err(Error::InvalidValue { - label: id.to_string(), - message: "Invalid VLAN value".to_string(), - }); + return Err(Error::invalid_value( + id.to_string(), + "Invalid VLAN value", + )); } Ok(Self(id)) } @@ -38,9 +38,9 @@ impl fmt::Display for VlanID { impl FromStr for VlanID { type Err = Error; fn from_str(s: &str) -> Result { - Self::new(s.parse().map_err(|e| Error::InvalidValue { - label: s.to_string(), - message: format!("{}", e), - })?) + Self::new( + s.parse::() + .map_err(|e| Error::invalid_value(s, e.to_string()))?, + ) } } diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index 9f91d38504..df5248b52d 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -515,6 +515,32 @@ fn print_task_details(bgtask: &BackgroundTask, details: &serde_json::Value) { ); } }; + } else if name == "phantom_disks" { + #[derive(Deserialize)] + struct TaskSuccess { + /// how many phantom disks were deleted ok + phantom_disk_deleted_ok: usize, + + /// how many phantom disks could not be deleted + phantom_disk_deleted_err: usize, + } + + match serde_json::from_value::(details.clone()) { + Err(error) => eprintln!( + "warning: failed to interpret task details: {:?}: {:?}", + error, details + ), + Ok(success) => { + println!( + " number of phantom disks deleted: {}", + success.phantom_disk_deleted_ok + ); + println!( + " number of phantom disk delete errors: {}", + success.phantom_disk_deleted_err + ); + } + }; } else { println!( "warning: unknown background task: {:?} \ diff --git a/dev-tools/omdb/tests/env.out b/dev-tools/omdb/tests/env.out index fd50d80c81..c08f592852 100644 --- a/dev-tools/omdb/tests/env.out +++ b/dev-tools/omdb/tests/env.out @@ -66,6 +66,10 @@ task: "nat_v4_garbage_collector" predetermined retention policy +task: "phantom_disks" + detects and un-deletes phantom disks + + --------------------------------------------- stderr: note: using Nexus URL http://127.0.0.1:REDACTED_PORT @@ -131,6 +135,10 @@ task: "nat_v4_garbage_collector" predetermined retention policy +task: "phantom_disks" + detects and un-deletes phantom disks + + --------------------------------------------- stderr: note: Nexus URL not specified. Will pick one from DNS. @@ -183,6 +191,10 @@ task: "nat_v4_garbage_collector" predetermined retention policy +task: "phantom_disks" + detects and un-deletes phantom disks + + --------------------------------------------- stderr: note: Nexus URL not specified. Will pick one from DNS. diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index 6bc3a85e8a..65520ab59c 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -260,6 +260,10 @@ task: "nat_v4_garbage_collector" predetermined retention policy +task: "phantom_disks" + detects and un-deletes phantom disks + + --------------------------------------------- stderr: note: using Nexus URL http://127.0.0.1:REDACTED_PORT/ @@ -357,6 +361,14 @@ task: "inventory_collection" last collection started: last collection done: +task: "phantom_disks" + configured period: every 30s + currently executing: no + last completed activation: iter 2, triggered by an explicit signal + started at (s ago) and ran for ms + number of phantom disks deleted: 0 + number of phantom disk delete errors: 0 + --------------------------------------------- stderr: note: using Nexus URL http://127.0.0.1:REDACTED_PORT/ diff --git a/nexus/db-model/Cargo.toml b/nexus/db-model/Cargo.toml index b7514c4806..477ce7d11f 100644 --- a/nexus/db-model/Cargo.toml +++ b/nexus/db-model/Cargo.toml @@ -26,6 +26,7 @@ serde.workspace = true serde_json.workspace = true steno.workspace = true strum.workspace = true +thiserror.workspace = true uuid.workspace = true db-macros.workspace = true diff --git a/nexus/db-model/src/instance_state.rs b/nexus/db-model/src/instance_state.rs index 6baec7afbd..6b4c71da79 100644 --- a/nexus/db-model/src/instance_state.rs +++ b/nexus/db-model/src/instance_state.rs @@ -6,6 +6,7 @@ use super::impl_enum_wrapper; use omicron_common::api::external; use serde::Deserialize; use serde::Serialize; +use std::fmt; use std::io::Write; impl_enum_wrapper!( @@ -40,6 +41,12 @@ impl InstanceState { } } +impl fmt::Display for InstanceState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + impl From for sled_agent_client::types::InstanceState { fn from(s: InstanceState) -> Self { use external::InstanceState::*; diff --git a/nexus/db-model/src/lib.rs b/nexus/db-model/src/lib.rs index ac5bad26f8..43bf83fd34 100644 --- a/nexus/db-model/src/lib.rs +++ b/nexus/db-model/src/lib.rs @@ -70,6 +70,7 @@ mod silo_user; mod silo_user_password_hash; mod sled; mod sled_instance; +mod sled_provision_state; mod sled_resource; mod sled_resource_kind; mod sled_underlay_subnet_allocation; @@ -152,6 +153,7 @@ pub use silo_user::*; pub use silo_user_password_hash::*; pub use sled::*; pub use sled_instance::*; +pub use sled_provision_state::*; pub use sled_resource::*; pub use sled_resource_kind::*; pub use sled_underlay_subnet_allocation::*; @@ -287,10 +289,9 @@ macro_rules! impl_enum_type { Ok($model_type::$enum_item) } )* - _ => { - Err(concat!("Unrecognized enum variant for ", - stringify!{$model_type}) - .into()) + other => { + let s = concat!("Unrecognized enum variant for ", stringify!{$model_type}); + Err(format!("{}: (raw bytes: {:?})", s, other).into()) } } } diff --git a/nexus/db-model/src/producer_endpoint.rs b/nexus/db-model/src/producer_endpoint.rs index 52a69e0508..f282f6f08f 100644 --- a/nexus/db-model/src/producer_endpoint.rs +++ b/nexus/db-model/src/producer_endpoint.rs @@ -52,7 +52,7 @@ pub struct ProducerEndpoint { #[diesel(embed)] identity: ProducerEndpointIdentity, - pub kind: Option, + pub kind: ProducerKind, pub ip: ipnetwork::IpNetwork, pub port: SqlU16, pub interval: f64, @@ -69,7 +69,7 @@ impl ProducerEndpoint { ) -> Self { Self { identity: ProducerEndpointIdentity::new(endpoint.id), - kind: endpoint.kind.map(Into::into), + kind: endpoint.kind.into(), ip: endpoint.address.ip().into(), port: endpoint.address.port().into(), base_route: endpoint.base_route.clone(), diff --git a/nexus/db-model/src/queries/region_allocation.rs b/nexus/db-model/src/queries/region_allocation.rs index 2025e79fb8..a1b9e0373a 100644 --- a/nexus/db-model/src/queries/region_allocation.rs +++ b/nexus/db-model/src/queries/region_allocation.rs @@ -23,6 +23,7 @@ // a CTE (where we want the alias name to come first). use crate::schema::dataset; +use crate::schema::sled; use crate::schema::zpool; table! { @@ -157,6 +158,7 @@ diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!( old_zpool_usage, zpool, + sled, proposed_dataset_changes, ); diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index afeac5e6cd..373785799e 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -146,6 +146,7 @@ table! { mtu -> Int4, fec -> crate::SwitchLinkFecEnum, speed -> crate::SwitchLinkSpeedEnum, + autoneg -> Bool, } } @@ -399,7 +400,7 @@ table! { id -> Uuid, time_created -> Timestamptz, time_modified -> Timestamptz, - kind -> Nullable, + kind -> crate::ProducerKindEnum, ip -> Inet, port -> Int4, interval -> Float8, @@ -741,6 +742,7 @@ table! { ip -> Inet, port -> Int4, last_used_address -> Inet, + provision_state -> crate::SledProvisionStateEnum, } } @@ -1299,7 +1301,7 @@ table! { /// /// This should be updated whenever the schema is changed. For more details, /// refer to: schema/crdb/README.adoc -pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(14, 0, 0); +pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(18, 0, 0); allow_tables_to_appear_in_same_query!( system_update, @@ -1368,3 +1370,7 @@ allow_tables_to_appear_in_same_query!( switch_port, switch_port_settings_bgp_peer_config ); + +allow_tables_to_appear_in_same_query!(disk, virtual_provisioning_resource); + +allow_tables_to_appear_in_same_query!(volume, virtual_provisioning_resource); diff --git a/nexus/db-model/src/semver_version.rs b/nexus/db-model/src/semver_version.rs index 966b436149..8e168e11a2 100644 --- a/nexus/db-model/src/semver_version.rs +++ b/nexus/db-model/src/semver_version.rs @@ -68,12 +68,10 @@ fn to_sortable_string(v: semver::Version) -> Result { let max = u64::pow(10, u32::from(PADDED_WIDTH)) - 1; if v.major > max || v.minor > max || v.patch > max { - return Err(external::Error::InvalidValue { - label: "version".to_string(), - message: format!( - "Major, minor, and patch version must be less than {max}" - ), - }); + return Err(external::Error::invalid_value( + "version", + format!("Major, minor, and patch version must be less than {max}"), + )); } let mut result = format!( diff --git a/nexus/db-model/src/sled.rs b/nexus/db-model/src/sled.rs index 4c82aa5d23..0f6d1b911e 100644 --- a/nexus/db-model/src/sled.rs +++ b/nexus/db-model/src/sled.rs @@ -4,8 +4,8 @@ use super::{ByteCount, Generation, SqlU16, SqlU32}; use crate::collection::DatastoreCollectionConfig; -use crate::ipv6; use crate::schema::{physical_disk, service, sled, zpool}; +use crate::{ipv6, SledProvisionState}; use chrono::{DateTime, Utc}; use db_macros::Asset; use nexus_types::{external_api::shared, external_api::views, identity::Asset}; @@ -59,6 +59,8 @@ pub struct Sled { /// The last IP address provided to an Oxide service on this sled pub last_used_address: ipv6::Ipv6Addr, + + provision_state: SledProvisionState, } impl Sled { @@ -81,6 +83,10 @@ impl Sled { pub fn serial_number(&self) -> &str { &self.serial_number } + + pub fn provision_state(&self) -> SledProvisionState { + self.provision_state + } } impl From for views::Sled { @@ -93,6 +99,7 @@ impl From for views::Sled { part: sled.part_number, revision: sled.revision, }, + provision_state: sled.provision_state.into(), usable_hardware_threads: sled.usable_hardware_threads.0, usable_physical_ram: *sled.usable_physical_ram, } @@ -188,6 +195,8 @@ impl SledUpdate { serial_number: self.serial_number, part_number: self.part_number, revision: self.revision, + // By default, sleds start as provisionable. + provision_state: SledProvisionState::Provisionable, usable_hardware_threads: self.usable_hardware_threads, usable_physical_ram: self.usable_physical_ram, reservoir_size: self.reservoir_size, diff --git a/nexus/db-model/src/sled_provision_state.rs b/nexus/db-model/src/sled_provision_state.rs new file mode 100644 index 0000000000..6cf81b9c70 --- /dev/null +++ b/nexus/db-model/src/sled_provision_state.rs @@ -0,0 +1,58 @@ +// 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 super::impl_enum_type; +use nexus_types::external_api::views; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +impl_enum_type!( + #[derive(Clone, SqlType, Debug, QueryId)] + #[diesel(postgres_type(name = "sled_provision_state"))] + pub struct SledProvisionStateEnum; + + #[derive(Clone, Copy, Debug, AsExpression, FromSqlRow, Serialize, Deserialize, PartialEq)] + #[diesel(sql_type = SledProvisionStateEnum)] + pub enum SledProvisionState; + + // Enum values + Provisionable => b"provisionable" + NonProvisionable => b"non_provisionable" +); + +impl From for views::SledProvisionState { + fn from(state: SledProvisionState) -> Self { + match state { + SledProvisionState::Provisionable => { + views::SledProvisionState::Provisionable + } + SledProvisionState::NonProvisionable => { + views::SledProvisionState::NonProvisionable + } + } + } +} + +impl TryFrom for SledProvisionState { + type Error = UnknownSledProvisionState; + + fn try_from(state: views::SledProvisionState) -> Result { + match state { + views::SledProvisionState::Provisionable => { + Ok(SledProvisionState::Provisionable) + } + views::SledProvisionState::NonProvisionable => { + Ok(SledProvisionState::NonProvisionable) + } + views::SledProvisionState::Unknown => { + Err(UnknownSledProvisionState) + } + } + } +} + +/// An unknown [`views::SledProvisionState`] was encountered. +#[derive(Clone, Debug, Error)] +#[error("Unknown SledProvisionState")] +pub struct UnknownSledProvisionState; diff --git a/nexus/db-model/src/switch_port.rs b/nexus/db-model/src/switch_port.rs index 44588899b6..6ff8612d2f 100644 --- a/nexus/db-model/src/switch_port.rs +++ b/nexus/db-model/src/switch_port.rs @@ -355,6 +355,7 @@ pub struct SwitchPortLinkConfig { pub mtu: SqlU16, pub fec: SwitchLinkFec, pub speed: SwitchLinkSpeed, + pub autoneg: bool, } impl SwitchPortLinkConfig { @@ -365,6 +366,7 @@ impl SwitchPortLinkConfig { mtu: u16, fec: SwitchLinkFec, speed: SwitchLinkSpeed, + autoneg: bool, ) -> Self { Self { port_settings_id, @@ -372,6 +374,7 @@ impl SwitchPortLinkConfig { link_name, fec, speed, + autoneg, mtu: mtu.into(), } } diff --git a/nexus/db-queries/src/db/column_walker.rs b/nexus/db-queries/src/db/column_walker.rs new file mode 100644 index 0000000000..64c3b450c8 --- /dev/null +++ b/nexus/db-queries/src/db/column_walker.rs @@ -0,0 +1,112 @@ +// 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/. + +//! CTE utility for iterating over all columns in a table. + +use diesel::prelude::*; +use std::marker::PhantomData; + +/// Used to iterate over a tuple of columns ("T"). +/// +/// Diesel exposes "AllColumns" as a tuple, which is difficult to iterate over +/// -- after all, all the types are distinct. However, each of these types +/// implements "Column", so we can use a macro to provide a +/// "convertion-to-iterator" implemenation for our expected tuples. +pub(crate) struct ColumnWalker { + remaining: PhantomData, +} + +impl ColumnWalker { + pub fn new() -> Self { + Self { remaining: PhantomData } + } +} + +macro_rules! impl_column_walker { + ( $len:literal $($column:ident)+ ) => ( + impl<$($column: Column),+> IntoIterator for ColumnWalker<($($column,)+)> { + type Item = &'static str; + type IntoIter = std::array::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + [$($column::NAME,)+].into_iter() + } + } + ); +} + +// implementations for 1 - 32 columns +impl_column_walker! { 1 A } +impl_column_walker! { 2 A B } +impl_column_walker! { 3 A B C } +impl_column_walker! { 4 A B C D } +impl_column_walker! { 5 A B C D E } +impl_column_walker! { 6 A B C D E F } +impl_column_walker! { 7 A B C D E F G } +impl_column_walker! { 8 A B C D E F G H } +impl_column_walker! { 9 A B C D E F G H I } +impl_column_walker! { 10 A B C D E F G H I J } +impl_column_walker! { 11 A B C D E F G H I J K } +impl_column_walker! { 12 A B C D E F G H I J K L } +impl_column_walker! { 13 A B C D E F G H I J K L M } +impl_column_walker! { 14 A B C D E F G H I J K L M N } +impl_column_walker! { 15 A B C D E F G H I J K L M N O } +impl_column_walker! { 16 A B C D E F G H I J K L M N O P } +impl_column_walker! { 17 A B C D E F G H I J K L M N O P Q } +impl_column_walker! { 18 A B C D E F G H I J K L M N O P Q R } +impl_column_walker! { 19 A B C D E F G H I J K L M N O P Q R S } +impl_column_walker! { 20 A B C D E F G H I J K L M N O P Q R S T } +impl_column_walker! { 21 A B C D E F G H I J K L M N O P Q R S T U } +impl_column_walker! { 22 A B C D E F G H I J K L M N O P Q R S T U V } +impl_column_walker! { 23 A B C D E F G H I J K L M N O P Q R S T U V W } +impl_column_walker! { 24 A B C D E F G H I J K L M N O P Q R S T U V W X } +impl_column_walker! { 25 A B C D E F G H I J K L M N O P Q R S T U V W X Y } +impl_column_walker! { 26 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z } +impl_column_walker! { 27 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 } +impl_column_walker! { 28 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 } +impl_column_walker! { 29 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 C1 } +impl_column_walker! { 30 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 C1 D1 } +impl_column_walker! { 31 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 C1 D1 E1 } +impl_column_walker! { 32 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A1 B1 C1 D1 E1 F1 } + +#[cfg(test)] +mod test { + use super::*; + + table! { + test_schema.test_table (id) { + id -> Uuid, + value -> Int4, + time_deleted -> Nullable, + } + } + + // We can convert all a tables columns into an iteratable format. + #[test] + fn test_walk_table() { + let all_columns = + ColumnWalker::<::AllColumns>::new(); + + let mut iter = all_columns.into_iter(); + assert_eq!(iter.next(), Some("id")); + assert_eq!(iter.next(), Some("value")); + assert_eq!(iter.next(), Some("time_deleted")); + assert_eq!(iter.next(), None); + } + + // We can, if we want to, also make a ColumnWalker out of an arbitrary tuple + // of columns. + #[test] + fn test_walk_columns() { + let all_columns = ColumnWalker::<( + test_table::columns::id, + test_table::columns::value, + )>::new(); + + let mut iter = all_columns.into_iter(); + assert_eq!(iter.next(), Some("id")); + assert_eq!(iter.next(), Some("value")); + assert_eq!(iter.next(), None); + } +} diff --git a/nexus/db-queries/src/db/datastore/disk.rs b/nexus/db-queries/src/db/datastore/disk.rs index a0d9bf12c3..94d950f86a 100644 --- a/nexus/db-queries/src/db/datastore/disk.rs +++ b/nexus/db-queries/src/db/datastore/disk.rs @@ -25,11 +25,14 @@ use crate::db::model::DiskUpdate; use crate::db::model::Instance; use crate::db::model::Name; use crate::db::model::Project; +use crate::db::model::VirtualProvisioningResource; +use crate::db::model::Volume; use crate::db::pagination::paginated; use crate::db::queries::disk::DiskSetClauseForAttach; use crate::db::update_and_check::UpdateAndCheck; use crate::db::update_and_check::UpdateStatus; use async_bb8_diesel::AsyncRunQueryDsl; +use chrono::DateTime; use chrono::Utc; use diesel::prelude::*; use omicron_common::api; @@ -564,7 +567,7 @@ impl DataStore { /// Updates a disk record to indicate it has been deleted. /// - /// Returns the volume ID of associated with the deleted disk. + /// Returns the disk before any modifications are made by this function. /// /// Does not attempt to modify any resources (e.g. regions) which may /// belong to the disk. @@ -630,16 +633,12 @@ impl DataStore { // destroyed, don't throw an error. return Ok(disk); } else if !ok_to_delete_states.contains(disk_state.state()) { - return Err(Error::InvalidRequest { - message: format!( - "disk cannot be deleted in state \"{}\"", - disk.runtime_state.disk_state - ), - }); + return Err(Error::invalid_request(format!( + "disk cannot be deleted in state \"{}\"", + disk.runtime_state.disk_state + ))); } else if disk_state.is_attached() { - return Err(Error::InvalidRequest { - message: String::from("disk is attached"), - }); + return Err(Error::invalid_request("disk is attached")); } else { // NOTE: This is a "catch-all" error case, more specific // errors should be preferred as they're more actionable. @@ -652,4 +651,289 @@ impl DataStore { } } } + + /// Set a disk to faulted and un-delete it + /// + /// If the disk delete saga unwinds, then the disk should _not_ remain + /// deleted: disk delete saga should be triggered again in order to fully + /// complete, and the only way to do that is to un-delete the disk. Set it + /// to faulted to ensure that it won't be used. + pub async fn project_undelete_disk_set_faulted_no_auth( + &self, + disk_id: &Uuid, + ) -> Result<(), Error> { + use db::schema::disk::dsl; + let conn = self.pool_connection_unauthorized().await?; + + let faulted = api::external::DiskState::Faulted.label(); + + let result = diesel::update(dsl::disk) + .filter(dsl::time_deleted.is_not_null()) + .filter(dsl::id.eq(*disk_id)) + .set(( + dsl::time_deleted.eq(None::>), + dsl::disk_state.eq(faulted), + )) + .check_if_exists::(*disk_id) + .execute_and_check(&conn) + .await + .map_err(|e| { + public_error_from_diesel( + e, + ErrorHandler::NotFoundByLookup( + ResourceType::Disk, + LookupType::ById(*disk_id), + ), + ) + })?; + + match result.status { + UpdateStatus::Updated => Ok(()), + UpdateStatus::NotUpdatedButExists => { + let disk = result.found; + let disk_state = disk.state(); + + if disk.time_deleted().is_none() + && disk_state.state() == &api::external::DiskState::Faulted + { + // To maintain idempotency, if the disk has already been + // faulted, don't throw an error. + return Ok(()); + } else { + // NOTE: This is a "catch-all" error case, more specific + // errors should be preferred as they're more actionable. + return Err(Error::InternalError { + internal_message: String::from( + "disk exists, but cannot be faulted", + ), + }); + } + } + } + } + + /// Find disks that have been deleted but still have a + /// `virtual_provisioning_resource` record: this indicates that a disk + /// delete saga partially succeeded, then unwound, which (before the fixes + /// in customer-support#58) would mean the disk was deleted but the project + /// it was in could not be deleted (due to an erroneous number of bytes + /// "still provisioned"). + pub async fn find_phantom_disks(&self) -> ListResultVec { + use db::schema::disk::dsl; + use db::schema::virtual_provisioning_resource::dsl as resource_dsl; + use db::schema::volume::dsl as volume_dsl; + + let conn = self.pool_connection_unauthorized().await?; + + let potential_phantom_disks: Vec<( + Disk, + Option, + Option, + )> = dsl::disk + .filter(dsl::time_deleted.is_not_null()) + .left_join( + resource_dsl::virtual_provisioning_resource + .on(resource_dsl::id.eq(dsl::id)), + ) + .left_join(volume_dsl::volume.on(dsl::volume_id.eq(volume_dsl::id))) + .select(( + Disk::as_select(), + Option::::as_select(), + Option::::as_select(), + )) + .load_async(&*conn) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + // The first forward steps of the disk delete saga (plus the volume + // delete sub saga) are as follows: + // + // 1. soft-delete the disk + // 2. call virtual_provisioning_collection_delete_disk + // 3. soft-delete the disk's volume + // + // Before the fixes as part of customer-support#58, steps 1 and 3 did + // not have undo steps, where step 2 did. In order to detect when the + // disk delete saga unwound, find entries where + // + // 1. the disk and volume are soft-deleted + // 2. the `virtual_provisioning_resource` exists + // + // It's important not to conflict with any currently running disk delete + // saga. + + Ok(potential_phantom_disks + .into_iter() + .filter(|(disk, resource, volume)| { + if let Some(volume) = volume { + // In this branch, the volume record exists. Because it was + // returned by the query above, if it is soft-deleted we + // then know the saga unwound before the volume record could + // be hard deleted. This won't conflict with a running disk + // delete saga, because the resource record should be None + // if the disk and volume were already soft deleted (if + // there is one, the saga will be at or past step 3). + disk.time_deleted().is_some() + && volume.time_deleted.is_some() + && resource.is_some() + } else { + // In this branch, the volume record was hard-deleted. The + // saga could still have unwound after hard deleting the + // volume record, so proceed with filtering. This won't + // conflict with a running disk delete saga because the + // resource record should be None if the disk was soft + // deleted and the volume was hard deleted (if there is one, + // the saga should be almost finished as the volume hard + // delete is the last thing it does). + disk.time_deleted().is_some() && resource.is_some() + } + }) + .map(|(disk, _, _)| disk) + .collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::db::datastore::datastore_test; + use nexus_test_utils::db::test_setup_database; + use nexus_types::external_api::params; + use omicron_common::api::external; + use omicron_test_utils::dev; + + #[tokio::test] + async fn test_undelete_disk_set_faulted_idempotent() { + let logctx = + dev::test_setup_log("test_undelete_disk_set_faulted_idempotent"); + let log = logctx.log.new(o!()); + let mut db = test_setup_database(&log).await; + let (opctx, db_datastore) = datastore_test(&logctx, &db).await; + + let silo_id = opctx.authn.actor().unwrap().silo_id().unwrap(); + + let (authz_project, _db_project) = db_datastore + .project_create( + &opctx, + Project::new( + silo_id, + params::ProjectCreate { + identity: external::IdentityMetadataCreateParams { + name: "testpost".parse().unwrap(), + description: "please ignore".to_string(), + }, + }, + ), + ) + .await + .unwrap(); + + let disk = db_datastore + .project_create_disk( + &opctx, + &authz_project, + Disk::new( + Uuid::new_v4(), + authz_project.id(), + Uuid::new_v4(), + params::DiskCreate { + identity: external::IdentityMetadataCreateParams { + name: "first-post".parse().unwrap(), + description: "just trying things out".to_string(), + }, + disk_source: params::DiskSource::Blank { + block_size: params::BlockSize::try_from(512) + .unwrap(), + }, + size: external::ByteCount::from(2147483648), + }, + db::model::BlockSize::Traditional, + DiskRuntimeState::new(), + ) + .unwrap(), + ) + .await + .unwrap(); + + let (.., authz_disk, db_disk) = LookupPath::new(&opctx, &db_datastore) + .disk_id(disk.id()) + .fetch() + .await + .unwrap(); + + db_datastore + .disk_update_runtime( + &opctx, + &authz_disk, + &db_disk.runtime().detach(), + ) + .await + .unwrap(); + + db_datastore + .project_delete_disk_no_auth( + &authz_disk.id(), + &[external::DiskState::Detached], + ) + .await + .unwrap(); + + // Assert initial state - deleting the Disk will make LookupPath::fetch + // not work. + { + LookupPath::new(&opctx, &db_datastore) + .disk_id(disk.id()) + .fetch() + .await + .unwrap_err(); + } + + // Function under test: call this twice to ensure it's idempotent + + db_datastore + .project_undelete_disk_set_faulted_no_auth(&authz_disk.id()) + .await + .unwrap(); + + // Assert state change + + { + let (.., db_disk) = LookupPath::new(&opctx, &db_datastore) + .disk_id(disk.id()) + .fetch() + .await + .unwrap(); + + assert!(db_disk.time_deleted().is_none()); + assert_eq!( + db_disk.runtime().disk_state, + external::DiskState::Faulted.label().to_string() + ); + } + + db_datastore + .project_undelete_disk_set_faulted_no_auth(&authz_disk.id()) + .await + .unwrap(); + + // Assert state is the same after the second call + + { + let (.., db_disk) = LookupPath::new(&opctx, &db_datastore) + .disk_id(disk.id()) + .fetch() + .await + .unwrap(); + + assert!(db_disk.time_deleted().is_none()); + assert_eq!( + db_disk.runtime().disk_state, + external::DiskState::Faulted.label().to_string() + ); + } + + db.cleanup().await.unwrap(); + logctx.cleanup_successful(); + } } diff --git a/nexus/db-queries/src/db/datastore/external_ip.rs b/nexus/db-queries/src/db/datastore/external_ip.rs index e663130a84..4d6d9f4c29 100644 --- a/nexus/db-queries/src/db/datastore/external_ip.rs +++ b/nexus/db-queries/src/db/datastore/external_ip.rs @@ -151,6 +151,7 @@ impl DataStore { "Requested external IP address not available", ) } else { + // XXX Should this be insufficient_capacity? Error::invalid_request( "No external IP addresses available", ) diff --git a/nexus/db-queries/src/db/datastore/ip_pool.rs b/nexus/db-queries/src/db/datastore/ip_pool.rs index fb300ef833..4497e3f2b4 100644 --- a/nexus/db-queries/src/db/datastore/ip_pool.rs +++ b/nexus/db-queries/src/db/datastore/ip_pool.rs @@ -194,11 +194,9 @@ impl DataStore { .optional() .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; if range.is_some() { - return Err(Error::InvalidRequest { - message: - "IP Pool cannot be deleted while it contains IP ranges" - .to_string(), - }); + return Err(Error::invalid_request( + "IP Pool cannot be deleted while it contains IP ranges", + )); } // Delete the pool, conditional on the rcgen not having changed. This @@ -224,10 +222,9 @@ impl DataStore { })?; if updated_rows == 0 { - return Err(Error::InvalidRequest { - message: "deletion failed due to concurrent modification" - .to_string(), - }); + return Err(Error::invalid_request( + "deletion failed due to concurrent modification", + )); } Ok(()) } diff --git a/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs b/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs index 274937b299..0b39b0afc1 100644 --- a/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs +++ b/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs @@ -123,9 +123,7 @@ impl DataStore { if let Some(nat_entry) = result.first() { Ok(nat_entry.clone()) } else { - Err(Error::InvalidRequest { - message: "no matching records".to_string(), - }) + Err(Error::invalid_request("no matching records")) } } @@ -184,9 +182,7 @@ impl DataStore { if let Some(nat_entry) = result.first() { Ok(nat_entry.clone()) } else { - Err(Error::InvalidRequest { - message: "no matching records".to_string(), - }) + Err(Error::invalid_request("no matching records")) } } @@ -240,9 +236,7 @@ impl DataStore { match latest { Some(value) => Ok(value), - None => Err(Error::InvalidRequest { - message: "sequence table is empty!".to_string(), - }), + None => Err(Error::invalid_request("sequence table is empty!")), } } diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index 3af16918ca..0bfbf56048 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -220,9 +220,7 @@ impl DataStore { opctx.authorize(authz::Action::Query, &authz::DATABASE).await?; let pool = self.pool.pool(); let connection = pool.get().await.map_err(|err| { - Error::unavail_internal(format!( - "Failed to access DB connection: {err}" - )) + Error::unavail(&format!("Failed to access DB connection: {err}")) })?; Ok(connection) } @@ -236,9 +234,7 @@ impl DataStore { &self, ) -> Result { let connection = self.pool.pool().get().await.map_err(|err| { - Error::unavail_internal(format!( - "Failed to access DB connection: {err}" - )) + Error::unavail(&format!("Failed to access DB connection: {err}")) })?; Ok(connection) } @@ -376,8 +372,8 @@ mod test { BlockSize, ComponentUpdate, ComponentUpdateIdentity, ConsoleSession, Dataset, DatasetKind, ExternalIp, PhysicalDisk, PhysicalDiskKind, Project, Rack, Region, Service, ServiceKind, SiloUser, SledBaseboard, - SledSystemHardware, SledUpdate, SshKey, SystemUpdate, - UpdateableComponentType, VpcSubnet, Zpool, + SledProvisionState, SledSystemHardware, SledUpdate, SshKey, + SystemUpdate, UpdateableComponentType, VpcSubnet, Zpool, }; use crate::db::queries::vpc_subnet::FilterConflictingVpcSubnetRangesQuery; use assert_matches::assert_matches; @@ -614,6 +610,35 @@ mod test { sled_id } + // Marks a sled as non-provisionable. + async fn mark_sled_non_provisionable( + datastore: &DataStore, + opctx: &OpContext, + sled_id: Uuid, + ) { + let (authz_sled, sled) = LookupPath::new(opctx, datastore) + .sled_id(sled_id) + .fetch_for(authz::Action::Modify) + .await + .unwrap(); + println!("sled: {:?}", sled); + let old_state = datastore + .sled_set_provision_state( + &opctx, + &authz_sled, + SledProvisionState::NonProvisionable, + ) + .await + .unwrap_or_else(|error| { + panic!( + "error marking sled {sled_id} as non-provisionable: {error}" + ) + }); + // The old state should always be provisionable since that's where we + // start. + assert_eq!(old_state, SledProvisionState::Provisionable); + } + fn test_zpool_size() -> ByteCount { ByteCount::from_gibibytes_u32(100) } @@ -774,13 +799,24 @@ mod test { let logctx = dev::test_setup_log("test_region_allocation_strat_random"); let mut db = test_setup_database(&logctx.log).await; let (opctx, datastore) = datastore_test(&logctx, &db).await; - create_test_datasets_for_region_allocation( + let test_datasets = create_test_datasets_for_region_allocation( &opctx, datastore.clone(), + // Even though we're going to mark one sled as non-provisionable to + // test that logic, we aren't forcing the datasets to be on + // distinct sleds, so REGION_REDUNDANCY_THRESHOLD is enough. REGION_REDUNDANCY_THRESHOLD, ) .await; + let non_provisionable_dataset_id = test_datasets[0].dataset_id; + mark_sled_non_provisionable( + &datastore, + &opctx, + test_datasets[0].sled_id, + ) + .await; + // Allocate regions from the datasets for this disk. Do it a few times // for good measure. for alloc_seed in 0..10 { @@ -813,6 +849,9 @@ mod test { // Must be 3 unique datasets assert!(disk_datasets.insert(dataset.id())); + // Dataset must not be non-provisionable. + assert_ne!(dataset.id(), non_provisionable_dataset_id); + // Must be 3 unique zpools assert!(disk_zpools.insert(dataset.pool_id)); @@ -841,12 +880,23 @@ mod test { let mut db = test_setup_database(&logctx.log).await; let (opctx, datastore) = datastore_test(&logctx, &db).await; - // Create a rack without enough sleds for a successful allocation when - // we require 3 distinct sleds. + // Create a rack with enough sleds for a successful allocation when we + // require 3 distinct provisionable sleds. let test_datasets = create_test_datasets_for_region_allocation( &opctx, datastore.clone(), - REGION_REDUNDANCY_THRESHOLD, + // We're going to mark one sled as non-provisionable to test that + // logic, and we *are* forcing the datasets to be on distinct + // sleds: hence threshold + 1. + REGION_REDUNDANCY_THRESHOLD + 1, + ) + .await; + + let non_provisionable_dataset_id = test_datasets[0].dataset_id; + mark_sled_non_provisionable( + &datastore, + &opctx, + test_datasets[0].sled_id, ) .await; @@ -888,6 +938,9 @@ mod test { // Must be 3 unique datasets assert!(disk_datasets.insert(dataset.id())); + // Dataset must not be non-provisionable. + assert_ne!(dataset.id(), non_provisionable_dataset_id); + // Must be 3 unique zpools assert!(disk_zpools.insert(dataset.pool_id)); @@ -920,11 +973,22 @@ mod test { let (opctx, datastore) = datastore_test(&logctx, &db).await; // Create a rack without enough sleds for a successful allocation when - // we require 3 distinct sleds. - create_test_datasets_for_region_allocation( + // we require 3 distinct provisionable sleds. + let test_datasets = create_test_datasets_for_region_allocation( &opctx, datastore.clone(), - REGION_REDUNDANCY_THRESHOLD - 1, + // Here, we need to have REGION_REDUNDANCY_THRESHOLD - 1 + // provisionable sleds to test this failure condition. We're going + // to mark one sled as non-provisionable to test that logic, so we + // need to add 1 to that number. + REGION_REDUNDANCY_THRESHOLD, + ) + .await; + + mark_sled_non_provisionable( + &datastore, + &opctx, + test_datasets[0].sled_id, ) .await; @@ -956,7 +1020,7 @@ mod test { "Saw error: \'{err}\', but expected \'{expected}\'" ); - assert!(matches!(err, Error::ServiceUnavailable { .. })); + assert!(matches!(err, Error::InsufficientCapacity { .. })); } let _ = db.cleanup().await; @@ -1101,7 +1165,7 @@ mod test { "Saw error: \'{err}\', but expected \'{expected}\'" ); - assert!(matches!(err, Error::ServiceUnavailable { .. })); + assert!(matches!(err, Error::InsufficientCapacity { .. })); let _ = db.cleanup().await; logctx.cleanup_successful(); diff --git a/nexus/db-queries/src/db/datastore/project.rs b/nexus/db-queries/src/db/datastore/project.rs index c447b5bf98..90cec5703c 100644 --- a/nexus/db-queries/src/db/datastore/project.rs +++ b/nexus/db-queries/src/db/datastore/project.rs @@ -78,9 +78,9 @@ macro_rules! generate_fn_to_ensure_none_in_project { "a" }; - return Err(Error::InvalidRequest { - message: format!("project to be deleted contains {article} {object}: {label}"), - }); + return Err(Error::invalid_request( + format!("project to be deleted contains {article} {object}: {label}") + )); } Ok(()) @@ -251,11 +251,9 @@ impl DataStore { })?; if updated_rows == 0 { - return Err(TxnError::CustomError(Error::InvalidRequest { - message: - "deletion failed due to concurrent modification" - .to_string(), - })); + return Err(TxnError::CustomError(Error::invalid_request( + "deletion failed due to concurrent modification", + ))); } self.virtual_provisioning_collection_delete_on_connection( diff --git a/nexus/db-queries/src/db/datastore/saga.rs b/nexus/db-queries/src/db/datastore/saga.rs index 2ec0c40799..1cd41a9806 100644 --- a/nexus/db-queries/src/db/datastore/saga.rs +++ b/nexus/db-queries/src/db/datastore/saga.rs @@ -87,8 +87,8 @@ impl DataStore { match result.status { UpdateStatus::Updated => Ok(()), - UpdateStatus::NotUpdatedButExists => Err(Error::InvalidRequest { - message: format!( + UpdateStatus::NotUpdatedButExists => Err(Error::invalid_request( + format!( "failed to update saga {:?} with state {:?}: preconditions not met: \ expected current_sec = {:?}, adopt_generation = {:?}, \ but found current_sec = {:?}, adopt_generation = {:?}, state = {:?}", @@ -100,7 +100,7 @@ impl DataStore { result.found.adopt_generation, result.found.saga_state, ) - }), + )), } } diff --git a/nexus/db-queries/src/db/datastore/silo.rs b/nexus/db-queries/src/db/datastore/silo.rs index ec3658c067..3cbb61f477 100644 --- a/nexus/db-queries/src/db/datastore/silo.rs +++ b/nexus/db-queries/src/db/datastore/silo.rs @@ -348,9 +348,9 @@ impl DataStore { .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; if project_found.is_some() { - return Err(Error::InvalidRequest { - message: "silo to be deleted contains a project".to_string(), - }); + return Err(Error::invalid_request( + "silo to be deleted contains a project", + )); } let now = Utc::now(); @@ -372,11 +372,9 @@ impl DataStore { })?; if updated_rows == 0 { - return Err(TxnError::CustomError(Error::InvalidRequest { - message: - "silo deletion failed due to concurrent modification" - .to_string(), - })); + return Err(TxnError::CustomError(Error::invalid_request( + "silo deletion failed due to concurrent modification", + ))); } self.virtual_provisioning_collection_delete_on_connection( diff --git a/nexus/db-queries/src/db/datastore/sled.rs b/nexus/db-queries/src/db/datastore/sled.rs index 1b4caa9d2b..6f9148aa1f 100644 --- a/nexus/db-queries/src/db/datastore/sled.rs +++ b/nexus/db-queries/src/db/datastore/sled.rs @@ -15,6 +15,7 @@ use crate::db::model::Sled; use crate::db::model::SledResource; use crate::db::model::SledUpdate; use crate::db::pagination::paginated; +use crate::db::update_and_check::UpdateAndCheck; use async_bb8_diesel::AsyncConnection; use async_bb8_diesel::AsyncRunQueryDsl; use chrono::Utc; @@ -153,6 +154,11 @@ impl DataStore { .and(sled_has_space_in_reservoir), ) .filter(sled_dsl::time_deleted.is_null()) + // Filter out sleds that are not provisionable. + .filter( + sled_dsl::provision_state + .eq(db::model::SledProvisionState::Provisionable), + ) .select(sled_dsl::id) .into_boxed(); @@ -194,8 +200,10 @@ impl DataStore { .await .map_err(|e| match e { TxnError::CustomError(SledReservationError::NotFound) => { - external::Error::unavail_external( + external::Error::insufficient_capacity( "No sleds can fit the requested instance", + "No sled targets found that had enough capacity \ + to fit the requested instance.", ) } TxnError::Database(e) => { @@ -217,6 +225,37 @@ impl DataStore { .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; Ok(()) } + + /// Sets the provision state for this sled. + /// + /// Returns the previous state. + pub async fn sled_set_provision_state( + &self, + opctx: &OpContext, + authz_sled: &authz::Sled, + state: db::model::SledProvisionState, + ) -> Result { + use db::schema::sled::dsl; + + opctx.authorize(authz::Action::Modify, authz_sled).await?; + + let sled_id = authz_sled.id(); + let query = diesel::update(dsl::sled) + .filter(dsl::time_deleted.is_null()) + .filter(dsl::id.eq(sled_id)) + .filter(dsl::provision_state.ne(state)) + .set(( + dsl::provision_state.eq(state), + dsl::time_modified.eq(Utc::now()), + )) + .check_if_exists::(sled_id); + let result = query + .execute_and_check(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(result.found.provision_state()) + } } #[cfg(test)] @@ -226,12 +265,15 @@ mod test { use crate::db::datastore::test::{ sled_baseboard_for_test, sled_system_hardware_for_test, }; + use crate::db::lookup::LookupPath; use crate::db::model::ByteCount; use crate::db::model::SqlU32; use nexus_test_utils::db::test_setup_database; + use nexus_types::identity::Asset; use omicron_common::api::external; use omicron_test_utils::dev; use std::net::{Ipv6Addr, SocketAddrV6}; + use std::num::NonZeroU32; fn rack_id() -> Uuid { Uuid::parse_str(nexus_test_utils::RACK_UUID).unwrap() @@ -243,19 +285,9 @@ mod test { let mut db = test_setup_database(&logctx.log).await; let (_opctx, datastore) = datastore_test(&logctx, &db).await; - let sled_id = Uuid::new_v4(); - let addr = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0); - let mut sled_update = SledUpdate::new( - sled_id, - addr, - sled_baseboard_for_test(), - sled_system_hardware_for_test(), - rack_id(), - ); - let observed_sled = datastore - .sled_upsert(sled_update.clone()) - .await - .expect("Could not upsert sled during test prep"); + let mut sled_update = test_new_sled_update(); + let observed_sled = + datastore.sled_upsert(sled_update.clone()).await.unwrap(); assert_eq!( observed_sled.usable_hardware_threads, sled_update.usable_hardware_threads @@ -301,4 +333,119 @@ mod test { db.cleanup().await.unwrap(); logctx.cleanup_successful(); } + + /// Test that new reservations aren't created on non-provisionable sleds. + #[tokio::test] + async fn sled_reservation_create_non_provisionable() { + let logctx = + dev::test_setup_log("sled_reservation_create_non_provisionable"); + let mut db = test_setup_database(&logctx.log).await; + let (opctx, datastore) = datastore_test(&logctx, &db).await; + + let sled_update = test_new_sled_update(); + let non_provisionable_sled = + datastore.sled_upsert(sled_update.clone()).await.unwrap(); + + let (authz_sled, _) = LookupPath::new(&opctx, &datastore) + .sled_id(non_provisionable_sled.id()) + .fetch_for(authz::Action::Modify) + .await + .unwrap(); + + let old_state = datastore + .sled_set_provision_state( + &opctx, + &authz_sled, + db::model::SledProvisionState::NonProvisionable, + ) + .await + .unwrap(); + assert_eq!( + old_state, + db::model::SledProvisionState::Provisionable, + "a newly created sled starts as provisionable" + ); + + // This should be an error since there are no provisionable sleds. + let resources = db::model::Resources::new( + 1, + // Just require the bare non-zero amount of RAM. + ByteCount::try_from(1024).unwrap(), + ByteCount::try_from(1024).unwrap(), + ); + let constraints = db::model::SledReservationConstraints::none(); + let error = datastore + .sled_reservation_create( + &opctx, + Uuid::new_v4(), + db::model::SledResourceKind::Instance, + resources.clone(), + constraints, + ) + .await + .unwrap_err(); + assert!(matches!(error, external::Error::InsufficientCapacity { .. })); + + // Now add a provisionable sled and try again. + let sled_update = test_new_sled_update(); + let provisionable_sled = + datastore.sled_upsert(sled_update.clone()).await.unwrap(); + + let sleds = datastore + .sled_list(&opctx, &first_page(NonZeroU32::new(10).unwrap())) + .await + .unwrap(); + println!("sleds: {:?}", sleds); + + // Try a few times to ensure that resources never get allocated to the + // non-provisionable sled. + for _ in 0..10 { + let constraints = db::model::SledReservationConstraints::none(); + let resource = datastore + .sled_reservation_create( + &opctx, + Uuid::new_v4(), + db::model::SledResourceKind::Instance, + resources.clone(), + constraints, + ) + .await + .unwrap(); + assert_eq!( + resource.sled_id, + provisionable_sled.id(), + "resource is always allocated to the provisionable sled" + ); + + datastore + .sled_reservation_delete(&opctx, resource.id) + .await + .unwrap(); + } + + db.cleanup().await.unwrap(); + logctx.cleanup_successful(); + } + + fn test_new_sled_update() -> SledUpdate { + let sled_id = Uuid::new_v4(); + let addr = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0); + SledUpdate::new( + sled_id, + addr, + sled_baseboard_for_test(), + sled_system_hardware_for_test(), + rack_id(), + ) + } + + /// Returns pagination parameters to fetch the first page of results for a + /// paginated endpoint + fn first_page<'a, T>(limit: NonZeroU32) -> DataPageParams<'a, T> { + DataPageParams { + marker: None, + direction: dropshot::PaginationOrder::Ascending, + limit, + } + } } diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index d7319347f0..6bd4e61f70 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -234,6 +234,7 @@ impl DataStore { c.mtu, c.fec.into(), c.speed.into(), + c.autoneg, )); } result.link_lldp = @@ -304,39 +305,41 @@ impl DataStore { .await?; let mut bgp_peer_config = Vec::new(); - for (interface_name, p) in ¶ms.bgp_peers { - use db::schema::bgp_config; - let bgp_config_id = match &p.bgp_config { - NameOrId::Id(id) => *id, - NameOrId::Name(name) => { - let name = name.to_string(); - bgp_config_dsl::bgp_config - .filter(bgp_config::time_deleted.is_null()) - .filter(bgp_config::name.eq(name)) - .select(bgp_config::id) - .limit(1) - .first_async::(&conn) - .await - .map_err(|_| - TxnError::CustomError( - SwitchPortSettingsCreateError::BgpConfigNotFound, - ) - )? - } - }; + for (interface_name, peer_config) in ¶ms.bgp_peers { + for p in &peer_config.peers { + use db::schema::bgp_config; + let bgp_config_id = match &p.bgp_config { + NameOrId::Id(id) => *id, + NameOrId::Name(name) => { + let name = name.to_string(); + bgp_config_dsl::bgp_config + .filter(bgp_config::time_deleted.is_null()) + .filter(bgp_config::name.eq(name)) + .select(bgp_config::id) + .limit(1) + .first_async::(&conn) + .await + .map_err(|_| + TxnError::CustomError( + SwitchPortSettingsCreateError::BgpConfigNotFound, + ) + )? + } + }; - bgp_peer_config.push(SwitchPortBgpPeerConfig::new( - psid, - bgp_config_id, - interface_name.clone(), - p.addr.into(), - p.hold_time.into(), - p.idle_hold_time.into(), - p.delay_open.into(), - p.connect_retry.into(), - p.keepalive.into(), - )); + bgp_peer_config.push(SwitchPortBgpPeerConfig::new( + psid, + bgp_config_id, + interface_name.clone(), + p.addr.into(), + p.hold_time.into(), + p.idle_hold_time.into(), + p.delay_open.into(), + p.connect_retry.into(), + p.keepalive.into(), + )); + } } result.bgp_peers = diesel::insert_into( @@ -1152,8 +1155,8 @@ mod test { use crate::db::datastore::{datastore_test, UpdatePrecondition}; use nexus_test_utils::db::test_setup_database; use nexus_types::external_api::params::{ - BgpAnnounceSetCreate, BgpConfigCreate, BgpPeerConfig, SwitchPortConfig, - SwitchPortGeometry, SwitchPortSettingsCreate, + BgpAnnounceSetCreate, BgpConfigCreate, BgpPeer, BgpPeerConfig, + SwitchPortConfig, SwitchPortGeometry, SwitchPortSettingsCreate, }; use omicron_common::api::external::{ IdentityMetadataCreateParams, Name, NameOrId, @@ -1217,19 +1220,21 @@ mod test { bgp_peers: HashMap::from([( "phy0".into(), BgpPeerConfig { - bgp_announce_set: NameOrId::Name( - "test-announce-set".parse().unwrap(), - ), - bgp_config: NameOrId::Name( - "test-bgp-config".parse().unwrap(), - ), - interface_name: "qsfp0".into(), - addr: "192.168.1.1".parse().unwrap(), - hold_time: 0, - idle_hold_time: 0, - delay_open: 0, - connect_retry: 0, - keepalive: 0, + peers: vec![BgpPeer { + bgp_announce_set: NameOrId::Name( + "test-announce-set".parse().unwrap(), + ), + bgp_config: NameOrId::Name( + "test-bgp-config".parse().unwrap(), + ), + interface_name: "qsfp0".into(), + addr: "192.168.1.1".parse().unwrap(), + hold_time: 0, + idle_hold_time: 0, + delay_open: 0, + connect_retry: 0, + keepalive: 0, + }], }, )]), addresses: HashMap::new(), diff --git a/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs b/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs index 83856e10c7..c5c2751723 100644 --- a/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs +++ b/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs @@ -124,10 +124,12 @@ impl DataStore { .get_result_async(conn) .await .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; - assert!( - collection.is_empty(), - "Collection deleted while non-empty: {collection:?}" - ); + + if !collection.is_empty() { + return Err(Error::internal_error(&format!( + "Collection deleted while non-empty: {collection:?}" + ))); + } Ok(()) } diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 37d7077777..2b47255fb4 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -340,7 +340,10 @@ impl DataStore { opctx.log, "failed to find a VNI after searching entire range"; ); - Err(Error::unavail_external("Failed to find a free VNI for this VPC")) + Err(Error::insufficient_capacity( + "No free virtual network was found", + "Failed to find a free VNI for this VPC", + )) } // Internal implementation for creating a VPC. @@ -470,11 +473,9 @@ impl DataStore { .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))? .is_some() { - return Err(Error::InvalidRequest { - message: String::from( - "VPC cannot be deleted while VPC Subnets exist", - ), - }); + return Err(Error::invalid_request( + "VPC cannot be deleted while VPC Subnets exist", + )); } // Delete the VPC, conditional on the subnet_gen not having changed. @@ -493,11 +494,9 @@ impl DataStore { ) })?; if updated_rows == 0 { - Err(Error::InvalidRequest { - message: String::from( - "deletion failed to to concurrent modification", - ), - }) + Err(Error::invalid_request( + "deletion failed due to concurrent modification", + )) } else { Ok(()) } @@ -783,12 +782,10 @@ impl DataStore { .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))? .is_some() { - return Err(Error::InvalidRequest { - message: String::from( - "VPC Subnet cannot be deleted while \ - network interfaces in the subnet exist", - ), - }); + return Err(Error::invalid_request( + "VPC Subnet cannot be deleted while network interfaces in the \ + subnet exist", + )); } // Delete the subnet, conditional on the rcgen not having changed. @@ -807,11 +804,9 @@ impl DataStore { ) })?; if updated_rows == 0 { - return Err(Error::InvalidRequest { - message: String::from( - "deletion failed to to concurrent modification", - ), - }); + return Err(Error::invalid_request( + "deletion failed due to concurrent modification", + )); } else { Ok(()) } diff --git a/nexus/db-queries/src/db/mod.rs b/nexus/db-queries/src/db/mod.rs index 8b7424a056..b7c7079b54 100644 --- a/nexus/db-queries/src/db/mod.rs +++ b/nexus/db-queries/src/db/mod.rs @@ -12,6 +12,7 @@ pub mod collection_attach; pub mod collection_detach; pub mod collection_detach_many; pub mod collection_insert; +mod column_walker; mod config; mod cte_utils; // This is marked public for use by the integration tests diff --git a/nexus/db-queries/src/db/queries/external_ip.rs b/nexus/db-queries/src/db/queries/external_ip.rs index cf182e080d..03bc4d9cae 100644 --- a/nexus/db-queries/src/db/queries/external_ip.rs +++ b/nexus/db-queries/src/db/queries/external_ip.rs @@ -989,9 +989,7 @@ mod tests { ); assert_eq!( err, - Error::InvalidRequest { - message: String::from("No external IP addresses available"), - } + Error::invalid_request("No external IP addresses available") ); context.success().await; } @@ -1045,9 +1043,7 @@ mod tests { ); assert_eq!( res.unwrap_err(), - Error::InvalidRequest { - message: String::from("No external IP addresses available"), - } + Error::invalid_request("No external IP addresses available") ); let res = context @@ -1067,9 +1063,7 @@ mod tests { ); assert_eq!( res.unwrap_err(), - Error::InvalidRequest { - message: String::from("No external IP addresses available"), - } + Error::invalid_request("No external IP addresses available") ); context.success().await; } @@ -1298,9 +1292,7 @@ mod tests { .expect_err("Should have failed to allocate after pool exhausted"); assert_eq!( err, - Error::InvalidRequest { - message: String::from("No external IP addresses available"), - } + Error::invalid_request("No external IP addresses available"), ); // But we should be able to allocate another SNat IP diff --git a/nexus/db-queries/src/db/queries/region_allocation.rs b/nexus/db-queries/src/db/queries/region_allocation.rs index 061aaffe33..9245d098fd 100644 --- a/nexus/db-queries/src/db/queries/region_allocation.rs +++ b/nexus/db-queries/src/db/queries/region_allocation.rs @@ -46,19 +46,23 @@ pub fn from_diesel(e: DieselError) -> external::Error { NOT_ENOUGH_UNIQUE_ZPOOLS_SENTINEL, ]; if let Some(sentinel) = matches_sentinel(&e, &sentinels) { + let external_message = "Not enough disk space"; match sentinel { NOT_ENOUGH_DATASETS_SENTINEL => { - return external::Error::unavail_external( + return external::Error::insufficient_capacity( + external_message, "Not enough datasets to allocate disks", ); } NOT_ENOUGH_ZPOOL_SPACE_SENTINEL => { - return external::Error::unavail_external( + return external::Error::insufficient_capacity( + external_message, "Not enough zpool space to allocate disks. There may not be enough disks with space for the requested region. You may also see this if your rack is in a degraded state, or you're running the default multi-rack topology configuration in a 1-sled development environment.", ); } NOT_ENOUGH_UNIQUE_ZPOOLS_SENTINEL => { - return external::Error::unavail_external( + return external::Error::insufficient_capacity( + external_message, "Not enough unique zpools selected while allocating disks", ); } @@ -290,6 +294,7 @@ impl CandidateZpools { seed: u128, distinct_sleds: bool, ) -> Self { + use schema::sled::dsl as sled_dsl; use schema::zpool::dsl as zpool_dsl; // Why are we using raw `diesel::dsl::sql` here? @@ -310,13 +315,20 @@ impl CandidateZpools { + diesel::dsl::sql(&zpool_size_delta.to_string())) .le(diesel::dsl::sql(zpool_dsl::total_size::NAME)); + // We need to join on the sled table to access provision_state. + let with_sled = sled_dsl::sled.on(zpool_dsl::sled_id.eq(sled_dsl::id)); let with_zpool = zpool_dsl::zpool - .on(zpool_dsl::id.eq(old_zpool_usage::dsl::pool_id)); + .on(zpool_dsl::id.eq(old_zpool_usage::dsl::pool_id)) + .inner_join(with_sled); + + let sled_is_provisionable = sled_dsl::provision_state + .eq(crate::db::model::SledProvisionState::Provisionable); let base_query = old_zpool_usage .query_source() .inner_join(with_zpool) .filter(it_will_fit) + .filter(sled_is_provisionable) .select((old_zpool_usage::dsl::pool_id,)); let query = if distinct_sleds { diff --git a/nexus/db-queries/src/db/update_and_check.rs b/nexus/db-queries/src/db/update_and_check.rs index d6bf14c083..fed79d5254 100644 --- a/nexus/db-queries/src/db/update_and_check.rs +++ b/nexus/db-queries/src/db/update_and_check.rs @@ -4,6 +4,7 @@ //! CTE implementation for "UPDATE with extended return status". +use super::column_walker::ColumnWalker; use super::pool::DbConnection; use async_bb8_diesel::AsyncRunQueryDsl; use diesel::associations::HasTable; @@ -21,7 +22,7 @@ use std::marker::PhantomData; /// allows referencing generics with names (and extending usage /// without re-stating those generic parameters everywhere). pub trait UpdateStatementExt { - type Table: QuerySource; + type Table: Table + QuerySource; type WhereClause; type Changeset; @@ -32,7 +33,7 @@ pub trait UpdateStatementExt { impl UpdateStatementExt for UpdateStatement where - T: QuerySource, + T: Table + QuerySource, { type Table = T; type WhereClause = U; @@ -201,11 +202,11 @@ where /// /// ```text /// // WITH found AS (SELECT FROM T WHERE ) -/// // updated AS (UPDATE T SET RETURNING *) +/// // updated AS (UPDATE T SET RETURNING ) /// // SELECT /// // found. /// // updated. -/// // found.* +/// // found. /// // FROM /// // found /// // LEFT JOIN @@ -217,41 +218,48 @@ impl QueryFragment for UpdateAndQueryStatement where US: UpdateStatementExt, US::Table: HasTable + Table, + ColumnWalker<<::Table as Table>::AllColumns>: + IntoIterator, PrimaryKey: diesel::Column, UpdateStatement: QueryFragment, { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { + let primary_key = as Column>::NAME; + out.push_sql("WITH found AS ("); self.find_subquery.walk_ast(out.reborrow())?; out.push_sql("), updated AS ("); self.update_statement.walk_ast(out.reborrow())?; - // TODO: Only need primary? Or would we actually want - // to pass the returned rows back through the result? - out.push_sql(" RETURNING *) "); + out.push_sql(" RETURNING "); + out.push_identifier(primary_key)?; + out.push_sql(") "); out.push_sql("SELECT"); - let name = as Column>::NAME; out.push_sql(" found."); - out.push_identifier(name)?; + out.push_identifier(primary_key)?; out.push_sql(", updated."); - out.push_identifier(name)?; - // TODO: I'd prefer to list all columns explicitly. But how? - // The types exist within Table::AllColumns, and each one - // has a name as "::Name". - // But Table::AllColumns is a tuple, which makes iteration - // a pain. - // - // TODO: Technically, we're repeating the PK here. - out.push_sql(", found.*"); + out.push_identifier(primary_key)?; + + // List all the "found" columns explicitly. + // This admittedly repeats the primary key, but that keeps the query + // "simple" since it returns all columns in the same order as + // AllColumns. + let all_columns = ColumnWalker::< + <::Table as Table>::AllColumns, + >::new(); + for column in all_columns.into_iter() { + out.push_sql(", found."); + out.push_identifier(column)?; + } out.push_sql(" FROM found LEFT JOIN updated ON"); out.push_sql(" found."); - out.push_identifier(name)?; + out.push_identifier(primary_key)?; out.push_sql(" = "); out.push_sql("updated."); - out.push_identifier(name)?; + out.push_identifier(primary_key)?; Ok(()) } diff --git a/nexus/examples/config.toml b/nexus/examples/config.toml index 3679fa8196..9d6bf2d22f 100644 --- a/nexus/examples/config.toml +++ b/nexus/examples/config.toml @@ -100,6 +100,7 @@ inventory.period_secs = 600 inventory.nkeep = 5 # Disable inventory collection altogether (for emergencies) inventory.disable = false +phantom_disks.period_secs = 30 [default_region_allocation_strategy] # allocate region on 3 random distinct zpools, on 3 random distinct sleds. diff --git a/nexus/src/app/address_lot.rs b/nexus/src/app/address_lot.rs index b87ae1b09f..847021bdd4 100644 --- a/nexus/src/app/address_lot.rs +++ b/nexus/src/app/address_lot.rs @@ -94,10 +94,9 @@ fn validate_blocks(lot: ¶ms::AddressLotCreate) -> Result<(), Error> { validate_v6_block(first, last)? } _ => { - return Err(Error::InvalidRequest { - message: "Block bounds must be in same address family" - .into(), - }) + return Err(Error::invalid_request( + "Block bounds must be in same address family", + )); } } } @@ -106,18 +105,18 @@ fn validate_blocks(lot: ¶ms::AddressLotCreate) -> Result<(), Error> { fn validate_v4_block(first: &Ipv4Addr, last: &Ipv4Addr) -> Result<(), Error> { if first > last { - return Err(Error::InvalidRequest { - message: "Invalid range, first must be <= last".into(), - }); + return Err(Error::invalid_request( + "Invalid range, first must be <= last", + )); } Ok(()) } fn validate_v6_block(first: &Ipv6Addr, last: &Ipv6Addr) -> Result<(), Error> { if first > last { - return Err(Error::InvalidRequest { - message: "Invalid range, first must be <= last".into(), - }); + return Err(Error::invalid_request( + "Invalid range, first must be <= last", + )); } Ok(()) } diff --git a/nexus/src/app/background/init.rs b/nexus/src/app/background/init.rs index d27248ffdc..cfa023a013 100644 --- a/nexus/src/app/background/init.rs +++ b/nexus/src/app/background/init.rs @@ -11,6 +11,7 @@ use super::dns_servers; use super::external_endpoints; use super::inventory_collection; use super::nat_cleanup; +use super::phantom_disks; use nexus_db_model::DnsGroup; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; @@ -52,6 +53,9 @@ pub struct BackgroundTasks { /// task handle for the task that collects inventory pub task_inventory_collection: common::TaskHandle, + + /// task handle for the task that detects phantom disks + pub task_phantom_disks: common::TaskHandle, } impl BackgroundTasks { @@ -122,7 +126,7 @@ impl BackgroundTasks { // Background task: inventory collector let task_inventory_collection = { let collector = inventory_collection::InventoryCollector::new( - datastore, + datastore.clone(), resolver, &nexus_id.to_string(), config.inventory.nkeep, @@ -143,6 +147,22 @@ impl BackgroundTasks { task }; + // Background task: phantom disk detection + let task_phantom_disks = { + let detector = phantom_disks::PhantomDiskDetector::new(datastore); + + let task = driver.register( + String::from("phantom_disks"), + String::from("detects and un-deletes phantom disks"), + config.phantom_disks.period_secs, + Box::new(detector), + opctx.child(BTreeMap::new()), + vec![], + ); + + task + }; + BackgroundTasks { driver, task_internal_dns_config, @@ -153,6 +173,7 @@ impl BackgroundTasks { external_endpoints, nat_cleanup, task_inventory_collection, + task_phantom_disks, } } diff --git a/nexus/src/app/background/mod.rs b/nexus/src/app/background/mod.rs index 954207cb3c..70b20224d4 100644 --- a/nexus/src/app/background/mod.rs +++ b/nexus/src/app/background/mod.rs @@ -12,6 +12,7 @@ mod external_endpoints; mod init; mod inventory_collection; mod nat_cleanup; +mod phantom_disks; mod status; pub use common::Driver; diff --git a/nexus/src/app/background/phantom_disks.rs b/nexus/src/app/background/phantom_disks.rs new file mode 100644 index 0000000000..b038d70ac6 --- /dev/null +++ b/nexus/src/app/background/phantom_disks.rs @@ -0,0 +1,104 @@ +// 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/. + +//! Background task for detecting and un-deleting phantom disks +//! +//! A "phantom" disk is one where a disk delete saga partially completed but +//! unwound: before a fix for customer-support#58, this would leave disks +//! deleted but would also leave a `virtual_provisioning_resource` record for +//! that disk. There would be no way to re-trigger the disk delete saga as the +//! disk was deleted, so the project that disk was in could not be deleted +//! because associated virtual provisioning resources were still being consumed. +//! +//! The fix for customer-support#58 changes the disk delete saga's unwind to +//! also un-delete the disk and set it to faulted. This enables it to be deleted +//! again. Correcting the disk delete saga's unwind means that phantom disks +//! will not be created in the future when the disk delete saga unwinds, but +//! this background task is required to apply the same fix for disks that are +//! already in this phantom state. + +use super::common::BackgroundTask; +use futures::future::BoxFuture; +use futures::FutureExt; +use nexus_db_queries::context::OpContext; +use nexus_db_queries::db::DataStore; +use serde_json::json; +use std::sync::Arc; + +pub struct PhantomDiskDetector { + datastore: Arc, +} + +impl PhantomDiskDetector { + pub fn new(datastore: Arc) -> Self { + PhantomDiskDetector { datastore } + } +} + +impl BackgroundTask for PhantomDiskDetector { + fn activate<'a, 'b, 'c>( + &'a mut self, + opctx: &'b OpContext, + ) -> BoxFuture<'c, serde_json::Value> + where + 'a: 'c, + 'b: 'c, + { + async { + let log = &opctx.log; + warn!(&log, "phantom disk task started"); + + let phantom_disks = match self.datastore.find_phantom_disks().await + { + Ok(phantom_disks) => phantom_disks, + Err(e) => { + warn!(&log, "error from find_phantom_disks: {:?}", e); + return json!({ + "error": + format!("failed find_phantom_disks: {:#}", e) + }); + } + }; + + let mut phantom_disk_deleted_ok = 0; + let mut phantom_disk_deleted_err = 0; + + for disk in phantom_disks { + warn!(&log, "phantom disk {} found!", disk.id()); + + // If a phantom disk is found, then un-delete it and set it to + // faulted: this will allow a user to request deleting it again. + + let result = self + .datastore + .project_undelete_disk_set_faulted_no_auth(&disk.id()) + .await; + + if let Err(e) = result { + error!( + &log, + "error un-deleting disk {} and setting to faulted: {:#}", + disk.id(), + e, + ); + phantom_disk_deleted_err += 1; + } else { + info!( + &log, + "phandom disk {} un-deleted andset to faulted ok", + disk.id(), + ); + phantom_disk_deleted_ok += 1; + } + } + + warn!(&log, "phantom disk task done"); + json!({ + "phantom_disk_deleted_ok": phantom_disk_deleted_ok, + "phantom_disk_deleted_err": phantom_disk_deleted_err, + }) + } + .boxed() + } +} diff --git a/nexus/src/app/device_auth.rs b/nexus/src/app/device_auth.rs index c9571ee91f..c70b339a36 100644 --- a/nexus/src/app/device_auth.rs +++ b/nexus/src/app/device_auth.rs @@ -114,9 +114,7 @@ impl super::Nexus { token, ) .await?; - Err(Error::InvalidRequest { - message: "device authorization request expired".to_string(), - }) + Err(Error::invalid_request("device authorization request expired")) } else { self.db_datastore .device_access_token_create( diff --git a/nexus/src/app/disk.rs b/nexus/src/app/disk.rs index 28d6c4506c..0ce86ff90a 100644 --- a/nexus/src/app/disk.rs +++ b/nexus/src/app/disk.rs @@ -140,48 +140,48 @@ impl super::Nexus { // Reject disks where the block size doesn't evenly divide the // total size if (params.size.to_bytes() % block_size) != 0 { - return Err(Error::InvalidValue { - label: String::from("size and block_size"), - message: format!( + return Err(Error::invalid_value( + "size and block_size", + format!( "total size must be a multiple of block size {}", block_size, ), - }); + )); } // Reject disks where the size isn't at least // MIN_DISK_SIZE_BYTES if params.size.to_bytes() < MIN_DISK_SIZE_BYTES as u64 { - return Err(Error::InvalidValue { - label: String::from("size"), - message: format!( + return Err(Error::invalid_value( + "size", + format!( "total size must be at least {}", ByteCount::from(MIN_DISK_SIZE_BYTES) ), - }); + )); } // Reject disks where the MIN_DISK_SIZE_BYTES doesn't evenly // divide the size if (params.size.to_bytes() % MIN_DISK_SIZE_BYTES as u64) != 0 { - return Err(Error::InvalidValue { - label: String::from("size"), - message: format!( + return Err(Error::invalid_value( + "size", + format!( "total size must be a multiple of {}", ByteCount::from(MIN_DISK_SIZE_BYTES) ), - }); + )); } // Reject disks where the size is greated than MAX_DISK_SIZE_BYTES if params.size.to_bytes() > MAX_DISK_SIZE_BYTES { - return Err(Error::InvalidValue { - label: String::from("size"), - message: format!( + return Err(Error::invalid_value( + "size", + format!( "total size must be less than {}", ByteCount::try_from(MAX_DISK_SIZE_BYTES).unwrap() ), - }); + )); } Ok(()) diff --git a/nexus/src/app/external_endpoints.rs b/nexus/src/app/external_endpoints.rs index a413ad15fa..1ab33c5c9c 100644 --- a/nexus/src/app/external_endpoints.rs +++ b/nexus/src/app/external_endpoints.rs @@ -737,7 +737,7 @@ fn endpoint_for_authority( let endpoint_config = config_rx.borrow(); let endpoints = endpoint_config.as_ref().ok_or_else(|| { error!(&log, "received request with no endpoints loaded"); - Error::unavail_external("endpoints not loaded") + Error::unavail("endpoints not loaded") })?; // See if there's an endpoint for the requested name. If so, use it. @@ -810,7 +810,6 @@ mod test { use nexus_types::identity::Resource; use omicron_common::api::external::Error; use omicron_common::api::external::IdentityMetadataCreateParams; - use omicron_common::api::external::MessageVariant; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -1533,20 +1532,14 @@ mod test { let result = endpoint_for_authority(&log, &authority, rx_channel); match result { - Err(Error::ServiceUnavailable { message }) => { + Err(Error::ServiceUnavailable { internal_message }) => { assert_eq!(rx_label, "none"); - assert_eq!( - message, - MessageVariant::External { - message: "endpoints not loaded".to_owned(), - internal_context: String::new(), - } - ); + assert_eq!(internal_message, "endpoints not loaded"); } Err(Error::InvalidRequest { message }) => { assert_eq!(rx_label, "empty"); assert_eq!( - message, + message.external_message(), format!( "HTTP request for unknown server name {:?}", authority.host() diff --git a/nexus/src/app/image.rs b/nexus/src/app/image.rs index 8fa9308c1d..5d3b2f3296 100644 --- a/nexus/src/app/image.rs +++ b/nexus/src/app/image.rs @@ -98,9 +98,8 @@ impl super::Nexus { let new_image = match ¶ms.source { params::ImageSource::Url { url, block_size } => { let db_block_size = db::model::BlockSize::try_from(*block_size) - .map_err(|e| Error::InvalidValue { - label: String::from("block_size"), - message: format!("block_size is invalid: {}", e), + .map_err(|e| { + Error::invalid_value("block_size", e.to_string()) })?; let image_id = Uuid::new_v4(); @@ -129,20 +128,17 @@ impl super::Nexus { })?; let response = client.head(url).send().await.map_err(|e| { - Error::InvalidValue { - label: String::from("url"), - message: format!("error querying url: {}", e), - } + Error::invalid_value( + "url", + format!("error querying url: {e}"), + ) })?; if !response.status().is_success() { - return Err(Error::InvalidValue { - label: String::from("url"), - message: format!( - "querying url returned: {}", - response.status() - ), - }); + return Err(Error::invalid_value( + "url", + format!("querying url returned: {}", response.status()), + )); } // grab total size from content length @@ -150,43 +146,47 @@ impl super::Nexus { .headers() .get(reqwest::header::CONTENT_LENGTH) .ok_or("no content length!") - .map_err(|e| Error::InvalidValue { - label: String::from("url"), - message: format!("error querying url: {}", e), + .map_err(|e| { + Error::invalid_value( + "url", + format!("error querying url: {e}",), + ) })?; let total_size = u64::from_str(content_length.to_str().map_err(|e| { - Error::InvalidValue { - label: String::from("url"), - message: format!("content length invalid: {}", e), - } + Error::invalid_value( + "url", + format!("content length invalid: {e}",), + ) })?) .map_err(|e| { - Error::InvalidValue { - label: String::from("url"), - message: format!("content length invalid: {}", e), - } + Error::invalid_value( + "url", + format!("content length invalid: {e}"), + ) })?; let size: external::ByteCount = total_size.try_into().map_err( - |e: external::ByteCountRangeError| Error::InvalidValue { - label: String::from("size"), - message: format!("total size is invalid: {}", e), + |e: external::ByteCountRangeError| { + Error::invalid_value( + "size", + format!("total size is invalid: {}", e), + ) }, )?; // validate total size is divisible by block size let block_size: u64 = (*block_size).into(); if (size.to_bytes() % block_size) != 0 { - return Err(Error::InvalidValue { - label: String::from("size"), - message: format!( + return Err(Error::invalid_value( + "size", + format!( "total size {} must be divisible by block size {}", size.to_bytes(), block_size ), - }); + )); } let new_image_volume = @@ -284,9 +284,11 @@ impl super::Nexus { // disk created from this image has to be larger than it. let size: u64 = 100 * 1024 * 1024; let size: external::ByteCount = - size.try_into().map_err(|e| Error::InvalidValue { - label: String::from("size"), - message: format!("size is invalid: {}", e), + size.try_into().map_err(|e| { + Error::invalid_value( + "size", + format!("size is invalid: {}", e), + ) })?; let new_image_volume = @@ -409,9 +411,9 @@ impl super::Nexus { ) .await } - ImageLookup::SiloImage(_) => Err(Error::InvalidRequest { - message: "Cannot promote a silo image".to_string(), - }), + ImageLookup::SiloImage(_) => { + Err(Error::invalid_request("Cannot promote a silo image")) + } } } @@ -437,9 +439,9 @@ impl super::Nexus { ) .await } - ImageLookup::ProjectImage(_) => Err(Error::InvalidRequest { - message: "Cannot demote a project image".to_string(), - }), + ImageLookup::ProjectImage(_) => { + Err(Error::invalid_request("Cannot demote a project image")) + } } } } diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 17d34863df..fa17a87856 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -197,13 +197,13 @@ impl super::Nexus { // Reject instances where the memory is not at least // MIN_MEMORY_BYTES_PER_INSTANCE if params.memory.to_bytes() < MIN_MEMORY_BYTES_PER_INSTANCE as u64 { - return Err(Error::InvalidValue { - label: String::from("size"), - message: format!( + return Err(Error::invalid_value( + "size", + format!( "memory must be at least {}", ByteCount::from(MIN_MEMORY_BYTES_PER_INSTANCE) ), - }); + )); } // Reject instances where the memory is not divisible by @@ -211,24 +211,24 @@ impl super::Nexus { if (params.memory.to_bytes() % MIN_MEMORY_BYTES_PER_INSTANCE as u64) != 0 { - return Err(Error::InvalidValue { - label: String::from("size"), - message: format!( + return Err(Error::invalid_value( + "size", + format!( "memory must be divisible by {}", ByteCount::from(MIN_MEMORY_BYTES_PER_INSTANCE) ), - }); + )); } // Reject instances where the memory is greater than the limit if params.memory.to_bytes() > MAX_MEMORY_BYTES_PER_INSTANCE { - return Err(Error::InvalidValue { - label: String::from("size"), - message: format!( + return Err(Error::invalid_value( + "size", + format!( "memory must be less than or equal to {}", ByteCount::try_from(MAX_MEMORY_BYTES_PER_INSTANCE).unwrap() ), - }); + )); } let saga_params = sagas::instance_create::Params { @@ -362,9 +362,7 @@ impl super::Nexus { } if instance.runtime().migration_id.is_some() { - return Err(Error::unavail_external( - "instance is already migrating", - )); + return Err(Error::conflict("instance is already migrating")); } // Kick off the migration saga @@ -773,12 +771,10 @@ impl super::Nexus { if allowed { Ok(InstanceStateChangeRequestAction::SendToSled(sled_id)) } else { - Err(Error::InvalidRequest { - message: format!( - "instance state cannot be changed from state \"{}\"", - effective_state - ), - }) + Err(Error::invalid_request(format!( + "instance state cannot be changed from state \"{}\"", + effective_state + ))) } } @@ -1202,10 +1198,9 @@ impl super::Nexus { // permissions on both) without verifying the shared hierarchy. To // mitigate that we verify that their parent projects have the same ID. if authz_project.id() != authz_project_disk.id() { - return Err(Error::InvalidRequest { - message: "disk must be in the same project as the instance" - .to_string(), - }); + return Err(Error::invalid_request( + "disk must be in the same project as the instance", + )); } // TODO(https://github.com/oxidecomputer/omicron/issues/811): @@ -1586,22 +1581,18 @@ impl super::Nexus { | InstanceState::Stopping | InstanceState::Stopped | InstanceState::Failed => { - Err(Error::unavail_external(format!( - "cannot connect to serial console of instance in state \ - {:?}", - vmm.runtime.state.0 - ))) - } - InstanceState::Destroyed => { - Err(Error::unavail_external(format!( - "cannot connect to serial console of instance in state \ - {:?}", - InstanceState::Stopped))) + Err(Error::invalid_request(format!( + "cannot connect to serial console of {} instance", + vmm.runtime.state.0, + ))) } + InstanceState::Destroyed => Err(Error::invalid_request( + "cannot connect to serial console of destroyed instance", + )), } } else { - Err(Error::unavail_external(format!( - "instance is in state {:?} and has no active serial console \ + Err(Error::invalid_request(format!( + "instance is {} and has no active serial console \ server", instance.runtime().nexus_state ))) diff --git a/nexus/src/app/oximeter.rs b/nexus/src/app/oximeter.rs index 21aec7a6b3..a168b35293 100644 --- a/nexus/src/app/oximeter.rs +++ b/nexus/src/app/oximeter.rs @@ -127,9 +127,7 @@ impl super::Nexus { for producer in producers.into_iter() { let producer_info = oximeter_client::types::ProducerEndpoint { id: producer.id(), - kind: producer - .kind - .map(|kind| nexus::ProducerKind::from(kind).into()), + kind: nexus::ProducerKind::from(producer.kind).into(), address: SocketAddr::new( producer.ip.ip(), producer.port.try_into().unwrap(), @@ -152,7 +150,7 @@ impl super::Nexus { pub(crate) async fn register_as_producer(&self, address: SocketAddr) { let producer_endpoint = nexus::ProducerEndpoint { id: self.id, - kind: Some(nexus::ProducerKind::Service), + kind: nexus::ProducerKind::Service, address, base_route: String::from("/metrics/collect"), interval: Duration::from_secs(10), @@ -390,8 +388,8 @@ impl super::Nexus { limit: std::num::NonZeroU32::new(1).unwrap(), }; let oxs = self.db_datastore.oximeter_list(&page_params).await?; - let info = oxs.first().ok_or_else(|| { - Error::unavail_external("no oximeter collectors available") + let info = oxs.first().ok_or_else(|| Error::ServiceUnavailable { + internal_message: String::from("no oximeter collectors available"), })?; let address = SocketAddr::from((info.ip.ip(), info.port.try_into().unwrap())); @@ -403,8 +401,7 @@ impl super::Nexus { fn map_oximeter_err(error: oximeter_db::Error) -> Error { match error { oximeter_db::Error::DatabaseUnavailable(_) => { - // XXX: should this be unavail_external? - Error::unavail_internal(error.to_string()) + 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 984ece2d0c..a21a566cfc 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -23,10 +23,16 @@ use nexus_db_queries::db::lookup::LookupPath; use nexus_types::external_api::params::Address; use nexus_types::external_api::params::AddressConfig; use nexus_types::external_api::params::AddressLotBlockCreate; +use nexus_types::external_api::params::BgpAnnounceSetCreate; +use nexus_types::external_api::params::BgpAnnouncementCreate; +use nexus_types::external_api::params::BgpConfigCreate; +use nexus_types::external_api::params::BgpPeer; +use nexus_types::external_api::params::LinkConfig; +use nexus_types::external_api::params::LldpServiceConfig; use nexus_types::external_api::params::RouteConfig; use nexus_types::external_api::params::SwitchPortConfig; use nexus_types::external_api::params::{ - AddressLotCreate, LoopbackAddressCreate, Route, SiloCreate, + AddressLotCreate, BgpPeerConfig, LoopbackAddressCreate, Route, SiloCreate, SwitchPortSettingsCreate, }; use nexus_types::external_api::shared::Baseboard; @@ -51,8 +57,8 @@ use sled_agent_client::types::EarlyNetworkConfigBody; use sled_agent_client::types::StartSledAgentRequest; use sled_agent_client::types::StartSledAgentRequestBody; use sled_agent_client::types::{ - BgpConfig, BgpPeerConfig, EarlyNetworkConfig, PortConfigV1, - RackNetworkConfigV1, RouteConfig as SledRouteConfig, + BgpConfig, BgpPeerConfig as SledBgpPeerConfig, EarlyNetworkConfig, + PortConfigV1, RackNetworkConfigV1, RouteConfig as SledRouteConfig, }; use std::collections::BTreeMap; use std::collections::BTreeSet; @@ -205,10 +211,9 @@ impl super::Nexus { }; let rack_network_config = request.rack_network_config.as_ref().ok_or( - Error::InvalidRequest { - message: "cannot initialize a rack without a network config" - .into(), - }, + Error::invalid_request( + "cannot initialize a rack without a network config", + ), )?; self.db_datastore @@ -406,6 +411,108 @@ impl super::Nexus { Error::internal_error(&format!("unable to retrieve authz_address_lot for infra address_lot: {e}")) })?; + let mut bgp_configs = HashMap::new(); + + for bgp_config in &rack_network_config.bgp { + bgp_configs.insert(bgp_config.asn, bgp_config.clone()); + + let bgp_config_name: Name = + format!("as{}", bgp_config.asn).parse().unwrap(); + + let announce_set_name: Name = + format!("as{}-announce", bgp_config.asn).parse().unwrap(); + + let address_lot_name: Name = + format!("as{}-lot", bgp_config.asn).parse().unwrap(); + + self.db_datastore + .address_lot_create( + &opctx, + &AddressLotCreate { + identity: IdentityMetadataCreateParams { + name: address_lot_name, + description: format!( + "Address lot for announce set in as {}", + bgp_config.asn + ), + }, + kind: AddressLotKind::Infra, + blocks: bgp_config + .originate + .iter() + .map(|o| AddressLotBlockCreate { + first_address: o.network().into(), + last_address: o.broadcast().into(), + }) + .collect(), + }, + ) + .await + .map_err(|e| { + Error::internal_error(&format!( + "unable to create address lot for BGP as {}: {}", + bgp_config.asn, e + )) + })?; + + self.db_datastore + .bgp_create_announce_set( + &opctx, + &BgpAnnounceSetCreate { + identity: IdentityMetadataCreateParams { + name: announce_set_name.clone(), + description: format!( + "Announce set for AS {}", + bgp_config.asn + ), + }, + announcement: bgp_config + .originate + .iter() + .map(|x| BgpAnnouncementCreate { + address_lot_block: NameOrId::Name( + format!("as{}", bgp_config.asn) + .parse() + .unwrap(), + ), + network: IpNetwork::from(*x).into(), + }) + .collect(), + }, + ) + .await + .map_err(|e| { + Error::internal_error(&format!( + "unable to create bgp announce set for as {}: {}", + bgp_config.asn, e + )) + })?; + + self.db_datastore + .bgp_config_set( + &opctx, + &BgpConfigCreate { + identity: IdentityMetadataCreateParams { + name: bgp_config_name, + description: format!( + "BGP config for AS {}", + bgp_config.asn + ), + }, + asn: bgp_config.asn, + bgp_announce_set_id: announce_set_name.into(), + vrf: None, + }, + ) + .await + .map_err(|e| { + Error::internal_error(&format!( + "unable to set bgp config for as {}: {}", + bgp_config.asn, e + )) + })?; + } + for (idx, uplink_config) in rack_network_config.ports.iter().enumerate() { @@ -503,6 +610,43 @@ impl super::Nexus { .routes .insert("phy0".to_string(), RouteConfig { routes }); + let peers: Vec = uplink_config + .bgp_peers + .iter() + .map(|r| BgpPeer { + bgp_announce_set: NameOrId::Name( + format!("as{}-announce", r.asn).parse().unwrap(), + ), + bgp_config: NameOrId::Name( + format!("as{}", r.asn).parse().unwrap(), + ), + interface_name: "phy0".into(), + addr: r.addr.into(), + hold_time: r.hold_time.unwrap_or(6) as u32, + idle_hold_time: r.idle_hold_time.unwrap_or(3) as u32, + delay_open: r.delay_open.unwrap_or(0) as u32, + connect_retry: r.connect_retry.unwrap_or(3) as u32, + keepalive: r.keepalive.unwrap_or(2) as u32, + }) + .collect(); + + port_settings_params + .bgp_peers + .insert("phy0".to_string(), BgpPeerConfig { peers }); + + let link = LinkConfig { + mtu: 1500, //TODO https://github.com/oxidecomputer/omicron/issues/2274 + lldp: LldpServiceConfig { + enabled: false, + lldp_config: None, + }, + fec: uplink_config.uplink_port_fec.into(), + speed: uplink_config.uplink_port_speed.into(), + autoneg: uplink_config.autoneg, + }; + + port_settings_params.links.insert("phy".to_string(), link); + match self .db_datastore .switch_port_settings_create( @@ -658,7 +802,7 @@ impl super::Nexus { addresses: info.addresses.iter().map(|a| a.address).collect(), bgp_peers: peer_info .iter() - .map(|(p, asn, addr)| BgpPeerConfig { + .map(|(p, asn, addr)| SledBgpPeerConfig { addr: *addr, asn: *asn, port: port.port_name.clone(), @@ -673,16 +817,21 @@ impl super::Nexus { port: port.port_name.clone(), uplink_port_fec: info .links - .get(0) //TODO breakout support + .get(0) //TODO https://github.com/oxidecomputer/omicron/issues/3062 .map(|l| l.fec) .unwrap_or(SwitchLinkFec::None) .into(), uplink_port_speed: info .links - .get(0) //TODO breakout support + .get(0) //TODO https://github.com/oxidecomputer/omicron/issues/3062 .map(|l| l.speed) .unwrap_or(SwitchLinkSpeed::Speed100G) .into(), + autoneg: info + .links + .get(0) //TODO breakout support + .map(|l| l.autoneg) + .unwrap_or(false), }; ports.push(p); diff --git a/nexus/src/app/sagas/disk_delete.rs b/nexus/src/app/sagas/disk_delete.rs index f2d80d64f5..8f6d74da0a 100644 --- a/nexus/src/app/sagas/disk_delete.rs +++ b/nexus/src/app/sagas/disk_delete.rs @@ -32,10 +32,8 @@ pub(crate) struct Params { declare_saga_actions! { disk_delete; DELETE_DISK_RECORD -> "deleted_disk" { - // TODO: See the comment on the "DeleteRegions" step, - // we may want to un-delete the disk if we cannot remove - // underlying regions. + sdd_delete_disk_record + - sdd_delete_disk_record_undo } SPACE_ACCOUNT -> "no_result1" { + sdd_account_space @@ -117,6 +115,21 @@ async fn sdd_delete_disk_record( Ok(disk) } +async fn sdd_delete_disk_record_undo( + sagactx: NexusActionContext, +) -> Result<(), anyhow::Error> { + let osagactx = sagactx.user_data(); + let params = sagactx.saga_params::()?; + + osagactx + .datastore() + .project_undelete_disk_set_faulted_no_auth(¶ms.disk_id) + .await + .map_err(ActionError::action_failed)?; + + Ok(()) +} + async fn sdd_account_space( sagactx: NexusActionContext, ) -> Result<(), ActionError> { diff --git a/nexus/src/app/sagas/snapshot_create.rs b/nexus/src/app/sagas/snapshot_create.rs index 77038c4098..3b4dfc0043 100644 --- a/nexus/src/app/sagas/snapshot_create.rs +++ b/nexus/src/app/sagas/snapshot_create.rs @@ -840,9 +840,14 @@ async fn ssc_attach_disk_to_pantry( _ => { // Return a 503 indicating that the user should retry - return Err(ActionError::action_failed(Error::unavail_external( - format!("disk is in state {:?}", db_disk.state()), - ))); + return Err(ActionError::action_failed( + Error::ServiceUnavailable { + internal_message: format!( + "disk is in state {:?}", + db_disk.state(), + ), + }, + )); } } diff --git a/nexus/src/app/sagas/switch_port_settings_common.rs b/nexus/src/app/sagas/switch_port_settings_common.rs index b328c6d1ac..9132645782 100644 --- a/nexus/src/app/sagas/switch_port_settings_common.rs +++ b/nexus/src/app/sagas/switch_port_settings_common.rs @@ -55,7 +55,7 @@ pub(crate) fn api_to_dpd_port_settings( link_id.to_string(), LinkSettings { params: LinkCreate { - autoneg: false, + autoneg: l.autoneg, lane: Some(LinkId(0)), kr: false, fec: match l.fec { @@ -251,6 +251,7 @@ pub(crate) async fn bootstore_update( .map(|l| l.speed) .unwrap_or(SwitchLinkSpeed::Speed100G) .into(), + autoneg: settings.links.get(0).map(|l| l.autoneg).unwrap_or(false), bgp_peers: peer_info .iter() .filter_map(|(p, asn)| { diff --git a/nexus/src/app/session.rs b/nexus/src/app/session.rs index 891124e1ac..5e63f47d01 100644 --- a/nexus/src/app/session.rs +++ b/nexus/src/app/session.rs @@ -154,6 +154,7 @@ impl super::Nexus { | Error::Forbidden | Error::InternalError { .. } | Error::ServiceUnavailable { .. } + | Error::InsufficientCapacity { .. } | Error::MethodNotAllowed { .. } | Error::TypeVersionMismatch { .. } | Error::Conflict { .. } => { diff --git a/nexus/src/app/silo.rs b/nexus/src/app/silo.rs index a6ffd8ef5e..f5f3fa00e7 100644 --- a/nexus/src/app/silo.rs +++ b/nexus/src/app/silo.rs @@ -822,25 +822,24 @@ impl super::Nexus { })?; let response = client.get(url).send().await.map_err(|e| { - Error::InvalidValue { - label: String::from("url"), - message: format!("error querying url: {}", e), - } + Error::invalid_value( + "url", + format!("error querying url: {e}"), + ) })?; if !response.status().is_success() { - return Err(Error::InvalidValue { - label: String::from("url"), - message: format!( - "querying url returned: {}", - response.status() - ), - }); + return Err(Error::invalid_value( + "url", + format!("querying url returned: {}", response.status()), + )); } - response.text().await.map_err(|e| Error::InvalidValue { - label: String::from("url"), - message: format!("error getting text from url: {}", e), + response.text().await.map_err(|e| { + Error::invalid_value( + "url", + format!("error getting text from url: {e}"), + ) })? } @@ -849,12 +848,11 @@ impl super::Nexus { &base64::engine::general_purpose::STANDARD, data, ) - .map_err(|e| Error::InvalidValue { - label: String::from("data"), - message: format!( - "error getting decoding base64 data: {}", - e - ), + .map_err(|e| { + Error::invalid_value( + "data", + format!("error getting decoding base64 data: {e}"), + ) })?; String::from_utf8_lossy(&bytes).into_owned() } diff --git a/nexus/src/app/sled.rs b/nexus/src/app/sled.rs index c2931f1441..44efc2934e 100644 --- a/nexus/src/app/sled.rs +++ b/nexus/src/app/sled.rs @@ -8,6 +8,7 @@ use crate::internal_api::params::{ PhysicalDiskDeleteRequest, PhysicalDiskPutRequest, SledAgentStartupInfo, SledRole, ZpoolPutRequest, }; +use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::lookup; @@ -142,6 +143,20 @@ impl super::Nexus { .await } + /// Returns the old state. + pub(crate) async fn sled_set_provision_state( + &self, + opctx: &OpContext, + sled_lookup: &lookup::Sled<'_>, + state: db::model::SledProvisionState, + ) -> Result { + let (authz_sled,) = + sled_lookup.lookup_for(authz::Action::Modify).await?; + self.db_datastore + .sled_set_provision_state(opctx, &authz_sled, state) + .await + } + // Physical disks pub(crate) async fn sled_list_physical_disks( diff --git a/nexus/src/app/switch_interface.rs b/nexus/src/app/switch_interface.rs index cfb0541742..0acb2b7fe7 100644 --- a/nexus/src/app/switch_interface.rs +++ b/nexus/src/app/switch_interface.rs @@ -95,9 +95,9 @@ impl super::Nexus { pub fn validate_switch_location(switch_location: &str) -> Result<(), Error> { if switch_location != "switch0" && switch_location != "switch1" { - return Err(Error::InvalidRequest { - message: "Switch location must be switch0 or switch1".into(), - }); + return Err(Error::invalid_request( + "Switch location must be switch0 or switch1", + )); } Ok(()) } diff --git a/nexus/src/app/test_interfaces.rs b/nexus/src/app/test_interfaces.rs index 6161a9a1c1..581b9a89bb 100644 --- a/nexus/src/app/test_interfaces.rs +++ b/nexus/src/app/test_interfaces.rs @@ -10,10 +10,9 @@ use sled_agent_client::Client as SledAgentClient; use std::sync::Arc; use uuid::Uuid; +pub use super::update::HostPhase1Updater; pub use super::update::MgsClients; -pub use super::update::RotUpdateError; pub use super::update::RotUpdater; -pub use super::update::SpUpdateError; pub use super::update::SpUpdater; pub use super::update::UpdateProgress; pub use gateway_client::types::SpType; diff --git a/nexus/src/app/update/common_sp_update.rs b/nexus/src/app/update/common_sp_update.rs new file mode 100644 index 0000000000..69a5b132a2 --- /dev/null +++ b/nexus/src/app/update/common_sp_update.rs @@ -0,0 +1,239 @@ +// 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/. + +//! Module containing implementation details shared amongst all MGS-to-SP-driven +//! updates. + +use super::MgsClients; +use super::UpdateProgress; +use gateway_client::types::SpType; +use gateway_client::types::SpUpdateStatus; +use slog::Logger; +use std::time::Duration; +use tokio::sync::watch; +use uuid::Uuid; + +type GatewayClientError = gateway_client::Error; + +/// Error type returned when an update to a component managed by the SP fails. +/// +/// Note that the SP manages itself, as well, so "SP component" here includes +/// the SP. +#[derive(Debug, thiserror::Error)] +pub enum SpComponentUpdateError { + #[error("error communicating with MGS")] + MgsCommunication(#[from] GatewayClientError), + #[error("different update is now preparing ({0})")] + DifferentUpdatePreparing(Uuid), + #[error("different update is now in progress ({0})")] + DifferentUpdateInProgress(Uuid), + #[error("different update is now complete ({0})")] + DifferentUpdateComplete(Uuid), + #[error("different update is now aborted ({0})")] + DifferentUpdateAborted(Uuid), + #[error("different update failed ({0})")] + DifferentUpdateFailed(Uuid), + #[error("update status lost (did the SP reset?)")] + UpdateStatusLost, + #[error("update was aborted")] + UpdateAborted, + #[error("update failed (error code {0})")] + UpdateFailedWithCode(u32), + #[error("update failed (error message {0})")] + UpdateFailedWithMessage(String), +} + +pub(super) trait SpComponentUpdater { + /// The target component. + /// + /// Should be produced via `SpComponent::const_as_str()`. + fn component(&self) -> &'static str; + + /// The type of the target SP. + fn target_sp_type(&self) -> SpType; + + /// The slot number of the target SP. + fn target_sp_slot(&self) -> u32; + + /// The target firmware slot for the component. + fn firmware_slot(&self) -> u16; + + /// The ID of this update. + fn update_id(&self) -> Uuid; + + /// The update payload data to send to MGS. + // TODO-performance This has to be convertible into a `reqwest::Body`, so we + // return an owned Vec. That requires all our implementors to clone the data + // at least once; maybe we should use `Bytes` instead (which is cheap to + // clone and also convertible into a reqwest::Body)? + fn update_data(&self) -> Vec; + + /// The sending half of the watch channel to report update progress. + fn progress(&self) -> &watch::Sender>; + + /// Logger to use while performing this update. + fn logger(&self) -> &Logger; +} + +pub(super) async fn deliver_update( + updater: &(dyn SpComponentUpdater + Send + Sync), + mgs_clients: &mut MgsClients, +) -> Result<(), SpComponentUpdateError> { + // How frequently do we poll MGS for the update progress? + const STATUS_POLL_INTERVAL: Duration = Duration::from_secs(3); + + // Start the update. + mgs_clients + .try_all_serially(updater.logger(), |client| async move { + client + .sp_component_update( + updater.target_sp_type(), + updater.target_sp_slot(), + updater.component(), + updater.firmware_slot(), + &updater.update_id(), + reqwest::Body::from(updater.update_data()), + ) + .await?; + updater.progress().send_replace(Some(UpdateProgress::Started)); + info!( + updater.logger(), "update started"; + "mgs_addr" => client.baseurl(), + ); + Ok(()) + }) + .await?; + + // Wait for the update to complete. + loop { + let status = mgs_clients + .try_all_serially(updater.logger(), |client| async move { + let update_status = client + .sp_component_update_status( + updater.target_sp_type(), + updater.target_sp_slot(), + updater.component(), + ) + .await?; + + debug!( + updater.logger(), "got update status"; + "mgs_addr" => client.baseurl(), + "status" => ?update_status, + ); + + Ok(update_status) + }) + .await?; + + if status_is_complete( + status.into_inner(), + updater.update_id(), + updater.progress(), + updater.logger(), + )? { + updater.progress().send_replace(Some(UpdateProgress::InProgress { + progress: Some(1.0), + })); + return Ok(()); + } + + tokio::time::sleep(STATUS_POLL_INTERVAL).await; + } +} + +fn status_is_complete( + status: SpUpdateStatus, + update_id: Uuid, + progress_tx: &watch::Sender>, + log: &Logger, +) -> Result { + match status { + // For `Preparing` and `InProgress`, we could check the progress + // information returned by these steps and try to check that + // we're still _making_ progress, but every Nexus instance needs + // to do that anyway in case we (or the MGS instance delivering + // the update) crash, so we'll omit that check here. Instead, we + // just sleep and we'll poll again shortly. + SpUpdateStatus::Preparing { id, progress } => { + if id == update_id { + let progress = progress.and_then(|progress| { + if progress.current > progress.total { + warn!( + log, "nonsense preparing progress"; + "current" => progress.current, + "total" => progress.total, + ); + None + } else if progress.total == 0 { + None + } else { + Some( + f64::from(progress.current) + / f64::from(progress.total), + ) + } + }); + progress_tx + .send_replace(Some(UpdateProgress::Preparing { progress })); + Ok(false) + } else { + Err(SpComponentUpdateError::DifferentUpdatePreparing(id)) + } + } + SpUpdateStatus::InProgress { id, bytes_received, total_bytes } => { + if id == update_id { + let progress = if bytes_received > total_bytes { + warn!( + log, "nonsense update progress"; + "bytes_received" => bytes_received, + "total_bytes" => total_bytes, + ); + None + } else if total_bytes == 0 { + None + } else { + Some(f64::from(bytes_received) / f64::from(total_bytes)) + }; + progress_tx.send_replace(Some(UpdateProgress::InProgress { + progress, + })); + Ok(false) + } else { + Err(SpComponentUpdateError::DifferentUpdateInProgress(id)) + } + } + SpUpdateStatus::Complete { id } => { + if id == update_id { + Ok(true) + } else { + Err(SpComponentUpdateError::DifferentUpdateComplete(id)) + } + } + SpUpdateStatus::None => Err(SpComponentUpdateError::UpdateStatusLost), + SpUpdateStatus::Aborted { id } => { + if id == update_id { + Err(SpComponentUpdateError::UpdateAborted) + } else { + Err(SpComponentUpdateError::DifferentUpdateAborted(id)) + } + } + SpUpdateStatus::Failed { code, id } => { + if id == update_id { + Err(SpComponentUpdateError::UpdateFailedWithCode(code)) + } else { + Err(SpComponentUpdateError::DifferentUpdateFailed(id)) + } + } + SpUpdateStatus::RotError { id, message } => { + if id == update_id { + Err(SpComponentUpdateError::UpdateFailedWithMessage(format!( + "rot error: {message}" + ))) + } else { + Err(SpComponentUpdateError::DifferentUpdateFailed(id)) + } + } + } +} diff --git a/nexus/src/app/update/host_phase1_updater.rs b/nexus/src/app/update/host_phase1_updater.rs new file mode 100644 index 0000000000..fb013d0ffe --- /dev/null +++ b/nexus/src/app/update/host_phase1_updater.rs @@ -0,0 +1,177 @@ +// 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/. + +//! Module containing types for updating host OS phase1 images via MGS. + +use super::common_sp_update::deliver_update; +use super::common_sp_update::SpComponentUpdater; +use super::MgsClients; +use super::SpComponentUpdateError; +use super::UpdateProgress; +use gateway_client::types::SpComponentFirmwareSlot; +use gateway_client::types::SpType; +use gateway_client::SpComponent; +use slog::Logger; +use tokio::sync::watch; +use uuid::Uuid; + +type GatewayClientError = gateway_client::Error; + +pub struct HostPhase1Updater { + log: Logger, + progress: watch::Sender>, + sp_type: SpType, + sp_slot: u32, + target_host_slot: u16, + update_id: Uuid, + // TODO-clarity maybe a newtype for this? TBD how we get this from + // wherever it's stored, which might give us a stronger type already. + phase1_data: Vec, +} + +impl HostPhase1Updater { + pub fn new( + sp_type: SpType, + sp_slot: u32, + target_host_slot: u16, + update_id: Uuid, + phase1_data: Vec, + log: &Logger, + ) -> Self { + let log = log.new(slog::o!( + "component" => "HostPhase1Updater", + "sp_type" => format!("{sp_type:?}"), + "sp_slot" => sp_slot, + "target_host_slot" => target_host_slot, + "update_id" => format!("{update_id}"), + )); + let progress = watch::Sender::new(None); + Self { + log, + progress, + sp_type, + sp_slot, + target_host_slot, + update_id, + phase1_data, + } + } + + pub fn progress_watcher(&self) -> watch::Receiver> { + self.progress.subscribe() + } + + /// Drive this host phase 1 update to completion (or failure). + /// + /// Only one MGS instance is required to drive an update; however, if + /// multiple MGS instances are available and passed to this method and an + /// error occurs communicating with one instance, `HostPhase1Updater` will + /// try the remaining instances before failing. + pub async fn update( + mut self, + mgs_clients: &mut MgsClients, + ) -> Result<(), SpComponentUpdateError> { + // The async block below wants a `&self` reference, but we take `self` + // for API clarity (to start a new update, the caller should construct a + // new instance of the updater). Create a `&self` ref that we use + // through the remainder of this method. + let me = &self; + + // Prior to delivering the update, ensure the correct target slot is + // activated. + // + // TODO-correctness Should we be doing this, or should a higher level + // executor set this up before calling us? + mgs_clients + .try_all_serially(&self.log, |client| async move { + me.mark_target_slot_active(&client).await + }) + .await?; + + // Deliver and drive the update to completion + deliver_update(&mut self, mgs_clients).await?; + + // Unlike SP and RoT updates, we have nothing to do after delivery of + // the update completes; signal to any watchers that we're done. + self.progress.send_replace(Some(UpdateProgress::Complete)); + + // wait for any progress watchers to be dropped before we return; + // otherwise, they'll get `RecvError`s when trying to check the current + // status + self.progress.closed().await; + + Ok(()) + } + + async fn mark_target_slot_active( + &self, + client: &gateway_client::Client, + ) -> Result<(), GatewayClientError> { + // TODO-correctness Should we always persist this choice? + let persist = true; + + let slot = self.firmware_slot(); + + // TODO-correctness Until + // https://github.com/oxidecomputer/hubris/issues/1172 is fixed, the + // host must be in A2 for this operation to succeed. After it is fixed, + // there will still be a window while a host is booting where this + // operation can fail. How do we handle this? + client + .sp_component_active_slot_set( + self.sp_type, + self.sp_slot, + self.component(), + persist, + &SpComponentFirmwareSlot { slot }, + ) + .await?; + + // TODO-correctness Should we send some kind of update to + // `self.progress`? We haven't actually started delivering an update + // yet, but it seems weird to give no indication that we have + // successfully (potentially) modified the state of the target sled. + + info!( + self.log, "host phase1 target slot marked active"; + "mgs_addr" => client.baseurl(), + ); + + Ok(()) + } +} + +impl SpComponentUpdater for HostPhase1Updater { + fn component(&self) -> &'static str { + SpComponent::HOST_CPU_BOOT_FLASH.const_as_str() + } + + fn target_sp_type(&self) -> SpType { + self.sp_type + } + + fn target_sp_slot(&self) -> u32 { + self.sp_slot + } + + fn firmware_slot(&self) -> u16 { + self.target_host_slot + } + + fn update_id(&self) -> Uuid { + self.update_id + } + + fn update_data(&self) -> Vec { + self.phase1_data.clone() + } + + fn progress(&self) -> &watch::Sender> { + &self.progress + } + + fn logger(&self) -> &Logger { + &self.log + } +} diff --git a/nexus/src/app/update/mgs_clients.rs b/nexus/src/app/update/mgs_clients.rs index 5915505829..4b200a1819 100644 --- a/nexus/src/app/update/mgs_clients.rs +++ b/nexus/src/app/update/mgs_clients.rs @@ -5,53 +5,14 @@ //! Module providing support for handling failover between multiple MGS clients use futures::Future; -use gateway_client::types::SpType; -use gateway_client::types::SpUpdateStatus; use gateway_client::Client; use slog::Logger; use std::collections::VecDeque; use std::sync::Arc; -use uuid::Uuid; pub(super) type GatewayClientError = gateway_client::Error; -pub(super) enum PollUpdateStatus { - Preparing { progress: Option }, - InProgress { progress: Option }, - Complete, -} - -#[derive(Debug, thiserror::Error)] -pub enum UpdateStatusError { - #[error("different update is now preparing ({0})")] - DifferentUpdatePreparing(Uuid), - #[error("different update is now in progress ({0})")] - DifferentUpdateInProgress(Uuid), - #[error("different update is now complete ({0})")] - DifferentUpdateComplete(Uuid), - #[error("different update is now aborted ({0})")] - DifferentUpdateAborted(Uuid), - #[error("different update failed ({0})")] - DifferentUpdateFailed(Uuid), - #[error("update status lost (did the SP reset?)")] - UpdateStatusLost, - #[error("update was aborted")] - UpdateAborted, - #[error("update failed (error code {0})")] - UpdateFailedWithCode(u32), - #[error("update failed (error message {0})")] - UpdateFailedWithMessage(String), -} - -#[derive(Debug, thiserror::Error)] -pub(super) enum PollUpdateStatusError { - #[error(transparent)] - StatusError(#[from] UpdateStatusError), - #[error(transparent)] - ClientError(#[from] GatewayClientError), -} - #[derive(Debug, Clone)] pub struct MgsClients { clients: VecDeque>, @@ -130,111 +91,4 @@ impl MgsClients { // errors. Return the error from the last MGS we tried. Err(GatewayClientError::CommunicationError(last_err.unwrap())) } - - /// Poll for the status of an expected-to-be-in-progress update. - pub(super) async fn poll_update_status( - &mut self, - sp_type: SpType, - sp_slot: u32, - component: &'static str, - update_id: Uuid, - log: &Logger, - ) -> Result { - let update_status = self - .try_all_serially(log, |client| async move { - let update_status = client - .sp_component_update_status(sp_type, sp_slot, component) - .await?; - - debug!( - log, "got update status"; - "mgs_addr" => client.baseurl(), - "status" => ?update_status, - ); - - Ok(update_status) - }) - .await? - .into_inner(); - - match update_status { - SpUpdateStatus::Preparing { id, progress } => { - if id == update_id { - let progress = progress.and_then(|progress| { - if progress.current > progress.total { - warn!( - log, "nonsense preparing progress"; - "current" => progress.current, - "total" => progress.total, - ); - None - } else if progress.total == 0 { - None - } else { - Some( - f64::from(progress.current) - / f64::from(progress.total), - ) - } - }); - Ok(PollUpdateStatus::Preparing { progress }) - } else { - Err(UpdateStatusError::DifferentUpdatePreparing(id).into()) - } - } - SpUpdateStatus::InProgress { id, bytes_received, total_bytes } => { - if id == update_id { - let progress = if bytes_received > total_bytes { - warn!( - log, "nonsense update progress"; - "bytes_received" => bytes_received, - "total_bytes" => total_bytes, - ); - None - } else if total_bytes == 0 { - None - } else { - Some(f64::from(bytes_received) / f64::from(total_bytes)) - }; - Ok(PollUpdateStatus::InProgress { progress }) - } else { - Err(UpdateStatusError::DifferentUpdateInProgress(id).into()) - } - } - SpUpdateStatus::Complete { id } => { - if id == update_id { - Ok(PollUpdateStatus::Complete) - } else { - Err(UpdateStatusError::DifferentUpdateComplete(id).into()) - } - } - SpUpdateStatus::None => { - Err(UpdateStatusError::UpdateStatusLost.into()) - } - SpUpdateStatus::Aborted { id } => { - if id == update_id { - Err(UpdateStatusError::UpdateAborted.into()) - } else { - Err(UpdateStatusError::DifferentUpdateAborted(id).into()) - } - } - SpUpdateStatus::Failed { code, id } => { - if id == update_id { - Err(UpdateStatusError::UpdateFailedWithCode(code).into()) - } else { - Err(UpdateStatusError::DifferentUpdateFailed(id).into()) - } - } - SpUpdateStatus::RotError { id, message } => { - if id == update_id { - Err(UpdateStatusError::UpdateFailedWithMessage(format!( - "rot error: {message}" - )) - .into()) - } else { - Err(UpdateStatusError::DifferentUpdateFailed(id).into()) - } - } - } - } } diff --git a/nexus/src/app/update/mod.rs b/nexus/src/app/update/mod.rs index 7d5c642822..36d4dbcb9e 100644 --- a/nexus/src/app/update/mod.rs +++ b/nexus/src/app/update/mod.rs @@ -26,13 +26,17 @@ use std::path::Path; use tokio::io::AsyncWriteExt; use uuid::Uuid; +mod common_sp_update; +mod host_phase1_updater; mod mgs_clients; mod rot_updater; mod sp_updater; -pub use mgs_clients::{MgsClients, UpdateStatusError}; -pub use rot_updater::{RotUpdateError, RotUpdater}; -pub use sp_updater::{SpUpdateError, SpUpdater}; +pub use common_sp_update::SpComponentUpdateError; +pub use host_phase1_updater::HostPhase1Updater; +pub use mgs_clients::MgsClients; +pub use rot_updater::RotUpdater; +pub use sp_updater::SpUpdater; #[derive(Debug, PartialEq, Clone)] pub enum UpdateProgress { @@ -64,14 +68,10 @@ impl super::Nexus { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; let updates_config = self.updates_config.as_ref().ok_or_else(|| { - Error::InvalidRequest { - message: "updates system not configured".into(), - } + Error::invalid_request("updates system not configured") })?; let base_url = self.tuf_base_url(opctx).await?.ok_or_else(|| { - Error::InvalidRequest { - message: "updates system not configured".into(), - } + Error::invalid_request("updates system not configured") })?; let trusted_root = tokio::fs::read(&updates_config.trusted_root) .await @@ -154,9 +154,7 @@ impl super::Nexus { ) -> Result, Error> { let mut base_url = self.tuf_base_url(opctx).await?.ok_or_else(|| { - Error::InvalidRequest { - message: "updates system not configured".into(), - } + Error::invalid_request("updates system not configured") })?; if !base_url.ends_with('/') { base_url.push('/'); diff --git a/nexus/src/app/update/rot_updater.rs b/nexus/src/app/update/rot_updater.rs index d7d21e3b3a..12126a7de9 100644 --- a/nexus/src/app/update/rot_updater.rs +++ b/nexus/src/app/update/rot_updater.rs @@ -4,40 +4,21 @@ //! Module containing types for updating RoTs via MGS. -use super::mgs_clients::PollUpdateStatusError; +use super::common_sp_update::deliver_update; +use super::common_sp_update::SpComponentUpdater; use super::MgsClients; +use super::SpComponentUpdateError; use super::UpdateProgress; -use super::UpdateStatusError; -use crate::app::update::mgs_clients::PollUpdateStatus; use gateway_client::types::RotSlot; use gateway_client::types::SpComponentFirmwareSlot; use gateway_client::types::SpType; use gateway_client::SpComponent; use slog::Logger; -use std::time::Duration; use tokio::sync::watch; use uuid::Uuid; type GatewayClientError = gateway_client::Error; -#[derive(Debug, thiserror::Error)] -pub enum RotUpdateError { - #[error("error communicating with MGS")] - MgsCommunication(#[from] GatewayClientError), - - #[error("failed checking update status: {0}")] - PollUpdateStatus(#[from] UpdateStatusError), -} - -impl From for RotUpdateError { - fn from(err: PollUpdateStatusError) -> Self { - match err { - PollUpdateStatusError::StatusError(err) => err.into(), - PollUpdateStatusError::ClientError(err) => err.into(), - } - } -} - pub struct RotUpdater { log: Logger, progress: watch::Sender>, @@ -89,9 +70,14 @@ impl RotUpdater { /// error occurs communicating with one instance, `RotUpdater` will try the /// remaining instances before failing. pub async fn update( - self, - mut mgs_clients: MgsClients, - ) -> Result<(), RotUpdateError> { + mut self, + mgs_clients: &mut MgsClients, + ) -> Result<(), SpComponentUpdateError> { + // Deliver and drive the update to "completion" (which isn't really + // complete for the RoT, since we still have to do the steps below after + // the delivery of the update completes). + deliver_update(&mut self, mgs_clients).await?; + // The async blocks below want `&self` references, but we take `self` // for API clarity (to start a new update, the caller should construct a // new updater). Create a `&self` ref that we use through the remainder @@ -100,23 +86,13 @@ impl RotUpdater { mgs_clients .try_all_serially(&self.log, |client| async move { - me.start_update_one_mgs(&client).await - }) - .await?; - - // `wait_for_update_completion` uses `try_all_mgs_clients` internally, - // so we don't wrap it here. - me.wait_for_update_completion(&mut mgs_clients).await?; - - mgs_clients - .try_all_serially(&self.log, |client| async move { - me.mark_target_slot_active_one_mgs(&client).await + me.mark_target_slot_active(&client).await }) .await?; mgs_clients .try_all_serially(&self.log, |client| async move { - me.finalize_update_via_reset_one_mgs(&client).await + me.finalize_update_via_reset(&client).await }) .await?; @@ -128,82 +104,7 @@ impl RotUpdater { Ok(()) } - async fn start_update_one_mgs( - &self, - client: &gateway_client::Client, - ) -> Result<(), GatewayClientError> { - let firmware_slot = self.target_rot_slot.as_u16(); - - // Start the update. - client - .sp_component_update( - self.sp_type, - self.sp_slot, - SpComponent::ROT.const_as_str(), - firmware_slot, - &self.update_id, - reqwest::Body::from(self.rot_hubris_archive.clone()), - ) - .await?; - - self.progress.send_replace(Some(UpdateProgress::Started)); - - info!( - self.log, "RoT update started"; - "mgs_addr" => client.baseurl(), - ); - - Ok(()) - } - - async fn wait_for_update_completion( - &self, - mgs_clients: &mut MgsClients, - ) -> Result<(), RotUpdateError> { - // How frequently do we poll MGS for the update progress? - const STATUS_POLL_INTERVAL: Duration = Duration::from_secs(3); - - loop { - let status = mgs_clients - .poll_update_status( - self.sp_type, - self.sp_slot, - SpComponent::ROT.const_as_str(), - self.update_id, - &self.log, - ) - .await?; - - // For `Preparing` and `InProgress`, we could check the progress - // information returned by these steps and try to check that - // we're still _making_ progress, but every Nexus instance needs - // to do that anyway in case we (or the MGS instance delivering - // the update) crash, so we'll omit that check here. Instead, we - // just sleep and we'll poll again shortly. - match status { - PollUpdateStatus::Preparing { progress } => { - self.progress.send_replace(Some( - UpdateProgress::Preparing { progress }, - )); - } - PollUpdateStatus::InProgress { progress } => { - self.progress.send_replace(Some( - UpdateProgress::InProgress { progress }, - )); - } - PollUpdateStatus::Complete => { - self.progress.send_replace(Some( - UpdateProgress::InProgress { progress: Some(1.0) }, - )); - return Ok(()); - } - } - - tokio::time::sleep(STATUS_POLL_INTERVAL).await; - } - } - - async fn mark_target_slot_active_one_mgs( + async fn mark_target_slot_active( &self, client: &gateway_client::Client, ) -> Result<(), GatewayClientError> { @@ -211,13 +112,13 @@ impl RotUpdater { // tell it to persist our choice. let persist = true; - let slot = self.target_rot_slot.as_u16(); + let slot = self.firmware_slot(); client .sp_component_active_slot_set( self.sp_type, self.sp_slot, - SpComponent::ROT.const_as_str(), + self.component(), persist, &SpComponentFirmwareSlot { slot }, ) @@ -236,16 +137,12 @@ impl RotUpdater { Ok(()) } - async fn finalize_update_via_reset_one_mgs( + async fn finalize_update_via_reset( &self, client: &gateway_client::Client, ) -> Result<(), GatewayClientError> { client - .sp_component_reset( - self.sp_type, - self.sp_slot, - SpComponent::ROT.const_as_str(), - ) + .sp_component_reset(self.sp_type, self.sp_slot, self.component()) .await?; self.progress.send_replace(Some(UpdateProgress::Complete)); @@ -258,15 +155,39 @@ impl RotUpdater { } } -trait RotSlotAsU16 { - fn as_u16(&self) -> u16; -} +impl SpComponentUpdater for RotUpdater { + fn component(&self) -> &'static str { + SpComponent::ROT.const_as_str() + } + + fn target_sp_type(&self) -> SpType { + self.sp_type + } -impl RotSlotAsU16 for RotSlot { - fn as_u16(&self) -> u16 { - match self { + fn target_sp_slot(&self) -> u32 { + self.sp_slot + } + + fn firmware_slot(&self) -> u16 { + match self.target_rot_slot { RotSlot::A => 0, RotSlot::B => 1, } } + + fn update_id(&self) -> Uuid { + self.update_id + } + + fn update_data(&self) -> Vec { + self.rot_hubris_archive.clone() + } + + fn progress(&self) -> &watch::Sender> { + &self.progress + } + + fn logger(&self) -> &Logger { + &self.log + } } diff --git a/nexus/src/app/update/sp_updater.rs b/nexus/src/app/update/sp_updater.rs index 419a733441..2a6ddc6de6 100644 --- a/nexus/src/app/update/sp_updater.rs +++ b/nexus/src/app/update/sp_updater.rs @@ -4,39 +4,19 @@ //! Module containing types for updating SPs via MGS. -use crate::app::update::mgs_clients::PollUpdateStatus; - -use super::mgs_clients::PollUpdateStatusError; +use super::common_sp_update::deliver_update; +use super::common_sp_update::SpComponentUpdater; use super::MgsClients; +use super::SpComponentUpdateError; use super::UpdateProgress; -use super::UpdateStatusError; use gateway_client::types::SpType; use gateway_client::SpComponent; use slog::Logger; -use std::time::Duration; use tokio::sync::watch; use uuid::Uuid; type GatewayClientError = gateway_client::Error; -#[derive(Debug, thiserror::Error)] -pub enum SpUpdateError { - #[error("error communicating with MGS")] - MgsCommunication(#[from] GatewayClientError), - - #[error("failed checking update status: {0}")] - PollUpdateStatus(#[from] UpdateStatusError), -} - -impl From for SpUpdateError { - fn from(err: PollUpdateStatusError) -> Self { - match err { - PollUpdateStatusError::StatusError(err) => err.into(), - PollUpdateStatusError::ClientError(err) => err.into(), - } - } -} - pub struct SpUpdater { log: Logger, progress: watch::Sender>, @@ -77,10 +57,15 @@ impl SpUpdater { /// error occurs communicating with one instance, `SpUpdater` will try the /// remaining instances before failing. pub async fn update( - self, - mut mgs_clients: MgsClients, - ) -> Result<(), SpUpdateError> { - // The async blocks below want `&self` references, but we take `self` + mut self, + mgs_clients: &mut MgsClients, + ) -> Result<(), SpComponentUpdateError> { + // Deliver and drive the update to "completion" (which isn't really + // complete for the SP, since we still have to reset it after the + // delivery of the update completes). + deliver_update(&mut self, mgs_clients).await?; + + // The async block below wants a `&self` reference, but we take `self` // for API clarity (to start a new SP update, the caller should // construct a new `SpUpdater`). Create a `&self` ref that we use // through the remainder of this method. @@ -88,17 +73,7 @@ impl SpUpdater { mgs_clients .try_all_serially(&self.log, |client| async move { - me.start_update_one_mgs(&client).await - }) - .await?; - - // `wait_for_update_completion` uses `try_all_mgs_clients` internally, - // so we don't wrap it here. - me.wait_for_update_completion(&mut mgs_clients).await?; - - mgs_clients - .try_all_serially(&self.log, |client| async move { - me.finalize_update_via_reset_one_mgs(&client).await + me.finalize_update_via_reset(&client).await }) .await?; @@ -110,102 +85,57 @@ impl SpUpdater { Ok(()) } - async fn start_update_one_mgs( + async fn finalize_update_via_reset( &self, client: &gateway_client::Client, ) -> Result<(), GatewayClientError> { - // The SP has two firmware slots, but they're aren't individually - // labled. We always request an update to slot 0, which means "the - // inactive slot". - let firmware_slot = 0; - - // Start the update. client - .sp_component_update( - self.sp_type, - self.sp_slot, - SpComponent::SP_ITSELF.const_as_str(), - firmware_slot, - &self.update_id, - reqwest::Body::from(self.sp_hubris_archive.clone()), - ) + .sp_component_reset(self.sp_type, self.sp_slot, self.component()) .await?; - self.progress.send_replace(Some(UpdateProgress::Started)); - + self.progress.send_replace(Some(UpdateProgress::Complete)); info!( - self.log, "SP update started"; + self.log, "SP update complete"; "mgs_addr" => client.baseurl(), ); Ok(()) } +} - async fn wait_for_update_completion( - &self, - mgs_clients: &mut MgsClients, - ) -> Result<(), SpUpdateError> { - // How frequently do we poll MGS for the update progress? - const STATUS_POLL_INTERVAL: Duration = Duration::from_secs(3); - - loop { - let status = mgs_clients - .poll_update_status( - self.sp_type, - self.sp_slot, - SpComponent::SP_ITSELF.const_as_str(), - self.update_id, - &self.log, - ) - .await?; - - // For `Preparing` and `InProgress`, we could check the progress - // information returned by these steps and try to check that - // we're still _making_ progress, but every Nexus instance needs - // to do that anyway in case we (or the MGS instance delivering - // the update) crash, so we'll omit that check here. Instead, we - // just sleep and we'll poll again shortly. - match status { - PollUpdateStatus::Preparing { progress } => { - self.progress.send_replace(Some( - UpdateProgress::Preparing { progress }, - )); - } - PollUpdateStatus::InProgress { progress } => { - self.progress.send_replace(Some( - UpdateProgress::InProgress { progress }, - )); - } - PollUpdateStatus::Complete => { - self.progress.send_replace(Some( - UpdateProgress::InProgress { progress: Some(1.0) }, - )); - return Ok(()); - } - } - - tokio::time::sleep(STATUS_POLL_INTERVAL).await; - } +impl SpComponentUpdater for SpUpdater { + fn component(&self) -> &'static str { + SpComponent::SP_ITSELF.const_as_str() } - async fn finalize_update_via_reset_one_mgs( - &self, - client: &gateway_client::Client, - ) -> Result<(), GatewayClientError> { - client - .sp_component_reset( - self.sp_type, - self.sp_slot, - SpComponent::SP_ITSELF.const_as_str(), - ) - .await?; + fn target_sp_type(&self) -> SpType { + self.sp_type + } - self.progress.send_replace(Some(UpdateProgress::Complete)); - info!( - self.log, "SP update complete"; - "mgs_addr" => client.baseurl(), - ); + fn target_sp_slot(&self) -> u32 { + self.sp_slot + } - Ok(()) + fn firmware_slot(&self) -> u16 { + // The SP has two firmware slots, but they're aren't individually + // labled. We always request an update to slot 0, which means "the + // inactive slot". + 0 + } + + fn update_id(&self) -> Uuid { + self.update_id + } + + fn update_data(&self) -> Vec { + self.sp_hubris_archive.clone() + } + + fn progress(&self) -> &watch::Sender> { + &self.progress + } + + fn logger(&self) -> &Logger { + &self.log } } diff --git a/nexus/src/app/vpc_router.rs b/nexus/src/app/vpc_router.rs index 81577f88e8..fd0b699cd4 100644 --- a/nexus/src/app/vpc_router.rs +++ b/nexus/src/app/vpc_router.rs @@ -129,9 +129,9 @@ impl super::Nexus { // router kind cannot be changed, but it might be able to save us a // database round-trip. if db_router.kind == VpcRouterKind::System { - return Err(Error::MethodNotAllowed { - internal_message: "Cannot delete system router".to_string(), - }); + return Err(Error::method_not_allowed( + "Cannot delete system router", + )); } self.db_datastore.vpc_delete_router(opctx, &authz_router).await } @@ -229,14 +229,12 @@ impl super::Nexus { match db_route.kind.0 { RouterRouteKind::Custom | RouterRouteKind::Default => (), _ => { - return Err(Error::MethodNotAllowed { - internal_message: format!( - "routes of type {} from the system table of VPC {:?} \ + return Err(Error::invalid_request(format!( + "routes of type {} from the system table of VPC {:?} \ are not modifiable", - db_route.kind.0, - vpc.id() - ), - }) + db_route.kind.0, + vpc.id() + ))); } } self.db_datastore @@ -255,10 +253,9 @@ impl super::Nexus { // Only custom routes can be deleted // TODO Shouldn't this constraint be checked by the database query? if db_route.kind.0 != RouterRouteKind::Custom { - return Err(Error::MethodNotAllowed { - internal_message: "DELETE not allowed on system routes" - .to_string(), - }); + return Err(Error::method_not_allowed( + "DELETE not allowed on system routes", + )); } self.db_datastore.router_delete_route(opctx, &authz_route).await } diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 78f675c28a..c92d0b2b8a 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -218,6 +218,7 @@ pub(crate) fn external_api() -> NexusApiDescription { api.register(rack_view)?; api.register(sled_list)?; api.register(sled_view)?; + api.register(sled_set_provision_state)?; api.register(sled_instance_list)?; api.register(sled_physical_disk_list)?; api.register(physical_disk_list)?; @@ -4483,6 +4484,47 @@ async fn sled_view( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Set the sled's provision state. +#[endpoint { + method = PUT, + path = "/v1/system/hardware/sleds/{sled_id}/provision-state", + tags = ["system/hardware"], +}] +async fn sled_set_provision_state( + rqctx: RequestContext>, + path_params: Path, + new_provision_state: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + + let path = path_params.into_inner(); + let provision_state = new_provision_state.into_inner().state; + + let opctx = crate::context::op_context_for_external_api(&rqctx).await?; + // Convert the external `SledProvisionState` into our internal data model. + let new_state = + db::model::SledProvisionState::try_from(provision_state).map_err( + |error| HttpError::for_bad_request(None, format!("{error}")), + )?; + + let sled_lookup = nexus.sled_lookup(&opctx, &path.sled_id)?; + + let old_state = nexus + .sled_set_provision_state(&opctx, &sled_lookup, new_state) + .await?; + + let response = params::SledProvisionStateResponse { + old_state: old_state.into(), + new_state: new_state.into(), + }; + + Ok(HttpResponseOk(response)) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// List instances running on a given sled #[endpoint { method = GET, @@ -5268,10 +5310,7 @@ async fn role_list( WhichPage::First(..) => None, WhichPage::Next(RolePage { last_seen }) => { Some(last_seen.split_once('.').ok_or_else(|| { - Error::InvalidValue { - label: last_seen.clone(), - message: String::from("bad page token"), - } + Error::invalid_value(last_seen.clone(), "bad page token") })?) .map(|(s1, s2)| (s1.to_string(), s2.to_string())) } diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index 1e7de6132b..52ff8910f9 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -1093,7 +1093,7 @@ pub async fn start_producer_server( let producer_address = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0); let server_info = ProducerEndpoint { id, - kind: Some(ProducerKind::Service), + kind: ProducerKind::Service, address: producer_address, base_route: "/collect".to_string(), interval: Duration::from_secs(1), diff --git a/nexus/tests/config.test.toml b/nexus/tests/config.test.toml index fbed9aed8e..a4436234f0 100644 --- a/nexus/tests/config.test.toml +++ b/nexus/tests/config.test.toml @@ -98,6 +98,7 @@ inventory.period_secs = 600 inventory.nkeep = 3 # Disable inventory collection altogether (for emergencies) inventory.disable = false +phantom_disks.period_secs = 30 [default_region_allocation_strategy] # we only have one sled in the test environment, so we need to use the diff --git a/nexus/tests/integration_tests/disks.rs b/nexus/tests/integration_tests/disks.rs index a5a8339c34..f7403275b1 100644 --- a/nexus/tests/integration_tests/disks.rs +++ b/nexus/tests/integration_tests/disks.rs @@ -1241,6 +1241,138 @@ async fn test_disk_virtual_provisioning_collection( ); } +#[nexus_test] +async fn test_disk_virtual_provisioning_collection_failed_delete( + cptestctx: &ControlPlaneTestContext, +) { + // Confirm that there's a panic deleting a project if a disk deletion fails + let client = &cptestctx.external_client; + let nexus = &cptestctx.server.apictx().nexus; + let datastore = nexus.datastore(); + + let disk_test = DiskTest::new(&cptestctx).await; + + populate_ip_pool(&client, "default", None).await; + let project_id1 = create_project(client, PROJECT_NAME).await.identity.id; + + let opctx = + OpContext::for_tests(cptestctx.logctx.log.new(o!()), datastore.clone()); + + // Create a 1 GB disk + let disk_size = ByteCount::from_gibibytes_u32(1); + let disks_url = get_disks_url(); + let disk_one = params::DiskCreate { + identity: IdentityMetadataCreateParams { + name: "disk-one".parse().unwrap(), + description: String::from("sells rainsticks"), + }, + disk_source: params::DiskSource::Blank { + block_size: params::BlockSize::try_from(512).unwrap(), + }, + size: disk_size, + }; + NexusRequest::new( + RequestBuilder::new(client, Method::POST, &disks_url) + .body(Some(&disk_one)) + .expect_status(Some(StatusCode::CREATED)), + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .expect("unexpected failure creating 1 GiB disk"); + + // Assert correct virtual provisioning collection numbers + let virtual_provisioning_collection = datastore + .virtual_provisioning_collection_get(&opctx, project_id1) + .await + .unwrap(); + assert_eq!( + virtual_provisioning_collection.virtual_disk_bytes_provisioned.0, + disk_size + ); + + // Set the third agent to fail when deleting regions + let zpool = &disk_test.zpools[2]; + let dataset = &zpool.datasets[0]; + disk_test + .sled_agent + .get_crucible_dataset(zpool.id, dataset.id) + .await + .set_region_deletion_error(true) + .await; + + // Delete the disk - expect this to fail + let disk_url = format!("/v1/disks/{}?project={}", "disk-one", PROJECT_NAME); + + NexusRequest::new( + RequestBuilder::new(client, Method::DELETE, &disk_url) + .expect_status(Some(StatusCode::INTERNAL_SERVER_ERROR)), + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .expect("unexpected success deleting 1 GiB disk"); + + // The virtual provisioning collection numbers haven't changed + let virtual_provisioning_collection = datastore + .virtual_provisioning_collection_get(&opctx, project_id1) + .await + .unwrap(); + assert_eq!( + virtual_provisioning_collection.virtual_disk_bytes_provisioned.0, + disk_size + ); + + // And the disk is now faulted + let disk = disk_get(&client, &disk_url).await; + assert_eq!(disk.state, DiskState::Faulted); + + // Set the third agent to respond normally + disk_test + .sled_agent + .get_crucible_dataset(zpool.id, dataset.id) + .await + .set_region_deletion_error(false) + .await; + + // Request disk delete again + NexusRequest::new( + RequestBuilder::new(client, Method::DELETE, &disk_url) + .expect_status(Some(StatusCode::NO_CONTENT)), + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .expect("unexpected failure deleting 1 GiB disk"); + + // Delete the project's default VPC subnet and VPC + let subnet_url = + format!("/v1/vpc-subnets/default?project={}&vpc=default", PROJECT_NAME); + NexusRequest::object_delete(&client, &subnet_url) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .expect("failed to make request"); + + let vpc_url = format!("/v1/vpcs/default?project={}", PROJECT_NAME); + NexusRequest::object_delete(&client, &vpc_url) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .expect("failed to make request"); + + // The project can be deleted now + let url = format!("/v1/projects/{}", PROJECT_NAME); + NexusRequest::new( + RequestBuilder::new(client, Method::DELETE, &url) + .expect_status(Some(StatusCode::NO_CONTENT)), + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .expect("unexpected failure deleting project"); +} + // Test disk size accounting #[nexus_test] async fn test_disk_size_accounting(cptestctx: &ControlPlaneTestContext) { diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 5dfdcc151d..536b96f7ae 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -50,6 +50,12 @@ lazy_static! { format!("/v1/system/hardware/uninitialized-sleds"); pub static ref HARDWARE_SLED_URL: String = format!("/v1/system/hardware/sleds/{}", SLED_AGENT_UUID); + pub static ref HARDWARE_SLED_PROVISION_STATE_URL: String = + format!("/v1/system/hardware/sleds/{}/provision-state", SLED_AGENT_UUID); + pub static ref DEMO_SLED_PROVISION_STATE: params::SledProvisionStateParams = + params::SledProvisionStateParams { + state: nexus_types::external_api::views::SledProvisionState::NonProvisionable, + }; pub static ref HARDWARE_SWITCH_URL: String = format!("/v1/system/hardware/switches/{}", SWITCH_UUID); pub static ref HARDWARE_DISK_URL: String = @@ -1609,6 +1615,15 @@ lazy_static! { allowed_methods: vec![AllowedMethod::Get], }, + VerifyEndpoint { + url: &HARDWARE_SLED_PROVISION_STATE_URL, + visibility: Visibility::Protected, + unprivileged_access: UnprivilegedAccess::None, + allowed_methods: vec![AllowedMethod::Put( + serde_json::to_value(&*DEMO_SLED_PROVISION_STATE).unwrap() + )], + }, + VerifyEndpoint { url: "/v1/system/hardware/switches", visibility: Visibility::Public, diff --git a/nexus/tests/integration_tests/host_phase1_updater.rs b/nexus/tests/integration_tests/host_phase1_updater.rs new file mode 100644 index 0000000000..01d546636e --- /dev/null +++ b/nexus/tests/integration_tests/host_phase1_updater.rs @@ -0,0 +1,584 @@ +// 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/. + +//! Tests `HostPhase1Updater`'s delivery of updates to host phase 1 flash via +//! MGS to SP. + +use gateway_client::types::SpType; +use gateway_messages::{SpPort, UpdateInProgressStatus, UpdateStatus}; +use gateway_test_utils::setup as mgs_setup; +use omicron_nexus::app::test_interfaces::{ + HostPhase1Updater, MgsClients, UpdateProgress, +}; +use rand::RngCore; +use sp_sim::SimulatedSp; +use std::mem; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use tokio::io::AsyncWriteExt; +use tokio::net::TcpListener; +use tokio::net::TcpStream; +use tokio::sync::mpsc; +use uuid::Uuid; + +fn make_fake_host_phase1_image() -> Vec { + let mut image = vec![0; 128]; + rand::thread_rng().fill_bytes(&mut image); + image +} + +#[tokio::test] +async fn test_host_phase1_updater_updates_sled() { + // Start MGS + Sim SP. + let mgstestctx = mgs_setup::test_setup( + "test_host_phase1_updater_updates_sled", + SpPort::One, + ) + .await; + + // Configure an MGS client. + let mut mgs_clients = + MgsClients::from_clients([gateway_client::Client::new( + &mgstestctx.client.url("/").to_string(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + )]); + + for target_host_slot in [0, 1] { + // Configure and instantiate an `HostPhase1Updater`. + let sp_type = SpType::Sled; + let sp_slot = 0; + let update_id = Uuid::new_v4(); + let phase1_data = make_fake_host_phase1_image(); + + let host_phase1_updater = HostPhase1Updater::new( + sp_type, + sp_slot, + target_host_slot, + update_id, + phase1_data.clone(), + &mgstestctx.logctx.log, + ); + + // Run the update. + host_phase1_updater + .update(&mut mgs_clients) + .await + .expect("update failed"); + + // Ensure the SP received the complete update. + let last_update_image = mgstestctx.simrack.gimlets[sp_slot as usize] + .last_host_phase1_update_data(target_host_slot) + .await + .expect("simulated host phase1 did not receive an update"); + + assert_eq!( + phase1_data.as_slice(), + &*last_update_image, + "simulated host phase1 update contents (len {}) \ + do not match test generated fake image (len {})", + last_update_image.len(), + phase1_data.len(), + ); + } + + mgstestctx.teardown().await; +} + +#[tokio::test] +async fn test_host_phase1_updater_remembers_successful_mgs_instance() { + // Start MGS + Sim SP. + let mgstestctx = mgs_setup::test_setup( + "test_host_phase1_updater_remembers_successful_mgs_instance", + SpPort::One, + ) + .await; + + // Also start a local TCP server that we will claim is an MGS instance, but + // it will close connections immediately after accepting them. This will + // allow us to count how many connections it receives, while simultaneously + // causing errors in the HostPhase1Updater when it attempts to use this + // "MGS". + let (failing_mgs_task, failing_mgs_addr, failing_mgs_conn_counter) = { + let socket = TcpListener::bind("[::1]:0").await.unwrap(); + let addr = socket.local_addr().unwrap(); + let conn_count = Arc::new(AtomicUsize::new(0)); + + let task = { + let conn_count = Arc::clone(&conn_count); + tokio::spawn(async move { + loop { + let (mut stream, _peer) = socket.accept().await.unwrap(); + conn_count.fetch_add(1, Ordering::SeqCst); + stream.shutdown().await.unwrap(); + } + }) + }; + + (task, addr, conn_count) + }; + + // Order the MGS clients such that the bogus MGS that immediately closes + // connections comes first. `HostPhase1Updater` should remember that the + // second MGS instance succeeds, and only send subsequent requests to it: we + // should only see a single attempted connection to the bogus MGS, even + // though delivering an update requires a bare minimum of three requests + // (start the update, query the status, reset the SP) and often more (if + // repeated queries are required to wait for completion). + let mut mgs_clients = MgsClients::from_clients([ + gateway_client::Client::new( + &format!("http://{failing_mgs_addr}"), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient1")), + ), + gateway_client::Client::new( + &mgstestctx.client.url("/").to_string(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + ), + ]); + + let sp_type = SpType::Sled; + let sp_slot = 0; + let target_host_slot = 0; + let update_id = Uuid::new_v4(); + let phase1_data = make_fake_host_phase1_image(); + + let host_phase1_updater = HostPhase1Updater::new( + sp_type, + sp_slot, + target_host_slot, + update_id, + phase1_data.clone(), + &mgstestctx.logctx.log, + ); + + host_phase1_updater.update(&mut mgs_clients).await.expect("update failed"); + + let last_update_image = mgstestctx.simrack.gimlets[sp_slot as usize] + .last_host_phase1_update_data(target_host_slot) + .await + .expect("simulated host phase1 did not receive an update"); + + assert_eq!( + phase1_data.as_slice(), + &*last_update_image, + "simulated host phase1 update contents (len {}) \ + do not match test generated fake image (len {})", + last_update_image.len(), + phase1_data.len(), + ); + + // Check that our bogus MGS only received a single connection attempt. + // (After HostPhase1Updater failed to talk to this instance, it should have + // fallen back to the valid one for all further requests.) + assert_eq!( + failing_mgs_conn_counter.load(Ordering::SeqCst), + 1, + "bogus MGS instance didn't receive the expected number of connections" + ); + failing_mgs_task.abort(); + + mgstestctx.teardown().await; +} + +#[tokio::test] +async fn test_host_phase1_updater_switches_mgs_instances_on_failure() { + enum MgsProxy { + One(TcpStream), + Two(TcpStream), + } + + // Start MGS + Sim SP. + let mgstestctx = mgs_setup::test_setup( + "test_host_phase1_updater_switches_mgs_instances_on_failure", + SpPort::One, + ) + .await; + let mgs_bind_addr = mgstestctx.client.bind_address; + + let spawn_mgs_proxy_task = |mut stream: TcpStream| { + tokio::spawn(async move { + let mut mgs_stream = TcpStream::connect(mgs_bind_addr) + .await + .expect("failed to connect to MGS"); + tokio::io::copy_bidirectional(&mut stream, &mut mgs_stream) + .await + .expect("failed to proxy connection to MGS"); + }) + }; + + // Start two MGS proxy tasks; when each receives an incoming TCP connection, + // it forwards that `TcpStream` along the `mgs_proxy_connections` channel + // along with a tag of which proxy it is. We'll use this below to flip flop + // between MGS "instances" (really these two proxies). + let (mgs_proxy_connections_tx, mut mgs_proxy_connections_rx) = + mpsc::unbounded_channel(); + let (mgs_proxy_one_task, mgs_proxy_one_addr) = { + let socket = TcpListener::bind("[::1]:0").await.unwrap(); + let addr = socket.local_addr().unwrap(); + let mgs_proxy_connections_tx = mgs_proxy_connections_tx.clone(); + let task = tokio::spawn(async move { + loop { + let (stream, _peer) = socket.accept().await.unwrap(); + mgs_proxy_connections_tx.send(MgsProxy::One(stream)).unwrap(); + } + }); + (task, addr) + }; + let (mgs_proxy_two_task, mgs_proxy_two_addr) = { + let socket = TcpListener::bind("[::1]:0").await.unwrap(); + let addr = socket.local_addr().unwrap(); + let task = tokio::spawn(async move { + loop { + let (stream, _peer) = socket.accept().await.unwrap(); + mgs_proxy_connections_tx.send(MgsProxy::Two(stream)).unwrap(); + } + }); + (task, addr) + }; + + // Disable connection pooling so each request gets a new TCP connection. + let client = + reqwest::Client::builder().pool_max_idle_per_host(0).build().unwrap(); + + // Configure two MGS clients pointed at our two proxy tasks. + let mut mgs_clients = MgsClients::from_clients([ + gateway_client::Client::new_with_client( + &format!("http://{mgs_proxy_one_addr}"), + client.clone(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient1")), + ), + gateway_client::Client::new_with_client( + &format!("http://{mgs_proxy_two_addr}"), + client, + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient2")), + ), + ]); + + let sp_type = SpType::Sled; + let sp_slot = 0; + let target_host_slot = 0; + let update_id = Uuid::new_v4(); + let phase1_data = make_fake_host_phase1_image(); + + let host_phase1_updater = HostPhase1Updater::new( + sp_type, + sp_slot, + target_host_slot, + update_id, + phase1_data.clone(), + &mgstestctx.logctx.log, + ); + + // Spawn the actual update task. + let mut update_task = tokio::spawn(async move { + host_phase1_updater.update(&mut mgs_clients).await + }); + + // Loop over incoming requests. We expect this sequence: + // + // 1. Connection arrives on the first proxy + // 2. We spawn a task to service that request, and set `should_swap` + // 3. Connection arrives on the first proxy + // 4. We drop that connection, flip `expected_proxy`, and clear + // `should_swap` + // 5. Connection arrives on the second proxy + // 6. We spawn a task to service that request, and set `should_swap` + // 7. Connection arrives on the second proxy + // 8. We drop that connection, flip `expected_proxy`, and clear + // `should_swap` + // + // ... repeat until the update is complete. + let mut expected_proxy = 0; + let mut proxy_one_count = 0; + let mut proxy_two_count = 0; + let mut total_requests_handled = 0; + let mut should_swap = false; + loop { + tokio::select! { + Some(proxy_stream) = mgs_proxy_connections_rx.recv() => { + let stream = match proxy_stream { + MgsProxy::One(stream) => { + assert_eq!(expected_proxy, 0); + proxy_one_count += 1; + stream + } + MgsProxy::Two(stream) => { + assert_eq!(expected_proxy, 1); + proxy_two_count += 1; + stream + } + }; + + // Should we trigger `HostPhase1Updater` to swap to the other + // MGS (proxy)? If so, do that by dropping this connection + // (which will cause a client failure) and note that we expect + // the next incoming request to come on the other proxy. + if should_swap { + mem::drop(stream); + expected_proxy ^= 1; + should_swap = false; + } else { + // Otherwise, handle this connection. + total_requests_handled += 1; + spawn_mgs_proxy_task(stream); + should_swap = true; + } + } + + result = &mut update_task => { + match result { + Ok(Ok(())) => { + mgs_proxy_one_task.abort(); + mgs_proxy_two_task.abort(); + break; + } + Ok(Err(err)) => panic!("update failed: {err}"), + Err(err) => panic!("update task panicked: {err}"), + } + } + } + } + + // A host flash update requires a minimum of 3 requests to MGS: set the + // active flash slot, post the update, and check the status. There may be + // more requests if the update is not yet complete when the status is + // checked, but we can just check that each of our proxies received at least + // 2 incoming requests; based on our outline above, if we got the minimum of + // 3 requests, it would look like this: + // + // 1. POST update -> first proxy (success) + // 2. GET status -> first proxy (fail) + // 3. GET status retry -> second proxy (success) + // 4. POST reset -> second proxy (fail) + // 5. POST reset -> first proxy (success) + // + // This pattern would repeat if multiple status requests were required, so + // we always expect the first proxy to see exactly one more connection + // attempt than the second (because it went first before they started + // swapping), and the two together should see a total of one less than + // double the number of successful requests required. + assert!(total_requests_handled >= 3); + assert_eq!(proxy_one_count, proxy_two_count + 1); + assert_eq!( + (proxy_one_count + proxy_two_count + 1) / 2, + total_requests_handled + ); + + let last_update_image = mgstestctx.simrack.gimlets[sp_slot as usize] + .last_host_phase1_update_data(target_host_slot) + .await + .expect("simulated host phase1 did not receive an update"); + + assert_eq!( + phase1_data.as_slice(), + &*last_update_image, + "simulated host phase1 update contents (len {}) \ + do not match test generated fake image (len {})", + last_update_image.len(), + phase1_data.len(), + ); + + mgstestctx.teardown().await; +} + +#[tokio::test] +async fn test_host_phase1_updater_delivers_progress() { + // Start MGS + Sim SP. + let mgstestctx = mgs_setup::test_setup( + "test_host_phase1_updater_delivers_progress", + SpPort::One, + ) + .await; + + // Configure an MGS client. + let mut mgs_clients = + MgsClients::from_clients([gateway_client::Client::new( + &mgstestctx.client.url("/").to_string(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + )]); + + let sp_type = SpType::Sled; + let sp_slot = 0; + let target_host_slot = 0; + let update_id = Uuid::new_v4(); + let phase1_data = make_fake_host_phase1_image(); + + let host_phase1_updater = HostPhase1Updater::new( + sp_type, + sp_slot, + target_host_slot, + update_id, + phase1_data.clone(), + &mgstestctx.logctx.log, + ); + + let phase1_data_len = phase1_data.len() as u32; + + // Subscribe to update progress, and check that there is no status yet; we + // haven't started the update. + let mut progress = host_phase1_updater.progress_watcher(); + assert_eq!(*progress.borrow_and_update(), None); + + // Install a semaphore on the requests our target SP will receive so we can + // inspect progress messages without racing. + let target_sp = &mgstestctx.simrack.gimlets[sp_slot as usize]; + let sp_accept_sema = target_sp.install_udp_accept_semaphore().await; + let mut sp_responses = target_sp.responses_sent_count().unwrap(); + + // Spawn the update on a background task so we can watch `progress` as it is + // applied. + let do_update_task = tokio::spawn(async move { + host_phase1_updater.update(&mut mgs_clients).await + }); + + // Allow the SP to respond to 2 messages: the message to activate the target + // flash slot and the "prepare update" messages that triggers the start of an + // update, then ensure we see the "started" progress. + sp_accept_sema.send(2).unwrap(); + progress.changed().await.unwrap(); + assert_eq!(*progress.borrow_and_update(), Some(UpdateProgress::Started)); + + // Ensure our simulated SP is in the state we expect: it's prepared for an + // update but has not yet received any data. + assert_eq!( + target_sp.current_update_status().await, + UpdateStatus::InProgress(UpdateInProgressStatus { + id: update_id.into(), + bytes_received: 0, + total_size: phase1_data_len, + }) + ); + + // Record the number of responses the SP has sent; we'll use + // `sp_responses.changed()` in the loop below, and want to mark whatever + // value this watch channel currently has as seen. + sp_responses.borrow_and_update(); + + // At this point, there are two clients racing each other to talk to our + // simulated SP: + // + // 1. MGS is trying to deliver the update + // 2. `host_phase1_updater` is trying to poll (via MGS) for update status + // + // and we want to ensure that we see any relevant progress reports from + // `host_phase1_updater`. We'll let one MGS -> SP message through at a time + // (waiting until our SP has responded by waiting for a change to + // `sp_responses`) then check its update state: if it changed, the packet we + // let through was data from MGS; otherwise, it was a status request from + // `host_phase1_updater`. + // + // This loop will continue until either: + // + // 1. We see an `UpdateStatus::InProgress` message indicating 100% delivery, + // at which point we break out of the loop + // 2. We time out waiting for the previous step (by timing out for either + // the SP to process a request or `host_phase1_updater` to realize + // there's been progress), at which point we panic and fail this test. + let mut prev_bytes_received = 0; + let mut expect_progress_change = false; + loop { + // Allow the SP to accept and respond to a single UDP packet. + sp_accept_sema.send(1).unwrap(); + + // Wait until the SP has sent a response, with a safety rail that we + // haven't screwed up our untangle-the-race logic: if we don't see the + // SP process any new messages after several seconds, our test is + // broken, so fail. + tokio::time::timeout(Duration::from_secs(10), sp_responses.changed()) + .await + .expect("timeout waiting for SP response count to change") + .expect("sp response count sender dropped"); + + // Inspec the SP's in-memory update state; we expect only `InProgress` + // or `Complete`, and in either case we note whether we expect to see + // status changes from `host_phase1_updater`. + match target_sp.current_update_status().await { + UpdateStatus::InProgress(sp_progress) => { + if sp_progress.bytes_received > prev_bytes_received { + prev_bytes_received = sp_progress.bytes_received; + expect_progress_change = true; + continue; + } + } + UpdateStatus::Complete(_) => { + if prev_bytes_received < phase1_data_len { + break; + } + } + status @ (UpdateStatus::None + | UpdateStatus::Preparing(_) + | UpdateStatus::SpUpdateAuxFlashChckScan { .. } + | UpdateStatus::Aborted(_) + | UpdateStatus::Failed { .. } + | UpdateStatus::RotError { .. }) => { + panic!("unexpected status {status:?}"); + } + } + + // If we get here, the most recent packet did _not_ change the SP's + // internal update state, so it was a status request from + // `host_phase1_updater`. If we expect the updater to see new progress, + // wait for that change here. + if expect_progress_change { + // Safety rail that we haven't screwed up our untangle-the-race + // logic: if we don't see a new progress after several seconds, our + // test is broken, so fail. + tokio::time::timeout(Duration::from_secs(10), progress.changed()) + .await + .expect("progress timeout") + .expect("progress watch sender dropped"); + let status = progress.borrow_and_update().clone().unwrap(); + expect_progress_change = false; + + assert!( + matches!(status, UpdateProgress::InProgress { .. }), + "unexpected progress status {status:?}" + ); + } + } + + // We know the SP has received a complete update, but `HostPhase1Updater` + // may still need to request status to realize that; release the socket + // semaphore so the SP can respond. + sp_accept_sema.send(usize::MAX).unwrap(); + + // Unlike the SP and RoT cases, there are no MGS/SP steps in between the + // update completing and `HostPhase1Updater` sending + // `UpdateProgress::Complete`. Therefore, it's a race whether we'll see + // some number of `InProgress` status before `Complete`, but we should + // quickly move to `Complete`. + loop { + tokio::time::timeout(Duration::from_secs(10), progress.changed()) + .await + .expect("progress timeout") + .expect("progress watch sender dropped"); + let status = progress.borrow_and_update().clone().unwrap(); + match status { + UpdateProgress::Complete => break, + UpdateProgress::InProgress { .. } => continue, + _ => panic!("unexpected progress status {status:?}"), + } + } + + // drop our progress receiver so `do_update_task` can complete + mem::drop(progress); + + do_update_task.await.expect("update task panicked").expect("update failed"); + + let last_update_image = target_sp + .last_host_phase1_update_data(target_host_slot) + .await + .expect("simulated host phase1 did not receive an update"); + + assert_eq!( + phase1_data.as_slice(), + &*last_update_image, + "simulated host phase1 update contents (len {}) \ + do not match test generated fake image (len {})", + last_update_image.len(), + phase1_data.len(), + ); + + mgstestctx.teardown().await; +} diff --git a/nexus/tests/integration_tests/mod.rs b/nexus/tests/integration_tests/mod.rs index 87c5c74f0f..4d7b41cfa8 100644 --- a/nexus/tests/integration_tests/mod.rs +++ b/nexus/tests/integration_tests/mod.rs @@ -12,6 +12,7 @@ mod commands; mod console_api; mod device_auth; mod disks; +mod host_phase1_updater; mod images; mod initialization; mod instances; diff --git a/nexus/tests/integration_tests/oximeter.rs b/nexus/tests/integration_tests/oximeter.rs index e97f36daf4..7dc453d713 100644 --- a/nexus/tests/integration_tests/oximeter.rs +++ b/nexus/tests/integration_tests/oximeter.rs @@ -361,7 +361,7 @@ async fn test_oximeter_collector_reregistration_gets_all_assignments() { ids.insert(id); let info = ProducerEndpoint { id, - kind: Some(ProducerKind::Service), + kind: ProducerKind::Service, address: SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 12345), base_route: String::from("/collect"), interval: Duration::from_secs(1), diff --git a/nexus/tests/integration_tests/rot_updater.rs b/nexus/tests/integration_tests/rot_updater.rs index 750f9571d0..2e6d65f8b1 100644 --- a/nexus/tests/integration_tests/rot_updater.rs +++ b/nexus/tests/integration_tests/rot_updater.rs @@ -45,10 +45,11 @@ async fn test_rot_updater_updates_sled() { .await; // Configure an MGS client. - let mgs_clients = MgsClients::from_clients([gateway_client::Client::new( - &mgstestctx.client.url("/").to_string(), - mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), - )]); + let mut mgs_clients = + MgsClients::from_clients([gateway_client::Client::new( + &mgstestctx.client.url("/").to_string(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + )]); // Configure and instantiate an `RotUpdater`. let sp_type = SpType::Sled; @@ -67,7 +68,7 @@ async fn test_rot_updater_updates_sled() { ); // Run the update. - rot_updater.update(mgs_clients).await.expect("update failed"); + rot_updater.update(&mut mgs_clients).await.expect("update failed"); // Ensure the RoT received the complete update. let last_update_image = mgstestctx.simrack.gimlets[sp_slot as usize] @@ -97,10 +98,11 @@ async fn test_rot_updater_updates_switch() { .await; // Configure an MGS client. - let mgs_clients = MgsClients::from_clients([gateway_client::Client::new( - &mgstestctx.client.url("/").to_string(), - mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), - )]); + let mut mgs_clients = + MgsClients::from_clients([gateway_client::Client::new( + &mgstestctx.client.url("/").to_string(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + )]); let sp_type = SpType::Switch; let sp_slot = 0; @@ -117,7 +119,7 @@ async fn test_rot_updater_updates_switch() { &mgstestctx.logctx.log, ); - rot_updater.update(mgs_clients).await.expect("update failed"); + rot_updater.update(&mut mgs_clients).await.expect("update failed"); let last_update_image = mgstestctx.simrack.sidecars[sp_slot as usize] .last_rot_update_data() @@ -177,7 +179,7 @@ async fn test_rot_updater_remembers_successful_mgs_instance() { // delivering an update requires a bare minimum of three requests (start the // update, query the status, reset the RoT) and often more (if repeated // queries are required to wait for completion). - let mgs_clients = MgsClients::from_clients([ + let mut mgs_clients = MgsClients::from_clients([ gateway_client::Client::new( &format!("http://{failing_mgs_addr}"), mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient1")), @@ -203,7 +205,7 @@ async fn test_rot_updater_remembers_successful_mgs_instance() { &mgstestctx.logctx.log, ); - rot_updater.update(mgs_clients).await.expect("update failed"); + rot_updater.update(&mut mgs_clients).await.expect("update failed"); let last_update_image = mgstestctx.simrack.gimlets[sp_slot as usize] .last_rot_update_data() @@ -295,7 +297,7 @@ async fn test_rot_updater_switches_mgs_instances_on_failure() { reqwest::Client::builder().pool_max_idle_per_host(0).build().unwrap(); // Configure two MGS clients pointed at our two proxy tasks. - let mgs_clients = MgsClients::from_clients([ + let mut mgs_clients = MgsClients::from_clients([ gateway_client::Client::new_with_client( &format!("http://{mgs_proxy_one_addr}"), client.clone(), @@ -324,7 +326,8 @@ async fn test_rot_updater_switches_mgs_instances_on_failure() { ); // Spawn the actual update task. - let mut update_task = tokio::spawn(rot_updater.update(mgs_clients)); + let mut update_task = + tokio::spawn(async move { rot_updater.update(&mut mgs_clients).await }); // Loop over incoming requests. We expect this sequence: // @@ -447,10 +450,11 @@ async fn test_rot_updater_delivers_progress() { .await; // Configure an MGS client. - let mgs_clients = MgsClients::from_clients([gateway_client::Client::new( - &mgstestctx.client.url("/").to_string(), - mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), - )]); + let mut mgs_clients = + MgsClients::from_clients([gateway_client::Client::new( + &mgstestctx.client.url("/").to_string(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + )]); let sp_type = SpType::Sled; let sp_slot = 0; @@ -483,10 +487,11 @@ async fn test_rot_updater_delivers_progress() { // Spawn the update on a background task so we can watch `progress` as it is // applied. - let do_update_task = tokio::spawn(rot_updater.update(mgs_clients)); + let do_update_task = + tokio::spawn(async move { rot_updater.update(&mut mgs_clients).await }); // Allow the SP to respond to 1 message: the "prepare update" messages that - // trigger the start of an update, then ensure we see the "started" + // triggers the start of an update, then ensure we see the "started" // progress. sp_accept_sema.send(1).unwrap(); progress.changed().await.unwrap(); @@ -556,7 +561,6 @@ async fn test_rot_updater_delivers_progress() { UpdateStatus::Complete(_) => { if prev_bytes_received < rot_image_len { prev_bytes_received = rot_image_len; - continue; } } status @ (UpdateStatus::None diff --git a/nexus/tests/integration_tests/schema.rs b/nexus/tests/integration_tests/schema.rs index 213e7f9e4f..6feafe415d 100644 --- a/nexus/tests/integration_tests/schema.rs +++ b/nexus/tests/integration_tests/schema.rs @@ -629,7 +629,17 @@ impl InformationSchema { self.referential_constraints, other.referential_constraints ); - similar_asserts::assert_eq!(self.statistics, other.statistics); + similar_asserts::assert_eq!( + self.statistics, + other.statistics, + "Statistics did not match. This often means that in dbinit.sql, a new \ + column was added into the middle of a table rather than to the end. \ + If that is the case:\n\n \ + \ + * Change dbinit.sql to add the column to the end of the table.\n\ + * Update nexus/db-model/src/schema.rs and the corresponding \ + Queryable/Insertable struct with the new column ordering." + ); similar_asserts::assert_eq!(self.sequences, other.sequences); similar_asserts::assert_eq!(self.pg_indexes, other.pg_indexes); } diff --git a/nexus/tests/integration_tests/sp_updater.rs b/nexus/tests/integration_tests/sp_updater.rs index 89735ac3d9..1b6764e609 100644 --- a/nexus/tests/integration_tests/sp_updater.rs +++ b/nexus/tests/integration_tests/sp_updater.rs @@ -46,10 +46,11 @@ async fn test_sp_updater_updates_sled() { .await; // Configure an MGS client. - let mgs_clients = MgsClients::from_clients([gateway_client::Client::new( - &mgstestctx.client.url("/").to_string(), - mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), - )]); + let mut mgs_clients = + MgsClients::from_clients([gateway_client::Client::new( + &mgstestctx.client.url("/").to_string(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + )]); // Configure and instantiate an `SpUpdater`. let sp_type = SpType::Sled; @@ -66,7 +67,7 @@ async fn test_sp_updater_updates_sled() { ); // Run the update. - sp_updater.update(mgs_clients).await.expect("update failed"); + sp_updater.update(&mut mgs_clients).await.expect("update failed"); // Ensure the SP received the complete update. let last_update_image = mgstestctx.simrack.gimlets[sp_slot as usize] @@ -96,10 +97,11 @@ async fn test_sp_updater_updates_switch() { .await; // Configure an MGS client. - let mgs_clients = MgsClients::from_clients([gateway_client::Client::new( - &mgstestctx.client.url("/").to_string(), - mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), - )]); + let mut mgs_clients = + MgsClients::from_clients([gateway_client::Client::new( + &mgstestctx.client.url("/").to_string(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + )]); let sp_type = SpType::Switch; let sp_slot = 0; @@ -114,7 +116,7 @@ async fn test_sp_updater_updates_switch() { &mgstestctx.logctx.log, ); - sp_updater.update(mgs_clients).await.expect("update failed"); + sp_updater.update(&mut mgs_clients).await.expect("update failed"); let last_update_image = mgstestctx.simrack.sidecars[sp_slot as usize] .last_sp_update_data() @@ -174,7 +176,7 @@ async fn test_sp_updater_remembers_successful_mgs_instance() { // delivering an update requires a bare minimum of three requests (start the // update, query the status, reset the SP) and often more (if repeated // queries are required to wait for completion). - let mgs_clients = MgsClients::from_clients([ + let mut mgs_clients = MgsClients::from_clients([ gateway_client::Client::new( &format!("http://{failing_mgs_addr}"), mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient1")), @@ -198,7 +200,7 @@ async fn test_sp_updater_remembers_successful_mgs_instance() { &mgstestctx.logctx.log, ); - sp_updater.update(mgs_clients).await.expect("update failed"); + sp_updater.update(&mut mgs_clients).await.expect("update failed"); let last_update_image = mgstestctx.simrack.gimlets[sp_slot as usize] .last_sp_update_data() @@ -290,7 +292,7 @@ async fn test_sp_updater_switches_mgs_instances_on_failure() { reqwest::Client::builder().pool_max_idle_per_host(0).build().unwrap(); // Configure two MGS clients pointed at our two proxy tasks. - let mgs_clients = MgsClients::from_clients([ + let mut mgs_clients = MgsClients::from_clients([ gateway_client::Client::new_with_client( &format!("http://{mgs_proxy_one_addr}"), client.clone(), @@ -317,7 +319,8 @@ async fn test_sp_updater_switches_mgs_instances_on_failure() { ); // Spawn the actual update task. - let mut update_task = tokio::spawn(sp_updater.update(mgs_clients)); + let mut update_task = + tokio::spawn(async move { sp_updater.update(&mut mgs_clients).await }); // Loop over incoming requests. We expect this sequence: // @@ -436,10 +439,11 @@ async fn test_sp_updater_delivers_progress() { .await; // Configure an MGS client. - let mgs_clients = MgsClients::from_clients([gateway_client::Client::new( - &mgstestctx.client.url("/").to_string(), - mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), - )]); + let mut mgs_clients = + MgsClients::from_clients([gateway_client::Client::new( + &mgstestctx.client.url("/").to_string(), + mgstestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + )]); let sp_type = SpType::Sled; let sp_slot = 0; @@ -470,10 +474,11 @@ async fn test_sp_updater_delivers_progress() { // Spawn the update on a background task so we can watch `progress` as it is // applied. - let do_update_task = tokio::spawn(sp_updater.update(mgs_clients)); + let do_update_task = + tokio::spawn(async move { sp_updater.update(&mut mgs_clients).await }); // Allow the SP to respond to 2 messages: the caboose check and the "prepare - // update" messages that trigger the start of an update, then ensure we see + // update" messages that triggers the start of an update, then ensure we see // the "started" progress. sp_accept_sema.send(2).unwrap(); progress.changed().await.unwrap(); @@ -543,7 +548,6 @@ async fn test_sp_updater_delivers_progress() { UpdateStatus::Complete(_) => { if prev_bytes_received < sp_image_len { prev_bytes_received = sp_image_len; - continue; } } status @ (UpdateStatus::None diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index d163fc6b06..df4d96c6d1 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -10,7 +10,7 @@ use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::params::{ Address, AddressConfig, AddressLotBlockCreate, AddressLotCreate, - BgpAnnounceSetCreate, BgpAnnouncementCreate, BgpConfigCreate, + BgpAnnounceSetCreate, BgpAnnouncementCreate, BgpConfigCreate, BgpPeer, BgpPeerConfig, LinkConfig, LinkFec, LinkSpeed, LldpServiceConfig, Route, RouteConfig, SwitchInterfaceConfig, SwitchInterfaceKind, SwitchPortApplySettings, SwitchPortSettingsCreate, @@ -118,6 +118,7 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { lldp: LldpServiceConfig { enabled: false, lldp_config: None }, fec: LinkFec::None, speed: LinkSpeed::Speed100G, + autoneg: false, }, ); // interfaces @@ -252,15 +253,17 @@ async fn test_port_settings_basic_crud(ctx: &ControlPlaneTestContext) { settings.bgp_peers.insert( "phy0".into(), BgpPeerConfig { - bgp_config: NameOrId::Name("as47".parse().unwrap()), //TODO - bgp_announce_set: NameOrId::Name("instances".parse().unwrap()), //TODO - interface_name: "phy0".to_string(), - addr: "1.2.3.4".parse().unwrap(), - hold_time: 6, - idle_hold_time: 6, - delay_open: 0, - connect_retry: 3, - keepalive: 2, + peers: vec![BgpPeer { + bgp_config: NameOrId::Name("as47".parse().unwrap()), + bgp_announce_set: NameOrId::Name("instances".parse().unwrap()), + interface_name: "phy0".to_string(), + addr: "1.2.3.4".parse().unwrap(), + hold_time: 6, + idle_hold_time: 6, + delay_open: 0, + connect_retry: 3, + keepalive: 2, + }], }, ); let _created: SwitchPortSettingsView = NexusRequest::objects_post( diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index dd387ab979..7e57d00df2 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -120,6 +120,7 @@ rack_view GET /v1/system/hardware/racks/{rac sled_instance_list GET /v1/system/hardware/sleds/{sled_id}/instances sled_list GET /v1/system/hardware/sleds sled_physical_disk_list GET /v1/system/hardware/sleds/{sled_id}/disks +sled_set_provision_state PUT /v1/system/hardware/sleds/{sled_id}/provision-state sled_view GET /v1/system/hardware/sleds/{sled_id} switch_list GET /v1/system/hardware/switches switch_view GET /v1/system/hardware/switches/{switch_id} diff --git a/nexus/types/Cargo.toml b/nexus/types/Cargo.toml index 9cb94a8484..8cbbd8626c 100644 --- a/nexus/types/Cargo.toml +++ b/nexus/types/Cargo.toml @@ -14,6 +14,7 @@ parse-display.workspace = true schemars = { workspace = true, features = ["chrono", "uuid1"] } serde.workspace = true serde_json.workspace = true +serde_with.workspace = true steno.workspace = true strum.workspace = true uuid.workspace = true diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index a0169ae777..3303d38367 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -75,6 +75,23 @@ pub struct SledSelector { pub sled: Uuid, } +/// Parameters for `sled_set_provision_state`. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct SledProvisionStateParams { + /// The provision state. + pub state: super::views::SledProvisionState, +} + +/// Response to `sled_set_provision_state`. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct SledProvisionStateResponse { + /// The old provision state. + pub old_state: super::views::SledProvisionState, + + /// The new provision state. + pub new_state: super::views::SledProvisionState, +} + pub struct SwitchSelector { /// ID of the switch pub switch: Uuid, @@ -1337,6 +1354,18 @@ pub enum LinkFec { Rs, } +impl From for LinkFec { + fn from(x: omicron_common::api::internal::shared::PortFec) -> LinkFec { + match x { + omicron_common::api::internal::shared::PortFec::Firecode => { + Self::Firecode + } + omicron_common::api::internal::shared::PortFec::None => Self::None, + omicron_common::api::internal::shared::PortFec::Rs => Self::Rs, + } + } +} + /// The speed of a link. #[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -1361,6 +1390,40 @@ pub enum LinkSpeed { Speed400G, } +impl From for LinkSpeed { + fn from(x: omicron_common::api::internal::shared::PortSpeed) -> Self { + match x { + omicron_common::api::internal::shared::PortSpeed::Speed0G => { + Self::Speed0G + } + omicron_common::api::internal::shared::PortSpeed::Speed1G => { + Self::Speed1G + } + omicron_common::api::internal::shared::PortSpeed::Speed10G => { + Self::Speed10G + } + omicron_common::api::internal::shared::PortSpeed::Speed25G => { + Self::Speed25G + } + omicron_common::api::internal::shared::PortSpeed::Speed40G => { + Self::Speed40G + } + omicron_common::api::internal::shared::PortSpeed::Speed50G => { + Self::Speed50G + } + omicron_common::api::internal::shared::PortSpeed::Speed100G => { + Self::Speed100G + } + omicron_common::api::internal::shared::PortSpeed::Speed200G => { + Self::Speed200G + } + omicron_common::api::internal::shared::PortSpeed::Speed400G => { + Self::Speed400G + } + } + } +} + /// Switch link configuration. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct LinkConfig { @@ -1375,6 +1438,9 @@ pub struct LinkConfig { /// The speed of the link. pub speed: LinkSpeed, + + /// Whether or not to set autonegotiation + pub autoneg: bool, } /// The LLDP configuration associated with a port. LLDP may be either enabled or @@ -1462,12 +1528,17 @@ pub struct BgpConfigListSelector { pub name_or_id: Option, } +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpPeerConfig { + pub peers: Vec, +} + /// A BGP peer configuration for an interface. Includes the set of announcements /// that will be advertised to the peer identified by `addr`. The `bgp_config` /// parameter is a reference to global BGP parameters. The `interface_name` /// indicates what interface the peer should be contacted on. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct BgpPeerConfig { +pub struct BgpPeer { /// The set of announcements advertised by the peer. pub bgp_announce_set: NameOrId, diff --git a/nexus/types/src/external_api/views.rs b/nexus/types/src/external_api/views.rs index 9dfe36d63b..6d02623f34 100644 --- a/nexus/types/src/external_api/views.rs +++ b/nexus/types/src/external_api/views.rs @@ -17,6 +17,7 @@ use omicron_common::api::external::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::rust::deserialize_ignore_any; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::net::IpAddr; @@ -286,12 +287,38 @@ pub struct Sled { pub baseboard: Baseboard, /// The rack to which this Sled is currently attached pub rack_id: Uuid, + /// The provision state of the sled. + pub provision_state: SledProvisionState, /// The number of hardware threads which can execute on this sled pub usable_hardware_threads: u32, /// Amount of RAM which may be used by the Sled's OS pub usable_physical_ram: ByteCount, } +/// The provision state of a sled. +/// +/// This controls whether new resources are going to be provisioned on this +/// sled. +#[derive( + Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, +)] +#[serde(rename_all = "snake_case")] +pub enum SledProvisionState { + /// New resources will be provisioned on this sled. + Provisionable, + + /// New resources will not be provisioned on this sled. However, existing + /// resources will continue to be on this sled unless manually migrated + /// off. + NonProvisionable, + + /// This is a state that isn't known yet. + /// + /// This is defined to avoid API breakage. + #[serde(other, deserialize_with = "deserialize_ignore_any")] + Unknown, +} + /// An operator's view of an instance running on a given sled #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct SledInstance { diff --git a/openapi/bootstrap-agent.json b/openapi/bootstrap-agent.json index 2c7ffbc337..0c5bd15050 100644 --- a/openapi/bootstrap-agent.json +++ b/openapi/bootstrap-agent.json @@ -510,6 +510,11 @@ "$ref": "#/components/schemas/IpNetwork" } }, + "autoneg": { + "description": "Whether or not to set autonegotiation", + "default": false, + "type": "boolean" + }, "bgp_peers": { "description": "BGP peers on this port", "type": "array", diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index c358b4109b..caf1414f53 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -2568,9 +2568,60 @@ "datum", "type" ] + }, + { + "type": "object", + "properties": { + "datum": { + "$ref": "#/components/schemas/MissingDatum" + }, + "type": { + "type": "string", + "enum": [ + "missing" + ] + } + }, + "required": [ + "datum", + "type" + ] } ] }, + "DatumType": { + "description": "The type of an individual datum of a metric.", + "type": "string", + "enum": [ + "bool", + "i8", + "u8", + "i16", + "u16", + "i32", + "u32", + "i64", + "u64", + "f32", + "f64", + "string", + "bytes", + "cumulative_i64", + "cumulative_u64", + "cumulative_f32", + "cumulative_f64", + "histogram_i8", + "histogram_u8", + "histogram_i16", + "histogram_u16", + "histogram_i32", + "histogram_u32", + "histogram_i64", + "histogram_u64", + "histogram_f32", + "histogram_f64" + ] + }, "DiskRuntimeState": { "description": "Runtime state of the Disk, which includes its attach state and some minimal metadata", "type": "object", @@ -4128,7 +4179,75 @@ "content", "type" ] + }, + { + "type": "object", + "properties": { + "content": { + "type": "object", + "properties": { + "datum_type": { + "$ref": "#/components/schemas/DatumType" + } + }, + "required": [ + "datum_type" + ] + }, + "type": { + "type": "string", + "enum": [ + "missing_datum_requires_start_time" + ] + } + }, + "required": [ + "content", + "type" + ] + }, + { + "type": "object", + "properties": { + "content": { + "type": "object", + "properties": { + "datum_type": { + "$ref": "#/components/schemas/DatumType" + } + }, + "required": [ + "datum_type" + ] + }, + "type": { + "type": "string", + "enum": [ + "missing_datum_cannot_have_start_time" + ] + } + }, + "required": [ + "content", + "type" + ] + } + ] + }, + "MissingDatum": { + "type": "object", + "properties": { + "datum_type": { + "$ref": "#/components/schemas/DatumType" + }, + "start_time": { + "nullable": true, + "type": "string", + "format": "date-time" } + }, + "required": [ + "datum_type" ] }, "Name": { @@ -4240,6 +4359,11 @@ "$ref": "#/components/schemas/IpNetwork" } }, + "autoneg": { + "description": "Whether or not to set autonegotiation", + "default": false, + "type": "boolean" + }, "bgp_peers": { "description": "BGP peers on this port", "type": "array", @@ -4343,7 +4467,6 @@ ] }, "kind": { - "nullable": true, "description": "The kind of producer.", "allOf": [ { @@ -4356,7 +4479,8 @@ "address", "base_route", "id", - "interval" + "interval", + "kind" ] }, "ProducerKind": { diff --git a/openapi/nexus.json b/openapi/nexus.json index 704aa393db..a6dffc6265 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -3817,6 +3817,55 @@ } } }, + "/v1/system/hardware/sleds/{sled_id}/provision-state": { + "put": { + "tags": [ + "system/hardware" + ], + "summary": "Set the sled's provision state.", + "operationId": "sled_set_provision_state", + "parameters": [ + { + "in": "path", + "name": "sled_id", + "description": "ID of the sled", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SledProvisionStateParams" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SledProvisionStateResponse" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/system/hardware/switch-port": { "get": { "tags": [ @@ -7816,7 +7865,7 @@ "switch" ] }, - "BgpPeerConfig": { + "BgpPeer": { "description": "A BGP peer configuration for an interface. Includes the set of announcements that will be advertised to the peer identified by `addr`. The `bgp_config` parameter is a reference to global BGP parameters. The `interface_name` indicates what interface the peer should be contacted on.", "type": "object", "properties": { @@ -7888,6 +7937,20 @@ "keepalive" ] }, + "BgpPeerConfig": { + "type": "object", + "properties": { + "peers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpPeer" + } + } + }, + "required": [ + "peers" + ] + }, "BgpPeerState": { "description": "The current state of a BGP peer.", "oneOf": [ @@ -9679,9 +9742,60 @@ "datum", "type" ] + }, + { + "type": "object", + "properties": { + "datum": { + "$ref": "#/components/schemas/MissingDatum" + }, + "type": { + "type": "string", + "enum": [ + "missing" + ] + } + }, + "required": [ + "datum", + "type" + ] } ] }, + "DatumType": { + "description": "The type of an individual datum of a metric.", + "type": "string", + "enum": [ + "bool", + "i8", + "u8", + "i16", + "u16", + "i32", + "u32", + "i64", + "u64", + "f32", + "f64", + "string", + "bytes", + "cumulative_i64", + "cumulative_u64", + "cumulative_f32", + "cumulative_f64", + "histogram_i8", + "histogram_u8", + "histogram_i16", + "histogram_u16", + "histogram_i32", + "histogram_u32", + "histogram_i64", + "histogram_u64", + "histogram_f32", + "histogram_f64" + ] + }, "DerEncodedKeyPair": { "type": "object", "properties": { @@ -11889,6 +12003,10 @@ "description": "Switch link configuration.", "type": "object", "properties": { + "autoneg": { + "description": "Whether or not to set autonegotiation", + "type": "boolean" + }, "fec": { "description": "The forward error correction mode of the link.", "allOf": [ @@ -11921,6 +12039,7 @@ } }, "required": [ + "autoneg", "fec", "lldp", "mtu", @@ -12201,6 +12320,22 @@ "items" ] }, + "MissingDatum": { + "type": "object", + "properties": { + "datum_type": { + "$ref": "#/components/schemas/DatumType" + }, + "start_time": { + "nullable": true, + "type": "string", + "format": "date-time" + } + }, + "required": [ + "datum_type" + ] + }, "Name": { "title": "A name unique within the parent collection", "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.", @@ -12976,6 +13111,14 @@ "type": "string", "format": "uuid" }, + "provision_state": { + "description": "The provision state of the sled.", + "allOf": [ + { + "$ref": "#/components/schemas/SledProvisionState" + } + ] + }, "rack_id": { "description": "The rack to which this Sled is currently attached", "type": "string", @@ -13009,6 +13152,7 @@ "required": [ "baseboard", "id", + "provision_state", "rack_id", "time_created", "time_modified", @@ -13099,6 +13243,75 @@ "items" ] }, + "SledProvisionState": { + "description": "The provision state of a sled.\n\nThis controls whether new resources are going to be provisioned on this sled.", + "oneOf": [ + { + "description": "New resources will be provisioned on this sled.", + "type": "string", + "enum": [ + "provisionable" + ] + }, + { + "description": "New resources will not be provisioned on this sled. However, existing resources will continue to be on this sled unless manually migrated off.", + "type": "string", + "enum": [ + "non_provisionable" + ] + }, + { + "description": "This is a state that isn't known yet.\n\nThis is defined to avoid API breakage.", + "type": "string", + "enum": [ + "unknown" + ] + } + ] + }, + "SledProvisionStateParams": { + "description": "Parameters for `sled_set_provision_state`.", + "type": "object", + "properties": { + "state": { + "description": "The provision state.", + "allOf": [ + { + "$ref": "#/components/schemas/SledProvisionState" + } + ] + } + }, + "required": [ + "state" + ] + }, + "SledProvisionStateResponse": { + "description": "Response to `sled_set_provision_state`.", + "type": "object", + "properties": { + "new_state": { + "description": "The new provision state.", + "allOf": [ + { + "$ref": "#/components/schemas/SledProvisionState" + } + ] + }, + "old_state": { + "description": "The old provision state.", + "allOf": [ + { + "$ref": "#/components/schemas/SledProvisionState" + } + ] + } + }, + "required": [ + "new_state", + "old_state" + ] + }, "SledResultsPage": { "description": "A single page of results", "type": "object", diff --git a/openapi/oximeter.json b/openapi/oximeter.json index f7e534c95d..f5c78d53cd 100644 --- a/openapi/oximeter.json +++ b/openapi/oximeter.json @@ -212,7 +212,6 @@ ] }, "kind": { - "nullable": true, "description": "The kind of producer.", "allOf": [ { @@ -225,7 +224,8 @@ "address", "base_route", "id", - "interval" + "interval", + "kind" ] }, "ProducerEndpointResultsPage": { diff --git a/openapi/sled-agent.json b/openapi/sled-agent.json index ed202ddbdb..5e217b27a4 100644 --- a/openapi/sled-agent.json +++ b/openapi/sled-agent.json @@ -377,14 +377,35 @@ } } }, - "/services": { + "/omicron-zones": { + "get": { + "operationId": "omicron_zones_get", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OmicronZonesConfig" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, "put": { - "operationId": "services_put", + "operationId": "omicron_zones_put", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ServiceEnsureBody" + "$ref": "#/components/schemas/OmicronZonesConfig" } } }, @@ -2370,131 +2391,6 @@ "value" ] }, - "DatasetKind": { - "description": "The type of a dataset, and an auxiliary information necessary to successfully launch a zone managing the associated data.", - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "cockroach_db" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "crucible" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "clickhouse" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "clickhouse_keeper" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "external_dns" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "internal_dns" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "DatasetName": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/components/schemas/DatasetKind" - }, - "pool_name": { - "$ref": "#/components/schemas/ZpoolName" - } - }, - "required": [ - "kind", - "pool_name" - ] - }, - "DatasetRequest": { - "description": "Describes a request to provision a specific dataset", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "name": { - "$ref": "#/components/schemas/DatasetName" - }, - "service_address": { - "type": "string" - } - }, - "required": [ - "id", - "name", - "service_address" - ] - }, "Datum": { "description": "A `Datum` is a single sampled data point from a metric.", "oneOf": [ @@ -3002,9 +2898,60 @@ "datum", "type" ] + }, + { + "type": "object", + "properties": { + "datum": { + "$ref": "#/components/schemas/MissingDatum" + }, + "type": { + "type": "string", + "enum": [ + "missing" + ] + } + }, + "required": [ + "datum", + "type" + ] } ] }, + "DatumType": { + "description": "The type of an individual datum of a metric.", + "type": "string", + "enum": [ + "bool", + "i8", + "u8", + "i16", + "u16", + "i32", + "u32", + "i64", + "u64", + "f32", + "f64", + "string", + "bytes", + "cumulative_i64", + "cumulative_u64", + "cumulative_f32", + "cumulative_f64", + "histogram_i8", + "histogram_u8", + "histogram_i16", + "histogram_u16", + "histogram_i32", + "histogram_u32", + "histogram_i64", + "histogram_u64", + "histogram_f32", + "histogram_f64" + ] + }, "DeleteVirtualNetworkInterfaceHost": { "description": "The data needed to identify a virtual IP for which a sled maintains an OPTE virtual-to-physical mapping such that that mapping can be deleted.", "type": "object", @@ -4923,7 +4870,75 @@ "content", "type" ] + }, + { + "type": "object", + "properties": { + "content": { + "type": "object", + "properties": { + "datum_type": { + "$ref": "#/components/schemas/DatumType" + } + }, + "required": [ + "datum_type" + ] + }, + "type": { + "type": "string", + "enum": [ + "missing_datum_requires_start_time" + ] + } + }, + "required": [ + "content", + "type" + ] + }, + { + "type": "object", + "properties": { + "content": { + "type": "object", + "properties": { + "datum_type": { + "$ref": "#/components/schemas/DatumType" + } + }, + "required": [ + "datum_type" + ] + }, + "type": { + "type": "string", + "enum": [ + "missing_datum_cannot_have_start_time" + ] + } + }, + "required": [ + "content", + "type" + ] + } + ] + }, + "MissingDatum": { + "type": "object", + "properties": { + "datum_type": { + "$ref": "#/components/schemas/DatumType" + }, + "start_time": { + "nullable": true, + "type": "string", + "format": "date-time" } + }, + "required": [ + "datum_type" ] }, "Name": { @@ -5027,367 +5042,317 @@ } ] }, - "PortConfigV1": { + "OmicronZoneConfig": { + "description": "Describes one Omicron-managed zone running on a sled", "type": "object", "properties": { - "addresses": { - "description": "This port's addresses.", - "type": "array", - "items": { - "$ref": "#/components/schemas/IpNetwork" - } - }, - "bgp_peers": { - "description": "BGP peers on this port", - "type": "array", - "items": { - "$ref": "#/components/schemas/BgpPeerConfig" - } - }, - "port": { - "description": "Nmae of the port this config applies to.", - "type": "string" - }, - "routes": { - "description": "The set of routes associated with this port.", - "type": "array", - "items": { - "$ref": "#/components/schemas/RouteConfig" - } - }, - "switch": { - "description": "Switch the port belongs to.", - "allOf": [ - { - "$ref": "#/components/schemas/SwitchLocation" - } - ] + "id": { + "type": "string", + "format": "uuid" }, - "uplink_port_fec": { - "description": "Port forward error correction type.", - "allOf": [ - { - "$ref": "#/components/schemas/PortFec" - } - ] + "underlay_address": { + "type": "string", + "format": "ipv6" }, - "uplink_port_speed": { - "description": "Port speed.", - "allOf": [ - { - "$ref": "#/components/schemas/PortSpeed" - } - ] + "zone_type": { + "$ref": "#/components/schemas/OmicronZoneType" } }, "required": [ - "addresses", - "bgp_peers", - "port", - "routes", - "switch", - "uplink_port_fec", - "uplink_port_speed" - ] - }, - "PortFec": { - "description": "Switchport FEC options", - "type": "string", - "enum": [ - "firecode", - "none", - "rs" - ] - }, - "PortSpeed": { - "description": "Switchport Speed options", - "type": "string", - "enum": [ - "speed0_g", - "speed1_g", - "speed10_g", - "speed25_g", - "speed40_g", - "speed50_g", - "speed100_g", - "speed200_g", - "speed400_g" + "id", + "underlay_address", + "zone_type" ] }, - "PriorityDimension": { - "description": "A dimension along with bundles can be sorted, to determine priority.", - "oneOf": [ - { - "description": "Sorting by time, with older bundles with lower priority.", - "type": "string", - "enum": [ - "time" - ] - }, - { - "description": "Sorting by the cause for creating the bundle.", - "type": "string", - "enum": [ - "cause" - ] + "OmicronZoneDataset": { + "description": "Describes a persistent ZFS dataset associated with an Omicron zone", + "type": "object", + "properties": { + "pool_name": { + "$ref": "#/components/schemas/ZpoolName" } - ] - }, - "PriorityOrder": { - "description": "The priority order for bundles during cleanup.\n\nBundles are sorted along the dimensions in [`PriorityDimension`], with each dimension appearing exactly once. During cleanup, lesser-priority bundles are pruned first, to maintain the dataset quota. Note that bundles are sorted by each dimension in the order in which they appear, with each dimension having higher priority than the next.", - "type": "array", - "items": { - "$ref": "#/components/schemas/PriorityDimension" }, - "minItems": 2, - "maxItems": 2 + "required": [ + "pool_name" + ] }, - "ProducerResultsItem": { + "OmicronZoneType": { + "description": "Describes what kind of zone this is (i.e., what component is running in it) as well as any type-specific configuration", "oneOf": [ { "type": "object", "properties": { - "info": { + "address": { + "type": "string" + }, + "dns_servers": { "type": "array", "items": { - "$ref": "#/components/schemas/Sample" + "type": "string", + "format": "ip" } }, - "status": { + "domain": { + "nullable": true, + "type": "string" + }, + "nic": { + "description": "The service vNIC providing outbound connectivity using OPTE.", + "allOf": [ + { + "$ref": "#/components/schemas/NetworkInterface" + } + ] + }, + "ntp_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "snat_cfg": { + "description": "The SNAT configuration for outbound connections.", + "allOf": [ + { + "$ref": "#/components/schemas/SourceNatConfig" + } + ] + }, + "type": { "type": "string", "enum": [ - "ok" + "boundary_ntp" ] } }, "required": [ - "info", - "status" + "address", + "dns_servers", + "nic", + "ntp_servers", + "snat_cfg", + "type" ] }, { "type": "object", "properties": { - "info": { - "$ref": "#/components/schemas/MetricsError" + "address": { + "type": "string" }, - "status": { + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, + "type": { "type": "string", "enum": [ - "err" + "clickhouse" ] } }, "required": [ - "info", - "status" + "address", + "dataset", + "type" ] - } - ] - }, - "QuantizationError": { - "description": "Errors occurring during quantizated bin generation.", - "oneOf": [ + }, { "type": "object", "properties": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, "type": { "type": "string", "enum": [ - "overflow" + "clickhouse_keeper" ] } }, "required": [ + "address", + "dataset", "type" ] }, { "type": "object", "properties": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, "type": { "type": "string", "enum": [ - "precision" + "cockroach_db" ] } }, "required": [ + "address", + "dataset", "type" ] }, { "type": "object", "properties": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, "type": { "type": "string", "enum": [ - "invalid_base" + "crucible" ] } }, "required": [ + "address", + "dataset", "type" ] }, { "type": "object", "properties": { + "address": { + "type": "string" + }, "type": { "type": "string", "enum": [ - "invalid_steps" + "crucible_pantry" ] } }, "required": [ + "address", "type" ] }, { "type": "object", "properties": { + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, + "dns_address": { + "description": "The address at which the external DNS server is reachable.", + "type": "string" + }, + "http_address": { + "description": "The address at which the external DNS server API is reachable.", + "type": "string" + }, + "nic": { + "description": "The service vNIC providing external connectivity using OPTE.", + "allOf": [ + { + "$ref": "#/components/schemas/NetworkInterface" + } + ] + }, "type": { "type": "string", "enum": [ - "uneven_steps_for_base" + "external_dns" ] } }, "required": [ + "dataset", + "dns_address", + "http_address", + "nic", "type" ] }, { "type": "object", "properties": { + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, + "dns_address": { + "type": "string" + }, + "gz_address": { + "description": "The addresses in the global zone which should be created\n\nFor the DNS service, which exists outside the sleds's typical subnet - adding an address in the GZ is necessary to allow inter-zone traffic routing.", + "type": "string", + "format": "ipv6" + }, + "gz_address_index": { + "description": "The address is also identified with an auxiliary bit of information to ensure that the created global zone address can have a unique name.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "http_address": { + "type": "string" + }, "type": { "type": "string", "enum": [ - "powers_out_of_order" + "internal_dns" ] } }, "required": [ + "dataset", + "dns_address", + "gz_address", + "gz_address_index", + "http_address", "type" ] - } - ] - }, - "RackNetworkConfigV1": { - "description": "Initial network configuration", - "type": "object", - "properties": { - "bgp": { - "description": "BGP configurations for connecting the rack to external networks", - "type": "array", - "items": { - "$ref": "#/components/schemas/BgpConfig" - } - }, - "infra_ip_first": { - "description": "First ip address to be used for configuring network infrastructure", - "type": "string", - "format": "ipv4" - }, - "infra_ip_last": { - "description": "Last ip address to be used for configuring network infrastructure", - "type": "string", - "format": "ipv4" }, - "ports": { - "description": "Uplinks for connecting the rack to external networks", - "type": "array", - "items": { - "$ref": "#/components/schemas/PortConfigV1" - } - }, - "rack_subnet": { - "$ref": "#/components/schemas/Ipv6Network" - } - }, - "required": [ - "bgp", - "infra_ip_first", - "infra_ip_last", - "ports", - "rack_subnet" - ] - }, - "RouteConfig": { - "type": "object", - "properties": { - "destination": { - "description": "The destination of the route.", - "allOf": [ - { - "$ref": "#/components/schemas/IpNetwork" - } - ] - }, - "nexthop": { - "description": "The nexthop/gateway address.", - "type": "string", - "format": "ip" - } - }, - "required": [ - "destination", - "nexthop" - ] - }, - "Sample": { - "description": "A concrete type representing a single, timestamped measurement from a timeseries.", - "type": "object", - "properties": { - "measurement": { - "description": "The measured value of the metric at this sample", - "allOf": [ - { - "$ref": "#/components/schemas/Measurement" + { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "dns_servers": { + "type": "array", + "items": { + "type": "string", + "format": "ip" + } + }, + "domain": { + "nullable": true, + "type": "string" + }, + "ntp_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "internal_ntp" + ] } + }, + "required": [ + "address", + "dns_servers", + "ntp_servers", + "type" ] }, - "metric": { - "$ref": "#/components/schemas/FieldSet" - }, - "target": { - "$ref": "#/components/schemas/FieldSet" - }, - "timeseries_name": { - "description": "The name of the timeseries this sample belongs to", - "type": "string" - } - }, - "required": [ - "measurement", - "metric", - "target", - "timeseries_name" - ] - }, - "SemverVersion": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" - }, - "ServiceEnsureBody": { - "description": "Used to request that the Sled initialize multiple services.", - "type": "object", - "properties": { - "services": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ServiceZoneRequest" - } - } - }, - "required": [ - "services" - ] - }, - "ServiceType": { - "description": "Describes service-specific parameters.", - "oneOf": [ { "type": "object", "properties": { @@ -5439,331 +5404,396 @@ { "type": "object", "properties": { - "dns_address": { - "description": "The address at which the external DNS server is reachable.", - "type": "string" - }, - "http_address": { - "description": "The address at which the external DNS server API is reachable.", + "address": { "type": "string" }, - "nic": { - "description": "The service vNIC providing external connectivity using OPTE.", - "allOf": [ - { - "$ref": "#/components/schemas/NetworkInterface" - } - ] - }, "type": { "type": "string", "enum": [ - "external_dns" + "oximeter" ] } }, "required": [ - "dns_address", - "http_address", - "nic", + "address", "type" ] + } + ] + }, + "OmicronZonesConfig": { + "description": "Describes the set of Omicron-managed zones running on a sled", + "type": "object", + "properties": { + "generation": { + "description": "generation number of this configuration\n\nThis generation number is owned by the control plane (i.e., RSS or Nexus, depending on whether RSS-to-Nexus handoff has happened). It should not be bumped within Sled Agent.\n\nSled Agent rejects attempts to set the configuration to a generation older than the one it's currently running.", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] }, - { - "type": "object", - "properties": { - "dns_address": { - "type": "string" - }, - "gz_address": { - "description": "The addresses in the global zone which should be created\n\nFor the DNS service, which exists outside the sleds's typical subnet - adding an address in the GZ is necessary to allow inter-zone traffic routing.", - "type": "string", - "format": "ipv6" - }, - "gz_address_index": { - "description": "The address is also identified with an auxiliary bit of information to ensure that the created global zone address can have a unique name.", - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "http_address": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "internal_dns" - ] + "zones": { + "description": "list of running zones", + "type": "array", + "items": { + "$ref": "#/components/schemas/OmicronZoneConfig" + } + } + }, + "required": [ + "generation", + "zones" + ] + }, + "PortConfigV1": { + "type": "object", + "properties": { + "addresses": { + "description": "This port's addresses.", + "type": "array", + "items": { + "$ref": "#/components/schemas/IpNetwork" + } + }, + "autoneg": { + "description": "Whether or not to set autonegotiation", + "default": false, + "type": "boolean" + }, + "bgp_peers": { + "description": "BGP peers on this port", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpPeerConfig" + } + }, + "port": { + "description": "Nmae of the port this config applies to.", + "type": "string" + }, + "routes": { + "description": "The set of routes associated with this port.", + "type": "array", + "items": { + "$ref": "#/components/schemas/RouteConfig" + } + }, + "switch": { + "description": "Switch the port belongs to.", + "allOf": [ + { + "$ref": "#/components/schemas/SwitchLocation" } - }, - "required": [ - "dns_address", - "gz_address", - "gz_address_index", - "http_address", - "type" ] }, + "uplink_port_fec": { + "description": "Port forward error correction type.", + "allOf": [ + { + "$ref": "#/components/schemas/PortFec" + } + ] + }, + "uplink_port_speed": { + "description": "Port speed.", + "allOf": [ + { + "$ref": "#/components/schemas/PortSpeed" + } + ] + } + }, + "required": [ + "addresses", + "bgp_peers", + "port", + "routes", + "switch", + "uplink_port_fec", + "uplink_port_speed" + ] + }, + "PortFec": { + "description": "Switchport FEC options", + "type": "string", + "enum": [ + "firecode", + "none", + "rs" + ] + }, + "PortSpeed": { + "description": "Switchport Speed options", + "type": "string", + "enum": [ + "speed0_g", + "speed1_g", + "speed10_g", + "speed25_g", + "speed40_g", + "speed50_g", + "speed100_g", + "speed200_g", + "speed400_g" + ] + }, + "PriorityDimension": { + "description": "A dimension along with bundles can be sorted, to determine priority.", + "oneOf": [ + { + "description": "Sorting by time, with older bundles with lower priority.", + "type": "string", + "enum": [ + "time" + ] + }, + { + "description": "Sorting by the cause for creating the bundle.", + "type": "string", + "enum": [ + "cause" + ] + } + ] + }, + "PriorityOrder": { + "description": "The priority order for bundles during cleanup.\n\nBundles are sorted along the dimensions in [`PriorityDimension`], with each dimension appearing exactly once. During cleanup, lesser-priority bundles are pruned first, to maintain the dataset quota. Note that bundles are sorted by each dimension in the order in which they appear, with each dimension having higher priority than the next.", + "type": "array", + "items": { + "$ref": "#/components/schemas/PriorityDimension" + }, + "minItems": 2, + "maxItems": 2 + }, + "ProducerResultsItem": { + "oneOf": [ { "type": "object", "properties": { - "address": { - "type": "string" + "info": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Sample" + } }, - "type": { + "status": { "type": "string", "enum": [ - "oximeter" + "ok" ] } }, "required": [ - "address", - "type" + "info", + "status" ] }, { "type": "object", "properties": { - "address": { - "type": "string" + "info": { + "$ref": "#/components/schemas/MetricsError" }, - "type": { + "status": { "type": "string", "enum": [ - "crucible_pantry" + "err" ] } }, "required": [ - "address", - "type" + "info", + "status" ] - }, + } + ] + }, + "QuantizationError": { + "description": "Errors occurring during quantizated bin generation.", + "oneOf": [ { "type": "object", "properties": { - "address": { - "type": "string" - }, - "dns_servers": { - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, - "domain": { - "nullable": true, - "type": "string" - }, - "nic": { - "description": "The service vNIC providing outbound connectivity using OPTE.", - "allOf": [ - { - "$ref": "#/components/schemas/NetworkInterface" - } - ] - }, - "ntp_servers": { - "type": "array", - "items": { - "type": "string" - } - }, - "snat_cfg": { - "description": "The SNAT configuration for outbound connections.", - "allOf": [ - { - "$ref": "#/components/schemas/SourceNatConfig" - } - ] - }, "type": { "type": "string", "enum": [ - "boundary_ntp" + "overflow" ] } }, "required": [ - "address", - "dns_servers", - "nic", - "ntp_servers", - "snat_cfg", "type" ] }, { "type": "object", "properties": { - "address": { - "type": "string" - }, - "dns_servers": { - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, - "domain": { - "nullable": true, - "type": "string" - }, - "ntp_servers": { - "type": "array", - "items": { - "type": "string" - } - }, "type": { "type": "string", "enum": [ - "internal_ntp" + "precision" ] } }, "required": [ - "address", - "dns_servers", - "ntp_servers", "type" ] }, { "type": "object", "properties": { - "address": { - "type": "string" - }, "type": { "type": "string", "enum": [ - "clickhouse" + "invalid_base" ] } }, "required": [ - "address", "type" ] }, { "type": "object", "properties": { - "address": { - "type": "string" - }, "type": { "type": "string", "enum": [ - "clickhouse_keeper" + "invalid_steps" ] } }, "required": [ - "address", "type" ] }, { "type": "object", "properties": { - "address": { - "type": "string" - }, "type": { "type": "string", "enum": [ - "cockroach_db" + "uneven_steps_for_base" ] } }, "required": [ - "address", "type" ] }, { "type": "object", "properties": { - "address": { - "type": "string" - }, "type": { "type": "string", "enum": [ - "crucible" + "powers_out_of_order" ] } }, "required": [ - "address", "type" ] } ] }, - "ServiceZoneRequest": { - "description": "Describes a request to create a zone running one or more services.", + "RackNetworkConfigV1": { + "description": "Initial network configuration", "type": "object", "properties": { - "addresses": { + "bgp": { + "description": "BGP configurations for connecting the rack to external networks", "type": "array", "items": { - "type": "string", - "format": "ipv6" + "$ref": "#/components/schemas/BgpConfig" } }, - "dataset": { - "nullable": true, - "default": null, - "allOf": [ - { - "$ref": "#/components/schemas/DatasetRequest" - } - ] + "infra_ip_first": { + "description": "First ip address to be used for configuring network infrastructure", + "type": "string", + "format": "ipv4" }, - "id": { + "infra_ip_last": { + "description": "Last ip address to be used for configuring network infrastructure", "type": "string", - "format": "uuid" + "format": "ipv4" }, - "services": { + "ports": { + "description": "Uplinks for connecting the rack to external networks", "type": "array", "items": { - "$ref": "#/components/schemas/ServiceZoneService" + "$ref": "#/components/schemas/PortConfigV1" } }, - "zone_type": { - "$ref": "#/components/schemas/ZoneType" + "rack_subnet": { + "$ref": "#/components/schemas/Ipv6Network" } }, "required": [ - "addresses", - "id", - "services", - "zone_type" + "bgp", + "infra_ip_first", + "infra_ip_last", + "ports", + "rack_subnet" ] }, - "ServiceZoneService": { - "description": "Used to request that the Sled initialize a single service.", + "RouteConfig": { "type": "object", "properties": { - "details": { - "$ref": "#/components/schemas/ServiceType" + "destination": { + "description": "The destination of the route.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNetwork" + } + ] }, - "id": { + "nexthop": { + "description": "The nexthop/gateway address.", "type": "string", - "format": "uuid" + "format": "ip" } }, "required": [ - "details", - "id" + "destination", + "nexthop" + ] + }, + "Sample": { + "description": "A concrete type representing a single, timestamped measurement from a timeseries.", + "type": "object", + "properties": { + "measurement": { + "description": "The measured value of the metric at this sample", + "allOf": [ + { + "$ref": "#/components/schemas/Measurement" + } + ] + }, + "metric": { + "$ref": "#/components/schemas/FieldSet" + }, + "target": { + "$ref": "#/components/schemas/FieldSet" + }, + "timeseries_name": { + "description": "The name of the timeseries this sample belongs to", + "type": "string" + } + }, + "required": [ + "measurement", + "metric", + "target", + "timeseries_name" ] }, + "SemverVersion": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, "SetVirtualNetworkInterfaceHost": { "description": "A mapping from a virtual NIC to a physical host", "type": "object", @@ -6429,23 +6459,6 @@ "version" ] }, - "ZoneType": { - "description": "The type of zone which may be requested from Sled Agent", - "type": "string", - "enum": [ - "clickhouse", - "clickhouse_keeper", - "cockroach_db", - "crucible_pantry", - "crucible", - "external_dns", - "internal_dns", - "nexus", - "ntp", - "oximeter", - "switch" - ] - }, "Zpool": { "type": "object", "properties": { diff --git a/openapi/wicketd.json b/openapi/wicketd.json index 60ad9a42df..804b2029c6 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -1545,6 +1545,11 @@ "$ref": "#/components/schemas/IpNetwork" } }, + "autoneg": { + "description": "Whether or not to set autonegotiation", + "default": false, + "type": "boolean" + }, "bgp_peers": { "description": "BGP peers on this port", "type": "array", diff --git a/oximeter/collector/src/agent.rs b/oximeter/collector/src/agent.rs index f6da172909..4135125a48 100644 --- a/oximeter/collector/src/agent.rs +++ b/oximeter/collector/src/agent.rs @@ -659,6 +659,24 @@ mod tests { use tokio::time::Instant; use uuid::Uuid; + // Interval on which oximeter collects from producers in these tests. + const COLLECTION_INTERVAL: Duration = Duration::from_secs(1); + + // Interval in calls to `tokio::time::advance`. This must be sufficiently + // small relative to `COLLECTION_INTERVAL` to ensure all ticks of internal + // timers complete as expected. + const TICK_INTERVAL: Duration = Duration::from_millis(10); + + // Total number of collection attempts. + const N_COLLECTIONS: u64 = 5; + + // Period these tests wait using `tokio::time::advance()` before checking + // their test conditions. + const TEST_WAIT_PERIOD: Duration = Duration::from_millis( + COLLECTION_INTERVAL.as_millis() as u64 * N_COLLECTIONS + + COLLECTION_INTERVAL.as_millis() as u64 / 2, + ); + // Test that we count successful collections from a target correctly. #[tokio::test] async fn test_self_stat_collection_count() { @@ -692,13 +710,12 @@ mod tests { let _task = tokio::task::spawn(server); // Register the dummy producer. - let interval = Duration::from_secs(1); let endpoint = ProducerEndpoint { id: Uuid::new_v4(), - kind: Some(ProducerKind::Service), + kind: ProducerKind::Service, address, base_route: String::from("/"), - interval, + interval: COLLECTION_INTERVAL, }; collector .register_producer(endpoint) @@ -708,10 +725,8 @@ mod tests { // Step time until there has been exactly `N_COLLECTIONS` collections. tokio::time::pause(); let now = Instant::now(); - const N_COLLECTIONS: usize = 5; - let wait_for = interval * N_COLLECTIONS as u32 + interval / 2; - while now.elapsed() < wait_for { - tokio::time::advance(interval / 10).await; + while now.elapsed() < TEST_WAIT_PERIOD { + tokio::time::advance(TICK_INTERVAL).await; } // Request the statistics from the task itself. @@ -729,7 +744,7 @@ mod tests { .await .expect("failed to request statistics from task"); let stats = rx.await.expect("failed to receive statistics from task"); - assert_eq!(stats.collections.datum.value(), N_COLLECTIONS as u64); + assert_eq!(stats.collections.datum.value(), N_COLLECTIONS); assert!(stats.failed_collections.is_empty()); logctx.cleanup_successful(); } @@ -751,10 +766,9 @@ mod tests { // Register a bogus producer, which is equivalent to a producer that is // unreachable. - let interval = Duration::from_secs(1); let endpoint = ProducerEndpoint { id: Uuid::new_v4(), - kind: Some(ProducerKind::Service), + kind: ProducerKind::Service, address: SocketAddr::V6(SocketAddrV6::new( Ipv6Addr::LOCALHOST, 0, @@ -762,7 +776,7 @@ mod tests { 0, )), base_route: String::from("/"), - interval, + interval: COLLECTION_INTERVAL, }; collector .register_producer(endpoint) @@ -772,10 +786,8 @@ mod tests { // Step time until there has been exactly `N_COLLECTIONS` collections. tokio::time::pause(); let now = Instant::now(); - const N_COLLECTIONS: usize = 5; - let wait_for = interval * N_COLLECTIONS as u32 + interval / 2; - while now.elapsed() < wait_for { - tokio::time::advance(interval / 10).await; + while now.elapsed() < TEST_WAIT_PERIOD { + tokio::time::advance(TICK_INTERVAL).await; } // Request the statistics from the task itself. @@ -801,7 +813,7 @@ mod tests { .unwrap() .datum .value(), - N_COLLECTIONS as u64 + N_COLLECTIONS, ); assert_eq!(stats.failed_collections.len(), 1); logctx.cleanup_successful(); @@ -840,13 +852,12 @@ mod tests { let _task = tokio::task::spawn(server); // Register the rather flaky producer. - let interval = Duration::from_secs(1); let endpoint = ProducerEndpoint { id: Uuid::new_v4(), - kind: Some(ProducerKind::Service), + kind: ProducerKind::Service, address, base_route: String::from("/"), - interval, + interval: COLLECTION_INTERVAL, }; collector .register_producer(endpoint) @@ -856,10 +867,8 @@ mod tests { // Step time until there has been exactly `N_COLLECTIONS` collections. tokio::time::pause(); let now = Instant::now(); - const N_COLLECTIONS: usize = 5; - let wait_for = interval * N_COLLECTIONS as u32 + interval / 2; - while now.elapsed() < wait_for { - tokio::time::advance(interval / 10).await; + while now.elapsed() < TEST_WAIT_PERIOD { + tokio::time::advance(TICK_INTERVAL).await; } // Request the statistics from the task itself. @@ -885,7 +894,7 @@ mod tests { .unwrap() .datum .value(), - N_COLLECTIONS as u64 + N_COLLECTIONS, ); assert_eq!(stats.failed_collections.len(), 1); logctx.cleanup_successful(); diff --git a/oximeter/db/notes.txt b/oximeter/db/notes.txt deleted file mode 100644 index 66c3871d46..0000000000 --- a/oximeter/db/notes.txt +++ /dev/null @@ -1,232 +0,0 @@ -Some notes on querying - -For pagination: - -- Timeseries name is enough for paginated list timeseries endpoint. -It's just normal keyset pagination. - -- For the timeseries data, we'll be using limit/offset pagination. We'll -run the query to get the consistent timeseries keys each time. This is -the `ScanParams` part of the `WhichPage`. The `PageSelector` is the offset. - - -Now, how to run more complex queries? A good example is something like, -aggregating the timeseries across all but one field. For example, let's -look at the Nexus HTTP latency data. The fields are: - -- name (String) -- id (Uuid) -- route (String) -- method (String) -- status_code (I64) - -Imagine we wanted to look at the average latency by route, so averaged -across all methods and status codes. (Let's ingore name/id) - -We need to group the timeseries keys by route, to find the set of keys -consistent with each different route. ClickHouse provides the `groupArray` -function, which is an aggregate function that collects multiple values -into an array. So we can do: - -``` -SELECT - field_value, - groupArray(timeseries_key) -FROM fields_string -WHERE field_name = 'route' -GROUP BY field_value; - - -┌─field_value───────────────────────────────────────────┬─groupArray(timeseries_key)────────────────┐ -│ /metrics/producers │ [1916712826069192294,6228796576473532827] │ -│ /metrics/collectors │ [1500085842574282480] │ -│ /metrics/collect/e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c │ [15389669872422126367] │ -│ /sled_agents/fb0f7546-4d46-40ca-9d56-cbb810684ca7 │ [1166666993114742619] │ -└───────────────────────────────────────────────────────┴───────────────────────────────────────────┘ -``` - -This gives an array of timeseries keys where the route is each of the values -on the left. - -So at a very high level, we can average all the timeseries values where the keys -are in each of these different arrays. - - -This kinda works. It produces an array of arrays, the counts for each of the -histograms, grouped by the field value. - -``` -SELECT - field_value, - groupArray(counts) -FROM -( - SELECT - field_value, - timeseries_key - FROM fields_string - WHERE field_name = 'route' -) AS f0 -INNER JOIN -( - SELECT * - FROM measurements_histogramf64 -) AS meas USING (timeseries_key) -GROUP BY field_value -``` - -We can extend this `groupArray(bins), groupArray(counts)` to get both. - - -Ok, we're getting somewhere. The aggregation "combinators" modify the behavior of -aggregations, in pretty suprising and powerful ways. For example: - -``` -SELECT - field_value, - sumForEach(counts) -FROM -( - SELECT - field_value, - timeseries_key - FROM fields_string - WHERE field_name = 'route' -) AS f0 -INNER JOIN -( - SELECT * - FROM measurements_histogramf64 -) AS meas USING (timeseries_key) -GROUP BY field_value -``` - -This applies the `-ForEach` combinator to the sum aggregation. This applies the -aggregation to corresponding elements of a sequence (table?) of arrays. We can -do this with any of the aggregations, `avg`, `min`, etc. - - -The `-Resample` combinator also looks interesting. It uses its arguments to create -a set of intervals, and applies the aggregation within each of those intervals. -So sort of a group-by interval or window function. - -Another useful method is `toStartOfInterval`. This takes a timestamp and an interval, -say 5 seconds, or 10 minutes, and returns the interval into which that timestamp -falls. Could be very helpful for aligning/binning data to time intervals. But -it does "round", in that the bins don't start at the first timestamp, but at -the rounded-down interval from that timestamp. - -It's possible to build intervals that start exactly at the first timestamp with: - -``` -SELECT - timestamp, - toStartOfInterval(timestamp, toIntervalMinute(1)) + ( - SELECT toSecond(min(timestamp)) - FROM measurements_histogramf64 - ) -FROM measurements_histogramf64 -``` - -Or some other rounding shenanigans. - - -Putting lots of this together: - -``` -SELECT - f0.field_name, - f0.field_value, - f1.field_name, - f1.field_value, - minForEach(bins), - avgForEach(counts) -FROM -( - SELECT - field_name, - field_value, - timeseries_key - FROM fields_string - WHERE field_name = 'route' -) AS f0 -INNER JOIN -( - SELECT - field_name, - field_value, - timeseries_key - FROM fields_i64 - WHERE field_name = 'status_code' -) AS f1 ON f0.timeseries_key = f1.timeseries_key -INNER JOIN -( - SELECT * - FROM measurements_histogramf64 -) AS meas ON f1.timeseries_key = meas.timeseries_key -GROUP BY - f0.field_name, - f0.field_value, - f1.field_name, - f1.field_value -``` - -This selects the field name/value, and the bin and average count for each -histogram, grouping by route and status code. - -These inner select statements look similar to the ones we already -implement in `field.as_query`. But in that case we select *, and here we -probably don't want to do that to avoid errors about things not being -in aggregations or group by's. - -This works (or is syntactically valid) for scalars, if we replace the -combinators with their non-combinator version: e.g, `avgForEach` -> `avg`. - - -Other rando thoughts. - -It'd be nice to have the query builder be able to handle all these, but -I'm not sure how worth it that is. For example, I don't even think we need -the timeseries keys in this query. For the fields where we are specifying -a condition, we have subqueries like: - -``` -SELECT * -FROM fields_{TYPE} -WHERE field_name = NAME -AND field_value OP VALUE; -``` - -For ones where we _don't_ care, we just have the first three lines: - -``` -SELECT * -FROM fields_{TYPE} -WHERE field_name = NAME; -``` - -We can join successive entries on timeseries keys. - -For straight SELECT queries, that's pretty much it, like we have currently. -For AGGREGATION queries, we need to - -- Have a group-by for each (field_name, field_value) pair. This is true -even when we're unselective on the field, because we are still taking that -field, and we still need to group the keys accordingly. -- Select the consistent timeseries keys. This is so we can correlate the -results of the aggregation back to the field names/values which we still -get from the key-select query. -- Apply the aggregation to the measurements. For scalars, this just the -aggregation. For histograms, this is the `-Array` or `-ForEach` combinator -for that aggregation, depending on what we're applying. -- ??? to the timestamps? -- some alignment, grouping, subsampling? It seems -this has to come from the aggregation query, because there's not a useful -default. - -Speaking of defaults, how do these functions behave with missing data? -Or more subtly, what happens if two histograms (say) have the same number -of bins, but the actual bin edges are different? ClickHouse itself doesn't -deal with this AFAICT, which means we'd need to do that in the client. -Ah, but that is unlikely, since we're only aggregating data from the -same timeseries, with the same key. So far anyway. I'm not sure what'll -happen when we start correlating data between timeseries. diff --git a/oximeter/db/schema/replicated/4/up01.sql b/oximeter/db/schema/replicated/4/up01.sql new file mode 100644 index 0000000000..f36745ae2e --- /dev/null +++ b/oximeter/db/schema/replicated/4/up01.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_bool_local MODIFY COLUMN datum Nullable(UInt8) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up02.sql b/oximeter/db/schema/replicated/4/up02.sql new file mode 100644 index 0000000000..0f76398652 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up02.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_bool MODIFY COLUMN datum Nullable(UInt8) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up03.sql b/oximeter/db/schema/replicated/4/up03.sql new file mode 100644 index 0000000000..175b23d71b --- /dev/null +++ b/oximeter/db/schema/replicated/4/up03.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i8_local MODIFY COLUMN datum Nullable(Int8) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up04.sql b/oximeter/db/schema/replicated/4/up04.sql new file mode 100644 index 0000000000..4c8f22d8e6 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up04.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i8 MODIFY COLUMN datum Nullable(Int8) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up05.sql b/oximeter/db/schema/replicated/4/up05.sql new file mode 100644 index 0000000000..82490a81ca --- /dev/null +++ b/oximeter/db/schema/replicated/4/up05.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u8_local MODIFY COLUMN datum Nullable(UInt8) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up06.sql b/oximeter/db/schema/replicated/4/up06.sql new file mode 100644 index 0000000000..c689682127 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up06.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u8 MODIFY COLUMN datum Nullable(UInt8) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up07.sql b/oximeter/db/schema/replicated/4/up07.sql new file mode 100644 index 0000000000..43eb40515b --- /dev/null +++ b/oximeter/db/schema/replicated/4/up07.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i16_local MODIFY COLUMN datum Nullable(Int16) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up08.sql b/oximeter/db/schema/replicated/4/up08.sql new file mode 100644 index 0000000000..1d983a3c83 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up08.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i16 MODIFY COLUMN datum Nullable(Int16) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up09.sql b/oximeter/db/schema/replicated/4/up09.sql new file mode 100644 index 0000000000..e52c2adf5f --- /dev/null +++ b/oximeter/db/schema/replicated/4/up09.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u16_local MODIFY COLUMN datum Nullable(UInt16) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up10.sql b/oximeter/db/schema/replicated/4/up10.sql new file mode 100644 index 0000000000..d8a69fff1a --- /dev/null +++ b/oximeter/db/schema/replicated/4/up10.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u16 MODIFY COLUMN datum Nullable(UInt16) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up11.sql b/oximeter/db/schema/replicated/4/up11.sql new file mode 100644 index 0000000000..b3c2d8de92 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up11.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i32_local MODIFY COLUMN datum Nullable(Int32) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up12.sql b/oximeter/db/schema/replicated/4/up12.sql new file mode 100644 index 0000000000..65fca2e1b2 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up12.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i32 MODIFY COLUMN datum Nullable(Int32) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up13.sql b/oximeter/db/schema/replicated/4/up13.sql new file mode 100644 index 0000000000..df7c520e35 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up13.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u32_local MODIFY COLUMN datum Nullable(UInt32) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up14.sql b/oximeter/db/schema/replicated/4/up14.sql new file mode 100644 index 0000000000..a4cb43fb90 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up14.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u32 MODIFY COLUMN datum Nullable(UInt32) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up15.sql b/oximeter/db/schema/replicated/4/up15.sql new file mode 100644 index 0000000000..f7583dbdee --- /dev/null +++ b/oximeter/db/schema/replicated/4/up15.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i64_local MODIFY COLUMN datum Nullable(Int64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up16.sql b/oximeter/db/schema/replicated/4/up16.sql new file mode 100644 index 0000000000..b458243d74 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up16.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i64 MODIFY COLUMN datum Nullable(Int64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up17.sql b/oximeter/db/schema/replicated/4/up17.sql new file mode 100644 index 0000000000..9229a97704 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up17.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u64_local MODIFY COLUMN datum Nullable(UInt64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up18.sql b/oximeter/db/schema/replicated/4/up18.sql new file mode 100644 index 0000000000..6e2a2a5191 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up18.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u64 MODIFY COLUMN datum Nullable(UInt64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up19.sql b/oximeter/db/schema/replicated/4/up19.sql new file mode 100644 index 0000000000..8f16b5d41e --- /dev/null +++ b/oximeter/db/schema/replicated/4/up19.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_f32_local MODIFY COLUMN datum Nullable(Float32) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up20.sql b/oximeter/db/schema/replicated/4/up20.sql new file mode 100644 index 0000000000..9263592740 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up20.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_f32 MODIFY COLUMN datum Nullable(Float32) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up21.sql b/oximeter/db/schema/replicated/4/up21.sql new file mode 100644 index 0000000000..72abba6216 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up21.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_f64_local MODIFY COLUMN datum Nullable(Float64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up22.sql b/oximeter/db/schema/replicated/4/up22.sql new file mode 100644 index 0000000000..0d8522bc03 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up22.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_f64 MODIFY COLUMN datum Nullable(Float64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up23.sql b/oximeter/db/schema/replicated/4/up23.sql new file mode 100644 index 0000000000..96b94c2895 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up23.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativei64_local MODIFY COLUMN datum Nullable(Int64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up24.sql b/oximeter/db/schema/replicated/4/up24.sql new file mode 100644 index 0000000000..55df76c25f --- /dev/null +++ b/oximeter/db/schema/replicated/4/up24.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativei64 MODIFY COLUMN datum Nullable(Int64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up25.sql b/oximeter/db/schema/replicated/4/up25.sql new file mode 100644 index 0000000000..fac7369482 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up25.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativeu64_local MODIFY COLUMN datum Nullable(UInt64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up26.sql b/oximeter/db/schema/replicated/4/up26.sql new file mode 100644 index 0000000000..182b2b4704 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up26.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativeu64 MODIFY COLUMN datum Nullable(UInt64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up27.sql b/oximeter/db/schema/replicated/4/up27.sql new file mode 100644 index 0000000000..b482d00f81 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up27.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativef32_local MODIFY COLUMN datum Nullable(Float32) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up28.sql b/oximeter/db/schema/replicated/4/up28.sql new file mode 100644 index 0000000000..cefbe56395 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up28.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativef32 MODIFY COLUMN datum Nullable(Float32) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up29.sql b/oximeter/db/schema/replicated/4/up29.sql new file mode 100644 index 0000000000..59e21f353d --- /dev/null +++ b/oximeter/db/schema/replicated/4/up29.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativef64_local MODIFY COLUMN datum Nullable(Float64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up30.sql b/oximeter/db/schema/replicated/4/up30.sql new file mode 100644 index 0000000000..a609e6ad3c --- /dev/null +++ b/oximeter/db/schema/replicated/4/up30.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativef64 MODIFY COLUMN datum Nullable(Float64) \ No newline at end of file diff --git a/oximeter/db/schema/replicated/4/up31.sql b/oximeter/db/schema/replicated/4/up31.sql new file mode 100644 index 0000000000..3726895dd0 --- /dev/null +++ b/oximeter/db/schema/replicated/4/up31.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_string_local MODIFY COLUMN datum Nullable(String); diff --git a/oximeter/db/schema/replicated/4/up32.sql b/oximeter/db/schema/replicated/4/up32.sql new file mode 100644 index 0000000000..5a09705e7e --- /dev/null +++ b/oximeter/db/schema/replicated/4/up32.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_string MODIFY COLUMN datum Nullable(String); diff --git a/oximeter/db/schema/replicated/db-init.sql b/oximeter/db/schema/replicated/db-init.sql index 4429f41364..27df02b709 100644 --- a/oximeter/db/schema/replicated/db-init.sql +++ b/oximeter/db/schema/replicated/db-init.sql @@ -24,7 +24,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_bool_local ON CLUSTER oximeter_ timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt8 + datum Nullable(UInt8) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_bool_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_bool ON CLUSTER oximeter_cluste timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt8 + datum Nullable(UInt8) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_bool_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -44,7 +44,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i8_local ON CLUSTER oximeter_cl timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int8 + datum Nullable(Int8) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_i8_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -55,7 +55,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i8 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int8 + datum Nullable(Int8) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_i8_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -64,7 +64,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u8_local ON CLUSTER oximeter_cl timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt8 + datum Nullable(UInt8) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_u8_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u8 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt8 + datum Nullable(UInt8) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_u8_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -84,7 +84,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i16_local ON CLUSTER oximeter_c timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int16 + datum Nullable(Int16) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_i16_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -95,7 +95,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i16 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int16 + datum Nullable(Int16) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_i16_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -104,7 +104,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u16_local ON CLUSTER oximeter_c timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt16 + datum Nullable(UInt16) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_u16_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -115,7 +115,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u16 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt16 + datum Nullable(UInt16) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_u16_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -124,7 +124,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i32_local ON CLUSTER oximeter_c timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int32 + datum Nullable(Int32) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_i32_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -135,7 +135,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i32 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int32 + datum Nullable(Int32) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_i32_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -144,7 +144,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u32_local ON CLUSTER oximeter_c timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt32 + datum Nullable(UInt32) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_u32_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -155,7 +155,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u32 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt32 + datum Nullable(UInt32) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_u32_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -164,7 +164,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i64_local ON CLUSTER oximeter_c timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int64 + datum Nullable(Int64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_i64_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -175,7 +175,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i64 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int64 + datum Nullable(Int64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_i64_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -184,7 +184,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u64_local ON CLUSTER oximeter_c timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt64 + datum Nullable(UInt64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_u64_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -195,7 +195,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u64 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt64 + datum Nullable(UInt64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_u64_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -204,7 +204,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_f32_local ON CLUSTER oximeter_c timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Float32 + datum Nullable(Float32) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_f32_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -215,7 +215,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_f32 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Float32 + datum Nullable(Float32) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_f32_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -224,7 +224,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_f64_local ON CLUSTER oximeter_c timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Float64 + datum Nullable(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_f64_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -235,7 +235,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_f64 ON CLUSTER oximeter_cluster timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Float64 + datum Nullable(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_f64_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -244,7 +244,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_string_local ON CLUSTER oximete timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum String + datum Nullable(String) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_string_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -255,7 +255,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_string ON CLUSTER oximeter_clus timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum String + datum Nullable(String) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_string_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -285,7 +285,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativei64_local ON CLUSTER timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum Int64 + datum Nullable(Int64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_cumulativei64_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -297,7 +297,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativei64 ON CLUSTER oximet timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum Int64 + datum Nullable(Int64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_cumulativei64_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -307,7 +307,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativeu64_local ON CLUSTER timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum UInt64 + datum Nullable(UInt64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_cumulativeu64_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -319,7 +319,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativeu64 ON CLUSTER oximet timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum UInt64 + datum Nullable(UInt64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_cumulativeu64_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -329,7 +329,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativef32_local ON CLUSTER timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum Float32 + datum Nullable(Float32) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_cumulativef32_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -341,7 +341,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativef32 ON CLUSTER oximet timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum Float32 + datum Nullable(Float32) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_cumulativef32_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -351,7 +351,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativef64_local ON CLUSTER timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum Float64 + datum Nullable(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_cumulativef64_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -363,7 +363,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativef64 ON CLUSTER oximet timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum Float64 + datum Nullable(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_cumulativef64_local', xxHash64(splitByChar(':', timeseries_name)[1])); diff --git a/oximeter/db/schema/single-node/4/up01.sql b/oximeter/db/schema/single-node/4/up01.sql new file mode 100644 index 0000000000..ccccc9c5fb --- /dev/null +++ b/oximeter/db/schema/single-node/4/up01.sql @@ -0,0 +1,9 @@ +/* + * To support missing measurements, we are making all scalar datum columns + * Nullable, so that a NULL value (None in Rust) represents a missing datum at + * the provided timestamp. + * + * Note that arrays cannot be made Nullable, so we need to use an empty array as + * the sentinel value implying a missing measurement. + */ +ALTER TABLE oximeter.measurements_bool MODIFY COLUMN datum Nullable(UInt8) diff --git a/oximeter/db/schema/single-node/4/up02.sql b/oximeter/db/schema/single-node/4/up02.sql new file mode 100644 index 0000000000..4c8f22d8e6 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up02.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i8 MODIFY COLUMN datum Nullable(Int8) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up03.sql b/oximeter/db/schema/single-node/4/up03.sql new file mode 100644 index 0000000000..c689682127 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up03.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u8 MODIFY COLUMN datum Nullable(UInt8) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up04.sql b/oximeter/db/schema/single-node/4/up04.sql new file mode 100644 index 0000000000..1d983a3c83 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up04.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i16 MODIFY COLUMN datum Nullable(Int16) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up05.sql b/oximeter/db/schema/single-node/4/up05.sql new file mode 100644 index 0000000000..d8a69fff1a --- /dev/null +++ b/oximeter/db/schema/single-node/4/up05.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u16 MODIFY COLUMN datum Nullable(UInt16) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up06.sql b/oximeter/db/schema/single-node/4/up06.sql new file mode 100644 index 0000000000..65fca2e1b2 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up06.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i32 MODIFY COLUMN datum Nullable(Int32) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up07.sql b/oximeter/db/schema/single-node/4/up07.sql new file mode 100644 index 0000000000..a4cb43fb90 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up07.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u32 MODIFY COLUMN datum Nullable(UInt32) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up08.sql b/oximeter/db/schema/single-node/4/up08.sql new file mode 100644 index 0000000000..b458243d74 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up08.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_i64 MODIFY COLUMN datum Nullable(Int64) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up09.sql b/oximeter/db/schema/single-node/4/up09.sql new file mode 100644 index 0000000000..6e2a2a5191 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up09.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_u64 MODIFY COLUMN datum Nullable(UInt64) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up10.sql b/oximeter/db/schema/single-node/4/up10.sql new file mode 100644 index 0000000000..9263592740 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up10.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_f32 MODIFY COLUMN datum Nullable(Float32) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up11.sql b/oximeter/db/schema/single-node/4/up11.sql new file mode 100644 index 0000000000..0d8522bc03 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up11.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_f64 MODIFY COLUMN datum Nullable(Float64) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up12.sql b/oximeter/db/schema/single-node/4/up12.sql new file mode 100644 index 0000000000..55df76c25f --- /dev/null +++ b/oximeter/db/schema/single-node/4/up12.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativei64 MODIFY COLUMN datum Nullable(Int64) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up13.sql b/oximeter/db/schema/single-node/4/up13.sql new file mode 100644 index 0000000000..182b2b4704 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up13.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativeu64 MODIFY COLUMN datum Nullable(UInt64) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up14.sql b/oximeter/db/schema/single-node/4/up14.sql new file mode 100644 index 0000000000..cefbe56395 --- /dev/null +++ b/oximeter/db/schema/single-node/4/up14.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativef32 MODIFY COLUMN datum Nullable(Float32) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up15.sql b/oximeter/db/schema/single-node/4/up15.sql new file mode 100644 index 0000000000..a609e6ad3c --- /dev/null +++ b/oximeter/db/schema/single-node/4/up15.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_cumulativef64 MODIFY COLUMN datum Nullable(Float64) \ No newline at end of file diff --git a/oximeter/db/schema/single-node/4/up16.sql b/oximeter/db/schema/single-node/4/up16.sql new file mode 100644 index 0000000000..5a09705e7e --- /dev/null +++ b/oximeter/db/schema/single-node/4/up16.sql @@ -0,0 +1 @@ +ALTER TABLE oximeter.measurements_string MODIFY COLUMN datum Nullable(String); diff --git a/oximeter/db/schema/single-node/db-init.sql b/oximeter/db/schema/single-node/db-init.sql index ee5e91c4b7..510c1071c8 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 UInt8 + datum Nullable(UInt8) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i8 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int8 + datum Nullable(Int8) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -46,7 +46,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u8 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt8 + datum Nullable(UInt8) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -57,7 +57,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i16 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int16 + datum Nullable(Int16) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -68,7 +68,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u16 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt16 + datum Nullable(UInt16) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -79,7 +79,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i32 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int32 + datum Nullable(Int32) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -90,7 +90,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u32 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt32 + datum Nullable(UInt32) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -101,7 +101,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_i64 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Int64 + datum Nullable(Int64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -112,7 +112,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_u64 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum UInt64 + datum Nullable(UInt64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -123,7 +123,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_f32 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Float32 + datum Nullable(Float32) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -134,7 +134,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_f64 timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum Float64 + datum Nullable(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -145,7 +145,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_string timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), - datum String + datum Nullable(String) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, timestamp) @@ -156,6 +156,13 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_bytes timeseries_name String, timeseries_key UInt64, timestamp DateTime64(9, 'UTC'), + /* + * NOTE: Right now we can't unambiguously record a nullable byte array. + * Arrays cannot be nested in `Nullable()` types, and encoding the array as + * a string isn't palatable for a few reasons. + * See: https://github.com/oxidecomputer/omicron/issues/4551 for more + * details. + */ datum Array(UInt8) ) ENGINE = MergeTree() @@ -168,7 +175,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativei64 timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum Int64 + datum Nullable(Int64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -180,7 +187,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativeu64 timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum UInt64 + datum Nullable(UInt64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -192,7 +199,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativef32 timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum Float32 + datum Nullable(Float32) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -205,7 +212,7 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_cumulativef64 timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), - datum Float64 + datum Nullable(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -217,6 +224,16 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami8 timeseries_key UInt64, start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), + /* + * NOTE: Array types cannot be Nullable, see + * https://clickhouse.com/docs/en/sql-reference/data-types/nullable + * for more details. + * + * This means we need to use empty arrays to indicate a missing value. This + * is unfortunate, and at this point relies on the fact that an + * `oximeter::Histogram` cannot have zero bins. If that changes, we'll need + * to figure out another way to represent missing samples here. + */ bins Array(Int8), counts Array(UInt64) ) diff --git a/oximeter/db/src/client.rs b/oximeter/db/src/client.rs index e1ed06554c..c8a7db20cb 100644 --- a/oximeter/db/src/client.rs +++ b/oximeter/db/src/client.rs @@ -1190,7 +1190,7 @@ mod tests { use super::*; use crate::query; use crate::query::field_table_name; - use crate::query::measurement_table_name; + use bytes::Bytes; use chrono::Utc; use omicron_test_utils::dev::clickhouse::{ ClickHouseCluster, ClickHouseInstance, @@ -1198,8 +1198,10 @@ mod tests { use omicron_test_utils::dev::test_setup_log; use oximeter::histogram::Histogram; use oximeter::test_util; + use oximeter::types::MissingDatum; use oximeter::Datum; use oximeter::FieldValue; + use oximeter::Measurement; use oximeter::Metric; use oximeter::Target; use std::net::Ipv6Addr; @@ -2957,76 +2959,102 @@ mod tests { Ok(()) } + async fn test_recall_missing_scalar_measurement_impl( + measurement: Measurement, + client: &Client, + ) -> Result<(), Error> { + let start_time = if measurement.datum().is_cumulative() { + Some(Utc::now()) + } else { + None + }; + let missing_datum = Datum::from( + MissingDatum::new(measurement.datum_type(), start_time).unwrap(), + ); + let missing_measurement = Measurement::new(Utc::now(), missing_datum); + test_recall_measurement_impl(missing_measurement, client).await?; + Ok(()) + } + async fn recall_measurement_bool_test( client: &Client, ) -> Result<(), Error> { let datum = Datum::Bool(true); - let as_json = serde_json::Value::from(1_u64); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } async fn recall_measurement_i8_test(client: &Client) -> Result<(), Error> { let datum = Datum::I8(1); - let as_json = serde_json::Value::from(1_i8); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } async fn recall_measurement_u8_test(client: &Client) -> Result<(), Error> { let datum = Datum::U8(1); - let as_json = serde_json::Value::from(1_u8); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } async fn recall_measurement_i16_test(client: &Client) -> Result<(), Error> { let datum = Datum::I16(1); - let as_json = serde_json::Value::from(1_i16); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } async fn recall_measurement_u16_test(client: &Client) -> Result<(), Error> { let datum = Datum::U16(1); - let as_json = serde_json::Value::from(1_u16); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } async fn recall_measurement_i32_test(client: &Client) -> Result<(), Error> { let datum = Datum::I32(1); - let as_json = serde_json::Value::from(1_i32); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } async fn recall_measurement_u32_test(client: &Client) -> Result<(), Error> { let datum = Datum::U32(1); - let as_json = serde_json::Value::from(1_u32); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } async fn recall_measurement_i64_test(client: &Client) -> Result<(), Error> { let datum = Datum::I64(1); - let as_json = serde_json::Value::from(1_i64); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } async fn recall_measurement_u64_test(client: &Client) -> Result<(), Error> { let datum = Datum::U64(1); - let as_json = serde_json::Value::from(1_u64); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } @@ -3034,9 +3062,9 @@ mod tests { async fn recall_measurement_f32_test(client: &Client) -> Result<(), Error> { const VALUE: f32 = 1.1; let datum = Datum::F32(VALUE); - // NOTE: This is intentionally an f64. - let as_json = serde_json::Value::from(1.1_f64); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } @@ -3044,18 +3072,43 @@ mod tests { async fn recall_measurement_f64_test(client: &Client) -> Result<(), Error> { const VALUE: f64 = 1.1; let datum = Datum::F64(VALUE); - let as_json = serde_json::Value::from(VALUE); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } + async fn recall_measurement_string_test( + client: &Client, + ) -> Result<(), Error> { + let value = String::from("foo"); + let datum = Datum::String(value.clone()); + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) + .await?; + Ok(()) + } + + async fn recall_measurement_bytes_test( + client: &Client, + ) -> Result<(), Error> { + let value = Bytes::from(vec![0, 1, 2]); + let datum = Datum::Bytes(value.clone()); + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + // NOTE: We don't currently support missing byte array samples. + Ok(()) + } + async fn recall_measurement_cumulative_i64_test( client: &Client, ) -> Result<(), Error> { let datum = Datum::CumulativeI64(1.into()); - let as_json = serde_json::Value::from(1_i64); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } @@ -3064,8 +3117,9 @@ mod tests { client: &Client, ) -> Result<(), Error> { let datum = Datum::CumulativeU64(1.into()); - let as_json = serde_json::Value::from(1_u64); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } @@ -3074,8 +3128,9 @@ mod tests { client: &Client, ) -> Result<(), Error> { let datum = Datum::CumulativeF64(1.1.into()); - let as_json = serde_json::Value::from(1.1_f64); - test_recall_measurement_impl::(datum, None, as_json, client) + let measurement = Measurement::new(Utc::now(), datum); + test_recall_measurement_impl(measurement.clone(), client).await?; + test_recall_missing_scalar_measurement_impl(measurement, client) .await?; Ok(()) } @@ -3089,13 +3144,15 @@ mod tests { Datum: From>, serde_json::Value: From, { - let (bins, counts) = hist.to_arrays(); let datum = Datum::from(hist); - let as_json = serde_json::Value::Array( - counts.into_iter().map(Into::into).collect(), + let measurement = Measurement::new(Utc::now(), datum); + let missing_datum = Datum::Missing( + MissingDatum::new(measurement.datum_type(), Some(Utc::now())) + .unwrap(), ); - test_recall_measurement_impl(datum, Some(bins), as_json, client) - .await?; + let missing_measurement = Measurement::new(Utc::now(), missing_datum); + test_recall_measurement_impl(measurement, client).await?; + test_recall_measurement_impl(missing_measurement, client).await?; Ok(()) } @@ -3192,54 +3249,23 @@ mod tests { Ok(()) } - async fn test_recall_measurement_impl + Copy>( - datum: Datum, - maybe_bins: Option>, - json_datum: serde_json::Value, + async fn test_recall_measurement_impl( + measurement: Measurement, client: &Client, ) -> Result<(), Error> { // Insert a record from this datum. const TIMESERIES_NAME: &str = "foo:bar"; const TIMESERIES_KEY: u64 = 101; - let mut inserted_row = serde_json::Map::new(); - inserted_row - .insert("timeseries_name".to_string(), TIMESERIES_NAME.into()); - inserted_row - .insert("timeseries_key".to_string(), TIMESERIES_KEY.into()); - inserted_row.insert( - "timestamp".to_string(), - Utc::now() - .format(crate::DATABASE_TIMESTAMP_FORMAT) - .to_string() - .into(), - ); - - // Insert the start time and possibly bins. - if let Some(start_time) = datum.start_time() { - inserted_row.insert( - "start_time".to_string(), - start_time - .format(crate::DATABASE_TIMESTAMP_FORMAT) - .to_string() - .into(), - ); - } - if let Some(bins) = &maybe_bins { - let bins = serde_json::Value::Array( - bins.iter().copied().map(Into::into).collect(), + let (measurement_table, inserted_row) = + crate::model::unroll_measurement_row_impl( + TIMESERIES_NAME.to_string(), + TIMESERIES_KEY, + &measurement, ); - inserted_row.insert("bins".to_string(), bins); - inserted_row.insert("counts".to_string(), json_datum); - } else { - inserted_row.insert("datum".to_string(), json_datum); - } - let inserted_row = serde_json::Value::from(inserted_row); - - let measurement_table = measurement_table_name(datum.datum_type()); - let row = serde_json::to_string(&inserted_row).unwrap(); let insert_sql = format!( - "INSERT INTO oximeter.{measurement_table} FORMAT JSONEachRow {row}", + "INSERT INTO {measurement_table} FORMAT JSONEachRow {inserted_row}", ); + println!("Inserted row: {}", inserted_row); client .execute(insert_sql) .await @@ -3247,21 +3273,22 @@ mod tests { // Select it exactly back out. let select_sql = format!( - "SELECT * FROM oximeter.{} LIMIT 2 FORMAT {};", + "SELECT * FROM {} WHERE timestamp = '{}' FORMAT {};", measurement_table, + measurement.timestamp().format(crate::DATABASE_TIMESTAMP_FORMAT), crate::DATABASE_SELECT_FORMAT, ); let body = client .execute_with_body(select_sql) .await .expect("Failed to select measurement row"); - println!("{}", body); - let actual_row: serde_json::Value = serde_json::from_str(&body) - .expect("Failed to parse measurement row JSON"); - println!("{actual_row:?}"); - println!("{inserted_row:?}"); + let (_, actual_row) = crate::model::parse_measurement_from_row( + &body, + measurement.datum_type(), + ); + println!("Actual row: {actual_row:?}"); assert_eq!( - actual_row, inserted_row, + actual_row, measurement, "Actual and expected measurement rows do not match" ); Ok(()) @@ -3311,6 +3338,10 @@ mod tests { recall_measurement_f64_test(&client).await.unwrap(); + recall_measurement_string_test(&client).await.unwrap(); + + recall_measurement_bytes_test(&client).await.unwrap(); + recall_measurement_cumulative_i64_test(&client).await.unwrap(); recall_measurement_cumulative_u64_test(&client).await.unwrap(); diff --git a/oximeter/db/src/model.rs b/oximeter/db/src/model.rs index 715e025a04..d92e646e89 100644 --- a/oximeter/db/src/model.rs +++ b/oximeter/db/src/model.rs @@ -26,6 +26,7 @@ use oximeter::types::Field; use oximeter::types::FieldType; use oximeter::types::FieldValue; use oximeter::types::Measurement; +use oximeter::types::MissingDatum; use oximeter::types::Sample; use serde::Deserialize; use serde::Serialize; @@ -43,7 +44,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 = 3; +pub const OXIMETER_VERSION: u64 = 4; // Wrapper type to represent a boolean in the database. // @@ -212,6 +213,7 @@ impl From for DbFieldType { } } } + #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] pub enum DbDatumType { Bool, @@ -402,7 +404,7 @@ macro_rules! declare_measurement_row { timeseries_key: TimeseriesKey, #[serde(with = "serde_timestamp")] timestamp: DateTime, - datum: $datum_type, + datum: Option<$datum_type>, } impl_table_name!{$name, "measurements", $data_type} @@ -433,7 +435,7 @@ macro_rules! declare_cumulative_measurement_row { start_time: DateTime, #[serde(with = "serde_timestamp")] timestamp: DateTime, - datum: $datum_type, + datum: Option<$datum_type>, } impl_table_name!{$name, "measurements", $data_type} @@ -456,6 +458,22 @@ struct DbHistogram { pub counts: Vec, } +// We use an empty histogram to indicate a missing sample. +// +// While ClickHouse supports nullable types, the inner type can't be a +// "composite", which includes arrays. I.e., `Nullable(Array(UInt8))` can't be +// used. This is unfortunate, but we are aided by the fact that it's not +// possible to have an `oximeter` histogram that contains zero bins right now. +// This is checked by a test in `oximeter::histogram`. +// +// That means we can currently use an empty array from the database as a +// sentinel for a missing sample. +impl DbHistogram { + fn null() -> Self { + Self { bins: vec![], counts: vec![] } + } +} + impl From<&Histogram> for DbHistogram where T: traits::HistogramSupport, @@ -647,270 +665,571 @@ pub(crate) fn unroll_measurement_row(sample: &Sample) -> (String, String) { let timeseries_name = sample.timeseries_name.clone(); let timeseries_key = crate::timeseries_key(sample); let measurement = &sample.measurement; + unroll_measurement_row_impl(timeseries_name, timeseries_key, measurement) +} + +/// Given a sample's measurement, return a table name and row to insert. +/// +/// This returns a tuple giving the name of the table, and the JSON +/// representation for the serialized row to be inserted into that table, +/// written out as a string. +pub(crate) fn unroll_measurement_row_impl( + timeseries_name: String, + timeseries_key: TimeseriesKey, + measurement: &Measurement, +) -> (String, String) { let timestamp = measurement.timestamp(); let extract_start_time = |measurement: &Measurement| { measurement .start_time() .expect("Cumulative measurements must have a start time") }; + match measurement.datum() { Datum::Bool(inner) => { + let datum = Some(DbBool::from(*inner)); let row = BoolMeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: DbBool::from(*inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::I8(inner) => { + let datum = Some(*inner); let row = I8MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::U8(inner) => { + let datum = Some(*inner); let row = U8MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::I16(inner) => { + let datum = Some(*inner); let row = I16MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::U16(inner) => { + let datum = Some(*inner); let row = U16MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::I32(inner) => { + let datum = Some(*inner); let row = I32MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::U32(inner) => { + let datum = Some(*inner); let row = U32MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::I64(inner) => { + let datum = Some(*inner); let row = I64MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::U64(inner) => { + let datum = Some(*inner); let row = U64MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::F32(inner) => { + let datum = Some(*inner); let row = F32MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::F64(inner) => { + let datum = Some(*inner); let row = F64MeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: *inner, + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::String(ref inner) => { + Datum::String(inner) => { + let datum = Some(inner.clone()); let row = StringMeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: inner.clone(), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::Bytes(ref inner) => { + Datum::Bytes(inner) => { + let datum = Some(inner.clone()); let row = BytesMeasurementRow { timeseries_name, timeseries_key, timestamp, - datum: inner.clone(), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::CumulativeI64(inner) => { + let datum = Some(inner.value()); let row = CumulativeI64MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: inner.value(), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::CumulativeU64(inner) => { + let datum = Some(inner.value()); let row = CumulativeU64MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: inner.value(), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::CumulativeF32(inner) => { + let datum = Some(inner.value()); let row = CumulativeF32MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: inner.value(), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } Datum::CumulativeF64(inner) => { + let datum = Some(inner.value()); let row = CumulativeF64MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: inner.value(), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramI8(ref inner) => { + Datum::HistogramI8(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramI8MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramU8(ref inner) => { + Datum::HistogramU8(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramU8MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramI16(ref inner) => { + Datum::HistogramI16(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramI16MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramU16(ref inner) => { + Datum::HistogramU16(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramU16MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramI32(ref inner) => { + Datum::HistogramI32(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramI32MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramU32(ref inner) => { + Datum::HistogramU32(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramU32MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramI64(ref inner) => { + Datum::HistogramI64(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramI64MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramU64(ref inner) => { + Datum::HistogramU64(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramU64MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramF32(ref inner) => { + Datum::HistogramF32(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramF32MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } - Datum::HistogramF64(ref inner) => { + Datum::HistogramF64(inner) => { + let datum = DbHistogram::from(inner); let row = HistogramF64MeasurementRow { timeseries_name, timeseries_key, start_time: extract_start_time(measurement), timestamp, - datum: DbHistogram::from(inner), + datum, }; (row.table_name(), serde_json::to_string(&row).unwrap()) } + Datum::Missing(missing) => { + match missing.datum_type() { + DatumType::Bool => { + let row = BoolMeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::I8 => { + let row = I8MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::U8 => { + let row = U8MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::I16 => { + let row = I16MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::U16 => { + let row = U16MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::I32 => { + let row = I32MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::U32 => { + let row = U32MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::I64 => { + let row = I64MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::U64 => { + let row = U64MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::F32 => { + let row = F32MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::F64 => { + let row = F64MeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::String => { + let row = StringMeasurementRow { + timeseries_name, + timeseries_key, + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::Bytes => { + // See https://github.com/oxidecomputer/omicron/issues/4551. + // + // This is actually unreachable today because the constuctor + // for `oximeter::types::MissingDatum` fails when using a + // `DatumType::Bytes`. + unreachable!(); + } + DatumType::CumulativeI64 => { + let row = CumulativeI64MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::CumulativeU64 => { + let row = CumulativeU64MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::CumulativeF32 => { + let row = CumulativeF32MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::CumulativeF64 => { + let row = CumulativeF64MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: None, + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramI8 => { + let row = HistogramI8MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramU8 => { + let row = HistogramU8MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramI16 => { + let row = HistogramI16MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramU16 => { + let row = HistogramU16MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramI32 => { + let row = HistogramI32MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramU32 => { + let row = HistogramU32MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramI64 => { + let row = HistogramI64MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramU64 => { + let row = HistogramU64MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramF32 => { + let row = HistogramF32MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + DatumType::HistogramF64 => { + let row = HistogramF64MeasurementRow { + timeseries_name, + timeseries_key, + start_time: extract_start_time(measurement), + timestamp, + datum: DbHistogram::null(), + }; + (row.table_name(), serde_json::to_string(&row).unwrap()) + } + } + } } } @@ -984,7 +1303,7 @@ struct DbTimeseriesScalarGaugeSample { timeseries_key: TimeseriesKey, #[serde(with = "serde_timestamp")] timestamp: DateTime, - datum: T, + datum: Option, } // A scalar timestamped sample from a cumulative timeseries, as extracted from a query to the @@ -996,7 +1315,7 @@ struct DbTimeseriesScalarCumulativeSample { start_time: DateTime, #[serde(with = "serde_timestamp")] timestamp: DateTime, - datum: T, + datum: Option, } // A histogram timestamped sample from a timeseries, as extracted from a query to the database. @@ -1014,9 +1333,15 @@ struct DbTimeseriesHistogramSample { impl From> for Measurement where Datum: From, + T: FromDbScalar, { fn from(sample: DbTimeseriesScalarGaugeSample) -> Measurement { - let datum = Datum::from(sample.datum); + let datum = match sample.datum { + Some(datum) => Datum::from(datum), + None => { + Datum::Missing(MissingDatum::new(T::DATUM_TYPE, None).unwrap()) + } + }; Measurement::new(sample.timestamp, datum) } } @@ -1024,12 +1349,19 @@ where impl From> for Measurement where Datum: From>, - T: traits::Cumulative, + T: traits::Cumulative + FromDbCumulative, { fn from(sample: DbTimeseriesScalarCumulativeSample) -> Measurement { - let cumulative = - Cumulative::with_start_time(sample.start_time, sample.datum); - let datum = Datum::from(cumulative); + let datum = match sample.datum { + Some(datum) => Datum::from(Cumulative::with_start_time( + sample.start_time, + datum, + )), + None => Datum::Missing( + MissingDatum::new(T::DATUM_TYPE, Some(sample.start_time)) + .unwrap(), + ), + }; Measurement::new(sample.timestamp, datum) } } @@ -1037,26 +1369,157 @@ where impl From> for Measurement where Datum: From>, - T: traits::HistogramSupport, + T: traits::HistogramSupport + FromDbHistogram, { fn from(sample: DbTimeseriesHistogramSample) -> Measurement { - let datum = Datum::from( - Histogram::from_arrays( - sample.start_time, - sample.bins, - sample.counts, + let datum = if sample.bins.is_empty() { + assert!(sample.counts.is_empty()); + Datum::Missing( + MissingDatum::new(T::DATUM_TYPE, Some(sample.start_time)) + .unwrap(), ) - .unwrap(), - ); + } else { + Datum::from( + Histogram::from_arrays( + sample.start_time, + sample.bins, + sample.counts, + ) + .unwrap(), + ) + }; Measurement::new(sample.timestamp, datum) } } +// Helper trait providing the DatumType for a corresponding scalar DB value. +// +// This is used in `parse_timeseries_scalar_gauge_measurement`. +trait FromDbScalar { + const DATUM_TYPE: DatumType; +} + +impl FromDbScalar for DbBool { + const DATUM_TYPE: DatumType = DatumType::Bool; +} + +impl FromDbScalar for i8 { + const DATUM_TYPE: DatumType = DatumType::I8; +} + +impl FromDbScalar for u8 { + const DATUM_TYPE: DatumType = DatumType::U8; +} + +impl FromDbScalar for i16 { + const DATUM_TYPE: DatumType = DatumType::I16; +} + +impl FromDbScalar for u16 { + const DATUM_TYPE: DatumType = DatumType::U16; +} + +impl FromDbScalar for i32 { + const DATUM_TYPE: DatumType = DatumType::I32; +} + +impl FromDbScalar for u32 { + const DATUM_TYPE: DatumType = DatumType::U32; +} + +impl FromDbScalar for i64 { + const DATUM_TYPE: DatumType = DatumType::I64; +} + +impl FromDbScalar for u64 { + const DATUM_TYPE: DatumType = DatumType::U64; +} + +impl FromDbScalar for f32 { + const DATUM_TYPE: DatumType = DatumType::F32; +} + +impl FromDbScalar for f64 { + const DATUM_TYPE: DatumType = DatumType::F64; +} + +impl FromDbScalar for String { + const DATUM_TYPE: DatumType = DatumType::String; +} + +impl FromDbScalar for Bytes { + const DATUM_TYPE: DatumType = DatumType::Bytes; +} + +trait FromDbCumulative { + const DATUM_TYPE: DatumType; +} + +impl FromDbCumulative for i64 { + const DATUM_TYPE: DatumType = DatumType::CumulativeI64; +} + +impl FromDbCumulative for u64 { + const DATUM_TYPE: DatumType = DatumType::CumulativeU64; +} + +impl FromDbCumulative for f32 { + const DATUM_TYPE: DatumType = DatumType::CumulativeF32; +} + +impl FromDbCumulative for f64 { + const DATUM_TYPE: DatumType = DatumType::CumulativeF64; +} + +trait FromDbHistogram { + const DATUM_TYPE: DatumType; +} + +impl FromDbHistogram for i8 { + const DATUM_TYPE: DatumType = DatumType::HistogramI8; +} + +impl FromDbHistogram for u8 { + const DATUM_TYPE: DatumType = DatumType::HistogramU8; +} + +impl FromDbHistogram for i16 { + const DATUM_TYPE: DatumType = DatumType::HistogramI16; +} + +impl FromDbHistogram for u16 { + const DATUM_TYPE: DatumType = DatumType::HistogramU16; +} + +impl FromDbHistogram for i32 { + const DATUM_TYPE: DatumType = DatumType::HistogramI32; +} + +impl FromDbHistogram for u32 { + const DATUM_TYPE: DatumType = DatumType::HistogramU32; +} + +impl FromDbHistogram for i64 { + const DATUM_TYPE: DatumType = DatumType::HistogramI64; +} + +impl FromDbHistogram for u64 { + const DATUM_TYPE: DatumType = DatumType::HistogramU64; +} + +impl FromDbHistogram for f32 { + const DATUM_TYPE: DatumType = DatumType::HistogramF32; +} + +impl FromDbHistogram for f64 { + const DATUM_TYPE: DatumType = DatumType::HistogramF64; +} + fn parse_timeseries_scalar_gauge_measurement<'a, T>( line: &'a str, ) -> (TimeseriesKey, Measurement) where - T: Deserialize<'a> + Into, + T: Deserialize<'a> + Into + FromDbScalar, Datum: From, { let sample = @@ -1068,7 +1531,7 @@ fn parse_timeseries_scalar_cumulative_measurement<'a, T>( line: &'a str, ) -> (TimeseriesKey, Measurement) where - T: Deserialize<'a> + traits::Cumulative, + T: Deserialize<'a> + traits::Cumulative + FromDbCumulative, Datum: From>, { let sample = @@ -1081,7 +1544,7 @@ fn parse_timeseries_histogram_measurement( line: &str, ) -> (TimeseriesKey, Measurement) where - T: Into + traits::HistogramSupport, + T: Into + traits::HistogramSupport + FromDbHistogram, Datum: From>, { let sample = @@ -1459,6 +1922,27 @@ mod tests { } } + // Test that we correctly unroll a row when the measurement is missing its + // datum. + #[test] + fn test_unroll_missing_measurement_row() { + let sample = test_util::make_sample(); + let missing_sample = test_util::make_missing_sample(); + let (table_name, row) = unroll_measurement_row(&sample); + let (missing_table_name, missing_row) = + unroll_measurement_row(&missing_sample); + let row = serde_json::from_str::(&row).unwrap(); + let missing_row = + serde_json::from_str::(&missing_row).unwrap(); + println!("{row:#?}"); + println!("{missing_row:#?}"); + assert_eq!(table_name, missing_table_name); + assert_eq!(row.timeseries_name, missing_row.timeseries_name); + assert_eq!(row.timeseries_key, missing_row.timeseries_key); + assert!(row.datum.is_some()); + assert!(missing_row.datum.is_none()); + } + #[test] fn test_unroll_measurement_row() { let sample = test_util::make_hist_sample(); @@ -1473,14 +1957,13 @@ mod tests { ) .unwrap(); let measurement = &sample.measurement; - if let Datum::HistogramF64(hist) = measurement.datum() { - assert_eq!( - hist, &unpacked_hist, - "Unpacking histogram from database representation failed" - ); - } else { + let Datum::HistogramF64(hist) = measurement.datum() else { panic!("Expected a histogram measurement"); - } + }; + assert_eq!( + hist, &unpacked_hist, + "Unpacking histogram from database representation failed" + ); assert_eq!(unpacked.start_time, measurement.start_time().unwrap()); } @@ -1582,12 +2065,11 @@ mod tests { assert_eq!(key, 12); assert_eq!(measurement.start_time().unwrap(), start_time); assert_eq!(measurement.timestamp(), timestamp); - if let Datum::HistogramI64(hist) = measurement.datum() { - assert_eq!(hist.n_bins(), 3); - assert_eq!(hist.n_samples(), 2); - } else { + let Datum::HistogramI64(hist) = measurement.datum() else { panic!("Expected a histogram sample"); - } + }; + assert_eq!(hist.n_bins(), 3); + assert_eq!(hist.n_samples(), 2); } #[test] @@ -1624,4 +2106,14 @@ mod tests { "Histogram reconstructed from paired arrays is not correct" ); } + #[test] + fn test_parse_bytes_measurement() { + let s = r#"{"timeseries_key": 101, "timestamp": "2023-11-21 18:25:21.963714255", "datum": "\u0001\u0002\u0003"}"#; + let (_, meas) = parse_timeseries_scalar_gauge_measurement::(&s); + println!("{meas:?}"); + let Datum::Bytes(b) = meas.datum() else { + unreachable!(); + }; + assert_eq!(b.to_vec(), vec![1, 2, 3]); + } } diff --git a/oximeter/oximeter/Cargo.toml b/oximeter/oximeter/Cargo.toml index 8a69494d5a..0cb2d8cace 100644 --- a/oximeter/oximeter/Cargo.toml +++ b/oximeter/oximeter/Cargo.toml @@ -21,4 +21,5 @@ omicron-workspace-hack.workspace = true [dev-dependencies] approx.workspace = true rstest.workspace = true +serde_json.workspace = true trybuild.workspace = true diff --git a/oximeter/oximeter/src/histogram.rs b/oximeter/oximeter/src/histogram.rs index c399384ffa..aaf9297ca4 100644 --- a/oximeter/oximeter/src/histogram.rs +++ b/oximeter/oximeter/src/histogram.rs @@ -1353,13 +1353,10 @@ mod tests { } #[test] - fn test_foo() { - let bins: Vec = 10u16.bins(1, 3, 30.try_into().unwrap()).unwrap(); - println!("{bins:?}"); - dbg!(bins.len()); - let hist = Histogram::new(&bins).unwrap(); - for bin in hist.iter() { - println!("{}", bin.range); - } + fn test_empty_bins_not_supported() { + assert!(matches!( + Histogram::::new(&[]).unwrap_err(), + HistogramError::EmptyBins + )); } } diff --git a/oximeter/oximeter/src/test_util.rs b/oximeter/oximeter/src/test_util.rs index f3750d6d83..a9778d03bc 100644 --- a/oximeter/oximeter/src/test_util.rs +++ b/oximeter/oximeter/src/test_util.rs @@ -48,19 +48,27 @@ pub struct TestHistogram { pub datum: Histogram, } +const ID: Uuid = uuid::uuid!("e00ced4d-39d1-446a-ae85-a67f05c9750b"); + pub fn make_sample() -> Sample { let target = TestTarget::default(); - let metric = TestMetric { id: Uuid::new_v4(), good: true, datum: 1 }; + let metric = TestMetric { id: ID, good: true, datum: 1 }; Sample::new(&target, &metric).unwrap() } +pub fn make_missing_sample() -> Sample { + let target = TestTarget::default(); + let metric = TestMetric { id: ID, good: true, datum: 1 }; + Sample::new_missing(&target, &metric).unwrap() +} + pub fn make_hist_sample() -> Sample { let target = TestTarget::default(); let mut hist = histogram::Histogram::new(&[0.0, 5.0, 10.0]).unwrap(); hist.sample(1.0).unwrap(); hist.sample(2.0).unwrap(); hist.sample(6.0).unwrap(); - let metric = TestHistogram { id: Uuid::new_v4(), good: true, datum: hist }; + let metric = TestHistogram { id: ID, good: true, datum: hist }; Sample::new(&target, &metric).unwrap() } diff --git a/oximeter/oximeter/src/traits.rs b/oximeter/oximeter/src/traits.rs index 096abb8023..0934d231e3 100644 --- a/oximeter/oximeter/src/traits.rs +++ b/oximeter/oximeter/src/traits.rs @@ -30,8 +30,15 @@ use std::ops::AddAssign; /// definition can be thought of as a schema, and an instance of that struct as identifying an /// individual target. /// -/// Target fields may have one of a set of supported types: `bool`, `i64`, `String`, `IpAddr`, or -/// `Uuid`. Any number of fields greater than zero is supported. +/// Target fields may have one of a set of supported types: +/// +/// - `bool` +/// - any fixed-width integer, e.g., `u8` or `i64` +/// - `String` +/// - `IpAddr` +/// - `Uuid` +/// +/// Any number of fields greater than zero is supported. /// /// Examples /// -------- @@ -105,9 +112,28 @@ pub trait Target { /// One field of the struct is special, describing the actual measured data that the metric /// represents. This should be a field named `datum`, or another field (with any name you choose) /// annotated with the `#[datum]` attribute. This field represents the underlying data for the -/// metric, and must be one of the supported types, implementing the [`Datum`] trait. This can -/// be any of: `i64`, `f64`, `bool`, `String`, or `Bytes` for gauges, and `Cumulative` or -/// `Histogram` for cumulative metrics, where `T` is `i64` or `f64`. +/// metric, and must be one of the supported types, implementing the [`Datum`] trait. +/// +/// For gauge types, this can be any of: +/// +/// - `bool` +/// - a fixed-width integer, e.g. `u8` or `i64` +/// - `f32` or `f64` +/// - `String` +/// - `Bytes` +/// +/// Cumulative types can be any of `Cumulative`, where `T` is +/// +/// - `i64` +/// - `u64` +/// - `f32` +/// - `f64` +/// +/// Histogram types can be any `Histogram`, wher `T` is: +/// +/// - a fixed-width integer, e.g. `u8` or `i64` +/// - `f32` +/// - `f64` /// /// The value of the metric's data is _measured_ by using the `measure()` method, which returns a /// [`Measurement`]. This describes a timestamped data point for the metric. diff --git a/oximeter/oximeter/src/types.rs b/oximeter/oximeter/src/types.rs index 325974781e..23dbe2be6b 100644 --- a/oximeter/oximeter/src/types.rs +++ b/oximeter/oximeter/src/types.rs @@ -369,6 +369,7 @@ pub enum Datum { HistogramU64(histogram::Histogram), HistogramF32(histogram::Histogram), HistogramF64(histogram::Histogram), + Missing(MissingDatum), } impl Datum { @@ -402,6 +403,7 @@ impl Datum { Datum::HistogramU64(_) => DatumType::HistogramU64, Datum::HistogramF32(_) => DatumType::HistogramF32, Datum::HistogramF64(_) => DatumType::HistogramF64, + Datum::Missing(ref inner) => inner.datum_type(), } } @@ -440,6 +442,7 @@ impl Datum { Datum::HistogramU64(ref inner) => Some(inner.start_time()), Datum::HistogramF32(ref inner) => Some(inner.start_time()), Datum::HistogramF64(ref inner) => Some(inner.start_time()), + Datum::Missing(ref inner) => inner.start_time(), } } } @@ -495,6 +498,60 @@ impl From<&str> for Datum { } } +#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] +pub struct MissingDatum { + datum_type: DatumType, + start_time: Option>, +} + +impl MissingDatum { + pub fn datum_type(&self) -> DatumType { + self.datum_type + } + + pub fn start_time(&self) -> Option> { + self.start_time + } + + pub fn new( + datum_type: DatumType, + start_time: Option>, + ) -> Result { + // See https://github.com/oxidecomputer/omicron/issues/4551. + if datum_type == DatumType::Bytes { + return Err(MetricsError::DatumError(String::from( + "Missing samples from byte array types are not supported", + ))); + } + if datum_type.is_cumulative() && start_time.is_none() { + return Err(MetricsError::MissingDatumRequiresStartTime { + datum_type, + }); + } + if !datum_type.is_cumulative() && start_time.is_some() { + return Err(MetricsError::MissingDatumCannotHaveStartTime { + datum_type, + }); + } + Ok(Self { datum_type, start_time }) + } +} + +impl From for Datum { + fn from(d: MissingDatum) -> Datum { + Datum::Missing(d) + } +} + +impl From<&M> for MissingDatum { + fn from(metric: &M) -> Self { + MissingDatum { + datum_type: metric.datum_type(), + start_time: metric.start_time(), + } + } +} + /// A `Measurement` is a timestamped datum from a single metric #[derive(Clone, Debug, PartialEq, JsonSchema, Serialize, Deserialize)] pub struct Measurement { @@ -516,6 +573,11 @@ impl Measurement { Self { timestamp, datum: datum.into() } } + /// Return true if this measurement represents a missing datum. + pub fn is_missing(&self) -> bool { + matches!(self.datum, Datum::Missing(_)) + } + /// Return the datum for this measurement pub fn datum(&self) -> &Datum { &self.datum @@ -561,6 +623,12 @@ pub enum MetricsError { /// A field name is duplicated between the target and metric. #[error("Field '{name}' is duplicated between the target and metric")] DuplicateFieldName { name: String }, + + #[error("Missing datum of type {datum_type} requires a start time")] + MissingDatumRequiresStartTime { datum_type: DatumType }, + + #[error("Missing datum of type {datum_type} cannot have a start time")] + MissingDatumCannotHaveStartTime { datum_type: DatumType }, } impl From for omicron_common::api::external::Error { @@ -734,6 +802,29 @@ impl Sample { }) } + /// Construct a new missing sample, recorded at the time of the supplied + /// timestamp. + pub fn new_missing_with_timestamp( + timestamp: DateTime, + target: &T, + metric: &M, + ) -> Result + where + T: traits::Target, + M: traits::Metric, + { + let target_fields = FieldSet::from_target(target); + let metric_fields = FieldSet::from_metric(metric); + Self::verify_field_names(&target_fields, &metric_fields)?; + let datum = Datum::Missing(MissingDatum::from(metric)); + Ok(Self { + timeseries_name: crate::timeseries_name(target, metric), + target: target_fields, + metric: metric_fields, + measurement: Measurement { timestamp, datum }, + }) + } + /// Construct a new sample, created at the time the function is called. /// /// This materializes the data from the target and metric, and stores that information along @@ -746,6 +837,18 @@ impl Sample { Self::new_with_timestamp(Utc::now(), target, metric) } + /// Construct a new sample with a missing measurement. + pub fn new_missing( + target: &T, + metric: &M, + ) -> Result + where + T: traits::Target, + M: traits::Metric, + { + Self::new_missing_with_timestamp(Utc::now(), target, metric) + } + /// Return the fields for this sample. /// /// This returns the target fields and metric fields, chained, although there is no distinction @@ -951,7 +1054,7 @@ mod tests { fn test_measurement() { let measurement = Measurement::new(chrono::Utc::now(), 0i64); assert_eq!(measurement.datum_type(), DatumType::I64); - assert_eq!(measurement.start_time(), None); + assert!(measurement.start_time().is_none()); let datum = Cumulative::new(0i64); let measurement = Measurement::new(chrono::Utc::now(), datum); diff --git a/oximeter/producer/examples/producer.rs b/oximeter/producer/examples/producer.rs index baa4f57bf7..8dbe0b6ad9 100644 --- a/oximeter/producer/examples/producer.rs +++ b/oximeter/producer/examples/producer.rs @@ -125,7 +125,7 @@ async fn main() -> anyhow::Result<()> { registry.register_producer(producer).unwrap(); let server_info = ProducerEndpoint { id: registry.producer_id(), - kind: Some(ProducerKind::Service), + kind: ProducerKind::Service, address: args.address, base_route: "/collect".to_string(), interval: Duration::from_secs(10), diff --git a/package-manifest.toml b/package-manifest.toml index 26c45f0ff7..3bce4aafee 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -384,10 +384,10 @@ only_for_targets.image = "standard" # 3. Use source.type = "manual" instead of "prebuilt" source.type = "prebuilt" source.repo = "crucible" -source.commit = "51a3121c8318fc7ac97d74f917ce1d37962e785f" +source.commit = "945f040d259ca8013d3fb26f510453da7cd7b1a6" # The SHA256 digest is automatically posted to: # https://buildomat.eng.oxide.computer/public/file/oxidecomputer/crucible/image//crucible.sha256.txt -source.sha256 = "897d0fd6c0b82db42256a63a13c228152e1117434afa2681f649b291e3c6f46d" +source.sha256 = "f8c23cbf89fd0bbd928d8e3db1357bbea6e6b50560e221f873da5b56ed9d7527" output.type = "zone" [package.crucible-pantry] @@ -395,10 +395,10 @@ service_name = "crucible_pantry" only_for_targets.image = "standard" source.type = "prebuilt" source.repo = "crucible" -source.commit = "51a3121c8318fc7ac97d74f917ce1d37962e785f" +source.commit = "945f040d259ca8013d3fb26f510453da7cd7b1a6" # The SHA256 digest is automatically posted to: # https://buildomat.eng.oxide.computer/public/file/oxidecomputer/crucible/image//crucible-pantry.sha256.txt -source.sha256 = "fe545de7ac4f15454d7827927149c5f0fc68ce9545b4f1ef96aac9ac8039805a" +source.sha256 = "a25b31c81798eb65564dbe259858fdd9715784d212d3508791b1ef0cf6d17da6" output.type = "zone" # Refer to @@ -409,10 +409,10 @@ service_name = "propolis-server" only_for_targets.image = "standard" source.type = "prebuilt" source.repo = "propolis" -source.commit = "54398875a2125227d13827d4236dce943c019b1c" +source.commit = "3e1d129151c3621d28ead5c6e5760693ba6e7fec" # The SHA256 digest is automatically posted to: # https://buildomat.eng.oxide.computer/public/file/oxidecomputer/propolis/image//propolis-server.sha256.txt -source.sha256 = "01b8563db6626f90ee3fb6d97e7921b0a680373d843c1bea7ebf46fcea4f7b28" +source.sha256 = "cd341409eb2ffc3d8bec89fd20cad61d170f89d3adf926f6104eb01f4f4da881" output.type = "zone" [package.mg-ddm-gz] @@ -476,8 +476,8 @@ only_for_targets.image = "standard" # 2. Copy dendrite.tar.gz from dendrite/out to omicron/out source.type = "prebuilt" source.repo = "dendrite" -source.commit = "8ff834e7d0a6adb263240edd40537f2c0768f1a4" -source.sha256 = "c00e79f55e0bdf048069b2d18a4d009ddfef46e7e5d846887cf96e843a8884bd" +source.commit = "2af6adea85c62ac37e451148b84e5eb0ef005f36" +source.sha256 = "dc93b671cce54e83ed55faaa267f81ba9e65abcd6714aa559d68a8783d73b1c1" output.type = "zone" output.intermediate_only = true @@ -501,8 +501,8 @@ only_for_targets.image = "standard" # 2. Copy the output zone image from dendrite/out to omicron/out source.type = "prebuilt" source.repo = "dendrite" -source.commit = "8ff834e7d0a6adb263240edd40537f2c0768f1a4" -source.sha256 = "428cce1e9aa399b1b49c04e7fd0bc1cb0e3f3fae6fda96055892a42e010c9d6f" +source.commit = "2af6adea85c62ac37e451148b84e5eb0ef005f36" +source.sha256 = "c34b10d47fa3eb9f9f6b3655ea4ed8a726f93399ea177efea79f5c89f2ab5a1e" output.type = "zone" output.intermediate_only = true @@ -519,8 +519,8 @@ only_for_targets.image = "standard" # 2. Copy dendrite.tar.gz from dendrite/out to omicron/out/dendrite-softnpu.tar.gz source.type = "prebuilt" source.repo = "dendrite" -source.commit = "8ff834e7d0a6adb263240edd40537f2c0768f1a4" -source.sha256 = "5dd3534bec5eb4f857d0bf3994b26650288f650d409eec6aaa29860a2f481c37" +source.commit = "2af6adea85c62ac37e451148b84e5eb0ef005f36" +source.sha256 = "ce7065227c092ee82704f39a966b7441e3ae82d75eedb6eb281bd8b3e5873e32" output.type = "zone" output.intermediate_only = true diff --git a/schema/all-zone-requests.json b/schema/all-zone-requests.json index 468f00ee0c..4eb56d379d 100644 --- a/schema/all-zone-requests.json +++ b/schema/all-zone-requests.json @@ -1,6 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "AllZoneRequests", + "description": "A wrapper around `ZoneRequest` that allows it to be serialized to a JSON file.", "type": "object", "required": [ "generation", @@ -8,7 +9,12 @@ ], "properties": { "generation": { - "$ref": "#/definitions/Generation" + "description": "ledger generation (not an Omicron-provided generation)", + "allOf": [ + { + "$ref": "#/definitions/Generation" + } + ] }, "requests": { "type": "array", @@ -719,6 +725,7 @@ "minimum": 0.0 }, "ZoneRequest": { + "description": "This struct represents the combo of \"what zone did you ask for\" + \"where did we put it\".", "type": "object", "required": [ "root", @@ -734,7 +741,7 @@ } }, "ZoneType": { - "description": "The type of zone which may be requested from Sled Agent", + "description": "The type of zone that Sled Agent may run", "type": "string", "enum": [ "clickhouse", diff --git a/schema/all-zones-requests.json b/schema/all-zones-requests.json new file mode 100644 index 0000000000..0e43e9ee21 --- /dev/null +++ b/schema/all-zones-requests.json @@ -0,0 +1,632 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OmicronZonesConfigLocal", + "description": "Combines the Nexus-provided `OmicronZonesConfig` (which describes what Nexus wants for all of its zones) with the locally-determined configuration for these zones.", + "type": "object", + "required": [ + "ledger_generation", + "omicron_generation", + "zones" + ], + "properties": { + "ledger_generation": { + "description": "ledger-managed generation number\n\nThis generation is managed by the ledger facility itself. It's bumped whenever we write a new ledger. In practice, we don't currently have any reason to bump this _for a given Omicron generation_ so it's somewhat redundant. In principle, if we needed to modify the ledgered configuration due to some event that doesn't change the Omicron config (e.g., if we wanted to move the root filesystem to a different path), we could do that by bumping this generation.", + "allOf": [ + { + "$ref": "#/definitions/Generation" + } + ] + }, + "omicron_generation": { + "description": "generation of the Omicron-provided part of the configuration\n\nThis generation number is outside of Sled Agent's control. We store exactly what we were given and use this number to decide when to fail requests to establish an outdated configuration.\n\nYou can think of this as a major version number, with `ledger_generation` being a minor version number. See `is_newer_than()`.", + "allOf": [ + { + "$ref": "#/definitions/Generation" + } + ] + }, + "zones": { + "type": "array", + "items": { + "$ref": "#/definitions/OmicronZoneConfigLocal" + } + } + }, + "definitions": { + "Generation": { + "description": "Generation numbers stored in the database, used for optimistic concurrency control", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "IpNet": { + "oneOf": [ + { + "title": "v4", + "allOf": [ + { + "$ref": "#/definitions/Ipv4Net" + } + ] + }, + { + "title": "v6", + "allOf": [ + { + "$ref": "#/definitions/Ipv6Net" + } + ] + } + ] + }, + "Ipv4Net": { + "title": "An IPv4 subnet", + "description": "An IPv4 subnet, including prefix and subnet mask", + "examples": [ + "192.168.1.0/24" + ], + "type": "string", + "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" + }, + "Ipv6Net": { + "title": "An IPv6 subnet", + "description": "An IPv6 subnet, including prefix and subnet mask", + "examples": [ + "fd12:3456::/64" + ], + "type": "string", + "pattern": "^([fF][dD])[0-9a-fA-F]{2}:(([0-9a-fA-F]{1,4}:){6}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,6}:)([0-9a-fA-F]{1,4})?\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" + }, + "MacAddr": { + "title": "A MAC address", + "description": "A Media Access Control address, in EUI-48 format", + "examples": [ + "ff:ff:ff:ff:ff:ff" + ], + "type": "string", + "maxLength": 17, + "minLength": 5, + "pattern": "^([0-9a-fA-F]{0,2}:){5}[0-9a-fA-F]{0,2}$" + }, + "Name": { + "title": "A name unique within the parent collection", + "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.", + "type": "string", + "maxLength": 63, + "minLength": 1, + "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9-]*[a-zA-Z0-9]*$" + }, + "NetworkInterface": { + "description": "Information required to construct a virtual network interface", + "type": "object", + "required": [ + "id", + "ip", + "kind", + "mac", + "name", + "primary", + "slot", + "subnet", + "vni" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "ip": { + "type": "string", + "format": "ip" + }, + "kind": { + "$ref": "#/definitions/NetworkInterfaceKind" + }, + "mac": { + "$ref": "#/definitions/MacAddr" + }, + "name": { + "$ref": "#/definitions/Name" + }, + "primary": { + "type": "boolean" + }, + "slot": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "subnet": { + "$ref": "#/definitions/IpNet" + }, + "vni": { + "$ref": "#/definitions/Vni" + } + } + }, + "NetworkInterfaceKind": { + "description": "The type of network interface", + "oneOf": [ + { + "description": "A vNIC attached to a guest instance", + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "instance" + ] + } + } + }, + { + "description": "A vNIC associated with an internal service", + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "type": { + "type": "string", + "enum": [ + "service" + ] + } + } + } + ] + }, + "OmicronZoneConfig": { + "description": "Describes one Omicron-managed zone running on a sled", + "type": "object", + "required": [ + "id", + "underlay_address", + "zone_type" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "underlay_address": { + "type": "string", + "format": "ipv6" + }, + "zone_type": { + "$ref": "#/definitions/OmicronZoneType" + } + } + }, + "OmicronZoneConfigLocal": { + "description": "Combines the Nexus-provided `OmicronZoneConfig` (which describes what Nexus wants for this zone) with any locally-determined configuration (like the path to the root filesystem)", + "type": "object", + "required": [ + "root", + "zone" + ], + "properties": { + "root": { + "type": "string" + }, + "zone": { + "$ref": "#/definitions/OmicronZoneConfig" + } + } + }, + "OmicronZoneDataset": { + "description": "Describes a persistent ZFS dataset associated with an Omicron zone", + "type": "object", + "required": [ + "pool_name" + ], + "properties": { + "pool_name": { + "$ref": "#/definitions/ZpoolName" + } + } + }, + "OmicronZoneType": { + "description": "Describes what kind of zone this is (i.e., what component is running in it) as well as any type-specific configuration", + "oneOf": [ + { + "type": "object", + "required": [ + "address", + "dns_servers", + "nic", + "ntp_servers", + "snat_cfg", + "type" + ], + "properties": { + "address": { + "type": "string" + }, + "dns_servers": { + "type": "array", + "items": { + "type": "string", + "format": "ip" + } + }, + "domain": { + "type": [ + "string", + "null" + ] + }, + "nic": { + "description": "The service vNIC providing outbound connectivity using OPTE.", + "allOf": [ + { + "$ref": "#/definitions/NetworkInterface" + } + ] + }, + "ntp_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "snat_cfg": { + "description": "The SNAT configuration for outbound connections.", + "allOf": [ + { + "$ref": "#/definitions/SourceNatConfig" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "boundary_ntp" + ] + } + } + }, + { + "type": "object", + "required": [ + "address", + "dataset", + "type" + ], + "properties": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" + }, + "type": { + "type": "string", + "enum": [ + "clickhouse" + ] + } + } + }, + { + "type": "object", + "required": [ + "address", + "dataset", + "type" + ], + "properties": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" + }, + "type": { + "type": "string", + "enum": [ + "clickhouse_keeper" + ] + } + } + }, + { + "type": "object", + "required": [ + "address", + "dataset", + "type" + ], + "properties": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" + }, + "type": { + "type": "string", + "enum": [ + "cockroach_db" + ] + } + } + }, + { + "type": "object", + "required": [ + "address", + "dataset", + "type" + ], + "properties": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" + }, + "type": { + "type": "string", + "enum": [ + "crucible" + ] + } + } + }, + { + "type": "object", + "required": [ + "address", + "type" + ], + "properties": { + "address": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "crucible_pantry" + ] + } + } + }, + { + "type": "object", + "required": [ + "dataset", + "dns_address", + "http_address", + "nic", + "type" + ], + "properties": { + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" + }, + "dns_address": { + "description": "The address at which the external DNS server is reachable.", + "type": "string" + }, + "http_address": { + "description": "The address at which the external DNS server API is reachable.", + "type": "string" + }, + "nic": { + "description": "The service vNIC providing external connectivity using OPTE.", + "allOf": [ + { + "$ref": "#/definitions/NetworkInterface" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "external_dns" + ] + } + } + }, + { + "type": "object", + "required": [ + "dataset", + "dns_address", + "gz_address", + "gz_address_index", + "http_address", + "type" + ], + "properties": { + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" + }, + "dns_address": { + "type": "string" + }, + "gz_address": { + "description": "The addresses in the global zone which should be created\n\nFor the DNS service, which exists outside the sleds's typical subnet - adding an address in the GZ is necessary to allow inter-zone traffic routing.", + "type": "string", + "format": "ipv6" + }, + "gz_address_index": { + "description": "The address is also identified with an auxiliary bit of information to ensure that the created global zone address can have a unique name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "http_address": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "internal_dns" + ] + } + } + }, + { + "type": "object", + "required": [ + "address", + "dns_servers", + "ntp_servers", + "type" + ], + "properties": { + "address": { + "type": "string" + }, + "dns_servers": { + "type": "array", + "items": { + "type": "string", + "format": "ip" + } + }, + "domain": { + "type": [ + "string", + "null" + ] + }, + "ntp_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "internal_ntp" + ] + } + } + }, + { + "type": "object", + "required": [ + "external_dns_servers", + "external_ip", + "external_tls", + "internal_address", + "nic", + "type" + ], + "properties": { + "external_dns_servers": { + "description": "External DNS servers Nexus can use to resolve external hosts.", + "type": "array", + "items": { + "type": "string", + "format": "ip" + } + }, + "external_ip": { + "description": "The address at which the external nexus server is reachable.", + "type": "string", + "format": "ip" + }, + "external_tls": { + "description": "Whether Nexus's external endpoint should use TLS", + "type": "boolean" + }, + "internal_address": { + "description": "The address at which the internal nexus server is reachable.", + "type": "string" + }, + "nic": { + "description": "The service vNIC providing external connectivity using OPTE.", + "allOf": [ + { + "$ref": "#/definitions/NetworkInterface" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "nexus" + ] + } + } + }, + { + "type": "object", + "required": [ + "address", + "type" + ], + "properties": { + "address": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "oximeter" + ] + } + } + } + ] + }, + "SourceNatConfig": { + "description": "An IP address and port range used for source NAT, i.e., making outbound network connections from guests or services.", + "type": "object", + "required": [ + "first_port", + "ip", + "last_port" + ], + "properties": { + "first_port": { + "description": "The first port used for source NAT, inclusive.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "ip": { + "description": "The external address provided to the instance or service.", + "type": "string", + "format": "ip" + }, + "last_port": { + "description": "The last port used for source NAT, also inclusive.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + } + }, + "Vni": { + "description": "A Geneve Virtual Network Identifier", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "ZpoolName": { + "title": "The name of a Zpool", + "description": "Zpool names are of the format ox{i,p}_. They are either Internal or External, and should be unique", + "type": "string", + "pattern": "^ox[ip]_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + } + } +} \ No newline at end of file diff --git a/schema/crdb/15.0.0/up1.sql b/schema/crdb/15.0.0/up1.sql new file mode 100644 index 0000000000..04baa76370 --- /dev/null +++ b/schema/crdb/15.0.0/up1.sql @@ -0,0 +1,6 @@ +CREATE TYPE IF NOT EXISTS omicron.public.sled_provision_state AS ENUM ( + -- New resources can be provisioned onto the sled + 'provisionable', + -- New resources must not be provisioned onto the sled + 'non_provisionable' +); diff --git a/schema/crdb/15.0.0/up2.sql b/schema/crdb/15.0.0/up2.sql new file mode 100644 index 0000000000..e3ea2ba11c --- /dev/null +++ b/schema/crdb/15.0.0/up2.sql @@ -0,0 +1,3 @@ +ALTER TABLE omicron.public.sled + ADD COLUMN IF NOT EXISTS provision_state omicron.public.sled_provision_state + NOT NULL DEFAULT 'provisionable'; diff --git a/schema/crdb/15.0.0/up3.sql b/schema/crdb/15.0.0/up3.sql new file mode 100644 index 0000000000..aaa3feac20 --- /dev/null +++ b/schema/crdb/15.0.0/up3.sql @@ -0,0 +1,5 @@ +-- Drop the default column value for provision_state -- it should always be set +-- by Nexus. +ALTER TABLE omicron.public.sled + ALTER COLUMN provision_state + DROP DEFAULT; diff --git a/schema/crdb/16.0.0/up01.sql b/schema/crdb/16.0.0/up01.sql new file mode 100644 index 0000000000..f9806c5917 --- /dev/null +++ b/schema/crdb/16.0.0/up01.sql @@ -0,0 +1,14 @@ +/* + * Previous commits added the optional kind of a producer. In this version, + * we're making the value required and not nullable. We'll first delete all + * records with a NULL kind -- there should not be any, since all producers both + * in an out of tree have been updated. Nonetheless, this is safe because + * currently we're updating offline, and all producers should re-register when + * they are restarted. + * + * NOTE: Full table scans are disallowed, however we don't have an index on + * producer kind (and don't currently need one). Allow full table scans for the + * context of this one statement. + */ +SET LOCAL disallow_full_table_scans = off; +DELETE FROM omicron.public.metric_producer WHERE kind IS NULL; diff --git a/schema/crdb/16.0.0/up02.sql b/schema/crdb/16.0.0/up02.sql new file mode 100644 index 0000000000..9c1ad2ea47 --- /dev/null +++ b/schema/crdb/16.0.0/up02.sql @@ -0,0 +1,4 @@ +/* + * Next, we make the field itself required in the database. + */ +ALTER TABLE IF EXISTS omicron.public.metric_producer ALTER COLUMN kind SET NOT NULL; diff --git a/schema/crdb/17.0.0/up1.sql b/schema/crdb/17.0.0/up1.sql new file mode 100644 index 0000000000..d28d5ca4b5 --- /dev/null +++ b/schema/crdb/17.0.0/up1.sql @@ -0,0 +1 @@ +ALTER TABLE omicron.public.switch_port_settings_link_config ADD COLUMN IF NOT EXISTS autoneg BOOL NOT NULL DEFAULT false; diff --git a/schema/crdb/18.0.0/up01.sql b/schema/crdb/18.0.0/up01.sql new file mode 100644 index 0000000000..018bb36dcb --- /dev/null +++ b/schema/crdb/18.0.0/up01.sql @@ -0,0 +1,4 @@ +CREATE UNIQUE INDEX IF NOT EXISTS lookup_deleted_disk ON omicron.public.disk ( + id +) WHERE + time_deleted IS NOT NULL; diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index 728b084982..f82829a2d9 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -73,6 +73,13 @@ CREATE TABLE IF NOT EXISTS omicron.public.rack ( * Sleds */ +CREATE TYPE IF NOT EXISTS omicron.public.sled_provision_state AS ENUM ( + -- New resources can be provisioned onto the sled + 'provisionable', + -- New resources must not be provisioned onto the sled + 'non_provisionable' +); + CREATE TABLE IF NOT EXISTS omicron.public.sled ( /* Identity metadata (asset) */ id UUID PRIMARY KEY, @@ -104,6 +111,9 @@ CREATE TABLE IF NOT EXISTS omicron.public.sled ( /* The last address allocated to an Oxide service on this sled. */ last_used_address INET NOT NULL, + /* The state of whether resources should be provisioned onto the sled */ + provision_state omicron.public.sled_provision_state NOT NULL, + -- This constraint should be upheld, even for deleted disks -- in the fleet. CONSTRAINT serial_part_revision_unique UNIQUE ( @@ -1016,6 +1026,11 @@ CREATE UNIQUE INDEX IF NOT EXISTS lookup_disk_by_instance ON omicron.public.disk ) WHERE time_deleted IS NULL AND attach_instance_id IS NOT NULL; +CREATE UNIQUE INDEX IF NOT EXISTS lookup_deleted_disk ON omicron.public.disk ( + id +) WHERE + time_deleted IS NOT NULL; + CREATE TABLE IF NOT EXISTS omicron.public.image ( /* Identity metadata (resource) */ id UUID PRIMARY KEY, @@ -1172,7 +1187,7 @@ CREATE TABLE IF NOT EXISTS omicron.public.metric_producer ( id UUID PRIMARY KEY, time_created TIMESTAMPTZ NOT NULL, time_modified TIMESTAMPTZ NOT NULL, - kind omicron.public.producer_kind, + kind omicron.public.producer_kind NOT NULL, ip INET NOT NULL, port INT4 CHECK (port BETWEEN 0 AND 65535) NOT NULL, interval FLOAT NOT NULL, @@ -2990,6 +3005,8 @@ CREATE TABLE IF NOT EXISTS omicron.public.db_metadata ( CHECK (singleton = true) ); +ALTER TABLE omicron.public.switch_port_settings_link_config ADD COLUMN IF NOT EXISTS autoneg BOOL NOT NULL DEFAULT false; + INSERT INTO omicron.public.db_metadata ( singleton, time_created, @@ -2997,7 +3014,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - ( TRUE, NOW(), NOW(), '14.0.0', NULL) + ( TRUE, NOW(), NOW(), '18.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; diff --git a/schema/rss-service-plan.json b/schema/rss-service-plan-v2.json similarity index 80% rename from schema/rss-service-plan.json rename to schema/rss-service-plan-v2.json index 725caf0900..0bcd27b9cc 100644 --- a/schema/rss-service-plan.json +++ b/schema/rss-service-plan-v2.json @@ -13,136 +13,11 @@ "services": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/SledRequest" + "$ref": "#/definitions/SledConfig" } } }, "definitions": { - "DatasetKind": { - "description": "The type of a dataset, and an auxiliary information necessary to successfully launch a zone managing the associated data.", - "oneOf": [ - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "cockroach_db" - ] - } - } - }, - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "crucible" - ] - } - } - }, - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "clickhouse" - ] - } - } - }, - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "clickhouse_keeper" - ] - } - } - }, - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "external_dns" - ] - } - } - }, - { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "internal_dns" - ] - } - } - } - ] - }, - "DatasetName": { - "type": "object", - "required": [ - "kind", - "pool_name" - ], - "properties": { - "kind": { - "$ref": "#/definitions/DatasetKind" - }, - "pool_name": { - "$ref": "#/definitions/ZpoolName" - } - } - }, - "DatasetRequest": { - "description": "Describes a request to provision a specific dataset", - "type": "object", - "required": [ - "id", - "name", - "service_address" - ], - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "name": { - "$ref": "#/definitions/DatasetName" - }, - "service_address": { - "type": "string" - } - } - }, "DnsConfigParams": { "type": "object", "required": [ @@ -399,53 +274,96 @@ } ] }, - "ServiceType": { - "description": "Describes service-specific parameters.", + "OmicronZoneConfig": { + "description": "Describes one Omicron-managed zone running on a sled", + "type": "object", + "required": [ + "id", + "underlay_address", + "zone_type" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "underlay_address": { + "type": "string", + "format": "ipv6" + }, + "zone_type": { + "$ref": "#/definitions/OmicronZoneType" + } + } + }, + "OmicronZoneDataset": { + "description": "Describes a persistent ZFS dataset associated with an Omicron zone", + "type": "object", + "required": [ + "pool_name" + ], + "properties": { + "pool_name": { + "$ref": "#/definitions/ZpoolName" + } + } + }, + "OmicronZoneType": { + "description": "Describes what kind of zone this is (i.e., what component is running in it) as well as any type-specific configuration", "oneOf": [ { "type": "object", "required": [ - "external_dns_servers", - "external_ip", - "external_tls", - "internal_address", + "address", + "dns_servers", "nic", + "ntp_servers", + "snat_cfg", "type" ], "properties": { - "external_dns_servers": { - "description": "External DNS servers Nexus can use to resolve external hosts.", + "address": { + "type": "string" + }, + "dns_servers": { "type": "array", "items": { "type": "string", "format": "ip" } }, - "external_ip": { - "description": "The address at which the external nexus server is reachable.", - "type": "string", - "format": "ip" - }, - "external_tls": { - "description": "Whether Nexus's external endpoint should use TLS", - "type": "boolean" - }, - "internal_address": { - "description": "The address at which the internal nexus server is reachable.", - "type": "string" + "domain": { + "type": [ + "string", + "null" + ] }, "nic": { - "description": "The service vNIC providing external connectivity using OPTE.", + "description": "The service vNIC providing outbound connectivity using OPTE.", "allOf": [ { "$ref": "#/definitions/NetworkInterface" } ] }, + "ntp_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "snat_cfg": { + "description": "The SNAT configuration for outbound connections.", + "allOf": [ + { + "$ref": "#/definitions/SourceNatConfig" + } + ] + }, "type": { "type": "string", "enum": [ - "nexus" + "boundary_ntp" ] } } @@ -453,32 +371,21 @@ { "type": "object", "required": [ - "dns_address", - "http_address", - "nic", + "address", + "dataset", "type" ], "properties": { - "dns_address": { - "description": "The address at which the external DNS server is reachable.", + "address": { "type": "string" }, - "http_address": { - "description": "The address at which the external DNS server API is reachable.", - "type": "string" - }, - "nic": { - "description": "The service vNIC providing external connectivity using OPTE.", - "allOf": [ - { - "$ref": "#/definitions/NetworkInterface" - } - ] + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" }, "type": { "type": "string", "enum": [ - "external_dns" + "clickhouse" ] } } @@ -486,34 +393,43 @@ { "type": "object", "required": [ - "dns_address", - "gz_address", - "gz_address_index", - "http_address", + "address", + "dataset", "type" ], "properties": { - "dns_address": { + "address": { "type": "string" }, - "gz_address": { - "description": "The addresses in the global zone which should be created\n\nFor the DNS service, which exists outside the sleds's typical subnet - adding an address in the GZ is necessary to allow inter-zone traffic routing.", - "type": "string", - "format": "ipv6" - }, - "gz_address_index": { - "description": "The address is also identified with an auxiliary bit of information to ensure that the created global zone address can have a unique name.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" }, - "http_address": { + "type": { + "type": "string", + "enum": [ + "clickhouse_keeper" + ] + } + } + }, + { + "type": "object", + "required": [ + "address", + "dataset", + "type" + ], + "properties": { + "address": { "type": "string" }, + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" + }, "type": { "type": "string", "enum": [ - "internal_dns" + "cockroach_db" ] } } @@ -522,16 +438,20 @@ "type": "object", "required": [ "address", + "dataset", "type" ], "properties": { "address": { "type": "string" }, + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" + }, "type": { "type": "string", "enum": [ - "oximeter" + "crucible" ] } } @@ -557,56 +477,75 @@ { "type": "object", "required": [ - "address", - "dns_servers", + "dataset", + "dns_address", + "http_address", "nic", - "ntp_servers", - "snat_cfg", "type" ], "properties": { - "address": { - "type": "string" + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" }, - "dns_servers": { - "type": "array", - "items": { - "type": "string", - "format": "ip" - } + "dns_address": { + "description": "The address at which the external DNS server is reachable.", + "type": "string" }, - "domain": { - "type": [ - "string", - "null" - ] + "http_address": { + "description": "The address at which the external DNS server API is reachable.", + "type": "string" }, "nic": { - "description": "The service vNIC providing outbound connectivity using OPTE.", + "description": "The service vNIC providing external connectivity using OPTE.", "allOf": [ { "$ref": "#/definitions/NetworkInterface" } ] }, - "ntp_servers": { - "type": "array", - "items": { - "type": "string" - } - }, - "snat_cfg": { - "description": "The SNAT configuration for outbound connections.", - "allOf": [ - { - "$ref": "#/definitions/SourceNatConfig" - } + "type": { + "type": "string", + "enum": [ + "external_dns" ] + } + } + }, + { + "type": "object", + "required": [ + "dataset", + "dns_address", + "gz_address", + "gz_address_index", + "http_address", + "type" + ], + "properties": { + "dataset": { + "$ref": "#/definitions/OmicronZoneDataset" + }, + "dns_address": { + "type": "string" + }, + "gz_address": { + "description": "The addresses in the global zone which should be created\n\nFor the DNS service, which exists outside the sleds's typical subnet - adding an address in the GZ is necessary to allow inter-zone traffic routing.", + "type": "string", + "format": "ipv6" + }, + "gz_address_index": { + "description": "The address is also identified with an auxiliary bit of information to ensure that the created global zone address can have a unique name.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "http_address": { + "type": "string" }, "type": { "type": "string", "enum": [ - "boundary_ntp" + "internal_dns" ] } } @@ -653,53 +592,47 @@ { "type": "object", "required": [ - "address", + "external_dns_servers", + "external_ip", + "external_tls", + "internal_address", + "nic", "type" ], "properties": { - "address": { - "type": "string" + "external_dns_servers": { + "description": "External DNS servers Nexus can use to resolve external hosts.", + "type": "array", + "items": { + "type": "string", + "format": "ip" + } }, - "type": { + "external_ip": { + "description": "The address at which the external nexus server is reachable.", "type": "string", - "enum": [ - "clickhouse" - ] - } - } - }, - { - "type": "object", - "required": [ - "address", - "type" - ], - "properties": { - "address": { + "format": "ip" + }, + "external_tls": { + "description": "Whether Nexus's external endpoint should use TLS", + "type": "boolean" + }, + "internal_address": { + "description": "The address at which the internal nexus server is reachable.", "type": "string" }, - "type": { - "type": "string", - "enum": [ - "clickhouse_keeper" + "nic": { + "description": "The service vNIC providing external connectivity using OPTE.", + "allOf": [ + { + "$ref": "#/definitions/NetworkInterface" + } ] - } - } - }, - { - "type": "object", - "required": [ - "address", - "type" - ], - "properties": { - "address": { - "type": "string" }, "type": { "type": "string", "enum": [ - "cockroach_db" + "nexus" ] } } @@ -717,82 +650,24 @@ "type": { "type": "string", "enum": [ - "crucible" + "oximeter" ] } } } ] }, - "ServiceZoneRequest": { - "description": "Describes a request to create a zone running one or more services.", - "type": "object", - "required": [ - "addresses", - "id", - "services", - "zone_type" - ], - "properties": { - "addresses": { - "type": "array", - "items": { - "type": "string", - "format": "ipv6" - } - }, - "dataset": { - "default": null, - "anyOf": [ - { - "$ref": "#/definitions/DatasetRequest" - }, - { - "type": "null" - } - ] - }, - "id": { - "type": "string", - "format": "uuid" - }, - "services": { - "type": "array", - "items": { - "$ref": "#/definitions/ServiceZoneService" - } - }, - "zone_type": { - "$ref": "#/definitions/ZoneType" - } - } - }, - "ServiceZoneService": { - "description": "Used to request that the Sled initialize a single service.", + "SledConfig": { "type": "object", "required": [ - "details", - "id" + "zones" ], "properties": { - "details": { - "$ref": "#/definitions/ServiceType" - }, - "id": { - "type": "string", - "format": "uuid" - } - } - }, - "SledRequest": { - "type": "object", - "properties": { - "service": { - "description": "Services to be instantiated.", - "default": [], + "zones": { + "description": "zones configured for this sled", "type": "array", "items": { - "$ref": "#/definitions/ServiceZoneRequest" + "$ref": "#/definitions/OmicronZoneConfig" } } } @@ -860,23 +735,6 @@ "format": "uint32", "minimum": 0.0 }, - "ZoneType": { - "description": "The type of zone which may be requested from Sled Agent", - "type": "string", - "enum": [ - "clickhouse", - "clickhouse_keeper", - "cockroach_db", - "crucible_pantry", - "crucible", - "external_dns", - "internal_dns", - "nexus", - "ntp", - "oximeter", - "switch" - ] - }, "ZpoolName": { "title": "The name of a Zpool", "description": "Zpool names are of the format ox{i,p}_. They are either Internal or External, and should be unique", diff --git a/schema/rss-sled-plan.json b/schema/rss-sled-plan.json index 2ce8ae3bdc..2ef7a7b58a 100644 --- a/schema/rss-sled-plan.json +++ b/schema/rss-sled-plan.json @@ -381,6 +381,11 @@ "$ref": "#/definitions/IpNetwork" } }, + "autoneg": { + "description": "Whether or not to set autonegotiation", + "default": false, + "type": "boolean" + }, "bgp_peers": { "description": "BGP peers on this port", "type": "array", diff --git a/sled-agent/src/bin/services-ledger-check-migrate.rs b/sled-agent/src/bin/services-ledger-check-migrate.rs new file mode 100644 index 0000000000..456fdc74b7 --- /dev/null +++ b/sled-agent/src/bin/services-ledger-check-migrate.rs @@ -0,0 +1,80 @@ +// 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/. + +//! Test-migrates one or more old-format services ledger files to new-format +//! Omicron zones ledgers + +use anyhow::Context; +use camino::Utf8PathBuf; +use clap::Args; +use clap::Parser; +use omicron_common::cmd::fatal; +use omicron_common::cmd::CmdError; +use omicron_sled_agent::services::OmicronZonesConfigLocal; +use omicron_sled_agent::services_migration::AllZoneRequests; + +#[tokio::main] +async fn main() { + if let Err(message) = do_run().await { + fatal(CmdError::Failure(message)); + } +} + +#[derive(Debug, Parser)] +#[clap(about = "Test conversion of old-format services ledgers to new-format \ + zones ledgers")] +enum Converter { + /// checks whether one or more ledger file(s) can be converted successfully + Check(CheckArgs), + + /// for a given ledger file, prints the converted form + Show(ShowArgs), +} + +#[derive(Debug, Args)] +struct CheckArgs { + #[clap(action)] + files: Vec, +} + +#[derive(Debug, Args)] +struct ShowArgs { + #[clap(action)] + file: Utf8PathBuf, +} + +async fn do_run() -> Result<(), anyhow::Error> { + let args = Converter::parse(); + + let (files, do_show) = match args { + Converter::Check(CheckArgs { files }) => (files, false), + Converter::Show(ShowArgs { file }) => (vec![file], true), + }; + + for file_path in &files { + let contents = tokio::fs::read_to_string(file_path) + .await + .with_context(|| format!("read {:?}", &file_path))?; + let parsed: AllZoneRequests = serde_json::from_str(&contents) + .with_context(|| format!("parse {:?}", &file_path))?; + let converted = OmicronZonesConfigLocal::try_from(parsed) + .with_context(|| format!("convert contents of {:?}", &file_path))?; + if do_show { + println!( + "{:#}", + serde_json::to_string_pretty(&converted).with_context( + || format!("print contents of {:?}", &file_path) + )? + ); + } + eprintln!( + "{}: processed okay (zones: {})", + file_path, + converted.zones.len() + ); + } + + eprintln!("all files processed okay (files: {})", files.len()); + Ok(()) +} diff --git a/sled-agent/src/bootstrap/early_networking.rs b/sled-agent/src/bootstrap/early_networking.rs index bec309dc27..cb411a2546 100644 --- a/sled-agent/src/bootstrap/early_networking.rs +++ b/sled-agent/src/bootstrap/early_networking.rs @@ -548,23 +548,20 @@ impl<'a> EarlyNetworkSetup<'a> { let mut addrs = Vec::new(); for a in &port_config.addresses { + // TODO We're discarding the `uplink_cidr.prefix()` here and only using + // the IP address; at some point we probably need to give the full CIDR + // to dendrite? addrs.push(a.ip()); } - // TODO We're discarding the `uplink_cidr.prefix()` here and only using - // the IP address; at some point we probably need to give the full CIDR - // to dendrite? let link_settings = LinkSettings { - // TODO Allow user to configure link properties - // https://github.com/oxidecomputer/omicron/issues/3061 params: LinkCreate { - autoneg: false, - kr: false, + autoneg: port_config.autoneg, + kr: false, //NOTE: kr does not apply to user configurable links. fec: convert_fec(&port_config.uplink_port_fec), speed: convert_speed(&port_config.uplink_port_speed), lane: Some(LinkId(0)), }, - //addrs: vec![addr], addrs, }; dpd_port_settings.links.insert(link_id.to_string(), link_settings); @@ -866,6 +863,7 @@ mod tests { port: uplink.uplink_port, uplink_port_speed: uplink.uplink_port_speed, uplink_port_fec: uplink.uplink_port_fec, + autoneg: false, bgp_peers: vec![], }], bgp: vec![], diff --git a/sled-agent/src/bootstrap/params.rs b/sled-agent/src/bootstrap/params.rs index ab85915dc1..79189e7f49 100644 --- a/sled-agent/src/bootstrap/params.rs +++ b/sled-agent/src/bootstrap/params.rs @@ -347,6 +347,17 @@ pub(super) mod version { pub(crate) const V1: u32 = 1; } +#[cfg(test)] +pub fn test_config() -> RackInitializeRequest { + let manifest = std::env::var("CARGO_MANIFEST_DIR") + .expect("Cannot access manifest directory"); + let manifest = camino::Utf8PathBuf::from(manifest); + let path = manifest.join("../smf/sled-agent/non-gimlet/config-rss.toml"); + let contents = std::fs::read_to_string(&path).unwrap(); + toml::from_str(&contents) + .unwrap_or_else(|e| panic!("failed to parse {:?}: {}", &path, e)) +} + #[cfg(test)] mod tests { use std::net::Ipv6Addr; diff --git a/sled-agent/src/bootstrap/server.rs b/sled-agent/src/bootstrap/server.rs index f4948de83b..999e4cc0c8 100644 --- a/sled-agent/src/bootstrap/server.rs +++ b/sled-agent/src/bootstrap/server.rs @@ -259,7 +259,7 @@ impl Server { // we're responsible for, while continuing to handle hardware // notifications. This cannot fail: we retry indefinitely until // we're done loading services. - sled_agent.cold_boot_load_services().await; + sled_agent.load_services().await; SledAgentState::ServerStarted(sled_agent_server) } else { SledAgentState::Bootstrapping( diff --git a/sled-agent/src/common/disk.rs b/sled-agent/src/common/disk.rs index 57868937d0..54c56825eb 100644 --- a/sled-agent/src/common/disk.rs +++ b/sled-agent/src/common/disk.rs @@ -118,12 +118,10 @@ impl DiskStates { | DiskState::ImportingFromBulkWrites | DiskState::Destroyed | DiskState::Faulted => { - return Err(Error::InvalidRequest { - message: format!( - "cannot detach from {}", - self.current.disk_state - ), - }); + return Err(Error::invalid_request(format!( + "cannot detach from {}", + self.current.disk_state + ))); } }; } @@ -134,9 +132,9 @@ impl DiskStates { // (which is a no-op anyway). DiskState::Attaching(id) | DiskState::Attached(id) => { if uuid != id { - return Err(Error::InvalidRequest { - message: "disk is already attached".to_string(), - }); + return Err(Error::invalid_request( + "disk is already attached", + )); } return Ok(None); } @@ -157,12 +155,10 @@ impl DiskStates { | DiskState::Detaching(_) | DiskState::Destroyed | DiskState::Faulted => { - return Err(Error::InvalidRequest { - message: format!( - "cannot attach from {}", - self.current.disk_state - ), - }); + return Err(Error::invalid_request(format!( + "cannot attach from {}", + self.current.disk_state + ))); } } } diff --git a/sled-agent/src/http_entrypoints.rs b/sled-agent/src/http_entrypoints.rs index 2d0e2c4001..9c3a079dac 100644 --- a/sled-agent/src/http_entrypoints.rs +++ b/sled-agent/src/http_entrypoints.rs @@ -10,7 +10,7 @@ use crate::bootstrap::params::AddSledRequest; use crate::params::{ CleanupContextUpdate, DiskEnsureBody, InstanceEnsureBody, InstancePutMigrationIdsBody, InstancePutStateBody, - InstancePutStateResponse, InstanceUnregisterResponse, ServiceEnsureBody, + InstancePutStateResponse, InstanceUnregisterResponse, OmicronZonesConfig, SledRole, TimeSync, VpcFirewallRulesEnsureBody, ZoneBundleId, ZoneBundleMetadata, Zpool, }; @@ -51,7 +51,8 @@ pub fn api() -> SledApiDescription { api.register(instance_put_state)?; api.register(instance_register)?; api.register(instance_unregister)?; - api.register(services_put)?; + api.register(omicron_zones_get)?; + api.register(omicron_zones_put)?; api.register(zones_list)?; api.register(zone_bundle_list)?; api.register(zone_bundle_list_all)?; @@ -315,44 +316,28 @@ async fn zones_list( sa.zones_list().await.map(HttpResponseOk).map_err(HttpError::from) } +#[endpoint { + method = GET, + path = "/omicron-zones", +}] +async fn omicron_zones_get( + rqctx: RequestContext, +) -> Result, HttpError> { + let sa = rqctx.context(); + Ok(HttpResponseOk(sa.omicron_zones_list().await?)) +} + #[endpoint { method = PUT, - path = "/services", + path = "/omicron-zones", }] -async fn services_put( +async fn omicron_zones_put( rqctx: RequestContext, - body: TypedBody, + body: TypedBody, ) -> Result { - let sa = rqctx.context().clone(); + let sa = rqctx.context(); let body_args = body.into_inner(); - - // Spawn a separate task to run `services_ensure`: cancellation of this - // endpoint's future (as might happen if the client abandons the request or - // times out) could result in leaving zones partially configured and the - // in-memory state of the service manager invalid. See: - // oxidecomputer/omicron#3098. - let handler = async move { - match sa.services_ensure(body_args).await { - Ok(()) => Ok(()), - Err(e) => { - // Log the error here to make things clear even if the client - // has already disconnected. - error!(sa.logger(), "failed to initialize services: {e}"); - Err(e) - } - } - }; - match tokio::spawn(handler).await { - Ok(result) => result.map_err(|e| Error::from(e))?, - - Err(e) => { - return Err(HttpError::for_internal_error(format!( - "unexpected failure awaiting \"services_ensure\": {:#}", - e - ))); - } - } - + sa.omicron_zones_ensure(body_args).await?; Ok(HttpResponseUpdatedNoContent()) } diff --git a/sled-agent/src/instance.rs b/sled-agent/src/instance.rs index c37f0ffde6..d0b8987a62 100644 --- a/sled-agent/src/instance.rs +++ b/sled-agent/src/instance.rs @@ -861,13 +861,11 @@ impl Instance { } return Err(Error::Transition( - omicron_common::api::external::Error::Conflict { - internal_message: format!( - "wrong instance state generation: expected {}, got {}", - inner.state.instance().gen, - old_runtime.gen - ), - }, + omicron_common::api::external::Error::conflict(format!( + "wrong instance state generation: expected {}, got {}", + inner.state.instance().gen, + old_runtime.gen + )), )); } diff --git a/sled-agent/src/lib.rs b/sled-agent/src/lib.rs index 924fd4bd92..d77ec7a3c0 100644 --- a/sled-agent/src/lib.rs +++ b/sled-agent/src/lib.rs @@ -32,7 +32,8 @@ pub mod params; mod profile; pub mod rack_setup; pub mod server; -mod services; +pub mod services; +pub mod services_migration; mod sled_agent; mod smf_helper; mod storage_monitor; diff --git a/sled-agent/src/params.rs b/sled-agent/src/params.rs index b22bd84975..6be2ceabbd 100644 --- a/sled-agent/src/params.rs +++ b/sled-agent/src/params.rs @@ -9,6 +9,8 @@ pub use crate::zone_bundle::ZoneBundleMetadata; pub use illumos_utils::opte::params::DhcpConfig; pub use illumos_utils::opte::params::VpcFirewallRule; pub use illumos_utils::opte::params::VpcFirewallRulesEnsureBody; +use illumos_utils::zpool::ZpoolName; +use omicron_common::api::external::Generation; use omicron_common::api::internal::nexus::{ DiskRuntimeState, InstanceProperties, InstanceRuntimeState, SledInstanceState, VmmRuntimeState, @@ -18,13 +20,13 @@ use omicron_common::api::internal::shared::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use sled_hardware::Baseboard; pub use sled_hardware::DendriteAsic; +use sled_storage::dataset::DatasetKind; use sled_storage::dataset::DatasetName; use std::fmt::{Debug, Display, Formatter, Result as FormatResult}; use std::net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6}; +use std::str::FromStr; use std::time::Duration; -use thiserror::Error; use uuid::Uuid; /// Used to request a Disk state change @@ -229,252 +231,7 @@ pub struct Zpool { pub disk_type: DiskType, } -/// Describes service-specific parameters. -#[derive( - Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, -)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ServiceType { - Nexus { - /// The address at which the internal nexus server is reachable. - internal_address: SocketAddrV6, - /// The address at which the external nexus server is reachable. - external_ip: IpAddr, - /// The service vNIC providing external connectivity using OPTE. - nic: NetworkInterface, - /// Whether Nexus's external endpoint should use TLS - external_tls: bool, - /// External DNS servers Nexus can use to resolve external hosts. - external_dns_servers: Vec, - }, - ExternalDns { - /// The address at which the external DNS server API is reachable. - http_address: SocketAddrV6, - /// The address at which the external DNS server is reachable. - dns_address: SocketAddr, - /// The service vNIC providing external connectivity using OPTE. - nic: NetworkInterface, - }, - InternalDns { - http_address: SocketAddrV6, - dns_address: SocketAddrV6, - /// The addresses in the global zone which should be created - /// - /// For the DNS service, which exists outside the sleds's typical subnet - adding an - /// address in the GZ is necessary to allow inter-zone traffic routing. - gz_address: Ipv6Addr, - - /// The address is also identified with an auxiliary bit of information - /// to ensure that the created global zone address can have a unique name. - gz_address_index: u32, - }, - Oximeter { - address: SocketAddrV6, - }, - // We should never receive external requests to start wicketd, MGS, sp-sim - // dendrite, tfport, or maghemite: these are all services running in the - // global zone or switch zone that we start autonomously. We tag them with - // `serde(skip)` both to omit them from our OpenAPI definition and to avoid - // needing their contained types to implement `JsonSchema + Deserialize + - // Serialize`. - #[serde(skip)] - ManagementGatewayService, - #[serde(skip)] - Wicketd { - baseboard: Baseboard, - }, - #[serde(skip)] - Dendrite { - asic: DendriteAsic, - }, - #[serde(skip)] - Tfport { - pkt_source: String, - asic: DendriteAsic, - }, - #[serde(skip)] - Uplink, - #[serde(skip)] - MgDdm { - mode: String, - }, - #[serde(skip)] - Mgd, - #[serde(skip)] - SpSim, - CruciblePantry { - address: SocketAddrV6, - }, - BoundaryNtp { - address: SocketAddrV6, - ntp_servers: Vec, - dns_servers: Vec, - domain: Option, - /// The service vNIC providing outbound connectivity using OPTE. - nic: NetworkInterface, - /// The SNAT configuration for outbound connections. - snat_cfg: SourceNatConfig, - }, - InternalNtp { - address: SocketAddrV6, - ntp_servers: Vec, - dns_servers: Vec, - domain: Option, - }, - Clickhouse { - address: SocketAddrV6, - }, - ClickhouseKeeper { - address: SocketAddrV6, - }, - CockroachDb { - address: SocketAddrV6, - }, - Crucible { - address: SocketAddrV6, - }, -} - -impl std::fmt::Display for ServiceType { - fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult { - match self { - ServiceType::Nexus { .. } => write!(f, "nexus"), - ServiceType::ExternalDns { .. } => write!(f, "external_dns"), - ServiceType::InternalDns { .. } => write!(f, "internal_dns"), - ServiceType::Oximeter { .. } => write!(f, "oximeter"), - ServiceType::ManagementGatewayService => write!(f, "mgs"), - ServiceType::Wicketd { .. } => write!(f, "wicketd"), - ServiceType::Dendrite { .. } => write!(f, "dendrite"), - ServiceType::Tfport { .. } => write!(f, "tfport"), - ServiceType::Uplink { .. } => write!(f, "uplink"), - ServiceType::CruciblePantry { .. } => write!(f, "crucible/pantry"), - ServiceType::BoundaryNtp { .. } - | ServiceType::InternalNtp { .. } => write!(f, "ntp"), - ServiceType::MgDdm { .. } => write!(f, "mg-ddm"), - ServiceType::Mgd => write!(f, "mgd"), - ServiceType::SpSim => write!(f, "sp-sim"), - ServiceType::Clickhouse { .. } => write!(f, "clickhouse"), - ServiceType::ClickhouseKeeper { .. } => { - write!(f, "clickhouse_keeper") - } - ServiceType::CockroachDb { .. } => write!(f, "cockroachdb"), - ServiceType::Crucible { .. } => write!(f, "crucible"), - } - } -} - -impl crate::smf_helper::Service for ServiceType { - fn service_name(&self) -> String { - self.to_string() - } - fn smf_name(&self) -> String { - format!("svc:/oxide/{}", self.service_name()) - } - fn should_import(&self) -> bool { - true - } -} - -/// Error returned by attempting to convert an internal service (i.e., a service -/// started autonomously by sled-agent) into a -/// `sled_agent_client::types::ServiceType` to be sent to a remote sled-agent. -#[derive(Debug, Clone, Copy, Error)] -#[error("This service may only be started autonomously by sled-agent")] -pub struct AutonomousServiceOnlyError; - -impl TryFrom for sled_agent_client::types::ServiceType { - type Error = AutonomousServiceOnlyError; - - fn try_from(s: ServiceType) -> Result { - use sled_agent_client::types::ServiceType as AutoSt; - use ServiceType as St; - - match s { - St::Nexus { - internal_address, - external_ip, - nic, - external_tls, - external_dns_servers, - } => Ok(AutoSt::Nexus { - internal_address: internal_address.to_string(), - external_ip, - nic: nic.into(), - external_tls, - external_dns_servers, - }), - St::ExternalDns { http_address, dns_address, nic } => { - Ok(AutoSt::ExternalDns { - http_address: http_address.to_string(), - dns_address: dns_address.to_string(), - nic: nic.into(), - }) - } - St::InternalDns { - http_address, - dns_address, - gz_address, - gz_address_index, - } => Ok(AutoSt::InternalDns { - http_address: http_address.to_string(), - dns_address: dns_address.to_string(), - gz_address, - gz_address_index, - }), - St::Oximeter { address } => { - Ok(AutoSt::Oximeter { address: address.to_string() }) - } - St::CruciblePantry { address } => { - Ok(AutoSt::CruciblePantry { address: address.to_string() }) - } - St::BoundaryNtp { - address, - ntp_servers, - dns_servers, - domain, - nic, - snat_cfg, - } => Ok(AutoSt::BoundaryNtp { - address: address.to_string(), - ntp_servers, - dns_servers, - domain, - nic: nic.into(), - snat_cfg: snat_cfg.into(), - }), - St::InternalNtp { address, ntp_servers, dns_servers, domain } => { - Ok(AutoSt::InternalNtp { - address: address.to_string(), - ntp_servers, - dns_servers, - domain, - }) - } - St::Clickhouse { address } => { - Ok(AutoSt::Clickhouse { address: address.to_string() }) - } - St::ClickhouseKeeper { address } => { - Ok(AutoSt::ClickhouseKeeper { address: address.to_string() }) - } - St::CockroachDb { address } => { - Ok(AutoSt::CockroachDb { address: address.to_string() }) - } - St::Crucible { address } => { - Ok(AutoSt::Crucible { address: address.to_string() }) - } - St::ManagementGatewayService - | St::SpSim - | St::Wicketd { .. } - | St::Dendrite { .. } - | St::Tfport { .. } - | St::Uplink - | St::Mgd - | St::MgDdm { .. } => Err(AutonomousServiceOnlyError), - } - } -} - -/// The type of zone which may be requested from Sled Agent +/// The type of zone that Sled Agent may run #[derive( Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, )] @@ -493,24 +250,6 @@ pub enum ZoneType { Switch, } -impl From for sled_agent_client::types::ZoneType { - fn from(zt: ZoneType) -> Self { - match zt { - ZoneType::Clickhouse => Self::Clickhouse, - ZoneType::ClickhouseKeeper => Self::ClickhouseKeeper, - ZoneType::CockroachDb => Self::CockroachDb, - ZoneType::Crucible => Self::Crucible, - ZoneType::CruciblePantry => Self::CruciblePantry, - ZoneType::InternalDns => Self::InternalDns, - ZoneType::ExternalDns => Self::ExternalDns, - ZoneType::Nexus => Self::Nexus, - ZoneType::Ntp => Self::Ntp, - ZoneType::Oximeter => Self::Oximeter, - ZoneType::Switch => Self::Switch, - } - } -} - impl std::fmt::Display for ZoneType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use ZoneType::*; @@ -531,280 +270,516 @@ impl std::fmt::Display for ZoneType { } } -/// Describes a request to provision a specific dataset +/// Generation 1 of `OmicronZonesConfig` is always the set of no zones. +pub const OMICRON_ZONES_CONFIG_INITIAL_GENERATION: u32 = 1; + +/// Describes the set of Omicron-managed zones running on a sled #[derive( Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, )] -pub struct DatasetRequest { - pub id: Uuid, - pub name: DatasetName, - pub service_address: SocketAddrV6, -} - -impl From for sled_agent_client::types::DatasetRequest { - fn from(d: DatasetRequest) -> Self { +pub struct OmicronZonesConfig { + /// generation number of this configuration + /// + /// This generation number is owned by the control plane (i.e., RSS or + /// Nexus, depending on whether RSS-to-Nexus handoff has happened). It + /// should not be bumped within Sled Agent. + /// + /// Sled Agent rejects attempts to set the configuration to a generation + /// older than the one it's currently running. + pub generation: Generation, + + /// list of running zones + pub zones: Vec, +} + +impl From for sled_agent_client::types::OmicronZonesConfig { + fn from(local: OmicronZonesConfig) -> Self { Self { - id: d.id, - name: d.name.into(), - service_address: d.service_address.to_string(), + generation: local.generation.into(), + zones: local.zones.into_iter().map(|s| s.into()).collect(), } } } -/// Describes a request to create a zone running one or more services. +/// Describes one Omicron-managed zone running on a sled #[derive( Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, )] -pub struct ServiceZoneRequest { - // The UUID of the zone to be initialized. - // TODO: Should this be removed? If we have UUIDs on the services, what's - // the point of this? +pub struct OmicronZoneConfig { pub id: Uuid, - // The type of the zone to be created. - pub zone_type: ZoneType, - // The addresses on which the service should listen for requests. - pub addresses: Vec, - // Datasets which should be managed by this service. - #[serde(default)] - pub dataset: Option, - // Services that should be run in the zone - pub services: Vec, -} - -impl ServiceZoneRequest { - // The full name of the zone, if it was to be created as a zone. - pub fn zone_name(&self) -> String { - illumos_utils::running_zone::InstalledZone::get_zone_name( - &self.zone_type.to_string(), - self.zone_name_unique_identifier(), - ) - } + pub underlay_address: Ipv6Addr, + pub zone_type: OmicronZoneType, +} - // The name of a unique identifier for the zone, if one is necessary. - pub fn zone_name_unique_identifier(&self) -> Option { - match &self.zone_type { - // The switch zone is necessarily a singleton. - ZoneType::Switch => None, - // All other zones should be identified by their zone UUID. - ZoneType::Clickhouse - | ZoneType::ClickhouseKeeper - | ZoneType::CockroachDb - | ZoneType::Crucible - | ZoneType::ExternalDns - | ZoneType::InternalDns - | ZoneType::Nexus - | ZoneType::CruciblePantry - | ZoneType::Ntp - | ZoneType::Oximeter => Some(self.id), +impl From for sled_agent_client::types::OmicronZoneConfig { + fn from(local: OmicronZoneConfig) -> Self { + Self { + id: local.id, + underlay_address: local.underlay_address, + zone_type: local.zone_type.into(), } } } -impl TryFrom - for sled_agent_client::types::ServiceZoneRequest -{ - type Error = AutonomousServiceOnlyError; +impl OmicronZoneConfig { + /// If this kind of zone has an associated dataset, returns the dataset's + /// name. Othrwise, returns `None`. + pub fn dataset_name(&self) -> Option { + self.zone_type.dataset_name() + } - fn try_from(s: ServiceZoneRequest) -> Result { - let mut services = Vec::with_capacity(s.services.len()); - for service in s.services { - services.push(service.try_into()?); - } + /// If this kind of zone has an associated dataset, return the dataset's + /// name and the associated "service address". Otherwise, returns `None`. + pub fn dataset_name_and_address( + &self, + ) -> Option<(DatasetName, SocketAddrV6)> { + self.zone_type.dataset_name_and_address() + } - Ok(Self { - id: s.id, - zone_type: s.zone_type.into(), - addresses: s.addresses, - dataset: s.dataset.map(|d| d.into()), - services, - }) + /// Returns the name that is (or will be) used for the illumos zone + /// associated with this zone + pub fn zone_name(&self) -> String { + illumos_utils::running_zone::InstalledZone::get_zone_name( + &self.zone_type.zone_type_str(), + Some(self.id), + ) } -} -impl ServiceZoneRequest { - pub fn into_nexus_service_req( + /// Returns the structure that describes this zone to Nexus during rack + /// initialization + pub fn to_nexus_service_req( &self, sled_id: Uuid, - ) -> Result< - Vec, - AutonomousServiceOnlyError, - > { + ) -> nexus_client::types::ServicePutRequest { use nexus_client::types as NexusTypes; - let mut services = vec![]; - for svc in &self.services { - let service_id = svc.id; - let zone_id = Some(self.id); - match &svc.details { - ServiceType::Nexus { - external_ip, - internal_address, - nic, - .. - } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: internal_address.to_string(), - kind: NexusTypes::ServiceKind::Nexus { - external_address: *external_ip, - nic: NexusTypes::ServiceNic { - id: nic.id, - name: nic.name.clone(), - ip: nic.ip, - mac: nic.mac, - }, - }, - }); - } - ServiceType::ExternalDns { http_address, dns_address, nic } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: http_address.to_string(), - kind: NexusTypes::ServiceKind::ExternalDns { - external_address: dns_address.ip(), - nic: NexusTypes::ServiceNic { - id: nic.id, - name: nic.name.clone(), - ip: nic.ip, - mac: nic.mac, - }, - }, - }); - } - ServiceType::InternalDns { http_address, .. } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: http_address.to_string(), - kind: NexusTypes::ServiceKind::InternalDns, - }); + let service_id = self.id; + let zone_id = Some(self.id); + match &self.zone_type { + OmicronZoneType::Nexus { + external_ip, + internal_address, + nic, + .. + } => NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: internal_address.to_string(), + kind: NexusTypes::ServiceKind::Nexus { + external_address: *external_ip, + nic: NexusTypes::ServiceNic { + id: nic.id, + name: nic.name.clone(), + ip: nic.ip, + mac: nic.mac, + }, + }, + }, + OmicronZoneType::ExternalDns { + http_address, + dns_address, + nic, + .. + } => NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: http_address.to_string(), + kind: NexusTypes::ServiceKind::ExternalDns { + external_address: dns_address.ip(), + nic: NexusTypes::ServiceNic { + id: nic.id, + name: nic.name.clone(), + ip: nic.ip, + mac: nic.mac, + }, + }, + }, + OmicronZoneType::InternalDns { http_address, .. } => { + NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: http_address.to_string(), + kind: NexusTypes::ServiceKind::InternalDns, } - ServiceType::Oximeter { address } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: address.to_string(), - kind: NexusTypes::ServiceKind::Oximeter, - }); + } + OmicronZoneType::Oximeter { address } => { + NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: address.to_string(), + kind: NexusTypes::ServiceKind::Oximeter, } - ServiceType::CruciblePantry { address } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: address.to_string(), - kind: NexusTypes::ServiceKind::CruciblePantry, - }); + } + OmicronZoneType::CruciblePantry { address } => { + NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: address.to_string(), + kind: NexusTypes::ServiceKind::CruciblePantry, } - ServiceType::BoundaryNtp { address, snat_cfg, nic, .. } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: address.to_string(), - kind: NexusTypes::ServiceKind::BoundaryNtp { - snat: snat_cfg.into(), - nic: NexusTypes::ServiceNic { - id: nic.id, - name: nic.name.clone(), - ip: nic.ip, - mac: nic.mac, - }, + } + OmicronZoneType::BoundaryNtp { address, snat_cfg, nic, .. } => { + NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: address.to_string(), + kind: NexusTypes::ServiceKind::BoundaryNtp { + snat: snat_cfg.into(), + nic: NexusTypes::ServiceNic { + id: nic.id, + name: nic.name.clone(), + ip: nic.ip, + mac: nic.mac, }, - }); + }, } - ServiceType::InternalNtp { address, .. } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: address.to_string(), - kind: NexusTypes::ServiceKind::InternalNtp, - }); - } - ServiceType::Clickhouse { address } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: address.to_string(), - kind: NexusTypes::ServiceKind::Clickhouse, - }); + } + OmicronZoneType::InternalNtp { address, .. } => { + NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: address.to_string(), + kind: NexusTypes::ServiceKind::InternalNtp, } - ServiceType::ClickhouseKeeper { address } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: address.to_string(), - kind: NexusTypes::ServiceKind::ClickhouseKeeper, - }); + } + OmicronZoneType::Clickhouse { address, .. } => { + NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: address.to_string(), + kind: NexusTypes::ServiceKind::Clickhouse, } - ServiceType::Crucible { address } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: address.to_string(), - kind: NexusTypes::ServiceKind::Crucible, - }); + } + OmicronZoneType::ClickhouseKeeper { address, .. } => { + NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: address.to_string(), + kind: NexusTypes::ServiceKind::ClickhouseKeeper, } - ServiceType::CockroachDb { address } => { - services.push(NexusTypes::ServicePutRequest { - service_id, - zone_id, - sled_id, - address: address.to_string(), - kind: NexusTypes::ServiceKind::Cockroach, - }); + } + OmicronZoneType::Crucible { address, .. } => { + NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: address.to_string(), + kind: NexusTypes::ServiceKind::Crucible, } - ServiceType::ManagementGatewayService - | ServiceType::SpSim - | ServiceType::Wicketd { .. } - | ServiceType::Dendrite { .. } - | ServiceType::MgDdm { .. } - | ServiceType::Mgd - | ServiceType::Tfport { .. } - | ServiceType::Uplink => { - return Err(AutonomousServiceOnlyError); + } + OmicronZoneType::CockroachDb { address, .. } => { + NexusTypes::ServicePutRequest { + service_id, + zone_id, + sled_id, + address: address.to_string(), + kind: NexusTypes::ServiceKind::Cockroach, } } } + } +} - Ok(services) +/// Describes a persistent ZFS dataset associated with an Omicron zone +#[derive( + Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, +)] +pub struct OmicronZoneDataset { + pub pool_name: ZpoolName, +} + +impl From for sled_agent_client::types::OmicronZoneDataset { + fn from(local: OmicronZoneDataset) -> Self { + Self { + pool_name: sled_agent_client::types::ZpoolName::from_str( + &local.pool_name.to_string(), + ) + .unwrap(), + } } } -/// Used to request that the Sled initialize a single service. +/// Describes what kind of zone this is (i.e., what component is running in it) +/// as well as any type-specific configuration #[derive( Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, )] -pub struct ServiceZoneService { - pub id: Uuid, - pub details: ServiceType, +#[serde(tag = "type", rename_all = "snake_case")] +pub enum OmicronZoneType { + BoundaryNtp { + address: SocketAddrV6, + ntp_servers: Vec, + dns_servers: Vec, + domain: Option, + /// The service vNIC providing outbound connectivity using OPTE. + nic: NetworkInterface, + /// The SNAT configuration for outbound connections. + snat_cfg: SourceNatConfig, + }, + + Clickhouse { + address: SocketAddrV6, + dataset: OmicronZoneDataset, + }, + + ClickhouseKeeper { + address: SocketAddrV6, + dataset: OmicronZoneDataset, + }, + CockroachDb { + address: SocketAddrV6, + dataset: OmicronZoneDataset, + }, + + Crucible { + address: SocketAddrV6, + dataset: OmicronZoneDataset, + }, + CruciblePantry { + address: SocketAddrV6, + }, + ExternalDns { + dataset: OmicronZoneDataset, + /// The address at which the external DNS server API is reachable. + http_address: SocketAddrV6, + /// The address at which the external DNS server is reachable. + dns_address: SocketAddr, + /// The service vNIC providing external connectivity using OPTE. + nic: NetworkInterface, + }, + InternalDns { + dataset: OmicronZoneDataset, + http_address: SocketAddrV6, + dns_address: SocketAddrV6, + /// The addresses in the global zone which should be created + /// + /// For the DNS service, which exists outside the sleds's typical subnet + /// - adding an address in the GZ is necessary to allow inter-zone + /// traffic routing. + gz_address: Ipv6Addr, + + /// The address is also identified with an auxiliary bit of information + /// to ensure that the created global zone address can have a unique + /// name. + gz_address_index: u32, + }, + InternalNtp { + address: SocketAddrV6, + ntp_servers: Vec, + dns_servers: Vec, + domain: Option, + }, + Nexus { + /// The address at which the internal nexus server is reachable. + internal_address: SocketAddrV6, + /// The address at which the external nexus server is reachable. + external_ip: IpAddr, + /// The service vNIC providing external connectivity using OPTE. + nic: NetworkInterface, + /// Whether Nexus's external endpoint should use TLS + external_tls: bool, + /// External DNS servers Nexus can use to resolve external hosts. + external_dns_servers: Vec, + }, + Oximeter { + address: SocketAddrV6, + }, } -impl TryFrom - for sled_agent_client::types::ServiceZoneService -{ - type Error = AutonomousServiceOnlyError; +impl OmicronZoneType { + /// Returns a canonical string identifying the type of zone this is + /// + /// This is used to construct zone names, SMF service names, etc. + pub fn zone_type_str(&self) -> String { + match self { + OmicronZoneType::BoundaryNtp { .. } + | OmicronZoneType::InternalNtp { .. } => ZoneType::Ntp, + + OmicronZoneType::Clickhouse { .. } => ZoneType::Clickhouse, + OmicronZoneType::ClickhouseKeeper { .. } => { + ZoneType::ClickhouseKeeper + } + OmicronZoneType::CockroachDb { .. } => ZoneType::CockroachDb, + OmicronZoneType::Crucible { .. } => ZoneType::Crucible, + OmicronZoneType::CruciblePantry { .. } => ZoneType::CruciblePantry, + OmicronZoneType::ExternalDns { .. } => ZoneType::ExternalDns, + OmicronZoneType::InternalDns { .. } => ZoneType::InternalDns, + OmicronZoneType::Nexus { .. } => ZoneType::Nexus, + OmicronZoneType::Oximeter { .. } => ZoneType::Oximeter, + } + .to_string() + } + + /// If this kind of zone has an associated dataset, returns the dataset's + /// name. Othrwise, returns `None`. + pub fn dataset_name(&self) -> Option { + self.dataset_name_and_address().map(|d| d.0) + } + + /// If this kind of zone has an associated dataset, return the dataset's + /// name and the associated "service address". Otherwise, returns `None`. + pub fn dataset_name_and_address( + &self, + ) -> Option<(DatasetName, SocketAddrV6)> { + let (dataset, dataset_kind, address) = match self { + OmicronZoneType::BoundaryNtp { .. } + | OmicronZoneType::InternalNtp { .. } + | OmicronZoneType::Nexus { .. } + | OmicronZoneType::Oximeter { .. } + | OmicronZoneType::CruciblePantry { .. } => None, + OmicronZoneType::Clickhouse { dataset, address, .. } => { + Some((dataset, DatasetKind::Clickhouse, address)) + } + OmicronZoneType::ClickhouseKeeper { dataset, address, .. } => { + Some((dataset, DatasetKind::ClickhouseKeeper, address)) + } + OmicronZoneType::CockroachDb { dataset, address, .. } => { + Some((dataset, DatasetKind::CockroachDb, address)) + } + OmicronZoneType::Crucible { dataset, address, .. } => { + Some((dataset, DatasetKind::Crucible, address)) + } + OmicronZoneType::ExternalDns { dataset, http_address, .. } => { + Some((dataset, DatasetKind::ExternalDns, http_address)) + } + OmicronZoneType::InternalDns { dataset, http_address, .. } => { + Some((dataset, DatasetKind::InternalDns, http_address)) + } + }?; - fn try_from(s: ServiceZoneService) -> Result { - let details = s.details.try_into()?; - Ok(Self { id: s.id, details }) + Some(( + DatasetName::new(dataset.pool_name.clone(), dataset_kind), + *address, + )) } } -/// Used to request that the Sled initialize multiple services. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct ServiceEnsureBody { - pub services: Vec, +impl crate::smf_helper::Service for OmicronZoneType { + fn service_name(&self) -> String { + // For historical reasons, crucible-pantry is the only zone type whose + // SMF service does not match the canonical name that we use for the + // zone. + match self { + OmicronZoneType::CruciblePantry { .. } => { + "crucible/pantry".to_owned() + } + _ => self.zone_type_str(), + } + } + fn smf_name(&self) -> String { + format!("svc:/oxide/{}", self.service_name()) + } + fn should_import(&self) -> bool { + true + } +} + +impl From for sled_agent_client::types::OmicronZoneType { + fn from(local: OmicronZoneType) -> Self { + use sled_agent_client::types::OmicronZoneType as Other; + match local { + OmicronZoneType::BoundaryNtp { + address, + ntp_servers, + dns_servers, + domain, + nic, + snat_cfg, + } => Other::BoundaryNtp { + address: address.to_string(), + dns_servers, + domain, + ntp_servers, + snat_cfg: snat_cfg.into(), + nic: nic.into(), + }, + OmicronZoneType::Clickhouse { address, dataset } => { + Other::Clickhouse { + address: address.to_string(), + dataset: dataset.into(), + } + } + OmicronZoneType::ClickhouseKeeper { address, dataset } => { + Other::ClickhouseKeeper { + address: address.to_string(), + dataset: dataset.into(), + } + } + OmicronZoneType::CockroachDb { address, dataset } => { + Other::CockroachDb { + address: address.to_string(), + dataset: dataset.into(), + } + } + OmicronZoneType::Crucible { address, dataset } => Other::Crucible { + address: address.to_string(), + dataset: dataset.into(), + }, + OmicronZoneType::CruciblePantry { address } => { + Other::CruciblePantry { address: address.to_string() } + } + OmicronZoneType::ExternalDns { + dataset, + http_address, + dns_address, + nic, + } => Other::ExternalDns { + dataset: dataset.into(), + http_address: http_address.to_string(), + dns_address: dns_address.to_string(), + nic: nic.into(), + }, + OmicronZoneType::InternalDns { + dataset, + http_address, + dns_address, + gz_address, + gz_address_index, + } => Other::InternalDns { + dataset: dataset.into(), + http_address: http_address.to_string(), + dns_address: dns_address.to_string(), + gz_address, + gz_address_index, + }, + OmicronZoneType::InternalNtp { + address, + ntp_servers, + dns_servers, + domain, + } => Other::InternalNtp { + address: address.to_string(), + ntp_servers, + dns_servers, + domain, + }, + OmicronZoneType::Nexus { + internal_address, + external_ip, + nic, + external_tls, + external_dns_servers, + } => Other::Nexus { + external_dns_servers, + external_ip, + external_tls, + internal_address: internal_address.to_string(), + nic: nic.into(), + }, + OmicronZoneType::Oximeter { address } => { + Other::Oximeter { address: address.to_string() } + } + } + } } #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] diff --git a/sled-agent/src/rack_setup/plan/service.rs b/sled-agent/src/rack_setup/plan/service.rs index 980f5b6ebd..441c7fd842 100644 --- a/sled-agent/src/rack_setup/plan/service.rs +++ b/sled-agent/src/rack_setup/plan/service.rs @@ -5,10 +5,7 @@ //! Plan generation for "where should services be initialized". use crate::bootstrap::params::StartSledAgentRequest; -use crate::params::{ - DatasetRequest, ServiceType, ServiceZoneRequest, ServiceZoneService, - ZoneType, -}; +use crate::params::{OmicronZoneConfig, OmicronZoneDataset, OmicronZoneType}; use crate::rack_setup::config::SetupServiceConfig as Config; use camino::Utf8PathBuf; use dns_service_client::types::DnsConfigParams; @@ -97,20 +94,20 @@ pub enum PlanError { #[error("Ran out of sleds / U2 storage pools")] NotEnoughSleds, + + #[error("Found only v1 service plan")] + FoundV1, } -#[derive( - Clone, Debug, Default, Deserialize, Serialize, PartialEq, JsonSchema, -)] -pub struct SledRequest { - /// Services to be instantiated. - #[serde(default, rename = "service")] - pub services: Vec, +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +pub struct SledConfig { + /// zones configured for this sled + pub zones: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct Plan { - pub services: HashMap, + pub services: HashMap, pub dns_config: DnsConfigParams, } @@ -120,7 +117,8 @@ impl Ledgerable for Plan { } fn generation_bump(&mut self) {} } -const RSS_SERVICE_PLAN_FILENAME: &str = "rss-service-plan.json"; +const RSS_SERVICE_PLAN_V1_FILENAME: &str = "rss-service-plan.json"; +const RSS_SERVICE_PLAN_FILENAME: &str = "rss-service-plan-v2.json"; impl Plan { pub async fn load( @@ -142,11 +140,60 @@ impl Plan { if let Some(ledger) = ledger { info!(log, "RSS plan already created, loading from file"); Ok(Some(ledger.data().clone())) + } else if Self::has_v1(storage_manager).await.map_err(|err| { + PlanError::Io { + message: String::from("looking for v1 RSS plan"), + err, + } + })? { + // If we found no current-version service plan, but we _do_ find + // a v1 plan present, bail out. We do not expect to ever see this + // in practice because that would indicate that: + // + // - We ran RSS previously on this same system using an older + // version of the software that generates v1 service plans and it + // got far enough through RSS to have written the v1 service plan. + // - That means it must have finished initializing all sled agents, + // including itself, causing it to record a + // `StartSledAgentRequest`s in its ledger -- while still running + // the older RSS. + // - But we're currently running software that knows about v2 + // service plans. Thus, this process started some time after that + // ledger was written. + // - But the bootstrap agent refuses to execute RSS if it has a + // local `StartSledAgentRequest` ledgered. So we shouldn't get + // here if all of the above happened. + // + // This sounds like a complicated set of assumptions. If we got + // this wrong, we'll fail spuriously here and we'll have to figure + // out what happened. But the alternative is doing extra work to + // support a condition that we do not believe can ever happen in any + // system. + Err(PlanError::FoundV1) } else { Ok(None) } } + async fn has_v1( + storage_manager: &StorageHandle, + ) -> Result { + let paths = storage_manager + .get_latest_resources() + .await + .all_m2_mountpoints(CONFIG_DATASET) + .into_iter() + .map(|p| p.join(RSS_SERVICE_PLAN_V1_FILENAME)); + + for p in paths { + if p.try_exists()? { + return Ok(true); + } + } + + Ok(false) + } + async fn is_sled_scrimlet( log: &Logger, address: SocketAddrV6, @@ -235,41 +282,13 @@ impl Plan { Ok(u2_zpools) } - pub async fn create( - log: &Logger, + pub fn create_transient( config: &Config, - storage_manager: &StorageHandle, - sleds: &HashMap, + mut sled_info: Vec, ) -> Result { let mut dns_builder = internal_dns::DnsConfigBuilder::new(); let mut svc_port_builder = ServicePortBuilder::new(config); - // Load the information we need about each Sled to be able to allocate - // components on it. - let mut sled_info = { - let result: Result, PlanError> = - futures::future::try_join_all(sleds.values().map( - |sled_request| async { - let subnet = sled_request.body.subnet; - let sled_address = get_sled_address(subnet); - let u2_zpools = - Self::get_u2_zpools_from_sled(log, sled_address) - .await?; - let is_scrimlet = - Self::is_sled_scrimlet(log, sled_address).await?; - Ok(SledInfo::new( - sled_request.body.id, - subnet, - sled_address, - u2_zpools, - is_scrimlet, - )) - }, - )) - .await; - result? - }; - // Scrimlets get DNS records for running Dendrite. let scrimlets: Vec<_> = sled_info.iter().filter(|s| s.is_scrimlet).collect(); @@ -348,24 +367,18 @@ impl Plan { let dataset_name = sled.alloc_from_u2_zpool(DatasetKind::InternalDns)?; - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::InternalDns, - addresses: vec![ip], - dataset: Some(DatasetRequest { - id, - name: dataset_name, - service_address: http_address, - }), - services: vec![ServiceZoneService { - id, - details: ServiceType::InternalDns { - http_address, - dns_address, - gz_address: dns_subnet.gz_address().ip(), - gz_address_index: i.try_into().expect("Giant indices?"), + underlay_address: ip, + zone_type: OmicronZoneType::InternalDns { + dataset: OmicronZoneDataset { + pool_name: dataset_name.pool().clone(), }, - }], + http_address, + dns_address, + gz_address: dns_subnet.gz_address().ip(), + gz_address_index: i.try_into().expect("Giant indices?"), + }, }); } @@ -386,19 +399,15 @@ impl Plan { .unwrap(); let dataset_name = sled.alloc_from_u2_zpool(DatasetKind::CockroachDb)?; - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::CockroachDb, - addresses: vec![ip], - dataset: Some(DatasetRequest { - id, - name: dataset_name, - service_address: address, - }), - services: vec![ServiceZoneService { - id, - details: ServiceType::CockroachDb { address }, - }], + underlay_address: ip, + zone_type: OmicronZoneType::CockroachDb { + dataset: OmicronZoneDataset { + pool_name: dataset_name.pool().clone(), + }, + address, + }, }); } @@ -433,23 +442,17 @@ impl Plan { let dataset_kind = DatasetKind::ExternalDns; let dataset_name = sled.alloc_from_u2_zpool(dataset_kind)?; - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::ExternalDns, - addresses: vec![*http_address.ip()], - dataset: Some(DatasetRequest { - id, - name: dataset_name, - service_address: http_address, - }), - services: vec![ServiceZoneService { - id, - details: ServiceType::ExternalDns { - http_address, - dns_address, - nic, + underlay_address: *http_address.ip(), + zone_type: OmicronZoneType::ExternalDns { + dataset: OmicronZoneDataset { + pool_name: dataset_name.pool().clone(), }, - }], + http_address, + dns_address, + nic, + }, }); } @@ -471,33 +474,28 @@ impl Plan { ) .unwrap(); let (nic, external_ip) = svc_port_builder.next_nexus(id)?; - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::Nexus, - addresses: vec![address], - dataset: None, - services: vec![ServiceZoneService { - id, - details: ServiceType::Nexus { - internal_address: SocketAddrV6::new( - address, - omicron_common::address::NEXUS_INTERNAL_PORT, - 0, - 0, - ), - external_ip, - nic, - // Tell Nexus to use TLS if and only if the caller - // provided TLS certificates. This effectively - // determines the status of TLS for the lifetime of - // the rack. In production-like deployments, we'd - // always expect TLS to be enabled. It's only in - // development that it might not be. - external_tls: !config.external_certificates.is_empty(), - external_dns_servers: config.dns_servers.clone(), - }, - }], - }) + underlay_address: address, + zone_type: OmicronZoneType::Nexus { + internal_address: SocketAddrV6::new( + address, + omicron_common::address::NEXUS_INTERNAL_PORT, + 0, + 0, + ), + external_ip, + nic, + // Tell Nexus to use TLS if and only if the caller + // provided TLS certificates. This effectively + // determines the status of TLS for the lifetime of + // the rack. In production-like deployments, we'd + // always expect TLS to be enabled. It's only in + // development that it might not be. + external_tls: !config.external_certificates.is_empty(), + external_dns_servers: config.dns_servers.clone(), + }, + }); } // Provision Oximeter zones, continuing to stripe across sleds. @@ -518,22 +516,17 @@ impl Plan { omicron_common::address::OXIMETER_PORT, ) .unwrap(); - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::Oximeter, - addresses: vec![address], - dataset: None, - services: vec![ServiceZoneService { - id, - details: ServiceType::Oximeter { - address: SocketAddrV6::new( - address, - omicron_common::address::OXIMETER_PORT, - 0, - 0, - ), - }, - }], + underlay_address: address, + zone_type: OmicronZoneType::Oximeter { + address: SocketAddrV6::new( + address, + omicron_common::address::OXIMETER_PORT, + 0, + 0, + ), + }, }) } @@ -555,19 +548,15 @@ impl Plan { .unwrap(); let dataset_name = sled.alloc_from_u2_zpool(DatasetKind::Clickhouse)?; - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::Clickhouse, - addresses: vec![ip], - dataset: Some(DatasetRequest { - id, - name: dataset_name, - service_address: address, - }), - services: vec![ServiceZoneService { - id, - details: ServiceType::Clickhouse { address }, - }], + underlay_address: ip, + zone_type: OmicronZoneType::Clickhouse { + address, + dataset: OmicronZoneDataset { + pool_name: dataset_name.pool().clone(), + }, + }, }); } @@ -595,19 +584,15 @@ impl Plan { .unwrap(); let dataset_name = sled.alloc_from_u2_zpool(DatasetKind::ClickhouseKeeper)?; - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::ClickhouseKeeper, - addresses: vec![ip], - dataset: Some(DatasetRequest { - id, - name: dataset_name, - service_address: address, - }), - services: vec![ServiceZoneService { - id, - details: ServiceType::ClickhouseKeeper { address }, - }], + underlay_address: ip, + zone_type: OmicronZoneType::ClickhouseKeeper { + address, + dataset: OmicronZoneDataset { + pool_name: dataset_name.pool().clone(), + }, + }, }); } @@ -626,18 +611,13 @@ impl Plan { dns_builder .service_backend_zone(ServiceName::CruciblePantry, &zone, port) .unwrap(); - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::CruciblePantry, - addresses: vec![address], - dataset: None, - services: vec![ServiceZoneService { - id, - details: ServiceType::CruciblePantry { - address: SocketAddrV6::new(address, port, 0, 0), - }, - }], - }) + underlay_address: address, + zone_type: OmicronZoneType::CruciblePantry { + address: SocketAddrV6::new(address, port, 0, 0), + }, + }); } // Provision a Crucible zone on every zpool on every Sled. @@ -657,22 +637,13 @@ impl Plan { ) .unwrap(); - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::Crucible, - addresses: vec![ip], - dataset: Some(DatasetRequest { - id, - name: DatasetName::new( - pool.clone(), - DatasetKind::Crucible, - ), - service_address: address, - }), - services: vec![ServiceZoneService { - id, - details: ServiceType::Crucible { address }, - }], + underlay_address: ip, + zone_type: OmicronZoneType::Crucible { + address, + dataset: OmicronZoneDataset { pool_name: pool.clone() }, + }, }); } } @@ -685,47 +656,40 @@ impl Plan { let id = Uuid::new_v4(); let address = sled.addr_alloc.next().expect("Not enough addrs"); let zone = dns_builder.host_zone(id, address).unwrap(); + let ntp_address = SocketAddrV6::new(address, NTP_PORT, 0, 0); - let (services, svcname) = if idx < BOUNDARY_NTP_COUNT { + let (zone_type, svcname) = if idx < BOUNDARY_NTP_COUNT { boundary_ntp_servers.push(format!("{}.host.{}", id, DNS_ZONE)); let (nic, snat_cfg) = svc_port_builder.next_snat(id)?; ( - vec![ServiceZoneService { - id, - details: ServiceType::BoundaryNtp { - address: SocketAddrV6::new(address, NTP_PORT, 0, 0), - ntp_servers: config.ntp_servers.clone(), - dns_servers: config.dns_servers.clone(), - domain: None, - nic, - snat_cfg, - }, - }], + OmicronZoneType::BoundaryNtp { + address: ntp_address, + ntp_servers: config.ntp_servers.clone(), + dns_servers: config.dns_servers.clone(), + domain: None, + nic, + snat_cfg, + }, ServiceName::BoundaryNtp, ) } else { ( - vec![ServiceZoneService { - id, - details: ServiceType::InternalNtp { - address: SocketAddrV6::new(address, NTP_PORT, 0, 0), - ntp_servers: boundary_ntp_servers.clone(), - dns_servers: rack_dns_servers.clone(), - domain: None, - }, - }], + OmicronZoneType::InternalNtp { + address: ntp_address, + ntp_servers: boundary_ntp_servers.clone(), + dns_servers: rack_dns_servers.clone(), + domain: None, + }, ServiceName::InternalNtp, ) }; dns_builder.service_backend_zone(svcname, &zone, NTP_PORT).unwrap(); - sled.request.services.push(ServiceZoneRequest { + sled.request.zones.push(OmicronZoneConfig { id, - zone_type: ZoneType::Ntp, - addresses: vec![address], - dataset: None, - services, + underlay_address: address, + zone_type, }); } @@ -735,7 +699,42 @@ impl Plan { .collect(); let dns_config = dns_builder.build(); - let plan = Self { services, dns_config }; + Ok(Self { services, dns_config }) + } + + pub async fn create( + log: &Logger, + config: &Config, + storage_manager: &StorageHandle, + sleds: &HashMap, + ) -> Result { + // Load the information we need about each Sled to be able to allocate + // components on it. + let sled_info = { + let result: Result, PlanError> = + futures::future::try_join_all(sleds.values().map( + |sled_request| async { + let subnet = sled_request.body.subnet; + let sled_address = get_sled_address(subnet); + let u2_zpools = + Self::get_u2_zpools_from_sled(log, sled_address) + .await?; + let is_scrimlet = + Self::is_sled_scrimlet(log, sled_address).await?; + Ok(SledInfo::new( + sled_request.body.id, + subnet, + sled_address, + u2_zpools, + is_scrimlet, + )) + }, + )) + .await; + result? + }; + + let plan = Self::create_transient(config, sled_info)?; // Once we've constructed a plan, write it down to durable storage. let paths: Vec = storage_manager @@ -773,13 +772,13 @@ impl AddressBumpAllocator { } /// Wraps up the information used to allocate components to a Sled -struct SledInfo { +pub struct SledInfo { /// unique id for the sled agent - sled_id: Uuid, + pub sled_id: Uuid, /// the sled's unique IPv6 subnet subnet: Ipv6Subnet, /// the address of the Sled Agent on the sled's subnet - sled_address: SocketAddrV6, + pub sled_address: SocketAddrV6, /// the list of zpools on the Sled u2_zpools: Vec, /// spreads components across a Sled's zpools @@ -789,12 +788,12 @@ struct SledInfo { is_scrimlet: bool, /// allocator for addresses in this Sled's subnet addr_alloc: AddressBumpAllocator, - /// under-construction list of services being deployed to a Sled - request: SledRequest, + /// under-construction list of Omicron zones being deployed to a Sled + request: SledConfig, } impl SledInfo { - fn new( + pub fn new( sled_id: Uuid, subnet: Ipv6Subnet, sled_address: SocketAddrV6, @@ -1209,10 +1208,10 @@ mod tests { } #[test] - fn test_rss_service_plan_schema() { + fn test_rss_service_plan_v2_schema() { let schema = schemars::schema_for!(Plan); expectorate::assert_contents( - "../schema/rss-service-plan.json", + "../schema/rss-service-plan-v2.json", &serde_json::to_string_pretty(&schema).unwrap(), ); } diff --git a/sled-agent/src/rack_setup/service.rs b/sled-agent/src/rack_setup/service.rs index 7dcbfa7045..8038658fb1 100644 --- a/sled-agent/src/rack_setup/service.rs +++ b/sled-agent/src/rack_setup/service.rs @@ -11,15 +11,25 @@ //! - DNS records for those services //! - Handoff to Nexus, for control of Control Plane management //! -//! # Phases and Configuration Files +//! # Phases, state files, and restart behavior //! -//! Rack setup occurs in distinct phases which are denoted by the prescence of -//! configuration files. +//! Rack setup occurs in distinct phases that are denoted by the presence of +//! state files that get generated as RSS executes: //! //! - /pool/int/UUID/config/rss-sled-plan.json (Sled Plan) -//! - /pool/int/UUID/config/rss-service-plan.json (Service Plan) +//! - /pool/int/UUID/config/rss-service-plan-v2.json (Service Plan) //! - /pool/int/UUID/config/rss-plan-completed.marker (Plan Execution Complete) //! +//! These phases are described below. As each phase completes, a corresponding +//! state file is written. This mechanism is designed so that if RSS restarts +//! (e.g., after a crash) then it will resume execution using the same plans. +//! +//! The service plan file has "-v2" in the filename because its structure +//! changed in omicron#4466. It is possible that on startup, RSS finds an +//! older-form service plan. In that case, it fails altogether. We do not +//! expect this condition to happen in practice. See the implementation for +//! details. +//! //! ## Sled Plan //! //! RSS should start as a service executing on a Sidecar-attached Gimlet @@ -65,8 +75,8 @@ use crate::bootstrap::params::StartSledAgentRequest; use crate::bootstrap::rss_handle::BootstrapAgentHandle; use crate::nexus::{d2n_params, ConvertInto}; use crate::params::{ - AutonomousServiceOnlyError, ServiceType, ServiceZoneRequest, - ServiceZoneService, TimeSync, ZoneType, + OmicronZoneType, OmicronZonesConfig, TimeSync, + OMICRON_ZONES_CONFIG_INITIAL_GENERATION, }; use crate::rack_setup::plan::service::{ Plan as ServicePlan, PlanError as ServicePlanError, @@ -83,6 +93,7 @@ use nexus_client::{ types as NexusTypes, Client as NexusClient, Error as NexusError, }; use omicron_common::address::get_sled_address; +use omicron_common::api::external::Generation; use omicron_common::api::internal::shared::ExternalPortDiscovery; use omicron_common::backoff::{ retry_notify, retry_policy_internal_service_aggressive, BackoffError, @@ -257,45 +268,75 @@ impl ServiceInner { ServiceInner { log } } - async fn initialize_services_on_sled( + /// Requests that the specified sled configure zones as described by + /// `zones_config` + /// + /// This function succeeds even if the sled fails to apply the configuration + /// if the reason is that the sled is already running a newer configuration. + /// This might sound oddly specific but it's what our sole caller wants. + /// In particular, the caller is going to call this function a few times + /// with successive generation numbers. If we crash and go through the + /// process again, we might run into this case, and it's simplest to just + /// ignore it and proceed. + async fn initialize_zones_on_sled( &self, sled_address: SocketAddrV6, - services: &Vec, + zones_config: &OmicronZonesConfig, ) -> Result<(), SetupServiceError> { let dur = std::time::Duration::from_secs(60); let client = reqwest::ClientBuilder::new() .connect_timeout(dur) .build() .map_err(SetupServiceError::HttpClient)?; + let log = self.log.new(o!("sled_address" => sled_address.to_string())); let client = SledAgentClient::new_with_client( &format!("http://{}", sled_address), client, - self.log.new(o!("SledAgentClient" => sled_address.to_string())), + log.clone(), ); - let services = services - .iter() - .map(|s| s.clone().try_into()) - .collect::, AutonomousServiceOnlyError>>() - .map_err(|err| { - SetupServiceError::SledInitialization(err.to_string()) - })?; - - info!(self.log, "sending service requests..."); let services_put = || async { - info!(self.log, "initializing sled services: {:?}", services); - client - .services_put(&SledAgentTypes::ServiceEnsureBody { - services: services.clone(), - }) - .await - .map_err(BackoffError::transient)?; - Ok::<(), BackoffError>>(()) + info!( + log, + "attempting to set up sled's Omicron zones: {:?}", zones_config + ); + let result = + client.omicron_zones_put(&zones_config.clone().into()).await; + let Err(error) = result else { + return Ok::< + (), + BackoffError>, + >(()); + }; + + if let sled_agent_client::Error::ErrorResponse(response) = &error { + if response.status() == http::StatusCode::CONFLICT { + warn!( + log, + "ignoring attempt to initialize zones because \ + the server seems to be newer"; + "attempted_generation" => + i64::from(&zones_config.generation), + "req_id" => &response.request_id, + "server_message" => &response.message, + ); + + // If we attempt to initialize zones at generation X, and + // the server refuses because it's at some generation newer + // than X, then we treat that as success. See the doc + // comment on this function. + return Ok(()); + } + } + + // TODO Many other codes here should not be retried. See + // omicron#4578. + return Err(BackoffError::transient(error)); }; let log_failure = |error, delay| { warn!( - self.log, - "failed to initialize services"; + log, + "failed to initialize Omicron zones"; "error" => ?error, "retry_after" => ?delay, ); @@ -310,41 +351,26 @@ impl ServiceInner { Ok(()) } - // Ensure that all services of a particular type are running. + // Ensure that all services for a particular version are running. // // This is useful in a rack-setup context, where initial boot ordering // can matter for first-time-setup. // // Note that after first-time setup, the initialization order of // services should not matter. - async fn ensure_all_services_of_type( + // + // Further, it's possible that the target sled is already running a newer + // version. That's not an error here. + async fn ensure_zone_config_at_least( &self, - service_plan: &ServicePlan, - zone_types: &HashSet, + configs: &HashMap, ) -> Result<(), SetupServiceError> { - futures::future::join_all(service_plan.services.iter().map( - |(sled_address, services_request)| async move { - let services: Vec<_> = services_request - .services - .iter() - .filter_map(|service| { - if zone_types.contains(&service.zone_type) { - Some(service.clone()) - } else { - None - } - }) - .collect(); - if !services.is_empty() { - self.initialize_services_on_sled(*sled_address, &services) - .await?; - } - Ok(()) + cancel_safe_futures::future::join_all_then_try(configs.iter().map( + |(sled_address, zones_config)| async move { + self.initialize_zones_on_sled(*sled_address, zones_config).await }, )) - .await - .into_iter() - .collect::>()?; + .await?; Ok(()) } @@ -360,17 +386,15 @@ impl ServiceInner { let dns_server_ips = // iterate sleds service_plan.services.iter().filter_map( - |(_, services_request)| { - // iterate services for this sled - let dns_addrs: Vec = services_request - .services + |(_, sled_config)| { + // iterate zones for this sled + let dns_addrs: Vec = sled_config + .zones .iter() - .filter_map(|service| { - match &service.services[0] { - ServiceZoneService { - details: ServiceType::InternalDns { http_address, .. }, - .. - } => { + .filter_map(|zone_config| { + match &zone_config.zone_type { + OmicronZoneType::InternalDns { http_address, .. } + => { Some(*http_address) }, _ => None, @@ -546,25 +570,25 @@ impl ServiceInner { // a format which can be processed by Nexus. let mut services: Vec = vec![]; let mut datasets: Vec = vec![]; - for (addr, service_request) in service_plan.services.iter() { + for (addr, sled_config) in service_plan.services.iter() { let sled_id = *id_map .get(addr) .expect("Sled address in service plan, but not sled plan"); - for zone in &service_request.services { - services.extend(zone.into_nexus_service_req(sled_id).map_err( - |err| SetupServiceError::BadConfig(err.to_string()), - )?); + for zone in &sled_config.zones { + services.push(zone.to_nexus_service_req(sled_id)); } - for service in service_request.services.iter() { - if let Some(dataset) = &service.dataset { + for zone in &sled_config.zones { + if let Some((dataset_name, dataset_address)) = + zone.dataset_name_and_address() + { datasets.push(NexusTypes::DatasetCreateRequest { - zpool_id: dataset.name.pool().id(), - dataset_id: dataset.id, + zpool_id: dataset_name.pool().id(), + dataset_id: zone.id, request: NexusTypes::DatasetPutRequest { - address: dataset.service_address.to_string(), - kind: dataset.name.dataset().clone().convert(), + address: dataset_address.to_string(), + kind: dataset_name.dataset().clone().convert(), }, }) } @@ -598,14 +622,9 @@ impl ServiceInner { .collect(), addresses: config.addresses.clone(), switch: config.switch.into(), - uplink_port_speed: config - .uplink_port_speed - .clone() - .into(), - uplink_port_fec: config - .uplink_port_fec - .clone() - .into(), + uplink_port_speed: config.uplink_port_speed.into(), + uplink_port_fec: config.uplink_port_fec.into(), + autoneg: config.autoneg, bgp_peers: config .bgp_peers .iter() @@ -705,20 +724,22 @@ impl ServiceInner { ) -> Result<(), SetupServiceError> { // Now that datasets and zones have started for CockroachDB, // perform one-time initialization of the cluster. - let sled_address = - service_plan - .services - .iter() - .find_map(|(sled_address, sled_request)| { - if sled_request.services.iter().any(|service| { - service.zone_type == ZoneType::CockroachDb - }) { - Some(sled_address) - } else { - None - } - }) - .expect("Should not create service plans without CockroachDb"); + let sled_address = service_plan + .services + .iter() + .find_map(|(sled_address, sled_config)| { + if sled_config.zones.iter().any(|zone_config| { + matches!( + &zone_config.zone_type, + OmicronZoneType::CockroachDb { .. } + ) + }) { + Some(sled_address) + } else { + None + } + }) + .expect("Should not create service plans without CockroachDb"); let dur = std::time::Duration::from_secs(60); let client = reqwest::ClientBuilder::new() .connect_timeout(dur) @@ -758,8 +779,8 @@ impl ServiceInner { // time, it creates an allocation plan to provision subnets to an initial // set of sleds. // - // 2. SLED ALLOCATION PLAN EXECUTION. The RSS then carries out this plan, making - // requests to the sleds enumerated within the "allocation plan". + // 2. SLED ALLOCATION PLAN EXECUTION. The RSS then carries out this plan, + // making requests to the sleds enumerated within the "allocation plan". // // 3. SERVICE ALLOCATION PLAN CREATION. Now that Sled Agents are executing // on their respective subnets, they can be queried to create an @@ -770,7 +791,8 @@ impl ServiceInner { // // 5. MARKING SETUP COMPLETE. Once the RSS has successfully initialized the // rack, a marker file is created at "rss_completed_marker_path()". This - // indicates that the plan executed successfully, and no work remains. + // indicates that the plan executed successfully, and the only work + // remaining is to handoff to Nexus. async fn run( &self, config: &Config, @@ -951,11 +973,49 @@ impl ServiceInner { .await? }; + // The service plan describes all the zones that we will eventually + // deploy on each sled. But we cannot currently just deploy them all + // concurrently. We'll do it in a few stages, each corresponding to a + // version of each sled's configuration. + // + // - version 1: no services running + // (We don't have to do anything for this. But we do + // reserve this version number for "no services running" so + // that sled agents can begin with an initial, valid + // OmicronZonesConfig before they've got anything running.) + // - version 2: internal DNS only + // - version 3: internal DNS + NTP servers + // - version 4: internal DNS + NTP servers + CockroachDB + // - version 5: everything + // + // At each stage, we're specifying a complete configuration of what + // should be running on the sled -- including this version number. + // And Sled Agents will reject requests for versions older than the + // one they're currently running. Thus, the version number is a piece + // of global, distributed state. + // + // For now, we hardcode the requests we make to use specific version + // numbers. + let version1_nothing = + Generation::from(OMICRON_ZONES_CONFIG_INITIAL_GENERATION); + let version2_dns_only = version1_nothing.next(); + let version3_dns_and_ntp = version2_dns_only.next(); + let version4_cockroachdb = version3_dns_and_ntp.next(); + let version5_everything = version4_cockroachdb.next(); + // Set up internal DNS services first and write the initial // DNS configuration to the internal DNS servers. - let mut zone_types = HashSet::new(); - zone_types.insert(ZoneType::InternalDns); - self.ensure_all_services_of_type(&service_plan, &zone_types).await?; + let v1generator = OmicronZonesConfigGenerator::initial_version( + &service_plan, + version1_nothing, + ); + let v2generator = v1generator.new_version_with( + version2_dns_only, + &|zone_type: &OmicronZoneType| { + matches!(zone_type, OmicronZoneType::InternalDns { .. }) + }, + ); + self.ensure_zone_config_at_least(v2generator.sled_configs()).await?; self.initialize_internal_dns_records(&service_plan).await?; // Ask MGS in each switch zone which switch it is. @@ -964,10 +1024,17 @@ impl ServiceInner { .await; // Next start up the NTP services. - // Note we also specify internal DNS services again because it - // can ony be additive. - zone_types.insert(ZoneType::Ntp); - self.ensure_all_services_of_type(&service_plan, &zone_types).await?; + let v3generator = v2generator.new_version_with( + version3_dns_and_ntp, + &|zone_type: &OmicronZoneType| { + matches!( + zone_type, + OmicronZoneType::BoundaryNtp { .. } + | OmicronZoneType::InternalNtp { .. } + ) + }, + ); + self.ensure_zone_config_at_least(v3generator.sled_configs()).await?; // Wait until time is synchronized on all sleds before proceeding. self.wait_for_timesync(&sled_addresses).await?; @@ -975,35 +1042,22 @@ impl ServiceInner { info!(self.log, "Finished setting up Internal DNS and NTP"); // Wait until Cockroach has been initialized before running Nexus. - zone_types.insert(ZoneType::CockroachDb); - self.ensure_all_services_of_type(&service_plan, &zone_types).await?; + let v4generator = v3generator.new_version_with( + version4_cockroachdb, + &|zone_type: &OmicronZoneType| { + matches!(zone_type, OmicronZoneType::CockroachDb { .. }) + }, + ); + self.ensure_zone_config_at_least(v4generator.sled_configs()).await?; // Now that datasets and zones have started for CockroachDB, // perform one-time initialization of the cluster. self.initialize_cockroach(&service_plan).await?; - // Issue service initialization requests. - futures::future::join_all(service_plan.services.iter().map( - |(sled_address, services_request)| async move { - // With the current implementation of "initialize_services_on_sled", - // we must provide the set of *all* services that should be - // executing on a sled. - // - // This means re-requesting the DNS and NTP services, even if - // they are already running - this is fine, however, as the - // receiving sled agent doesn't modify the already-running - // service. - self.initialize_services_on_sled( - *sled_address, - &services_request.services, - ) - .await?; - Ok(()) - }, - )) - .await - .into_iter() - .collect::, SetupServiceError>>()?; + // Issue the rest of the zone initialization requests. + let v5generator = + v4generator.new_version_with(version5_everything, &|_| true); + self.ensure_zone_config_at_least(v5generator.sled_configs()).await?; info!(self.log, "Finished setting up services"); @@ -1036,3 +1090,272 @@ impl ServiceInner { Ok(()) } } + +/// Facilitates creating a sequence of OmicronZonesConfig objects for each sled +/// in a service plan to enable phased rollout of services +/// +/// The service plan itself defines which zones should appear on every sled. +/// However, we want to deploy these zones in phases: first internal DNS, then +/// NTP, then CockroachDB, etc. This interface generates sled configs for each +/// phase and enforces that: +/// +/// - each version includes all zones deployed in the previous iteration +/// - each sled's version number increases with each iteration +/// +struct OmicronZonesConfigGenerator<'a> { + service_plan: &'a ServicePlan, + last_configs: HashMap, +} + +impl<'a> OmicronZonesConfigGenerator<'a> { + /// Make a set of sled configurations for an initial version where each sled + /// has nothing deployed on it + fn initial_version( + service_plan: &'a ServicePlan, + initial_version: Generation, + ) -> Self { + let last_configs = service_plan + .services + .keys() + .map(|sled_address| { + ( + *sled_address, + OmicronZonesConfig { + generation: initial_version, + zones: vec![], + }, + ) + }) + .collect(); + Self { service_plan, last_configs } + } + + /// Returns the set of sled configurations produced for this version + fn sled_configs(&self) -> &HashMap { + &self.last_configs + } + + /// Produces a new set of configs for each sled based on the current set of + /// configurations, adding zones from the service plan matching + /// `zone_filter`. + /// + /// # Panics + /// + /// If `version` is not larger than the current version + fn new_version_with( + self, + version: Generation, + zone_filter: &(dyn Fn(&OmicronZoneType) -> bool + Send + Sync), + ) -> OmicronZonesConfigGenerator<'a> { + let last_configs = self + .service_plan + .services + .iter() + .map(|(sled_address, sled_config)| { + let mut zones = match self.last_configs.get(sled_address) { + Some(config) => { + assert!(version > config.generation); + config.zones.clone() + } + None => Vec::new(), + }; + + let zones_already = + zones.iter().map(|z| z.id).collect::>(); + zones.extend( + sled_config + .zones + .iter() + .filter(|z| { + !zones_already.contains(&z.id) + && zone_filter(&z.zone_type) + }) + .cloned(), + ); + + let config = OmicronZonesConfig { generation: version, zones }; + (*sled_address, config) + }) + .collect(); + Self { service_plan: self.service_plan, last_configs } + } +} + +#[cfg(test)] +mod test { + use super::OmicronZonesConfigGenerator; + use crate::{ + params::OmicronZoneType, + rack_setup::plan::service::{Plan as ServicePlan, SledInfo}, + }; + use illumos_utils::zpool::ZpoolName; + use omicron_common::{address::Ipv6Subnet, api::external::Generation}; + + fn make_test_service_plan() -> ServicePlan { + let rss_config = crate::bootstrap::params::test_config(); + let fake_sleds = vec![ + SledInfo::new( + "d4ba4bbe-8542-4907-bc8f-48df53eb5089".parse().unwrap(), + Ipv6Subnet::new("fd00:1122:3344:101::1".parse().unwrap()), + "[fd00:1122:3344:101::1]:80".parse().unwrap(), + vec![ + ZpoolName::new_internal( + "c5885278-0ae2-4f1e-9223-07f2ada818e1".parse().unwrap(), + ), + ZpoolName::new_internal( + "57465977-8275-43aa-a320-b6cd5cb20ca6".parse().unwrap(), + ), + ZpoolName::new_external( + "886f9fe7-bf70-4ddd-ae92-764dc3ed14ab".parse().unwrap(), + ), + ZpoolName::new_external( + "4c9061b1-345b-4985-8cbd-a2a899f15b68".parse().unwrap(), + ), + ZpoolName::new_external( + "b2bd488e-b187-42a0-b157-9ab0f70d91a8".parse().unwrap(), + ), + ], + true, + ), + SledInfo::new( + "b4359dea-665d-41ca-a681-f55912f2d5d0".parse().unwrap(), + Ipv6Subnet::new("fd00:1122:3344:102::1".parse().unwrap()), + "[fd00:1122:3344:102::1]:80".parse().unwrap(), + vec![ + ZpoolName::new_internal( + "34d6b5e5-a09f-4e96-a599-fa306ce6d983".parse().unwrap(), + ), + ZpoolName::new_internal( + "e9b8d1ea-da29-4b61-a493-c0ed319098da".parse().unwrap(), + ), + ZpoolName::new_external( + "37f8e903-2adb-4613-b78c-198122c289f0".parse().unwrap(), + ), + ZpoolName::new_external( + "b50f787c-97b3-4b91-a5bd-99d11fc86fb8".parse().unwrap(), + ), + ZpoolName::new_external( + "809e50c8-930e-413a-950c-69a540b688e2".parse().unwrap(), + ), + ], + true, + ), + ]; + let service_plan = + ServicePlan::create_transient(&rss_config, fake_sleds) + .expect("failed to create service plan"); + + service_plan + } + + #[test] + fn test_omicron_zone_configs() { + let service_plan = make_test_service_plan(); + + // Verify the initial state. + let g1 = Generation::new(); + let v1 = + OmicronZonesConfigGenerator::initial_version(&service_plan, g1); + assert_eq!( + service_plan.services.keys().len(), + v1.sled_configs().keys().len() + ); + for (_, configs) in v1.sled_configs() { + assert_eq!(configs.generation, g1); + assert!(configs.zones.is_empty()); + } + + // Verify that we can add a bunch of zones of a given type. + let g2 = g1.next(); + let v2 = v1.new_version_with(g2, &|zone_type| { + matches!(zone_type, OmicronZoneType::InternalDns { .. }) + }); + let mut v2_nfound = 0; + for (_, config) in v2.sled_configs() { + assert_eq!(config.generation, g2); + v2_nfound += config.zones.len(); + for z in &config.zones { + // The only zones we should find are the Internal DNS ones. + assert!(matches!( + &z.zone_type, + OmicronZoneType::InternalDns { .. } + )); + } + } + // There should have been at least one InternalDns zone. + assert!(v2_nfound > 0); + + // Try again to add zones of the same type. This should be a no-op. + let g3 = g2.next(); + let v3 = v2.new_version_with(g3, &|zone_type| { + matches!(zone_type, OmicronZoneType::InternalDns { .. }) + }); + let mut v3_nfound = 0; + for (_, config) in v3.sled_configs() { + assert_eq!(config.generation, g3); + v3_nfound += config.zones.len(); + for z in &config.zones { + // The only zones we should find are the Internal DNS ones. + assert!(matches!( + &z.zone_type, + OmicronZoneType::InternalDns { .. } + )); + } + } + assert_eq!(v2_nfound, v3_nfound); + + // Now try adding zones of a different type. We should still have all + // the Internal DNS ones, plus a few more. + let g4 = g3.next(); + let v4 = v3.new_version_with(g4, &|zone_type| { + matches!(zone_type, OmicronZoneType::Nexus { .. }) + }); + let mut v4_nfound_dns = 0; + let mut v4_nfound = 0; + for (_, config) in v4.sled_configs() { + assert_eq!(config.generation, g4); + v4_nfound += config.zones.len(); + for z in &config.zones { + match &z.zone_type { + OmicronZoneType::InternalDns { .. } => v4_nfound_dns += 1, + OmicronZoneType::Nexus { .. } => (), + _ => panic!("unexpectedly found a wrong zone type"), + } + } + } + assert_eq!(v4_nfound_dns, v3_nfound); + assert!(v4_nfound > v3_nfound); + + // Now try adding zones that match no filter. Again, this should be a + // no-op but we should still have all the same zones we had before. + let g5 = g4.next(); + let v5 = v4.new_version_with(g5, &|_| false); + let mut v5_nfound = 0; + for (_, config) in v5.sled_configs() { + assert_eq!(config.generation, g5); + v5_nfound += config.zones.len(); + for z in &config.zones { + assert!(matches!( + &z.zone_type, + OmicronZoneType::InternalDns { .. } + | OmicronZoneType::Nexus { .. } + )); + } + } + assert_eq!(v4_nfound, v5_nfound); + + // Finally, try adding the rest of the zones. + let g6 = g5.next(); + let v6 = v5.new_version_with(g6, &|_| true); + let mut v6_nfound = 0; + for (sled_address, config) in v6.sled_configs() { + assert_eq!(config.generation, g6); + v6_nfound += config.zones.len(); + assert_eq!( + config.zones.len(), + service_plan.services.get(sled_address).unwrap().zones.len() + ); + } + assert!(v6_nfound > v5_nfound); + } +} diff --git a/sled-agent/src/services.rs b/sled-agent/src/services.rs index 2caa640e22..dc309e8423 100644 --- a/sled-agent/src/services.rs +++ b/sled-agent/src/services.rs @@ -20,10 +20,10 @@ //! of what other services Nexus wants to have executing on the sled. //! //! To accomplish this, the following interfaces are exposed: -//! - [ServiceManager::ensure_all_services_persistent] exposes an API to request -//! a set of services that should persist beyond reboot. +//! - [ServiceManager::ensure_all_omicron_zones_persistent] exposes an API to +//! request a set of Omicron zones that should persist beyond reboot. //! - [ServiceManager::activate_switch] exposes an API to specifically enable -//! or disable (via [ServiceManager::deactivate_switch]) the switch zone. +//! or disable (via [ServiceManager::deactivate_switch]) the switch zone. use crate::bootstrap::early_networking::{ EarlyNetworkSetup, EarlyNetworkSetupError, @@ -31,11 +31,11 @@ use crate::bootstrap::early_networking::{ use crate::bootstrap::BootstrapNetworking; use crate::config::SidecarRevision; use crate::params::{ - DendriteAsic, ServiceEnsureBody, ServiceType, ServiceZoneRequest, - ServiceZoneService, TimeSync, ZoneBundleCause, ZoneBundleMetadata, - ZoneType, + DendriteAsic, OmicronZoneConfig, OmicronZoneType, OmicronZonesConfig, + TimeSync, ZoneBundleCause, ZoneBundleMetadata, ZoneType, }; use crate::profile::*; +use crate::services_migration::{AllZoneRequests, SERVICES_LEDGER_FILENAME}; use crate::smf_helper::Service; use crate::smf_helper::SmfHelper; use crate::zone_bundle::BundleError; @@ -89,28 +89,26 @@ use omicron_common::nexus_config::{ }; use once_cell::sync::OnceCell; use rand::prelude::SliceRandom; -use rand::SeedableRng; use sled_hardware::is_gimlet; use sled_hardware::underlay; use sled_hardware::underlay::BOOTSTRAP_PREFIX; use sled_hardware::Baseboard; use sled_hardware::SledMode; -use sled_storage::dataset::{CONFIG_DATASET, INSTALL_DATASET, ZONE_DATASET}; +use sled_storage::dataset::{ + DatasetKind, DatasetName, CONFIG_DATASET, INSTALL_DATASET, ZONE_DATASET, +}; use sled_storage::manager::StorageHandle; use slog::Logger; use std::collections::BTreeMap; use std::collections::HashSet; -use std::iter; use std::iter::FromIterator; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::time::{SystemTime, UNIX_EPOCH}; use tokio::io::AsyncWriteExt; -use tokio::sync::oneshot; use tokio::sync::Mutex; -use tokio::sync::MutexGuard; +use tokio::sync::{oneshot, MutexGuard}; use tokio::task::JoinHandle; use uuid::Uuid; @@ -198,9 +196,6 @@ pub enum Error { #[error("Could not initialize service {service} as requested: {message}")] BadServiceRequest { service: String, message: String }, - #[error("Services already configured for this Sled Agent")] - ServicesAlreadyConfigured, - #[error("Failed to get address: {0}")] GetAddressFailure(#[from] illumos_utils::zone::GetAddressError), @@ -224,6 +219,17 @@ pub enum Error { #[error("Error querying simnet devices")] Simnet(#[from] GetSimnetError), + + #[error( + "Requested generation ({requested}) is older than current ({current})" + )] + RequestedConfigOutdated { requested: Generation, current: Generation }, + + #[error("Requested generation {0} with different zones than before")] + RequestedConfigConflicts(Generation), + + #[error("Error migrating old-format services ledger: {0:#}")] + ServicesMigration(anyhow::Error), } impl Error { @@ -237,8 +243,18 @@ impl Error { impl From for omicron_common::api::external::Error { fn from(err: Error) -> Self { - omicron_common::api::external::Error::InternalError { - internal_message: err.to_string(), + match err { + err @ Error::RequestedConfigConflicts(_) => { + omicron_common::api::external::Error::invalid_request( + &err.to_string(), + ) + } + err @ Error::RequestedConfigOutdated { .. } => { + omicron_common::api::external::Error::conflict(&err.to_string()) + } + _ => omicron_common::api::external::Error::InternalError { + internal_message: err.to_string(), + }, } } } @@ -274,42 +290,176 @@ impl Config { } // The filename of the ledger, within the provided directory. -const SERVICES_LEDGER_FILENAME: &str = "services.json"; - -// A wrapper around `ZoneRequest`, which allows it to be serialized -// to a JSON file. -#[derive(Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] -struct AllZoneRequests { - generation: Generation, - requests: Vec, +const ZONES_LEDGER_FILENAME: &str = "omicron-zones.json"; + +/// Combines the Nexus-provided `OmicronZonesConfig` (which describes what Nexus +/// wants for all of its zones) with the locally-determined configuration for +/// these zones. +#[derive( + Clone, Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema, +)] +pub struct OmicronZonesConfigLocal { + /// generation of the Omicron-provided part of the configuration + /// + /// This generation number is outside of Sled Agent's control. We store + /// exactly what we were given and use this number to decide when to + /// fail requests to establish an outdated configuration. + /// + /// You can think of this as a major version number, with + /// `ledger_generation` being a minor version number. See + /// `is_newer_than()`. + pub omicron_generation: Generation, + + /// ledger-managed generation number + /// + /// This generation is managed by the ledger facility itself. It's bumped + /// whenever we write a new ledger. In practice, we don't currently have + /// any reason to bump this _for a given Omicron generation_ so it's + /// somewhat redundant. In principle, if we needed to modify the ledgered + /// configuration due to some event that doesn't change the Omicron config + /// (e.g., if we wanted to move the root filesystem to a different path), we + /// could do that by bumping this generation. + pub ledger_generation: Generation, + pub zones: Vec, } -impl Default for AllZoneRequests { - fn default() -> Self { - Self { generation: Generation::new(), requests: vec![] } +impl Ledgerable for OmicronZonesConfigLocal { + fn is_newer_than(&self, other: &OmicronZonesConfigLocal) -> bool { + self.omicron_generation > other.omicron_generation + || (self.omicron_generation == other.omicron_generation + && self.ledger_generation >= other.ledger_generation) + } + + fn generation_bump(&mut self) { + self.ledger_generation = self.ledger_generation.next(); } } -impl Ledgerable for AllZoneRequests { - fn is_newer_than(&self, other: &AllZoneRequests) -> bool { - self.generation >= other.generation +impl OmicronZonesConfigLocal { + /// Returns the initial configuration for generation 1, which has no zones + pub fn initial() -> OmicronZonesConfigLocal { + OmicronZonesConfigLocal { + omicron_generation: Generation::new(), + ledger_generation: Generation::new(), + zones: vec![], + } } - fn generation_bump(&mut self) { - self.generation = self.generation.next(); + pub fn to_omicron_zones_config(self) -> OmicronZonesConfig { + OmicronZonesConfig { + generation: self.omicron_generation, + zones: self.zones.into_iter().map(|z| z.zone).collect(), + } } } -// This struct represents the combo of "what zone did you ask for" + "where did -// we put it". -#[derive(Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] -struct ZoneRequest { - zone: ServiceZoneRequest, - // TODO: Consider collapsing "root" into ServiceZoneRequest +/// Combines the Nexus-provided `OmicronZoneConfig` (which describes what Nexus +/// wants for this zone) with any locally-determined configuration (like the +/// path to the root filesystem) +#[derive( + Clone, Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema, +)] +pub struct OmicronZoneConfigLocal { + pub zone: OmicronZoneConfig, #[schemars(with = "String")] + pub root: Utf8PathBuf, +} + +/// Describes how we want a switch zone to be configured +/// +/// This is analogous to `OmicronZoneConfig`, but for the switch zone (which is +/// operated autonomously by the Sled Agent, not managed by Omicron). +#[derive(Clone)] +struct SwitchZoneConfig { + id: Uuid, + addresses: Vec, + services: Vec, +} + +/// Describes one of several services that may be deployed in a switch zone +/// +/// Some of these are only present in certain configurations (e.g., with a real +/// Tofino vs. SoftNPU) or are configured differently depending on the +/// configuration. +#[derive(Clone)] +enum SwitchService { + ManagementGatewayService, + Wicketd { baseboard: Baseboard }, + Dendrite { asic: DendriteAsic }, + Tfport { pkt_source: String, asic: DendriteAsic }, + Uplink, + MgDdm { mode: String }, + Mgd, + SpSim, +} + +impl crate::smf_helper::Service for SwitchService { + fn service_name(&self) -> String { + match self { + SwitchService::ManagementGatewayService => "mgs", + SwitchService::Wicketd { .. } => "wicketd", + SwitchService::Dendrite { .. } => "dendrite", + SwitchService::Tfport { .. } => "tfport", + SwitchService::Uplink { .. } => "uplink", + SwitchService::MgDdm { .. } => "mg-ddm", + SwitchService::Mgd => "mgd", + SwitchService::SpSim => "sp-sim", + } + .to_owned() + } + fn smf_name(&self) -> String { + format!("svc:/oxide/{}", self.service_name()) + } + fn should_import(&self) -> bool { + true + } +} + +/// Combines the generic `SwitchZoneConfig` with other locally-determined +/// configuration +/// +/// This is analogous to `OmicronZoneConfigLocal`, but for the switch zone. +struct SwitchZoneConfigLocal { + zone: SwitchZoneConfig, root: Utf8PathBuf, } +/// Describes either an Omicron-managed zone or the switch zone, used for +/// functions that operate on either one or the other +enum ZoneArgs<'a> { + Omicron(&'a OmicronZoneConfigLocal), + Switch(&'a SwitchZoneConfigLocal), +} + +impl<'a> ZoneArgs<'a> { + /// If this is an Omicron zone, return its type + pub fn omicron_type(&self) -> Option<&'a OmicronZoneType> { + match self { + ZoneArgs::Omicron(zone_config) => Some(&zone_config.zone.zone_type), + ZoneArgs::Switch(_) => None, + } + } + + /// If this is a sled-local (switch) zone, iterate over the services it's + /// supposed to be running + pub fn sled_local_services( + &self, + ) -> Box + 'a> { + match self { + ZoneArgs::Omicron(_) => Box::new(std::iter::empty()), + ZoneArgs::Switch(request) => Box::new(request.zone.services.iter()), + } + } + + /// Return the root filesystem path for this zone + pub fn root(&self) -> &Utf8Path { + match self { + ZoneArgs::Omicron(zone_config) => &zone_config.root, + ZoneArgs::Switch(zone_request) => &zone_request.root, + } + } +} + struct Task { // A signal for the initializer task to terminate exit_tx: oneshot::Sender<()>, @@ -335,7 +485,7 @@ enum SledLocalZone { // of certain links. Initializing { // The request for the zone - request: ServiceZoneRequest, + request: SwitchZoneConfig, // A background task which keeps looping until the zone is initialized worker: Option, // Filesystems for the switch zone to mount @@ -348,7 +498,7 @@ enum SledLocalZone { // The Zone is currently running. Running { // The original request for the zone - request: ServiceZoneRequest, + request: SwitchZoneConfig, // The currently running zone zone: RunningZone, }, @@ -485,6 +635,173 @@ impl ServiceManager { .collect() } + async fn all_omicron_zone_ledgers(&self) -> Vec { + if let Some(dir) = self.inner.ledger_directory_override.get() { + return vec![dir.join(ZONES_LEDGER_FILENAME)]; + } + let resources = self.inner.storage.get_latest_resources().await; + resources + .all_m2_mountpoints(CONFIG_DATASET) + .into_iter() + .map(|p| p.join(ZONES_LEDGER_FILENAME)) + .collect() + } + + // Loads persistent configuration about any Omicron-managed zones that we're + // supposed to be running. + // + // For historical reasons, there are two possible places this configuration + // could live, each with its own format. This function first checks the + // newer one. If no configuration was found there, it checks the older + // one. If only the older one was found, it is converted into the new form + // so that future calls will only look at the new form. + async fn load_ledgered_zones( + &self, + // This argument attempts to ensure that the caller holds the right + // lock. + _map: &MutexGuard<'_, BTreeMap>, + ) -> Result>, Error> { + // First, try to load the current software's zone ledger. If that + // works, we're done. + let log = &self.inner.log; + let ledger_paths = self.all_omicron_zone_ledgers().await; + info!(log, "Loading Omicron zones from: {ledger_paths:?}"); + let maybe_ledger = + Ledger::::new(log, ledger_paths.clone()) + .await; + + if let Some(ledger) = maybe_ledger { + info!( + log, + "Loaded Omicron zones"; + "zones_config" => ?ledger.data() + ); + return Ok(Some(ledger)); + } + + // Now look for the ledger used by previous versions. If we find it, + // we'll convert it and write out a new ledger used by the current + // software. + info!( + log, + "Loading Omicron zones - No zones detected \ + (will look for old-format services)" + ); + let services_ledger_paths = self.all_service_ledgers().await; + info!( + log, + "Loading old-format services from: {services_ledger_paths:?}" + ); + + let maybe_ledger = + Ledger::::new(log, services_ledger_paths.clone()) + .await; + let maybe_converted = match maybe_ledger { + None => { + // The ledger ignores all errors attempting to load files. That + // might be fine most of the time. In this case, we want to + // raise a big red flag if we find an old-format ledger that we + // can't process. + if services_ledger_paths.iter().any(|p| p.exists()) { + Err(Error::ServicesMigration(anyhow!( + "failed to read or parse old-format ledger, \ + but one exists" + ))) + } else { + // There was no old-format ledger at all. + return Ok(None); + } + } + Some(ledger) => { + let all_services = ledger.into_inner(); + OmicronZonesConfigLocal::try_from(all_services) + .map_err(Error::ServicesMigration) + } + }; + + match maybe_converted { + Err(error) => { + // We've tried to test thoroughly so that this should never + // happen. If for some reason it does happen, engineering + // intervention is likely to be required to figure out how to + // proceed. The current software does not directly support + // whatever was in the ledger, and it's not safe to just come up + // with no zones when we're supposed to be running stuff. We'll + // need to figure out what's unexpected about what we found in + // the ledger and figure out how to fix the + // conversion. + error!( + log, + "Loading Omicron zones - found services but failed \ + to convert them (support intervention required): \ + {:#}", + error + ); + return Err(error); + } + Ok(new_config) => { + // We've successfully converted the old ledger. Write a new + // one. + info!( + log, + "Successfully migrated old-format services ledger to \ + zones ledger" + ); + let mut ledger = Ledger::::new_with( + log, + ledger_paths.clone(), + new_config, + ); + + ledger.commit().await?; + + // We could consider removing the old ledger here. That would + // not guarantee that it would be gone, though, because we could + // crash during `ledger.commit()` above having written at least + // one of the new ledgers. In that case, we won't go through + // this code path again on restart. If we wanted to ensure the + // old-format ledger was gone after the migration, we could + // consider unconditionally removing the old ledger paths in the + // caller, after we've got a copy of the new-format ledger. + // + // Should we? In principle, it shouldn't matter either way + // because we will never look at the old-format ledger unless we + // don't have a new-format one, and we should now have a + // new-format one forever now. + // + // When might it matter? Two cases: + // + // (1) If the sled agent is downgraded to a previous version + // that doesn't know about the new-format ledger. Do we + // want that sled agent to use the old-format one? It + // depends. If that downgrade happens immediately because + // the upgrade to the first new-format version was a + // disaster, then we'd probably rather the downgraded sled + // agent _did_ start its zones. If the downgrade happens + // months later, potentially after various additional + // reconfigurations, then that old-format ledger is probably + // out of date and shouldn't be used. There's no way to + // really know which case we're in, but the latter seems + // quite unlikely (why would we downgrade so far back after + // so long?). So that's a reason to keep the old-format + // ledger. + // + // (2) Suppose a developer or Oxide support engineer removes the + // new ledger for some reason, maybe thinking sled agent + // would come up with no zones running. They'll be + // surprised to discover that it actually starts running a + // potentially old set of zones. This probably only matters + // on a production system, and even then, it probably + // shouldn't happen. + // + // Given these cases, we're left ambivalent. We choose to keep + // the old ledger around. If nothing else, if something goes + // wrong, we'll have a copy of its last contents! + Ok(Some(ledger)) + } + } + } + // TODO(https://github.com/oxidecomputer/omicron/issues/2973): // // The sled agent retries this function indefinitely at the call-site, but @@ -495,65 +812,67 @@ impl ServiceManager { // more clearly. pub async fn load_services(&self) -> Result<(), Error> { let log = &self.inner.log; - let ledger_paths = self.all_service_ledgers().await; - info!(log, "Loading services from: {ledger_paths:?}"); - let mut existing_zones = self.inner.zones.lock().await; let Some(mut ledger) = - Ledger::::new(log, ledger_paths).await + self.load_ledgered_zones(&existing_zones).await? else { - info!(log, "Loading services - No services detected"); + // Nothing found -- nothing to do. + info!( + log, + "Loading Omicron zones - \ + no zones nor old-format services found" + ); return Ok(()); }; - let services = ledger.data_mut(); + + let zones_config = ledger.data_mut(); + info!( + log, + "Loaded Omicron zones"; + "zones_config" => ?zones_config + ); + let omicron_zones_config = + zones_config.clone().to_omicron_zones_config(); // Initialize internal DNS only first: we need it to look up the // boundary switch addresses. This dependency is implicit: when we call - // `ensure_all_services` below, we eventually land in + // `ensure_all_omicron_zones` below, we eventually land in // `opte_ports_needed()`, which for some service types (including Ntp // but _not_ including InternalDns), we perform internal DNS lookups. let all_zones_request = self - .ensure_all_services( + .ensure_all_omicron_zones( &mut existing_zones, - &AllZoneRequests::default(), - ServiceEnsureBody { - services: services - .requests - .clone() - .into_iter() - .filter(|svc| { - matches!( - svc.zone.zone_type, - ZoneType::InternalDns | ZoneType::Ntp - ) - }) - .map(|zone_request| zone_request.zone) - .collect(), + None, + omicron_zones_config.clone(), + |z: &OmicronZoneConfig| { + matches!( + z.zone_type, + OmicronZoneType::InternalDns { .. } + | OmicronZoneType::BoundaryNtp { .. } + | OmicronZoneType::InternalNtp { .. } + ) }, ) .await?; // Initialize NTP services next as they are required for time // synchronization, which is a pre-requisite for the other services. We - // keep `ZoneType::InternalDns` because `ensure_all_services` is - // additive. + // keep `OmicronZoneType::InternalDns` because + // `ensure_all_omicron_zones` is additive. + // TODO This looks like a duplicate of the block above -- why do we do + // this? let all_zones_request = self - .ensure_all_services( + .ensure_all_omicron_zones( &mut existing_zones, - &all_zones_request, - ServiceEnsureBody { - services: services - .requests - .clone() - .into_iter() - .filter(|svc| { - matches!( - svc.zone.zone_type, - ZoneType::InternalDns | ZoneType::Ntp - ) - }) - .map(|zone_request| zone_request.zone) - .collect(), + Some(&all_zones_request), + omicron_zones_config.clone(), + |z: &OmicronZoneConfig| { + matches!( + z.zone_type, + OmicronZoneType::InternalDns { .. } + | OmicronZoneType::BoundaryNtp { .. } + | OmicronZoneType::InternalNtp { .. } + ) }, ) .await?; @@ -595,17 +914,11 @@ impl ServiceManager { let mut existing_zones = self.inner.zones.lock().await; // Initialize all remaining services - self.ensure_all_services( + self.ensure_all_omicron_zones( &mut existing_zones, - &all_zones_request, - ServiceEnsureBody { - services: services - .requests - .clone() - .into_iter() - .map(|zone_request| zone_request.zone) - .collect(), - }, + Some(&all_zones_request), + omicron_zones_config, + |_| true, ) .await?; Ok(()) @@ -661,11 +974,11 @@ impl ServiceManager { // Check the services intended to run in the zone to determine whether any // physical devices need to be mapped into the zone when it is created. - fn devices_needed(req: &ServiceZoneRequest) -> Result, Error> { + fn devices_needed(zone_args: &ZoneArgs<'_>) -> Result, Error> { let mut devices = vec![]; - for svc in &req.services { - match &svc.details { - ServiceType::Dendrite { asic: DendriteAsic::TofinoAsic } => { + for svc_details in zone_args.sled_local_services() { + match svc_details { + SwitchService::Dendrite { asic: DendriteAsic::TofinoAsic } => { if let Ok(Some(n)) = tofino::get_tofino() { if let Ok(device_path) = n.device_path() { devices.push(device_path); @@ -676,7 +989,7 @@ impl ServiceManager { device: "tofino".to_string(), }); } - ServiceType::Dendrite { + SwitchService::Dendrite { asic: DendriteAsic::SoftNpuPropolisDevice, } => { devices.push("/dev/tty03".into()); @@ -700,18 +1013,17 @@ impl ServiceManager { // bootstrap address. fn bootstrap_address_needed( &self, - req: &ServiceZoneRequest, + zone_args: &ZoneArgs<'_>, ) -> Result, Error> { - match req.zone_type { - ZoneType::Switch => { - let link = self - .inner - .bootstrap_vnic_allocator - .new_bootstrap() - .map_err(Error::SledLocalVnicCreation)?; - Ok(Some((link, self.inner.switch_zone_bootstrap_address))) - } - _ => Ok(None), + if let ZoneArgs::Switch(_) = zone_args { + let link = self + .inner + .bootstrap_vnic_allocator + .new_bootstrap() + .map_err(Error::SledLocalVnicCreation)?; + Ok(Some((link, self.inner.switch_zone_bootstrap_address))) + } else { + Ok(None) } } @@ -736,7 +1048,7 @@ impl ServiceManager { // local addresses in the zone. fn links_needed( &self, - req: &ServiceZoneRequest, + zone_args: &ZoneArgs<'_>, ) -> Result, Error> { let mut links: Vec<(Link, bool)> = Vec::new(); @@ -744,12 +1056,12 @@ impl ServiceManager { Error::Underlay(underlay::Error::SystemDetection(e)) })?; - for svc in &req.services { - match &svc.details { - ServiceType::Tfport { pkt_source, asic: _ } => { - // The tfport service requires a MAC device to/from which sidecar - // packets may be multiplexed. If the link isn't present, don't - // bother trying to start the zone. + for svc_details in zone_args.sled_local_services() { + match &svc_details { + SwitchService::Tfport { pkt_source, asic: _ } => { + // The tfport service requires a MAC device to/from which + // sidecar packets may be multiplexed. If the link isn't + // present, don't bother trying to start the zone. match Dladm::verify_link(pkt_source) { Ok(link) => { // It's important that tfpkt does **not** receive a @@ -765,7 +1077,7 @@ impl ServiceManager { } } } - ServiceType::MgDdm { .. } => { + SwitchService::MgDdm { .. } => { // If on a non-gimlet, sled-agent can be configured to map // links into the switch zone. Validate those links here. for link in &self.inner.switch_zone_maghemite_links { @@ -796,15 +1108,18 @@ impl ServiceManager { } // Check the services intended to run in the zone to determine whether any - // OPTE ports need to be created and mapped into the zone when it is created. + // OPTE ports need to be created and mapped into the zone when it is + // created. async fn opte_ports_needed( &self, - req: &ServiceZoneRequest, + zone_args: &ZoneArgs<'_>, ) -> Result, Error> { // Only some services currently need OPTE ports if !matches!( - req.zone_type, - ZoneType::ExternalDns | ZoneType::Nexus | ZoneType::Ntp + zone_args.omicron_type(), + Some(OmicronZoneType::ExternalDns { .. }) + | Some(OmicronZoneType::Nexus { .. }) + | Some(OmicronZoneType::BoundaryNtp { .. }) ) { return Ok(vec![]); } @@ -851,100 +1166,120 @@ impl ServiceManager { }) .collect(); - let mut ports = vec![]; - for svc in &req.services { - let external_ip; - let (nic, snat, external_ips) = match &svc.details { - ServiceType::Nexus { external_ip, nic, .. } => { - (nic, None, std::slice::from_ref(external_ip)) - } - ServiceType::ExternalDns { dns_address, nic, .. } => { - external_ip = dns_address.ip(); - (nic, None, std::slice::from_ref(&external_ip)) - } - ServiceType::BoundaryNtp { nic, snat_cfg, .. } => { - (nic, Some(*snat_cfg), &[][..]) - } - _ => continue, - }; - - // Create the OPTE port for the service. - // Note we don't plumb any firewall rules at this point, - // Nexus will plumb them down later but the default OPTE - // config allows outbound access which is enough for - // Boundary NTP which needs to come up before Nexus. - let port = port_manager - .create_port(nic, snat, external_ips, &[], DhcpCfg::default()) - .map_err(|err| Error::ServicePortCreation { - service: svc.details.to_string(), - err: Box::new(err), - })?; + let external_ip; + let (zone_type_str, nic, snat, external_ips) = match &zone_args + .omicron_type() + { + Some( + zone_type @ OmicronZoneType::Nexus { external_ip, nic, .. }, + ) => ( + zone_type.zone_type_str(), + nic, + None, + std::slice::from_ref(external_ip), + ), + Some( + zone_type @ OmicronZoneType::ExternalDns { + dns_address, + nic, + .. + }, + ) => { + external_ip = dns_address.ip(); + ( + zone_type.zone_type_str(), + nic, + None, + std::slice::from_ref(&external_ip), + ) + } + Some( + zone_type @ OmicronZoneType::BoundaryNtp { + nic, snat_cfg, .. + }, + ) => (zone_type.zone_type_str(), nic, Some(*snat_cfg), &[][..]), + _ => unreachable!("unexpected zone type"), + }; - // We also need to update the switch with the NAT mappings - let (target_ip, first_port, last_port) = match snat { - Some(s) => (s.ip, s.first_port, s.last_port), - None => (external_ips[0], 0, u16::MAX), - }; + // Create the OPTE port for the service. + // Note we don't plumb any firewall rules at this point, + // Nexus will plumb them down later but the default OPTE + // config allows outbound access which is enough for + // Boundary NTP which needs to come up before Nexus. + let port = port_manager + .create_port(nic, snat, external_ips, &[], DhcpCfg::default()) + .map_err(|err| Error::ServicePortCreation { + service: zone_type_str.clone(), + err: Box::new(err), + })?; - for dpd_client in &dpd_clients { - // TODO-correctness(#2933): If we fail part-way we need to - // clean up previous entries instead of leaking them. - let nat_create = || async { - info!( - self.inner.log, "creating NAT entry for service"; - "service" => ?svc, - ); + // We also need to update the switch with the NAT mappings + let (target_ip, first_port, last_port) = match snat { + Some(s) => (s.ip, s.first_port, s.last_port), + None => (external_ips[0], 0, u16::MAX), + }; - dpd_client - .ensure_nat_entry( - &self.inner.log, - target_ip.into(), - dpd_client::types::MacAddr { - a: port.0.mac().into_array(), - }, - first_port, - last_port, - port.0.vni().as_u32(), - underlay_address, - ) - .await - .map_err(BackoffError::transient)?; + for dpd_client in &dpd_clients { + // TODO-correctness(#2933): If we fail part-way we need to + // clean up previous entries instead of leaking them. + let nat_create = || async { + info!( + self.inner.log, "creating NAT entry for service"; + "zone_type" => &zone_type_str, + ); - Ok::<(), BackoffError>>(()) - }; - let log_failure = |error, _| { - warn!( - self.inner.log, "failed to create NAT entry for service"; - "error" => ?error, - "service" => ?svc, - ); - }; - retry_notify( - retry_policy_internal_service_aggressive(), - nat_create, - log_failure, - ) - .await?; - } + dpd_client + .ensure_nat_entry( + &self.inner.log, + target_ip.into(), + dpd_client::types::MacAddr { + a: port.0.mac().into_array(), + }, + first_port, + last_port, + port.0.vni().as_u32(), + underlay_address, + ) + .await + .map_err(BackoffError::transient)?; - ports.push(port); + Ok::<(), BackoffError>>(()) + }; + let log_failure = |error, _| { + warn!( + self.inner.log, "failed to create NAT entry for service"; + "error" => ?error, + "zone_type" => &zone_type_str, + ); + }; + retry_notify( + retry_policy_internal_service_aggressive(), + nat_create, + log_failure, + ) + .await?; } - - Ok(ports) + Ok(vec![port]) } // Check the services intended to run in the zone to determine whether any // additional privileges need to be enabled for the zone. - fn privs_needed(req: &ServiceZoneRequest) -> Vec { + fn privs_needed(zone_args: &ZoneArgs<'_>) -> Vec { let mut needed = Vec::new(); - for svc in &req.services { - match &svc.details { - ServiceType::Tfport { .. } => { + for svc_details in zone_args.sled_local_services() { + match svc_details { + SwitchService::Tfport { .. } => { needed.push("default".to_string()); needed.push("sys_dl_config".to_string()); } - ServiceType::BoundaryNtp { .. } - | ServiceType::InternalNtp { .. } => { + _ => (), + } + } + + if let Some(omicron_zone_type) = zone_args.omicron_type() { + match omicron_zone_type { + OmicronZoneType::BoundaryNtp { .. } + | OmicronZoneType::InternalNtp { .. } => { needed.push("default".to_string()); needed.push("sys_time".to_string()); needed.push("proc_priocntl".to_string()); @@ -1048,13 +1383,13 @@ impl ServiceManager { async fn initialize_zone( &self, - request: &ZoneRequest, + request: ZoneArgs<'_>, filesystems: &[zone::Fs], data_links: &[String], ) -> Result { - let device_names = Self::devices_needed(&request.zone)?; + let device_names = Self::devices_needed(&request)?; let (bootstrap_vnic, bootstrap_name_and_address) = - match self.bootstrap_address_needed(&request.zone)? { + match self.bootstrap_address_needed(&request)? { Some((vnic, address)) => { let name = vnic.name().to_string(); (Some(vnic), Some((name, address))) @@ -1067,20 +1402,26 @@ impl ServiceManager { let links: Vec; let links_need_link_local: Vec; (links, links_need_link_local) = - self.links_needed(&request.zone)?.into_iter().unzip(); - let opte_ports = self.opte_ports_needed(&request.zone).await?; - let limit_priv = Self::privs_needed(&request.zone); + self.links_needed(&request)?.into_iter().unzip(); + let opte_ports = self.opte_ports_needed(&request).await?; + let limit_priv = Self::privs_needed(&request); // If the zone is managing a particular dataset, plumb that // dataset into the zone. Additionally, construct a "unique enough" name // so we can create multiple zones of this type without collision. - let unique_name = request.zone.zone_name_unique_identifier(); - let datasets = request - .zone - .dataset - .iter() - .map(|d| zone::Dataset { name: d.name.full() }) - .collect::>(); + let unique_name = match &request { + ZoneArgs::Omicron(zone_config) => Some(zone_config.zone.id), + ZoneArgs::Switch(_) => None, + }; + let datasets: Vec<_> = match &request { + ZoneArgs::Omicron(zone_config) => zone_config + .zone + .dataset_name() + .map(|n| zone::Dataset { name: n.full() }) + .into_iter() + .collect(), + ZoneArgs::Switch(_) => vec![], + }; let devices: Vec = device_names .iter() @@ -1103,6 +1444,13 @@ impl ServiceManager { .push(boot_zpool.dataset_mountpoint(INSTALL_DATASET)); } + let zone_type_str = match &request { + ZoneArgs::Omicron(zone_config) => { + zone_config.zone.zone_type.zone_type_str() + } + ZoneArgs::Switch(_) => "switch".to_string(), + }; + let mut zone_builder = ZoneBuilderFactory::default().builder(); if let Some(uuid) = unique_name { zone_builder = zone_builder.with_unique_name(uuid); @@ -1113,9 +1461,9 @@ impl ServiceManager { let installed_zone = zone_builder .with_log(self.inner.log.clone()) .with_underlay_vnic_allocator(&self.inner.underlay_vnic_allocator) - .with_zone_root_path(&request.root) + .with_zone_root_path(&request.root()) .with_zone_image_paths(zone_image_paths.as_slice()) - .with_zone_type(&request.zone.zone_type.to_string()) + .with_zone_type(&zone_type_str) .with_datasets(datasets.as_slice()) .with_filesystems(&filesystems) .with_data_links(&data_links) @@ -1130,8 +1478,16 @@ impl ServiceManager { // // These zones are self-assembling -- after they boot, there should // be no "zlogin" necessary to initialize. - match request.zone.zone_type { - ZoneType::Clickhouse => { + match &request { + ZoneArgs::Omicron(OmicronZoneConfigLocal { + zone: + OmicronZoneConfig { + zone_type: OmicronZoneType::Clickhouse { .. }, + underlay_address, + .. + }, + .. + }) => { let Some(info) = self.inner.sled_info.get() else { return Err(Error::SledAgentNotReady); }; @@ -1140,8 +1496,7 @@ impl ServiceManager { let datalink = installed_zone.get_control_vnic_name(); let gateway = &info.underlay_address.to_string(); - assert_eq!(request.zone.addresses.len(), 1); - let listen_addr = &request.zone.addresses[0].to_string(); + let listen_addr = &underlay_address.to_string(); let listen_port = &CLICKHOUSE_PORT.to_string(); let config = PropertyGroupBuilder::new("config") @@ -1167,7 +1522,16 @@ impl ServiceManager { })?; return Ok(RunningZone::boot(installed_zone).await?); } - ZoneType::ClickhouseKeeper => { + + ZoneArgs::Omicron(OmicronZoneConfigLocal { + zone: + OmicronZoneConfig { + zone_type: OmicronZoneType::ClickhouseKeeper { .. }, + underlay_address, + .. + }, + .. + }) => { let Some(info) = self.inner.sled_info.get() else { return Err(Error::SledAgentNotReady); }; @@ -1176,8 +1540,7 @@ impl ServiceManager { let datalink = installed_zone.get_control_vnic_name(); let gateway = &info.underlay_address.to_string(); - assert_eq!(request.zone.addresses.len(), 1); - let listen_addr = &request.zone.addresses[0].to_string(); + let listen_addr = &underlay_address.to_string(); let listen_port = &CLICKHOUSE_KEEPER_PORT.to_string(); let config = PropertyGroupBuilder::new("config") @@ -1206,7 +1569,16 @@ impl ServiceManager { })?; return Ok(RunningZone::boot(installed_zone).await?); } - ZoneType::CockroachDb => { + + ZoneArgs::Omicron(OmicronZoneConfigLocal { + zone: + OmicronZoneConfig { + zone_type: OmicronZoneType::CockroachDb { .. }, + underlay_address, + .. + }, + .. + }) => { let Some(info) = self.inner.sled_info.get() else { return Err(Error::SledAgentNotReady); }; @@ -1216,9 +1588,8 @@ impl ServiceManager { // Configure the CockroachDB service. let datalink = installed_zone.get_control_vnic_name(); let gateway = &info.underlay_address.to_string(); - assert_eq!(request.zone.addresses.len(), 1); let address = SocketAddr::new( - IpAddr::V6(request.zone.addresses[0]), + IpAddr::V6(*underlay_address), COCKROACH_PORT, ); let listen_addr = &address.ip().to_string(); @@ -1247,22 +1618,29 @@ impl ServiceManager { })?; return Ok(RunningZone::boot(installed_zone).await?); } - ZoneType::Crucible => { + + ZoneArgs::Omicron(OmicronZoneConfigLocal { + zone: + OmicronZoneConfig { + zone_type: OmicronZoneType::Crucible { dataset, .. }, + underlay_address, + .. + }, + .. + }) => { let Some(info) = self.inner.sled_info.get() else { return Err(Error::SledAgentNotReady); }; let datalink = installed_zone.get_control_vnic_name(); let gateway = &info.underlay_address.to_string(); - assert_eq!(request.zone.addresses.len(), 1); - let listen_addr = &request.zone.addresses[0].to_string(); + let listen_addr = &underlay_address.to_string(); let listen_port = &CRUCIBLE_PORT.to_string(); - let dataset_name = request - .zone - .dataset - .as_ref() - .map(|d| d.name.full()) - .expect("Crucible requires dataset"); + let dataset_name = DatasetName::new( + dataset.pool_name.clone(), + DatasetKind::Crucible, + ) + .full(); let uuid = &Uuid::new_v4().to_string(); let config = PropertyGroupBuilder::new("config") .add_property("datalink", "astring", datalink) @@ -1287,15 +1665,23 @@ impl ServiceManager { })?; return Ok(RunningZone::boot(installed_zone).await?); } - ZoneType::CruciblePantry => { + + ZoneArgs::Omicron(OmicronZoneConfigLocal { + zone: + OmicronZoneConfig { + zone_type: OmicronZoneType::CruciblePantry { .. }, + underlay_address, + .. + }, + .. + }) => { let Some(info) = self.inner.sled_info.get() else { return Err(Error::SledAgentNotReady); }; let datalink = installed_zone.get_control_vnic_name(); let gateway = &info.underlay_address.to_string(); - assert_eq!(request.zone.addresses.len(), 1); - let listen_addr = &request.zone.addresses[0].to_string(); + let listen_addr = &underlay_address.to_string(); let listen_port = &CRUCIBLE_PANTRY_PORT.to_string(); let config = PropertyGroupBuilder::new("config") @@ -1317,6 +1703,7 @@ impl ServiceManager { let running_zone = RunningZone::boot(installed_zone).await?; return Ok(running_zone); } + _ => {} } @@ -1347,7 +1734,7 @@ impl ServiceManager { self.inner.log, "Ensuring bootstrap address {} exists in {} zone", bootstrap_address.to_string(), - request.zone.zone_type.to_string() + &zone_type_str, ); running_zone.ensure_bootstrap_address(*bootstrap_address).await?; info!( @@ -1368,7 +1755,14 @@ impl ServiceManager { })?; } - for addr in &request.zone.addresses { + let addresses = match &request { + ZoneArgs::Omicron(OmicronZoneConfigLocal { + zone: OmicronZoneConfig { underlay_address, .. }, + .. + }) => std::slice::from_ref(underlay_address), + ZoneArgs::Switch(req) => &req.zone.addresses, + }; + for addr in addresses { if *addr == Ipv6Addr::LOCALHOST { continue; } @@ -1393,9 +1787,7 @@ impl ServiceManager { let sled_underlay_subnet = Ipv6Subnet::::new(info.underlay_address); - if request - .zone - .addresses + if addresses .iter() .any(|ip| sled_underlay_subnet.net().contains(*ip)) { @@ -1427,661 +1819,744 @@ impl ServiceManager { })?; } - for service in &request.zone.services { - // TODO: Related to - // https://github.com/oxidecomputer/omicron/pull/1124 , should we - // avoid importing this manifest? - debug!(self.inner.log, "importing manifest"); - - let smfh = SmfHelper::new(&running_zone, &service.details); - smfh.import_manifest()?; - - match &service.details { - ServiceType::Nexus { - internal_address, - external_tls, - external_dns_servers, - .. - } => { - info!(self.inner.log, "Setting up Nexus service"); - - let sled_info = self - .inner - .sled_info - .get() - .ok_or(Error::SledAgentNotReady)?; - - // While Nexus will be reachable via `external_ip`, it communicates - // atop an OPTE port which operates on a VPC private IP. OPTE will - // map the private IP to the external IP automatically. - let port_ip = running_zone - .ensure_address_for_port("public", 0) - .await? - .ip(); - - // Nexus takes a separate config file for parameters which - // cannot be known at packaging time. - let nexus_port = if *external_tls { 443 } else { 80 }; - let deployment_config = NexusDeploymentConfig { - id: request.zone.id, - rack_id: sled_info.rack_id, - techport_external_server_port: - NEXUS_TECHPORT_EXTERNAL_PORT, - - dropshot_external: ConfigDropshotWithTls { - tls: *external_tls, - dropshot: dropshot::ConfigDropshot { - bind_address: SocketAddr::new( - port_ip, nexus_port, - ), - // This has to be large enough to support: - // - bulk writes to disks - request_body_max_bytes: 8192 * 1024, + match &request { + ZoneArgs::Omicron(zone_config) => { + // TODO: Related to + // https://github.com/oxidecomputer/omicron/pull/1124 , should we + // avoid importing this manifest? + debug!(self.inner.log, "importing manifest"); + + let smfh = + SmfHelper::new(&running_zone, &zone_config.zone.zone_type); + smfh.import_manifest()?; + + match &zone_config.zone.zone_type { + OmicronZoneType::Nexus { + internal_address, + external_tls, + external_dns_servers, + .. + } => { + info!(self.inner.log, "Setting up Nexus service"); + + let sled_info = self + .inner + .sled_info + .get() + .ok_or(Error::SledAgentNotReady)?; + + // While Nexus will be reachable via `external_ip`, it + // communicates atop an OPTE port which operates on a + // VPC private IP. OPTE will map the private IP to the + // external IP automatically. + let port_ip = running_zone + .ensure_address_for_port("public", 0) + .await? + .ip(); + + // Nexus takes a separate config file for parameters + // which cannot be known at packaging time. + let nexus_port = if *external_tls { 443 } else { 80 }; + let deployment_config = NexusDeploymentConfig { + id: zone_config.zone.id, + rack_id: sled_info.rack_id, + techport_external_server_port: + NEXUS_TECHPORT_EXTERNAL_PORT, + + dropshot_external: ConfigDropshotWithTls { + tls: *external_tls, + dropshot: dropshot::ConfigDropshot { + bind_address: SocketAddr::new( + port_ip, nexus_port, + ), + // This has to be large enough to support: + // - bulk writes to disks + request_body_max_bytes: 8192 * 1024, + default_handler_task_mode: + HandlerTaskMode::Detached, + }, + }, + dropshot_internal: dropshot::ConfigDropshot { + bind_address: (*internal_address).into(), + // This has to be large enough to support, among + // other things, the initial list of TLS + // certificates provided by the customer during + // rack setup. + request_body_max_bytes: 10 * 1024 * 1024, default_handler_task_mode: HandlerTaskMode::Detached, }, - }, - dropshot_internal: dropshot::ConfigDropshot { - bind_address: (*internal_address).into(), - // This has to be large enough to support, among - // other things, the initial list of TLS - // certificates provided by the customer during rack - // setup. - request_body_max_bytes: 10 * 1024 * 1024, - default_handler_task_mode: - HandlerTaskMode::Detached, - }, - internal_dns: nexus_config::InternalDns::FromSubnet { - subnet: Ipv6Subnet::::new( - sled_info.underlay_address, + internal_dns: + nexus_config::InternalDns::FromSubnet { + subnet: Ipv6Subnet::::new( + sled_info.underlay_address, + ), + }, + database: nexus_config::Database::FromDns, + external_dns_servers: external_dns_servers.clone(), + }; + + // Copy the partial config file to the expected + // location. + let config_dir = Utf8PathBuf::from(format!( + "{}/var/svc/manifest/site/nexus", + running_zone.root() + )); + // The filename of a half-completed config, in need of + // parameters supplied at runtime. + const PARTIAL_LEDGER_FILENAME: &str = + "config-partial.toml"; + // The filename of a completed config, merging the + // partial config with additional appended parameters + // known at runtime. + const COMPLETE_LEDGER_FILENAME: &str = "config.toml"; + let partial_config_path = + config_dir.join(PARTIAL_LEDGER_FILENAME); + let config_path = + config_dir.join(COMPLETE_LEDGER_FILENAME); + tokio::fs::copy(partial_config_path, &config_path) + .await + .map_err(|err| Error::io_path(&config_path, err))?; + + // Serialize the configuration and append it into the + // file. + let serialized_cfg = + toml::Value::try_from(&deployment_config) + .expect("Cannot serialize config"); + let mut map = toml::map::Map::new(); + map.insert("deployment".to_string(), serialized_cfg); + let config_str = + toml::to_string(&map).map_err(|err| { + Error::TomlSerialize { + path: config_path.clone(), + err, + } + })?; + let mut file = tokio::fs::OpenOptions::new() + .append(true) + .open(&config_path) + .await + .map_err(|err| Error::io_path(&config_path, err))?; + file.write_all(b"\n\n") + .await + .map_err(|err| Error::io_path(&config_path, err))?; + file.write_all(config_str.as_bytes()) + .await + .map_err(|err| Error::io_path(&config_path, err))?; + } + + OmicronZoneType::ExternalDns { + http_address, + dns_address, + .. + } => { + info!( + self.inner.log, + "Setting up external-dns service" + ); + + // Like Nexus, we need to be reachable externally via + // `dns_address` but we don't listen on that address + // directly but instead on a VPC private IP. OPTE will + // en/decapsulate as appropriate. + let port_ip = running_zone + .ensure_address_for_port("public", 0) + .await? + .ip(); + let dns_address = + SocketAddr::new(port_ip, dns_address.port()); + + smfh.setprop( + "config/http_address", + format!( + "[{}]:{}", + http_address.ip(), + http_address.port(), ), - }, - database: nexus_config::Database::FromDns, - external_dns_servers: external_dns_servers.clone(), - }; + )?; + smfh.setprop( + "config/dns_address", + dns_address.to_string(), + )?; - // Copy the partial config file to the expected location. - let config_dir = Utf8PathBuf::from(format!( - "{}/var/svc/manifest/site/nexus", - running_zone.root() - )); - // The filename of a half-completed config, in need of parameters supplied at - // runtime. - const PARTIAL_LEDGER_FILENAME: &str = "config-partial.toml"; - // The filename of a completed config, merging the partial config with - // additional appended parameters known at runtime. - const COMPLETE_LEDGER_FILENAME: &str = "config.toml"; - let partial_config_path = - config_dir.join(PARTIAL_LEDGER_FILENAME); - let config_path = config_dir.join(COMPLETE_LEDGER_FILENAME); - tokio::fs::copy(partial_config_path, &config_path) - .await - .map_err(|err| Error::io_path(&config_path, err))?; - - // Serialize the configuration and append it into the file. - let serialized_cfg = - toml::Value::try_from(&deployment_config) - .expect("Cannot serialize config"); - let mut map = toml::map::Map::new(); - map.insert("deployment".to_string(), serialized_cfg); - let config_str = toml::to_string(&map).map_err(|err| { - Error::TomlSerialize { path: config_path.clone(), err } - })?; - let mut file = tokio::fs::OpenOptions::new() - .append(true) - .open(&config_path) - .await - .map_err(|err| Error::io_path(&config_path, err))?; - file.write_all(b"\n\n") - .await - .map_err(|err| Error::io_path(&config_path, err))?; - file.write_all(config_str.as_bytes()) - .await - .map_err(|err| Error::io_path(&config_path, err))?; - } - ServiceType::ExternalDns { - http_address, dns_address, .. - } => { - info!(self.inner.log, "Setting up external-dns service"); - - // Like Nexus, we need to be reachable externally via - // `dns_address` but we don't listen on that address - // directly but instead on a VPC private IP. OPTE will - // en/decapsulate as appropriate. - let port_ip = running_zone - .ensure_address_for_port("public", 0) - .await? - .ip(); - let dns_address = - SocketAddr::new(port_ip, dns_address.port()); - - smfh.setprop( - "config/http_address", - format!( - "[{}]:{}", - http_address.ip(), - http_address.port(), - ), - )?; - smfh.setprop( - "config/dns_address", - dns_address.to_string(), - )?; + // Refresh the manifest with the new properties we set, + // so they become "effective" properties when the + // service is enabled. + smfh.refresh()?; + } - // Refresh the manifest with the new properties we set, so - // they become "effective" properties when the service is - // enabled. - smfh.refresh()?; - } - ServiceType::InternalDns { - http_address, - dns_address, - gz_address, - gz_address_index, - } => { - info!(self.inner.log, "Setting up internal-dns service"); - - // Internal DNS zones require a special route through the - // global zone, since they are not on the same part of the - // underlay as most other services on this sled (the sled's - // subnet). - // - // We create an IP address in the dedicated portion of the - // underlay used for internal DNS servers, but we *also* - // add a number ("which DNS server is this") to ensure - // these addresses are given unique names. In the unlikely - // case that two internal DNS servers end up on the same - // machine (which is effectively a developer-only - // environment -- we wouldn't want this in prod!), they need - // to be given distinct names. - let addr_name = format!("internaldns{gz_address_index}"); - Zones::ensure_has_global_zone_v6_address( - self.inner.underlay_vnic.clone(), - *gz_address, - &addr_name, - ) - .map_err(|err| Error::GzAddress { - message: format!( + OmicronZoneType::InternalDns { + http_address, + dns_address, + gz_address, + gz_address_index, + .. + } => { + info!( + self.inner.log, + "Setting up internal-dns service" + ); + + // Internal DNS zones require a special route through + // the global zone, since they are not on the same part + // of the underlay as most other services on this sled + // (the sled's subnet). + // + // We create an IP address in the dedicated portion of + // the underlay used for internal DNS servers, but we + // *also* add a number ("which DNS server is this") to + // ensure these addresses are given unique names. In the + // unlikely case that two internal DNS servers end up on + // the same machine (which is effectively a + // developer-only environment -- we wouldn't want this + // in prod!), they need to be given distinct names. + let addr_name = + format!("internaldns{gz_address_index}"); + Zones::ensure_has_global_zone_v6_address( + self.inner.underlay_vnic.clone(), + *gz_address, + &addr_name, + ) + .map_err(|err| { + Error::GzAddress { + message: format!( "Failed to create address {} for Internal DNS zone", addr_name ), - err, - })?; - // If this address is in a new ipv6 prefix, notify maghemite so - // it can advertise it to other sleds. - self.advertise_prefix_of_address(*gz_address).await; - - running_zone.add_default_route(*gz_address).map_err( - |err| Error::ZoneCommand { - intent: "Adding Route".to_string(), - err, - }, - )?; + err, + } + })?; + // If this address is in a new ipv6 prefix, notify + // maghemite so it can advertise it to other sleds. + self.advertise_prefix_of_address(*gz_address).await; + + running_zone.add_default_route(*gz_address).map_err( + |err| Error::ZoneCommand { + intent: "Adding Route".to_string(), + err, + }, + )?; - smfh.setprop( - "config/http_address", - format!( - "[{}]:{}", - http_address.ip(), - http_address.port(), - ), - )?; - smfh.setprop( - "config/dns_address", - &format!( - "[{}]:{}", - dns_address.ip(), - dns_address.port(), - ), - )?; + smfh.setprop( + "config/http_address", + format!( + "[{}]:{}", + http_address.ip(), + http_address.port(), + ), + )?; + smfh.setprop( + "config/dns_address", + &format!( + "[{}]:{}", + dns_address.ip(), + dns_address.port(), + ), + )?; - // Refresh the manifest with the new properties we set, so - // they become "effective" properties when the service is - // enabled. - smfh.refresh()?; - } - ServiceType::Oximeter { address } => { - info!(self.inner.log, "Setting up oximeter service"); - smfh.setprop("config/id", request.zone.id)?; - smfh.setprop("config/address", address.to_string())?; - smfh.refresh()?; - } - ServiceType::ManagementGatewayService => { - info!(self.inner.log, "Setting up MGS service"); - smfh.setprop("config/id", request.zone.id)?; - - // Always tell MGS to listen on localhost so wicketd can - // contact it even before we have an underlay network. - smfh.addpropvalue( - "config/address", - &format!("[::1]:{MGS_PORT}"), - )?; + // Refresh the manifest with the new properties we set, + // so they become "effective" properties when the + // service is enabled. + smfh.refresh()?; + } - if let Some(address) = request.zone.addresses.get(0) { - // Don't use localhost twice - if *address != Ipv6Addr::LOCALHOST { - smfh.addpropvalue( - "config/address", - &format!("[{address}]:{MGS_PORT}"), - )?; - } + OmicronZoneType::Oximeter { address } => { + info!(self.inner.log, "Setting up oximeter service"); + smfh.setprop("config/id", zone_config.zone.id)?; + smfh.setprop("config/address", address.to_string())?; + smfh.refresh()?; } - if let Some(info) = self.inner.sled_info.get() { - smfh.setprop("config/rack_id", info.rack_id)?; + OmicronZoneType::BoundaryNtp { + ntp_servers, + dns_servers, + domain, + .. } + | OmicronZoneType::InternalNtp { + ntp_servers, + dns_servers, + domain, + .. + } => { + let boundary = matches!( + &zone_config.zone.zone_type, + OmicronZoneType::BoundaryNtp { .. } + ); + info!( + self.inner.log, + "Set up NTP service boundary={}, Servers={:?}", + boundary, + ntp_servers + ); - smfh.refresh()?; - } - ServiceType::SpSim => { - info!(self.inner.log, "Setting up Simulated SP service"); - } - ServiceType::Wicketd { baseboard } => { - info!(self.inner.log, "Setting up wicketd service"); + let sled_info = + if let Some(info) = self.inner.sled_info.get() { + info + } else { + return Err(Error::SledAgentNotReady); + }; - smfh.setprop( - "config/address", - &format!("[::1]:{WICKETD_PORT}"), - )?; + let rack_net = Ipv6Subnet::::new( + sled_info.underlay_address, + ) + .net(); - // If we're launching the switch zone, we'll have a - // bootstrap_address based on our call to - // `self.bootstrap_address_needed` (which always gives us an - // address for the switch zone. If we _don't_ have a - // bootstrap address, someone has requested wicketd in a - // non-switch zone; return an error. - let Some((_, bootstrap_address)) = - bootstrap_name_and_address - else { - return Err(Error::BadServiceRequest { - service: "wicketd".to_string(), - message: concat!( - "missing bootstrap address: ", - "wicketd can only be started in the ", - "switch zone", - ) - .to_string(), - }); - }; - smfh.setprop( - "config/artifact-address", - &format!( - "[{bootstrap_address}]:{BOOTSTRAP_ARTIFACT_PORT}" - ), - )?; - - smfh.setprop( - "config/mgs-address", - &format!("[::1]:{MGS_PORT}"), - )?; - - // We intentionally bind `nexus-proxy-address` to `::` so - // wicketd will serve this on all interfaces, particularly - // the tech port interfaces, allowing external clients to - // connect to this Nexus proxy. - smfh.setprop( - "config/nexus-proxy-address", - &format!("[::]:{WICKETD_NEXUS_PROXY_PORT}"), - )?; - if let Some(underlay_address) = self - .inner - .sled_info - .get() - .map(|info| info.underlay_address) - { - let rack_subnet = - Ipv6Subnet::::new(underlay_address); + smfh.setprop("config/allow", &format!("{}", rack_net))?; smfh.setprop( - "config/rack-subnet", - &rack_subnet.net().ip().to_string(), + "config/boundary", + if boundary { "true" } else { "false" }, )?; - } - let serialized_baseboard = - serde_json::to_string_pretty(&baseboard)?; - let serialized_baseboard_path = Utf8PathBuf::from(format!( - "{}/opt/oxide/baseboard.json", - running_zone.root() - )); - tokio::fs::write( - &serialized_baseboard_path, - &serialized_baseboard, - ) - .await - .map_err(|err| { - Error::io_path(&serialized_baseboard_path, err) - })?; - smfh.setprop( - "config/baseboard-file", - String::from("/opt/oxide/baseboard.json"), - )?; + if boundary { + // Configure OPTE port for boundary NTP + running_zone + .ensure_address_for_port("public", 0) + .await?; + } - smfh.refresh()?; - } - ServiceType::Dendrite { asic } => { - info!(self.inner.log, "Setting up dendrite service"); + smfh.delpropvalue("config/server", "*")?; + for server in ntp_servers { + smfh.addpropvalue("config/server", server)?; + } + self.configure_dns_client( + &running_zone, + &dns_servers, + &domain, + ) + .await?; - if let Some(info) = self.inner.sled_info.get() { - smfh.setprop("config/rack_id", info.rack_id)?; - smfh.setprop("config/sled_id", info.config.sled_id)?; - } else { - info!( - self.inner.log, - "no rack_id/sled_id available yet" - ); + smfh.refresh()?; + } + OmicronZoneType::Clickhouse { .. } + | OmicronZoneType::ClickhouseKeeper { .. } + | OmicronZoneType::CockroachDb { .. } + | OmicronZoneType::Crucible { .. } + | OmicronZoneType::CruciblePantry { .. } => { + panic!( + "{} is a service which exists as part of a \ + self-assembling zone", + &zone_config.zone.zone_type.zone_type_str(), + ) } + }; - smfh.delpropvalue("config/address", "*")?; - smfh.delpropvalue("config/dns_server", "*")?; - for address in &request.zone.addresses { - smfh.addpropvalue( - "config/address", - &format!("[{}]:{}", address, DENDRITE_PORT), - )?; - if *address != Ipv6Addr::LOCALHOST { - let az_prefix = - Ipv6Subnet::::new(*address); - for addr in Resolver::servers_from_subnet(az_prefix) + debug!(self.inner.log, "enabling service"); + smfh.enable()?; + } + ZoneArgs::Switch(request) => { + for service in &request.zone.services { + // TODO: Related to + // https://github.com/oxidecomputer/omicron/pull/1124 , should we + // avoid importing this manifest? + debug!(self.inner.log, "importing manifest"); + + let smfh = SmfHelper::new(&running_zone, service); + smfh.import_manifest()?; + + match service { + SwitchService::ManagementGatewayService => { + info!(self.inner.log, "Setting up MGS service"); + smfh.setprop("config/id", request.zone.id)?; + + // Always tell MGS to listen on localhost so wicketd + // can contact it even before we have an underlay + // network. + smfh.addpropvalue( + "config/address", + &format!("[::1]:{MGS_PORT}"), + )?; + + if let Some(address) = request.zone.addresses.get(0) { - smfh.addpropvalue( - "config/dns_server", - &format!("{addr}"), - )?; + // Don't use localhost twice + if *address != Ipv6Addr::LOCALHOST { + smfh.addpropvalue( + "config/address", + &format!("[{address}]:{MGS_PORT}"), + )?; + } + } + + if let Some(info) = self.inner.sled_info.get() { + smfh.setprop("config/rack_id", info.rack_id)?; } + + smfh.refresh()?; } - } - match asic { - DendriteAsic::TofinoAsic => { - // There should be exactly one device_name - // associated with this zone: the /dev path for - // the tofino ASIC. - let dev_cnt = device_names.len(); - if dev_cnt == 1 { + SwitchService::SpSim => { + info!( + self.inner.log, + "Setting up Simulated SP service" + ); + } + SwitchService::Wicketd { baseboard } => { + info!(self.inner.log, "Setting up wicketd service"); + + smfh.setprop( + "config/address", + &format!("[::1]:{WICKETD_PORT}"), + )?; + + // If we're launching the switch zone, we'll have a + // bootstrap_address based on our call to + // `self.bootstrap_address_needed` (which always + // gives us an address for the switch zone. If we + // _don't_ have a bootstrap address, someone has + // requested wicketd in a non-switch zone; return an + // error. + let Some((_, bootstrap_address)) = + bootstrap_name_and_address + else { + return Err(Error::BadServiceRequest { + service: "wicketd".to_string(), + message: concat!( + "missing bootstrap address: ", + "wicketd can only be started in the ", + "switch zone", + ) + .to_string(), + }); + }; + smfh.setprop( + "config/artifact-address", + &format!( + "[{bootstrap_address}]:{BOOTSTRAP_ARTIFACT_PORT}" + ), + )?; + + smfh.setprop( + "config/mgs-address", + &format!("[::1]:{MGS_PORT}"), + )?; + + // We intentionally bind `nexus-proxy-address` to + // `::` so wicketd will serve this on all + // interfaces, particularly the tech port + // interfaces, allowing external clients to connect + // to this Nexus proxy. + smfh.setprop( + "config/nexus-proxy-address", + &format!("[::]:{WICKETD_NEXUS_PROXY_PORT}"), + )?; + if let Some(underlay_address) = self + .inner + .sled_info + .get() + .map(|info| info.underlay_address) + { + let rack_subnet = Ipv6Subnet::::new( + underlay_address, + ); smfh.setprop( - "config/dev_path", - device_names[0].clone(), + "config/rack-subnet", + &rack_subnet.net().ip().to_string(), )?; - } else { - return Err(Error::SledLocalZone( - anyhow::anyhow!( - "{dev_cnt} devices needed for tofino asic" - ), - )); } + + let serialized_baseboard = + serde_json::to_string_pretty(&baseboard)?; + let serialized_baseboard_path = + Utf8PathBuf::from(format!( + "{}/opt/oxide/baseboard.json", + running_zone.root() + )); + tokio::fs::write( + &serialized_baseboard_path, + &serialized_baseboard, + ) + .await + .map_err(|err| { + Error::io_path(&serialized_baseboard_path, err) + })?; smfh.setprop( - "config/port_config", - "/opt/oxide/dendrite/misc/sidecar_config.toml", + "config/baseboard-file", + String::from("/opt/oxide/baseboard.json"), )?; - let sidecar_revision = - match self.inner.sidecar_revision { - SidecarRevision::Physical(ref rev) => rev, - _ => { - return Err(Error::SidecarRevision( - anyhow::anyhow!( - "expected physical sidecar revision" - ), - )) - } - }; - smfh.setprop("config/board_rev", sidecar_revision)?; + + smfh.refresh()?; } - DendriteAsic::TofinoStub => smfh.setprop( - "config/port_config", - "/opt/oxide/dendrite/misc/model_config.toml", - )?, - asic @ (DendriteAsic::SoftNpuZone - | DendriteAsic::SoftNpuPropolisDevice) => { - if asic == &DendriteAsic::SoftNpuZone { - smfh.setprop("config/mgmt", "uds")?; + SwitchService::Dendrite { asic } => { + info!( + self.inner.log, + "Setting up dendrite service" + ); + + if let Some(info) = self.inner.sled_info.get() { + smfh.setprop("config/rack_id", info.rack_id)?; smfh.setprop( - "config/uds_path", - "/opt/softnpu/stuff", + "config/sled_id", + info.config.sled_id, )?; + } else { + info!( + self.inner.log, + "no rack_id/sled_id available yet" + ); } - if asic == &DendriteAsic::SoftNpuPropolisDevice { - smfh.setprop("config/mgmt", "uart")?; + + smfh.delpropvalue("config/address", "*")?; + smfh.delpropvalue("config/dns_server", "*")?; + for address in &request.zone.addresses { + smfh.addpropvalue( + "config/address", + &format!("[{}]:{}", address, DENDRITE_PORT), + )?; + if *address != Ipv6Addr::LOCALHOST { + let az_prefix = + Ipv6Subnet::::new(*address); + for addr in + Resolver::servers_from_subnet(az_prefix) + { + smfh.addpropvalue( + "config/dns_server", + &format!("{addr}"), + )?; + } + } } - let s = match self.inner.sidecar_revision { - SidecarRevision::SoftZone(ref s) => s, - SidecarRevision::SoftPropolis(ref s) => s, - _ => { - return Err(Error::SidecarRevision( - anyhow::anyhow!( - "expected soft sidecar revision" - ), - )) + match asic { + DendriteAsic::TofinoAsic => { + // There should be exactly one device_name + // associated with this zone: the /dev path + // for the tofino ASIC. + let dev_cnt = device_names.len(); + if dev_cnt == 1 { + smfh.setprop( + "config/dev_path", + device_names[0].clone(), + )?; + } else { + return Err(Error::SledLocalZone( + anyhow::anyhow!( + "{dev_cnt} devices needed \ + for tofino asic" + ), + )); + } + smfh.setprop( + "config/port_config", + "/opt/oxide/dendrite/misc/sidecar_config.toml", + )?; + let sidecar_revision = + match self.inner.sidecar_revision { + SidecarRevision::Physical(ref rev) => rev, + _ => { + return Err(Error::SidecarRevision( + anyhow::anyhow!( + "expected physical \ + sidecar revision" + ), + )) + } + }; + smfh.setprop("config/board_rev", sidecar_revision)?; + } + DendriteAsic::TofinoStub => smfh.setprop( + "config/port_config", + "/opt/oxide/dendrite/misc/model_config.toml", + )?, + asic @ (DendriteAsic::SoftNpuZone + | DendriteAsic::SoftNpuPropolisDevice) => { + if asic == &DendriteAsic::SoftNpuZone { + smfh.setprop("config/mgmt", "uds")?; + smfh.setprop( + "config/uds_path", + "/opt/softnpu/stuff", + )?; + } + if asic == &DendriteAsic::SoftNpuPropolisDevice { + smfh.setprop("config/mgmt", "uart")?; + } + let s = match self.inner.sidecar_revision { + SidecarRevision::SoftZone(ref s) => s, + SidecarRevision::SoftPropolis(ref s) => s, + _ => { + return Err(Error::SidecarRevision( + anyhow::anyhow!( + "expected soft sidecar \ + revision" + ), + )) + } + }; + smfh.setprop( + "config/front_ports", + &s.front_port_count.to_string(), + )?; + smfh.setprop( + "config/rear_ports", + &s.rear_port_count.to_string(), + )?; + smfh.setprop( + "config/port_config", + "/opt/oxide/dendrite/misc/softnpu_single_sled_config.toml", + )? } }; - smfh.setprop( - "config/front_ports", - &s.front_port_count.to_string(), - )?; - smfh.setprop( - "config/rear_ports", - &s.rear_port_count.to_string(), - )?; - smfh.setprop( - "config/port_config", - "/opt/oxide/dendrite/misc/softnpu_single_sled_config.toml", - )? + smfh.refresh()?; } - }; - smfh.refresh()?; - } - ServiceType::Tfport { pkt_source, asic } => { - info!(self.inner.log, "Setting up tfport service"); + SwitchService::Tfport { pkt_source, asic } => { + info!(self.inner.log, "Setting up tfport service"); - let is_gimlet = is_gimlet().map_err(|e| { - Error::Underlay(underlay::Error::SystemDetection(e)) - })?; + let is_gimlet = is_gimlet().map_err(|e| { + Error::Underlay( + underlay::Error::SystemDetection(e), + ) + })?; - if is_gimlet { - // Collect the prefixes for each techport. - let techport_prefixes = match bootstrap_name_and_address - .as_ref() - { - Some((_, addr)) => { - Self::bootstrap_addr_to_techport_prefixes(addr) + if is_gimlet { + // Collect the prefixes for each techport. + let nameaddr = + bootstrap_name_and_address.as_ref(); + let techport_prefixes = match nameaddr { + Some((_, addr)) => { + Self::bootstrap_addr_to_techport_prefixes(addr) + } + None => { + return Err(Error::BadServiceRequest { + service: "tfport".into(), + message: "bootstrap addr missing" + .into(), + }); + } + }; + + for (i, prefix) in + techport_prefixes.into_iter().enumerate() + { + // Each `prefix` is an `Ipv6Subnet` + // including a netmask. Stringify just the + // network address, without the mask. + smfh.setprop( + format!("config/techport{i}_prefix"), + prefix.net().network().to_string(), + )?; + } + smfh.setprop("config/pkt_source", pkt_source)?; } - None => { - return Err(Error::BadServiceRequest { - service: "tfport".into(), - message: "bootstrap addr missing".into(), - }); + if asic == &DendriteAsic::SoftNpuZone { + smfh.setprop("config/flags", "--sync-only")?; + } + if asic == &DendriteAsic::SoftNpuPropolisDevice { + smfh.setprop("config/pkt_source", pkt_source)?; } - }; - - for (i, prefix) in - techport_prefixes.into_iter().enumerate() - { - // Each `prefix` is an `Ipv6Subnet` including a netmask. - // Stringify just the network address, without the mask. smfh.setprop( - format!("config/techport{i}_prefix"), - prefix.net().network().to_string(), + "config/host", + &format!("[{}]", Ipv6Addr::LOCALHOST), + )?; + smfh.setprop( + "config/port", + &format!("{}", DENDRITE_PORT), )?; - } - smfh.setprop("config/pkt_source", pkt_source)?; - } - if asic == &DendriteAsic::SoftNpuZone { - smfh.setprop("config/flags", "--sync-only")?; - } - if asic == &DendriteAsic::SoftNpuPropolisDevice { - smfh.setprop("config/pkt_source", pkt_source)?; - } - smfh.setprop( - "config/host", - &format!("[{}]", Ipv6Addr::LOCALHOST), - )?; - smfh.setprop("config/port", &format!("{}", DENDRITE_PORT))?; - - smfh.refresh()?; - } - ServiceType::BoundaryNtp { - ntp_servers, - dns_servers, - domain, - .. - } - | ServiceType::InternalNtp { - ntp_servers, - dns_servers, - domain, - .. - } => { - let boundary = matches!( - service.details, - ServiceType::BoundaryNtp { .. } - ); - info!( - self.inner.log, - "Set up NTP service boundary={}, Servers={:?}", - boundary, - ntp_servers - ); - - let sled_info = - if let Some(info) = self.inner.sled_info.get() { - info - } else { - return Err(Error::SledAgentNotReady); - }; - - let rack_net = Ipv6Subnet::::new( - sled_info.underlay_address, - ) - .net(); - - smfh.setprop("config/allow", &format!("{}", rack_net))?; - smfh.setprop( - "config/boundary", - if boundary { "true" } else { "false" }, - )?; - - if boundary { - // Configure OPTE port for boundary NTP - running_zone - .ensure_address_for_port("public", 0) - .await?; - } - - smfh.delpropvalue("config/server", "*")?; - for server in ntp_servers { - smfh.addpropvalue("config/server", server)?; - } - self.configure_dns_client( - &running_zone, - dns_servers, - &domain, - ) - .await?; - - smfh.refresh()?; - } - ServiceType::Uplink => { - // Nothing to do here - this service is special and - // configured in `ensure_switch_zone_uplinks_configured` - } - ServiceType::Mgd => { - info!(self.inner.log, "Setting up mgd service"); - smfh.setprop("config/admin_host", "::")?; - smfh.refresh()?; - } - ServiceType::MgDdm { mode } => { - info!(self.inner.log, "Setting up mg-ddm service"); - smfh.setprop("config/mode", &mode)?; - smfh.setprop("config/admin_host", "::")?; + smfh.refresh()?; + } + SwitchService::Uplink => { + // Nothing to do here - this service is special and + // configured in + // `ensure_switch_zone_uplinks_configured` + } + SwitchService::Mgd => { + info!(self.inner.log, "Setting up mgd service"); + smfh.setprop("config/admin_host", "::")?; + smfh.refresh()?; + } + SwitchService::MgDdm { mode } => { + info!(self.inner.log, "Setting up mg-ddm service"); - let is_gimlet = is_gimlet().map_err(|e| { - Error::Underlay(underlay::Error::SystemDetection(e)) - })?; + smfh.setprop("config/mode", &mode)?; + smfh.setprop("config/admin_host", "::")?; - let maghemite_interfaces: Vec = if is_gimlet { - (0..32) - .map(|i| { - // See the `tfport_name` function for how - // tfportd names the addrconf it creates. - // Right now, that's `tfportrear[0-31]_0` - // for all rear ports, which is what we're - // directing ddmd to listen for - // advertisements on. - // - // This may grow in a multi-rack future to - // include a subset of "front" ports too, - // when racks are cabled together. - AddrObject::new( - &format!("tfportrear{}_0", i), - IPV6_LINK_LOCAL_NAME, - ) - .unwrap() - }) - .collect() - } else { - self.inner - .switch_zone_maghemite_links - .iter() - .map(|i| { - AddrObject::new( - &i.to_string(), - IPV6_LINK_LOCAL_NAME, + let is_gimlet = is_gimlet().map_err(|e| { + Error::Underlay( + underlay::Error::SystemDetection(e), ) - .unwrap() - }) - .collect() - }; + })?; + + let maghemite_interfaces: Vec = + if is_gimlet { + (0..32) + .map(|i| { + // See the `tfport_name` function + // for how tfportd names the + // addrconf it creates. Right now, + // that's `tfportrear[0-31]_0` for + // all rear ports, which is what + // we're directing ddmd to listen + // for advertisements on. + // + // This may grow in a multi-rack + // future to include a subset of + // "front" ports too, when racks are + // cabled together. + AddrObject::new( + &format!("tfportrear{}_0", i), + IPV6_LINK_LOCAL_NAME, + ) + .unwrap() + }) + .collect() + } else { + self.inner + .switch_zone_maghemite_links + .iter() + .map(|i| { + AddrObject::new( + &i.to_string(), + IPV6_LINK_LOCAL_NAME, + ) + .unwrap() + }) + .collect() + }; - smfh.setprop( - "config/interfaces", - // `svccfg setprop` requires a list of values to be - // enclosed in `()`, and each string value to be - // enclosed in `""`. Note that we do _not_ need to - // escape the parentheses, since this is not passed - // through a shell, but directly to `exec(2)` in the - // zone. - format!( - "({})", - maghemite_interfaces - .iter() - .map(|interface| format!(r#""{}""#, interface)) - .join(" "), - ), - )?; + smfh.setprop( + "config/interfaces", + // `svccfg setprop` requires a list of values to + // be enclosed in `()`, and each string value to + // be enclosed in `""`. Note that we do _not_ + // need to escape the parentheses, since this is + // not passed through a shell, but directly to + // `exec(2)` in the zone. + format!( + "({})", + maghemite_interfaces + .iter() + .map(|interface| format!( + r#""{}""#, + interface + )) + .join(" "), + ), + )?; + + if is_gimlet { + // Ddm for a scrimlet needs to be configured to + // talk to dendrite + smfh.setprop("config/dpd_host", "[::1]")?; + smfh.setprop("config/dpd_port", DENDRITE_PORT)?; + } + smfh.setprop("config/dendrite", "true")?; - if is_gimlet { - // Ddm for a scrimlet needs to be configured to talk to - // dendrite - smfh.setprop("config/dpd_host", "[::1]")?; - smfh.setprop("config/dpd_port", DENDRITE_PORT)?; + smfh.refresh()?; + } } - smfh.setprop("config/dendrite", "true")?; - smfh.refresh()?; - } - ServiceType::Crucible { .. } - | ServiceType::CruciblePantry { .. } - | ServiceType::CockroachDb { .. } - | ServiceType::Clickhouse { .. } - | ServiceType::ClickhouseKeeper { .. } => { - panic!( - "{} is a service which exists as part of a self-assembling zone", - service.details, - ) + debug!(self.inner.log, "enabling service"); + smfh.enable()?; } } - - debug!(self.inner.log, "enabling service"); - smfh.enable()?; - } + }; Ok(running_zone) } // Populates `existing_zones` according to the requests in `services`. - async fn initialize_services_locked( + async fn initialize_omicron_zones_locked( &self, existing_zones: &mut BTreeMap, - requests: &Vec, + requests: &Vec, ) -> Result<(), Error> { if let Some(name) = requests .iter() @@ -2098,7 +2573,7 @@ impl ServiceManager { let futures = requests.iter().map(|request| { async move { self.initialize_zone( - request, + ZoneArgs::Omicron(request), // filesystems= &[], // data_links= @@ -2156,74 +2631,133 @@ impl ServiceManager { Err(BundleError::NoSuchZone { name: name.to_string() }) } - /// Ensures that particular services should be initialized. + /// Returns the current Omicron zone configuration + pub async fn omicron_zones_list( + &self, + ) -> Result { + let log = &self.inner.log; + + // We need to take the lock in order for the information in the ledger + // to be up-to-date. + let _existing_zones = self.inner.zones.lock().await; + + // Read the existing set of services from the ledger. + let zone_ledger_paths = self.all_omicron_zone_ledgers().await; + let ledger_data = match Ledger::::new( + log, + zone_ledger_paths.clone(), + ) + .await + { + Some(ledger) => ledger.data().clone(), + None => OmicronZonesConfigLocal::initial(), + }; + + Ok(ledger_data.to_omicron_zones_config()) + } + + /// Ensures that particular Omicron zones are running /// /// These services will be instantiated by this function, and will be /// recorded to a local file to ensure they start automatically on next /// boot. - pub async fn ensure_all_services_persistent( + pub async fn ensure_all_omicron_zones_persistent( &self, - request: ServiceEnsureBody, + request: OmicronZonesConfig, ) -> Result<(), Error> { let log = &self.inner.log; let mut existing_zones = self.inner.zones.lock().await; // Read the existing set of services from the ledger. - let service_paths = self.all_service_ledgers().await; - let mut ledger = - match Ledger::::new(log, service_paths.clone()) - .await - { - Some(ledger) => ledger, - None => Ledger::::new_with( - log, - service_paths.clone(), - AllZoneRequests::default(), - ), - }; - let ledger_zone_requests = ledger.data_mut(); + let zone_ledger_paths = self.all_omicron_zone_ledgers().await; + let mut ledger = match Ledger::::new( + log, + zone_ledger_paths.clone(), + ) + .await + { + Some(ledger) => ledger, + None => Ledger::::new_with( + log, + zone_ledger_paths.clone(), + OmicronZonesConfigLocal::initial(), + ), + }; + + let ledger_zone_config = ledger.data_mut(); + debug!(log, "ensure_all_omicron_zones_persistent"; + "request_generation" => request.generation.to_string(), + "ledger_generation" => + ledger_zone_config.omicron_generation.to_string(), + ); + + // Absolutely refuse to downgrade the configuration. + if ledger_zone_config.omicron_generation > request.generation { + return Err(Error::RequestedConfigOutdated { + requested: request.generation, + current: ledger_zone_config.omicron_generation, + }); + } - let mut zone_requests = self - .ensure_all_services( + // If the generation is the same as what we're running, but the contents + // aren't, that's a problem, too. + if ledger_zone_config.omicron_generation == request.generation + && ledger_zone_config.clone().to_omicron_zones_config().zones + != request.zones + { + return Err(Error::RequestedConfigConflicts(request.generation)); + } + + let new_config = self + .ensure_all_omicron_zones( &mut existing_zones, - ledger_zone_requests, + Some(ledger_zone_config), request, + |_| true, ) .await?; - // Update the services in the ledger and write it back to both M.2s - ledger_zone_requests.requests.clear(); - ledger_zone_requests.requests.append(&mut zone_requests.requests); + // Update the zones in the ledger and write it back to both M.2s + *ledger_zone_config = new_config; ledger.commit().await?; Ok(()) } - // Ensures that only the following services are running. + // Ensures that only the following Omicron zones are running. // // Does not record any information such that these services are // re-instantiated on boot. - async fn ensure_all_services( + async fn ensure_all_omicron_zones( &self, + // The MutexGuard here attempts to ensure that the caller has the right + // lock held when calling this function. existing_zones: &mut MutexGuard<'_, BTreeMap>, - old_request: &AllZoneRequests, - request: ServiceEnsureBody, - ) -> Result { + old_config: Option<&OmicronZonesConfigLocal>, + new_request: OmicronZonesConfig, + filter: F, + ) -> Result + where + F: Fn(&OmicronZoneConfig) -> bool, + { let log = &self.inner.log; // Do some data-normalization to ensure we can compare the "requested // set" vs the "existing set" as HashSets. - let old_services_set: HashSet = HashSet::from_iter( - old_request.requests.iter().map(|r| r.zone.clone()), - ); - let requested_services_set = - HashSet::from_iter(request.services.into_iter()); + let old_zones_set: HashSet = old_config + .map(|old_config| { + HashSet::from_iter( + old_config.zones.iter().map(|z| z.zone.clone()), + ) + }) + .unwrap_or_else(HashSet::new); + let requested_zones_set = + HashSet::from_iter(new_request.zones.into_iter().filter(filter)); let zones_to_be_removed = - old_services_set.difference(&requested_services_set); - let zones_to_be_added = - requested_services_set.difference(&old_services_set); + old_zones_set.difference(&requested_zones_set); + let zones_to_be_added = requested_zones_set.difference(&old_zones_set); // Destroy zones that should not be running for zone in zones_to_be_removed { @@ -2256,13 +2790,13 @@ impl ServiceManager { } // Create zones that should be running - let mut zone_requests = AllZoneRequests::default(); let all_u2_roots = self .inner .storage .get_latest_resources() .await .all_u2_mountpoints(ZONE_DATASET); + let mut new_zones = Vec::new(); for zone in zones_to_be_added { // Check if we think the zone should already be running let name = zone.zone_name(); @@ -2294,6 +2828,7 @@ impl ServiceManager { } } } + // For each new zone request, we pick an arbitrary U.2 to store // the zone filesystem. Note: This isn't known to Nexus right now, // so it's a local-to-sled decision. @@ -2306,22 +2841,27 @@ impl ServiceManager { .ok_or_else(|| Error::U2NotFound)? .clone(); - zone_requests - .requests - .push(ZoneRequest { zone: zone.clone(), root }); + new_zones.push(OmicronZoneConfigLocal { zone: zone.clone(), root }); } - self.initialize_services_locked( - existing_zones, - &zone_requests.requests, - ) - .await?; - for old_zone in &old_request.requests { - if requested_services_set.contains(&old_zone.zone) { - zone_requests.requests.push(old_zone.clone()); + self.initialize_omicron_zones_locked(existing_zones, &new_zones) + .await?; + + if let Some(old_config) = old_config { + for old_zone in &old_config.zones { + if requested_zones_set.contains(&old_zone.zone) { + new_zones.push(old_zone.clone()); + } } } - Ok(zone_requests) + + Ok(OmicronZonesConfigLocal { + omicron_generation: new_request.generation, + ledger_generation: old_config + .map(|c| c.ledger_generation) + .unwrap_or_else(Generation::new), + zones: new_zones, + }) } pub async fn cockroachdb_initialize(&self) -> Result<(), Error> { @@ -2389,10 +2929,7 @@ impl ServiceManager { Ok(()) } - pub fn boottime_rewrite<'a>( - &self, - zones: impl Iterator, - ) { + pub fn boottime_rewrite(&self) { if self .inner .time_synced @@ -2403,33 +2940,13 @@ impl ServiceManager { return; } - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("SystemTime before UNIX EPOCH"); - - info!(self.inner.log, "Setting boot time to {:?}", now); - - let files: Vec = zones - .map(|z| z.root()) - .chain(iter::once(Utf8PathBuf::from("/"))) - .flat_map(|r| [r.join("var/adm/utmpx"), r.join("var/adm/wtmpx")]) - .collect(); - - for file in files { - let mut command = std::process::Command::new(PFEXEC); - let cmd = command.args(&[ - "/usr/platform/oxide/bin/tmpx", - &format!("{}", now.as_secs()), - &file.as_str(), - ]); - match execute(cmd) { - Err(e) => { - warn!(self.inner.log, "Updating {} failed: {}", &file, e); - } - Ok(_) => { - info!(self.inner.log, "Updated {}", &file); - } - } + // Call out to the 'tmpx' utility program which will rewrite the wtmpx + // and utmpx databases in every zone, including the global zone, to + // reflect the adjusted system boot time. + let mut command = std::process::Command::new(PFEXEC); + let cmd = command.args(&["/usr/platform/oxide/bin/tmpx", "-Z"]); + if let Err(e) = execute(cmd) { + warn!(self.inner.log, "Updating [wu]tmpx databases failed: {}", e); } } @@ -2438,7 +2955,7 @@ impl ServiceManager { if let Some(true) = self.inner.skip_timesync { info!(self.inner.log, "Configured to skip timesync checks"); - self.boottime_rewrite(existing_zones.values()); + self.boottime_rewrite(); return Ok(TimeSync { sync: true, ref_id: 0, @@ -2492,7 +3009,7 @@ impl ServiceManager { && correction.abs() <= 0.05; if sync { - self.boottime_rewrite(existing_zones.values()); + self.boottime_rewrite(); } Ok(TimeSync { @@ -2527,7 +3044,8 @@ impl ServiceManager { let mut data_links: Vec = vec![]; let services = match self.inner.sled_mode { - // A pure gimlet sled should not be trying to activate a switch zone. + // A pure gimlet sled should not be trying to activate a switch + // zone. SledMode::Gimlet => { return Err(Error::SledLocalZone(anyhow::anyhow!( "attempted to activate switch zone on non-scrimlet sled" @@ -2538,16 +3056,16 @@ impl ServiceManager { SledMode::Auto | SledMode::Scrimlet { asic: DendriteAsic::TofinoAsic } => { vec![ - ServiceType::Dendrite { asic: DendriteAsic::TofinoAsic }, - ServiceType::ManagementGatewayService, - ServiceType::Tfport { + SwitchService::Dendrite { asic: DendriteAsic::TofinoAsic }, + SwitchService::ManagementGatewayService, + SwitchService::Tfport { pkt_source: "tfpkt0".to_string(), asic: DendriteAsic::TofinoAsic, }, - ServiceType::Uplink, - ServiceType::Wicketd { baseboard }, - ServiceType::Mgd, - ServiceType::MgDdm { mode: "transit".to_string() }, + SwitchService::Uplink, + SwitchService::Wicketd { baseboard }, + SwitchService::Mgd, + SwitchService::MgDdm { mode: "transit".to_string() }, ] } @@ -2556,17 +3074,17 @@ impl ServiceManager { } => { data_links = vec!["vioif0".to_owned()]; vec![ - ServiceType::Dendrite { asic }, - ServiceType::ManagementGatewayService, - ServiceType::Uplink, - ServiceType::Wicketd { baseboard }, - ServiceType::Mgd, - ServiceType::MgDdm { mode: "transit".to_string() }, - ServiceType::Tfport { + SwitchService::Dendrite { asic }, + SwitchService::ManagementGatewayService, + SwitchService::Uplink, + SwitchService::Wicketd { baseboard }, + SwitchService::Mgd, + SwitchService::MgDdm { mode: "transit".to_string() }, + SwitchService::Tfport { pkt_source: "vioif0".to_string(), asic, }, - ServiceType::SpSim, + SwitchService::SpSim, ] } @@ -2586,17 +3104,17 @@ impl ServiceManager { data_links = Dladm::get_simulated_tfports()?; } vec![ - ServiceType::Dendrite { asic }, - ServiceType::ManagementGatewayService, - ServiceType::Uplink, - ServiceType::Wicketd { baseboard }, - ServiceType::Mgd, - ServiceType::MgDdm { mode: "transit".to_string() }, - ServiceType::Tfport { + SwitchService::Dendrite { asic }, + SwitchService::ManagementGatewayService, + SwitchService::Uplink, + SwitchService::Wicketd { baseboard }, + SwitchService::Mgd, + SwitchService::MgDdm { mode: "transit".to_string() }, + SwitchService::Tfport { pkt_source: "tfpkt0".to_string(), asic, }, - ServiceType::SpSim, + SwitchService::SpSim, ] } }; @@ -2605,19 +3123,10 @@ impl ServiceManager { if let Some((ip, _)) = underlay_info { vec![ip] } else { vec![] }; addresses.push(Ipv6Addr::LOCALHOST); - let request = ServiceZoneRequest { - id: Uuid::new_v4(), - zone_type: ZoneType::Switch, - addresses, - dataset: None, - services: services - .into_iter() - .map(|s| ServiceZoneService { id: Uuid::new_v4(), details: s }) - .collect(), - }; + let request = + SwitchZoneConfig { id: Uuid::new_v4(), addresses, services }; self.ensure_zone( - ZoneType::Switch, // request= Some(request), // filesystems= @@ -2685,7 +3194,7 @@ impl ServiceManager { } }; - let smfh = SmfHelper::new(&zone, &ServiceType::Uplink); + let smfh = SmfHelper::new(&zone, &SwitchService::Uplink); // We want to delete all the properties in the `uplinks` group, but we // don't know their names, so instead we'll delete and recreate the @@ -2710,7 +3219,6 @@ impl ServiceManager { /// Ensures that no switch zone is active. pub async fn deactivate_switch(&self) -> Result<(), Error> { self.ensure_zone( - ZoneType::Switch, // request= None, // filesystems= @@ -2727,12 +3235,11 @@ impl ServiceManager { fn start_zone( self, zone: &mut SledLocalZone, - request: ServiceZoneRequest, + request: SwitchZoneConfig, filesystems: Vec, data_links: Vec, ) { let (exit_tx, exit_rx) = oneshot::channel(); - let zone_type = request.zone_type.clone(); *zone = SledLocalZone::Initializing { request, filesystems, @@ -2740,7 +3247,7 @@ impl ServiceManager { worker: Some(Task { exit_tx, initializer: tokio::task::spawn(async move { - self.initialize_zone_loop(zone_type, exit_rx).await + self.initialize_zone_loop(exit_rx).await }), }), }; @@ -2749,21 +3256,14 @@ impl ServiceManager { // Moves the current state to align with the "request". async fn ensure_zone( &self, - zone_type: ZoneType, - request: Option, + request: Option, filesystems: Vec, data_links: Vec, ) -> Result<(), Error> { let log = &self.inner.log; - let mut sled_zone; - match zone_type { - ZoneType::Switch => { - sled_zone = self.inner.switch_zone.lock().await; - } - _ => panic!("Unhandled zone type"), - } - let zone_typestr = zone_type.to_string(); + let mut sled_zone = self.inner.switch_zone.lock().await; + let zone_typestr = "switch"; match (&mut *sled_zone, request) { (SledLocalZone::Disabled, Some(request)) => { @@ -2832,10 +3332,10 @@ impl ServiceManager { } for service in &request.services { - let smfh = SmfHelper::new(&zone, &service.details); + let smfh = SmfHelper::new(&zone, service); - match &service.details { - ServiceType::ManagementGatewayService => { + match service { + SwitchService::ManagementGatewayService => { // Remove any existing `config/address` values // without deleting the property itself. smfh.delpropvalue("config/address", "*")?; @@ -2853,8 +3353,9 @@ impl ServiceManager { &format!("[{address}]:{MGS_PORT}"), )?; - // It should be impossible for the `sled_info` not to be set here, - // as the underlay is set at the same time. + // It should be impossible for the `sled_info` not + // to be set here, as the underlay is set at the + // same time. if let Some(info) = self.inner.sled_info.get() { smfh.setprop("config/rack_id", info.rack_id)?; } else { @@ -2869,7 +3370,7 @@ impl ServiceManager { smfh.refresh()?; } - ServiceType::Dendrite { .. } => { + SwitchService::Dendrite { .. } => { info!(self.inner.log, "configuring dendrite zone"); if let Some(info) = self.inner.sled_info.get() { smfh.setprop("config/rack_id", info.rack_id)?; @@ -2905,7 +3406,7 @@ impl ServiceManager { } smfh.refresh()?; } - ServiceType::Wicketd { .. } => { + SwitchService::Wicketd { .. } => { if let Some(&address) = first_address { let rack_subnet = Ipv6Subnet::::new(address); @@ -2928,15 +3429,16 @@ impl ServiceManager { ); } } - ServiceType::Tfport { .. } => { + SwitchService::Tfport { .. } => { // Since tfport and dpd communicate using localhost, - // the tfport service shouldn't need to be restarted. + // the tfport service shouldn't need to be + // restarted. } - ServiceType::Uplink { .. } => { + SwitchService::Uplink { .. } => { // Only configured in // `ensure_switch_zone_uplinks_configured` } - ServiceType::MgDdm { mode } => { + SwitchService::MgDdm { mode } => { smfh.delpropvalue("config/mode", "*")?; smfh.addpropvalue("config/mode", &mode)?; smfh.refresh()?; @@ -2985,52 +3487,28 @@ impl ServiceManager { // switch zone were on a U.2 device we would not be able to run RSS, as // we could not create the U.2 disks due to lack of encryption. To break // the cycle we put the switch zone root fs on the ramdisk. - let root = if request.zone_type == ZoneType::Switch { - Utf8PathBuf::from(ZONE_ZFS_RAMDISK_DATASET_MOUNTPOINT) - } else { - let all_u2_roots = self - .inner - .storage - .get_latest_resources() - .await - .all_u2_mountpoints(ZONE_DATASET); - let mut rng = rand::rngs::StdRng::from_entropy(); - all_u2_roots - .choose(&mut rng) - .ok_or_else(|| Error::U2NotFound)? - .clone() - }; - - let request = ZoneRequest { zone: request.clone(), root }; + let root = Utf8PathBuf::from(ZONE_ZFS_RAMDISK_DATASET_MOUNTPOINT); + let zone_request = + SwitchZoneConfigLocal { root, zone: request.clone() }; + let zone_args = ZoneArgs::Switch(&zone_request); let zone = - self.initialize_zone(&request, filesystems, data_links).await?; - *sled_zone = - SledLocalZone::Running { request: request.zone.clone(), zone }; + self.initialize_zone(zone_args, filesystems, data_links).await?; + *sled_zone = SledLocalZone::Running { request: request.clone(), zone }; Ok(()) } // Body of a tokio task responsible for running until the switch zone is // inititalized, or it has been told to stop. - async fn initialize_zone_loop( - &self, - zone_type: ZoneType, - mut exit_rx: oneshot::Receiver<()>, - ) { + async fn initialize_zone_loop(&self, mut exit_rx: oneshot::Receiver<()>) { loop { { - let mut sled_zone; - match zone_type { - ZoneType::Switch => { - sled_zone = self.inner.switch_zone.lock().await; - } - _ => panic!("Unhandled zone type"), - } + let mut sled_zone = self.inner.switch_zone.lock().await; match self.try_initialize_sled_local_zone(&mut sled_zone).await { Ok(()) => return, Err(e) => warn!( self.inner.log, - "Failed to initialize {zone_type}: {e}" + "Failed to initialize switch zone: {e}" ), } } @@ -3050,7 +3528,6 @@ impl ServiceManager { #[cfg(test)] mod test { use super::*; - use crate::params::{ServiceZoneService, ZoneType}; use illumos_utils::zpool::ZpoolName; use illumos_utils::{ dladm::{ @@ -3154,27 +3631,96 @@ mod test { ] } + // Configures our mock implementations to work for cases where we configure + // multiple zones in one `ensure_all_omicron_zones_persistent()` call. + // + // This is looser than the expectations created by ensure_new_service() + // because these functions may return any number of times. + fn expect_new_services() -> Vec> { + illumos_utils::USE_MOCKS.store(true, Ordering::SeqCst); + // Create a VNIC + let create_vnic_ctx = MockDladm::create_vnic_context(); + create_vnic_ctx.expect().returning( + |physical_link: &Etherstub, _, _, _, _| { + assert_eq!(&physical_link.0, &UNDERLAY_ETHERSTUB_NAME); + Ok(()) + }, + ); + + // Install the Omicron Zone + let install_ctx = MockZones::install_omicron_zone_context(); + install_ctx.expect().returning(|_, _, name, _, _, _, _, _, _| { + assert!(name.starts_with(EXPECTED_ZONE_NAME_PREFIX)); + Ok(()) + }); + + // Boot the zone. + let boot_ctx = MockZones::boot_context(); + boot_ctx.expect().returning(|name| { + assert!(name.starts_with(EXPECTED_ZONE_NAME_PREFIX)); + Ok(()) + }); + + // After calling `MockZones::boot`, `RunningZone::boot` will then look + // up the zone ID for the booted zone. This goes through + // `MockZone::id` to find the zone and get its ID. + let id_ctx = MockZones::id_context(); + let id = Arc::new(std::sync::Mutex::new(1)); + id_ctx.expect().returning(move |name| { + assert!(name.starts_with(EXPECTED_ZONE_NAME_PREFIX)); + let mut value = id.lock().unwrap(); + let rv = *value; + *value = rv + 1; + Ok(Some(rv)) + }); + + // Ensure the address exists + let ensure_address_ctx = MockZones::ensure_address_context(); + ensure_address_ctx.expect().returning(|_, _, _| { + Ok(ipnetwork::IpNetwork::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 64) + .unwrap()) + }); + + // Wait for the networking service. + let wait_ctx = svc::wait_for_service_context(); + wait_ctx.expect().returning(|_, _, _| Ok(())); + + // Import the manifest, enable the service + let execute_ctx = illumos_utils::execute_helper_context(); + execute_ctx.expect().times(..).returning(|_| { + Ok(std::process::Output { + status: std::process::ExitStatus::from_raw(0), + stdout: vec![], + stderr: vec![], + }) + }); + + vec![ + Box::new(create_vnic_ctx), + Box::new(install_ctx), + Box::new(boot_ctx), + Box::new(id_ctx), + Box::new(ensure_address_ctx), + Box::new(wait_ctx), + Box::new(execute_ctx), + ] + } + // Prepare to call "ensure" for a new service, then actually call "ensure". - async fn ensure_new_service(mgr: &ServiceManager, id: Uuid) { + async fn ensure_new_service( + mgr: &ServiceManager, + id: Uuid, + generation: Generation, + ) { let _expectations = expect_new_service(); - - mgr.ensure_all_services_persistent(ServiceEnsureBody { - services: vec![ServiceZoneRequest { + let address = + SocketAddrV6::new(Ipv6Addr::LOCALHOST, OXIMETER_PORT, 0, 0); + mgr.ensure_all_omicron_zones_persistent(OmicronZonesConfig { + generation, + zones: vec![OmicronZoneConfig { id, - zone_type: ZoneType::Oximeter, - addresses: vec![Ipv6Addr::LOCALHOST], - dataset: None, - services: vec![ServiceZoneService { - id, - details: ServiceType::Oximeter { - address: SocketAddrV6::new( - Ipv6Addr::LOCALHOST, - OXIMETER_PORT, - 0, - 0, - ), - }, - }], + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: OmicronZoneType::Oximeter { address }, }], }) .await @@ -3183,24 +3729,19 @@ mod test { // Prepare to call "ensure" for a service which already exists. We should // return the service without actually installing a new zone. - async fn ensure_existing_service(mgr: &ServiceManager, id: Uuid) { - mgr.ensure_all_services_persistent(ServiceEnsureBody { - services: vec![ServiceZoneRequest { + async fn ensure_existing_service( + mgr: &ServiceManager, + id: Uuid, + generation: Generation, + ) { + let address = + SocketAddrV6::new(Ipv6Addr::LOCALHOST, OXIMETER_PORT, 0, 0); + mgr.ensure_all_omicron_zones_persistent(OmicronZonesConfig { + generation, + zones: vec![OmicronZoneConfig { id, - zone_type: ZoneType::Oximeter, - addresses: vec![Ipv6Addr::LOCALHOST], - dataset: None, - services: vec![ServiceZoneService { - id, - details: ServiceType::Oximeter { - address: SocketAddrV6::new( - Ipv6Addr::LOCALHOST, - OXIMETER_PORT, - 0, - 0, - ), - }, - }], + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: OmicronZoneType::Oximeter { address }, }], }) .await @@ -3276,48 +3817,104 @@ mod test { handle } + #[derive(Clone)] + struct LedgerTestHelper<'a> { + log: slog::Logger, + ddmd_client: DdmAdminClient, + storage_handle: StorageHandle, + zone_bundler: ZoneBundler, + test_config: &'a TestConfig, + } + + impl<'a> LedgerTestHelper<'a> { + async fn new( + log: slog::Logger, + test_config: &'a TestConfig, + ) -> LedgerTestHelper { + let ddmd_client = DdmAdminClient::localhost(&log).unwrap(); + let storage_handle = setup_storage().await; + let zone_bundler = ZoneBundler::new( + log.clone(), + storage_handle.clone(), + Default::default(), + ); + + LedgerTestHelper { + log, + ddmd_client, + storage_handle, + zone_bundler, + test_config, + } + } + + fn new_service_manager(self) -> ServiceManager { + let log = &self.log; + let mgr = ServiceManager::new( + log, + self.ddmd_client, + make_bootstrap_networking_config(), + SledMode::Auto, + Some(true), + SidecarRevision::Physical("rev-test".to_string()), + vec![], + self.storage_handle, + self.zone_bundler, + ); + self.test_config.override_paths(&mgr); + mgr + } + + fn sled_agent_started( + log: &slog::Logger, + test_config: &TestConfig, + mgr: &ServiceManager, + ) { + let port_manager = PortManager::new( + log.new(o!("component" => "PortManager")), + Ipv6Addr::new( + 0xfd00, 0x1de, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ), + ); + + mgr.sled_agent_started( + test_config.make_config(), + port_manager, + Ipv6Addr::LOCALHOST, + Uuid::new_v4(), + None, + ) + .unwrap(); + } + } + #[tokio::test] #[serial_test::serial] async fn test_ensure_service() { let logctx = omicron_test_utils::dev::test_setup_log("test_ensure_service"); - let log = logctx.log.clone(); let test_config = TestConfig::new().await; + let helper = + LedgerTestHelper::new(logctx.log.clone(), &test_config).await; + let mgr = helper.new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + + let v1 = Generation::new(); + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found.generation, v1); + assert!(found.zones.is_empty()); + + let v2 = v1.next(); + let id = Uuid::new_v4(); + ensure_new_service(&mgr, id, v2).await; - let storage_handle = setup_storage().await; - let zone_bundler = ZoneBundler::new( - log.clone(), - storage_handle.clone(), - Default::default(), - ); - let mgr = ServiceManager::new( - &log, - DdmAdminClient::localhost(&log).unwrap(), - make_bootstrap_networking_config(), - SledMode::Auto, - Some(true), - SidecarRevision::Physical("rev-test".to_string()), - vec![], - storage_handle, - zone_bundler, - ); - test_config.override_paths(&mgr); - - let port_manager = PortManager::new( - logctx.log.new(o!("component" => "PortManager")), - Ipv6Addr::new(0xfd00, 0x1de, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), - ); - mgr.sled_agent_started( - test_config.make_config(), - port_manager, - Ipv6Addr::LOCALHOST, - Uuid::new_v4(), - None, - ) - .unwrap(); + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found.generation, v2); + assert_eq!(found.zones.len(), 1); + assert_eq!(found.zones[0].id, id); - let id = Uuid::new_v4(); - ensure_new_service(&mgr, id).await; drop_service_manager(mgr); logctx.cleanup_successful(); @@ -3329,44 +3926,23 @@ mod test { let logctx = omicron_test_utils::dev::test_setup_log( "test_ensure_service_which_already_exists", ); - let log = logctx.log.clone(); let test_config = TestConfig::new().await; + let helper = + LedgerTestHelper::new(logctx.log.clone(), &test_config).await; + let mgr = helper.new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); - let storage_handle = setup_storage().await; - let zone_bundler = ZoneBundler::new( - log.clone(), - storage_handle.clone(), - Default::default(), - ); - let mgr = ServiceManager::new( - &log, - DdmAdminClient::localhost(&log).unwrap(), - make_bootstrap_networking_config(), - SledMode::Auto, - Some(true), - SidecarRevision::Physical("rev-test".to_string()), - vec![], - storage_handle, - zone_bundler, - ); - test_config.override_paths(&mgr); - - let port_manager = PortManager::new( - logctx.log.new(o!("component" => "PortManager")), - Ipv6Addr::new(0xfd00, 0x1de, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), - ); - mgr.sled_agent_started( - test_config.make_config(), - port_manager, - Ipv6Addr::LOCALHOST, - Uuid::new_v4(), - None, - ) - .unwrap(); - + let v2 = Generation::new().next(); let id = Uuid::new_v4(); - ensure_new_service(&mgr, id).await; - ensure_existing_service(&mgr, id).await; + ensure_new_service(&mgr, id, v2).await; + let v3 = v2.next(); + ensure_existing_service(&mgr, id, v3).await; + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found.generation, v3); + assert_eq!(found.zones.len(), 1); + assert_eq!(found.zones[0].id, id); + drop_service_manager(mgr); logctx.cleanup_successful(); @@ -3378,77 +3954,31 @@ mod test { let logctx = omicron_test_utils::dev::test_setup_log( "test_services_are_recreated_on_reboot", ); - let log = logctx.log.clone(); let test_config = TestConfig::new().await; - let ddmd_client = DdmAdminClient::localhost(&log).unwrap(); - let bootstrap_networking = make_bootstrap_networking_config(); - - // First, spin up a ServiceManager, create a new service, and tear it - // down. - let storage_handle = setup_storage().await; - let zone_bundler = ZoneBundler::new( - log.clone(), - storage_handle.clone(), - Default::default(), - ); - let mgr = ServiceManager::new( - &log, - ddmd_client.clone(), - bootstrap_networking.clone(), - SledMode::Auto, - Some(true), - SidecarRevision::Physical("rev-test".to_string()), - vec![], - storage_handle.clone(), - zone_bundler.clone(), - ); - test_config.override_paths(&mgr); + let helper = + LedgerTestHelper::new(logctx.log.clone(), &test_config).await; - let port_manager = PortManager::new( - log.new(o!("component" => "PortManager")), - Ipv6Addr::new(0xfd00, 0x1de, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), - ); - mgr.sled_agent_started( - test_config.make_config(), - port_manager, - Ipv6Addr::LOCALHOST, - Uuid::new_v4(), - None, - ) - .unwrap(); + // First, spin up a ServiceManager, create a new zone, and then tear + // down the ServiceManager. + let mgr = helper.clone().new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + let v2 = Generation::new().next(); let id = Uuid::new_v4(); - ensure_new_service(&mgr, id).await; + ensure_new_service(&mgr, id, v2).await; drop_service_manager(mgr); // Before we re-create the service manager - notably, using the same // config file! - expect that a service gets initialized. let _expectations = expect_new_service(); - let mgr = ServiceManager::new( - &log, - ddmd_client, - bootstrap_networking, - SledMode::Auto, - Some(true), - SidecarRevision::Physical("rev-test".to_string()), - vec![], - storage_handle.clone(), - zone_bundler.clone(), - ); - test_config.override_paths(&mgr); + let mgr = helper.new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); - let port_manager = PortManager::new( - log.new(o!("component" => "PortManager")), - Ipv6Addr::new(0xfd00, 0x1de, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), - ); - mgr.sled_agent_started( - test_config.make_config(), - port_manager, - Ipv6Addr::LOCALHOST, - Uuid::new_v4(), - None, - ) - .unwrap(); + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found.generation, v2); + assert_eq!(found.zones.len(), 1); + assert_eq!(found.zones[0].id, id); drop_service_manager(mgr); @@ -3461,85 +3991,325 @@ mod test { let logctx = omicron_test_utils::dev::test_setup_log( "test_services_do_not_persist_without_config", ); - let log = logctx.log.clone(); let test_config = TestConfig::new().await; - let ddmd_client = DdmAdminClient::localhost(&log).unwrap(); - let bootstrap_networking = make_bootstrap_networking_config(); - - // First, spin up a ServiceManager, create a new service, and tear it - // down. - let storage_handle = setup_storage().await; - let zone_bundler = ZoneBundler::new( - log.clone(), - storage_handle.clone(), - Default::default(), - ); - let mgr = ServiceManager::new( - &log, - ddmd_client.clone(), - bootstrap_networking.clone(), - SledMode::Auto, - Some(true), - SidecarRevision::Physical("rev-test".to_string()), - vec![], - storage_handle.clone(), - zone_bundler.clone(), - ); - test_config.override_paths(&mgr); + let helper = + LedgerTestHelper::new(logctx.log.clone(), &test_config).await; - let port_manager = PortManager::new( - log.new(o!("component" => "PortManager")), - Ipv6Addr::new(0xfd00, 0x1de, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), - ); - mgr.sled_agent_started( - test_config.make_config(), - port_manager, - Ipv6Addr::LOCALHOST, - Uuid::new_v4(), - None, - ) - .unwrap(); + // First, spin up a ServiceManager, create a new zone, and then tear + // down the ServiceManager. + let mgr = helper.clone().new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + let v1 = Generation::new(); + let v2 = v1.next(); let id = Uuid::new_v4(); - ensure_new_service(&mgr, id).await; + ensure_new_service(&mgr, id, v2).await; drop_service_manager(mgr); - // Next, delete the ledger. This means the service we just created will - // not be remembered on the next initialization. + // Next, delete the ledger. This means the zone we just created will not + // be remembered on the next initialization. std::fs::remove_file( - test_config.config_dir.path().join(SERVICES_LEDGER_FILENAME), + test_config.config_dir.path().join(ZONES_LEDGER_FILENAME), ) .unwrap(); // Observe that the old service is not re-initialized. - let mgr = ServiceManager::new( - &log, - ddmd_client, - bootstrap_networking, - SledMode::Auto, - Some(true), - SidecarRevision::Physical("rev-test".to_string()), - vec![], - storage_handle, - zone_bundler.clone(), - ); - test_config.override_paths(&mgr); + let mgr = helper.new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found.generation, v1); + assert!(found.zones.is_empty()); + + drop_service_manager(mgr); + + logctx.cleanup_successful(); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_bad_generations() { + // Start like the normal tests. + let logctx = + omicron_test_utils::dev::test_setup_log("test_bad_generations"); + let test_config = TestConfig::new().await; + let helper = + LedgerTestHelper::new(logctx.log.clone(), &test_config).await; + let mgr = helper.new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + + // Like the normal tests, set up a generation with one zone in it. + let v1 = Generation::new(); + let v2 = v1.next(); + let id1 = Uuid::new_v4(); + + let _expectations = expect_new_services(); + let address = + SocketAddrV6::new(Ipv6Addr::LOCALHOST, OXIMETER_PORT, 0, 0); + let mut zones = vec![OmicronZoneConfig { + id: id1, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: OmicronZoneType::Oximeter { address }, + }]; + mgr.ensure_all_omicron_zones_persistent(OmicronZonesConfig { + generation: v2, + zones: zones.clone(), + }) + .await + .unwrap(); + + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found.generation, v2); + assert_eq!(found.zones.len(), 1); + assert_eq!(found.zones[0].id, id1); + + // Make a new list of zones that we're going to try with a bunch of + // different generation numbers. + let id2 = Uuid::new_v4(); + zones.push(OmicronZoneConfig { + id: id2, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: OmicronZoneType::Oximeter { address }, + }); + + // Now try to apply that list with an older generation number. This + // shouldn't work and the reported state should be unchanged. + let error = mgr + .ensure_all_omicron_zones_persistent(OmicronZonesConfig { + generation: v1, + zones: zones.clone(), + }) + .await + .expect_err("unexpectedly went backwards in zones generation"); + assert!(matches!( + error, + Error::RequestedConfigOutdated { requested, current } + if requested == v1 && current == v2 + )); + let found2 = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found, found2); + + // Now try to apply that list with the same generation number that we + // used before. This shouldn't work either. + let error = mgr + .ensure_all_omicron_zones_persistent(OmicronZonesConfig { + generation: v2, + zones: zones.clone(), + }) + .await + .expect_err("unexpectedly changed a single zone generation"); + assert!(matches!( + error, + Error::RequestedConfigConflicts(vr) if vr == v2 + )); + let found3 = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found, found3); + + // But we should be able to apply this new list of zones as long as we + // advance the generation number. + let v3 = v2.next(); + mgr.ensure_all_omicron_zones_persistent(OmicronZonesConfig { + generation: v3, + zones: zones.clone(), + }) + .await + .expect("failed to remove all zones in a new generation"); + let found4 = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found4.generation, v3); + let mut our_zones = zones; + our_zones.sort_by(|a, b| a.id.cmp(&b.id)); + let mut found_zones = found4.zones; + found_zones.sort_by(|a, b| a.id.cmp(&b.id)); + assert_eq!(our_zones, found_zones); + + drop_service_manager(mgr); + + logctx.cleanup_successful(); + } - let port_manager = PortManager::new( - log.new(o!("component" => "PortManager")), - Ipv6Addr::new(0xfd00, 0x1de, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01), + #[tokio::test] + #[serial_test::serial] + async fn test_old_ledger_migration() { + let logctx = omicron_test_utils::dev::test_setup_log( + "test_old_ledger_migration", ); - mgr.sled_agent_started( - test_config.make_config(), - port_manager, - Ipv6Addr::LOCALHOST, - Uuid::new_v4(), - None, + let test_config = TestConfig::new().await; + + // Before we start the service manager, stuff one of our old-format + // service ledgers into place. + let contents = + include_str!("../tests/old-service-ledgers/rack2-sled10.json"); + std::fs::write( + test_config.config_dir.path().join(SERVICES_LEDGER_FILENAME), + contents, + ) + .expect("failed to copy example old-format services ledger into place"); + + // Now start the service manager. + let helper = + LedgerTestHelper::new(logctx.log.clone(), &test_config).await; + let mgr = helper.clone().new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + + // Trigger the migration code. (Yes, it's hokey that we create this + // fake argument.) + let unused = Mutex::new(BTreeMap::new()); + let migrated_ledger = mgr + .load_ledgered_zones(&unused.lock().await) + .await + .expect("failed to load ledgered zones") + .unwrap(); + + // As a quick check, the migrated ledger should have some zones. + let migrated_config = migrated_ledger.data(); + assert!(!migrated_config.zones.is_empty()); + + // The ServiceManager should now report the migrated zones, meaning that + // they've been copied into the new-format ledger. + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found, migrated_config.clone().to_omicron_zones_config()); + // They should both match the expected converted output. + let expected: OmicronZonesConfigLocal = serde_json::from_str( + include_str!("../tests/output/new-zones-ledgers/rack2-sled10.json"), ) .unwrap(); + let expected_config = expected.to_omicron_zones_config(); + assert_eq!(found, expected_config); + // Just to be sure, shut down the manager and create a new one without + // triggering migration again. It should also report the same zones. drop_service_manager(mgr); + let mgr = helper.new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found, expected_config); + + drop_service_manager(mgr); + logctx.cleanup_successful(); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_old_ledger_migration_continue() { + // This test is just like "test_old_ledger_migration", except that we + // deploy a new zone after migration and before shutting down the + // service manager. This tests that new changes modify the new, + // migrated config. + let logctx = omicron_test_utils::dev::test_setup_log( + "test_old_ledger_migration_continue", + ); + let test_config = TestConfig::new().await; + + // Before we start the service manager, stuff one of our old-format + // service ledgers into place. + let contents = + include_str!("../tests/old-service-ledgers/rack2-sled10.json"); + std::fs::write( + test_config.config_dir.path().join(SERVICES_LEDGER_FILENAME), + contents, + ) + .expect("failed to copy example old-format services ledger into place"); + + // Now start the service manager. + let helper = + LedgerTestHelper::new(logctx.log.clone(), &test_config).await; + let mgr = helper.clone().new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + + // Trigger the migration code. + let unused = Mutex::new(BTreeMap::new()); + let migrated_ledger = mgr + .load_ledgered_zones(&unused.lock().await) + .await + .expect("failed to load ledgered zones") + .unwrap(); + + // The other test verified that migration has happened normally so let's + // assume it has. Now provision a new zone. + let vv = migrated_ledger.data().omicron_generation.next(); + let id = Uuid::new_v4(); + + let _expectations = expect_new_services(); + let address = + SocketAddrV6::new(Ipv6Addr::LOCALHOST, OXIMETER_PORT, 0, 0); + let mut zones = + migrated_ledger.data().clone().to_omicron_zones_config().zones; + zones.push(OmicronZoneConfig { + id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: OmicronZoneType::Oximeter { address }, + }); + mgr.ensure_all_omicron_zones_persistent(OmicronZonesConfig { + generation: vv, + zones, + }) + .await + .expect("failed to add new zone after migration"); + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found.generation, vv); + assert_eq!(found.zones.len(), migrated_ledger.data().zones.len() + 1); + + // Just to be sure, shut down the manager and create a new one without + // triggering migration again. It should now report one more zone than + // was migrated earlier. + drop_service_manager(mgr); + + let mgr = helper.new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + let found = + mgr.omicron_zones_list().await.expect("failed to list zones"); + assert_eq!(found.generation, vv); + assert_eq!(found.zones.len(), migrated_ledger.data().zones.len() + 1); + + drop_service_manager(mgr); + logctx.cleanup_successful(); + } + + #[tokio::test] + #[serial_test::serial] + async fn test_old_ledger_migration_bad() { + let logctx = omicron_test_utils::dev::test_setup_log( + "test_old_ledger_migration_bad", + ); + let test_config = TestConfig::new().await; + let helper = + LedgerTestHelper::new(logctx.log.clone(), &test_config).await; + + // Before we start things, stuff a broken ledger into place. For this + // to test what we want, it needs to be a valid ledger that we simply + // failed to convert. + std::fs::write( + test_config.config_dir.path().join(SERVICES_LEDGER_FILENAME), + "{", + ) + .expect("failed to copy example old-format services ledger into place"); + + // Start the service manager. + let mgr = helper.new_service_manager(); + LedgerTestHelper::sled_agent_started(&logctx.log, &test_config, &mgr); + + // Trigger the migration code. + let unused = Mutex::new(BTreeMap::new()); + let error = mgr + .load_ledgered_zones(&unused.lock().await) + .await + .expect_err("succeeded in loading bogus ledgered zones"); + assert_eq!( + "Error migrating old-format services ledger: failed to read or \ + parse old-format ledger, but one exists", + format!("{:#}", error) + ); + logctx.cleanup_successful(); } @@ -3557,19 +4327,19 @@ mod test { } #[test] - fn test_all_zone_requests_schema() { - let schema = schemars::schema_for!(AllZoneRequests); + fn test_zone_bundle_metadata_schema() { + let schema = schemars::schema_for!(ZoneBundleMetadata); expectorate::assert_contents( - "../schema/all-zone-requests.json", + "../schema/zone-bundle-metadata.json", &serde_json::to_string_pretty(&schema).unwrap(), ); } #[test] - fn test_zone_bundle_metadata_schema() { - let schema = schemars::schema_for!(ZoneBundleMetadata); + fn test_all_zones_requests_schema() { + let schema = schemars::schema_for!(OmicronZonesConfigLocal); expectorate::assert_contents( - "../schema/zone-bundle-metadata.json", + "../schema/all-zones-requests.json", &serde_json::to_string_pretty(&schema).unwrap(), ); } diff --git a/sled-agent/src/services_migration.rs b/sled-agent/src/services_migration.rs new file mode 100644 index 0000000000..bedd4759c8 --- /dev/null +++ b/sled-agent/src/services_migration.rs @@ -0,0 +1,624 @@ +// 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/. + +//! Sled Agents are responsible for running zones that make up much of the +//! control plane (Omicron). Configuration for these zones is owned by the +//! control plane, but that configuration must be persisted locally in order to +//! support cold boot of the control plane. (The control plane can't very well +//! tell sled agents what to run if it's not online yet!) +//! +//! Historically, these configurations were represented as an +//! `AllZonesRequests`, which contains a bunch of `ZoneRequest`s, each +//! containing a `ServiceZoneRequest`. This last structure was quite general +//! and made it possible to express a world of configurations that are not +//! actually valid. To avoid spreading extra complexity, these structures were +//! replaced with `OmicronZonesConfigLocal` and `OmicronZonesConfig`, +//! respectively. Upgrading production systems across this change requires +//! migrating any locally-stored configuration in the old format into the new +//! one. +//! +//! This file defines these old-format types and functions to convert them to +//! the new types, solely to perform that migration. We can remove all this +//! when we're satified that all deployed systems that we care about have moved +//! past this change. + +use crate::params::{ + OmicronZoneConfig, OmicronZoneDataset, OmicronZoneType, ZoneType, + OMICRON_ZONES_CONFIG_INITIAL_GENERATION, +}; +use crate::services::{OmicronZoneConfigLocal, OmicronZonesConfigLocal}; +use anyhow::{anyhow, ensure, Context}; +use camino::Utf8PathBuf; +use omicron_common::api::external::Generation; +use omicron_common::api::internal::shared::{ + NetworkInterface, SourceNatConfig, +}; +use omicron_common::ledger::Ledgerable; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use sled_storage::dataset::{DatasetKind, DatasetName}; +use std::fmt::Debug; +use std::net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6}; +use uuid::Uuid; + +/// The filename of the ledger containing this old-format configuration. +pub const SERVICES_LEDGER_FILENAME: &str = "services.json"; + +/// A wrapper around `ZoneRequest` that allows it to be serialized to a JSON +/// file. +#[derive(Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +pub struct AllZoneRequests { + /// ledger generation (not an Omicron-provided generation) + generation: Generation, + requests: Vec, +} + +impl Default for AllZoneRequests { + fn default() -> Self { + Self { generation: Generation::new(), requests: vec![] } + } +} + +impl Ledgerable for AllZoneRequests { + fn is_newer_than(&self, other: &AllZoneRequests) -> bool { + self.generation >= other.generation + } + + fn generation_bump(&mut self) { + self.generation = self.generation.next(); + } +} + +impl TryFrom for OmicronZonesConfigLocal { + type Error = anyhow::Error; + + fn try_from(input: AllZoneRequests) -> Result { + // The Omicron generation number that we choose here (2) deserves some + // explanation. + // + // This is supposed to be the control-plane-issued generation number for + // this configuration. But any configuration that we're converting here + // predates the point where the control plane issued generation numbers + // at all. So what should we assign it? Well, what are the + // constraints? + // + // - It must be newer than generation 1 because generation 1 canonically + // represents the initial state of having no zones deployed. If we + // used generation 1 here, any code could ignore this configuration on + // the grounds that it's no newer than what it already has. (The + // contents of a given generation are supposed to be immutable.) + // + // - It should be older than anything else that the control plane might + // try to send us so that if the control plane wants to change + // anything, we won't ignore its request because we think this + // configuration is newer. But really this has to be the control + // plane's responsibility, not ours. That is: Nexus needs to ask us + // what our generation number is and subsequent configurations should + // use newer generation numbers. It's not a great plan for it to + // assume anything about the generation numbers deployed on sleds + // whose configurations it's never seen. (In practice, newly deployed + // systems currently wind up with generation 5, so it _could_ choose + // something like 6 to start with -- or some larger number to leave + // some buffer.) + // + // In summary, 2 seems fine. + let omicron_generation = + Generation::from(OMICRON_ZONES_CONFIG_INITIAL_GENERATION).next(); + + // The ledger generation doesn't really matter. In case it's useful, we + // pick the generation from the ledger that we loaded. + let ledger_generation = input.generation; + + let ndatasets_input = + input.requests.iter().filter(|r| r.zone.dataset.is_some()).count(); + + let zones = input + .requests + .into_iter() + .map(OmicronZoneConfigLocal::try_from) + .collect::, _>>() + .context( + "mapping `AllZoneRequests` to `OmicronZonesConfigLocal`", + )?; + + // As a quick check, the number of datasets in the old and new + // generations ought to be the same. + let ndatasets_output = + zones.iter().filter(|r| r.zone.dataset_name().is_some()).count(); + ensure!( + ndatasets_input == ndatasets_output, + "conversion produced a different number of datasets" + ); + + Ok(OmicronZonesConfigLocal { + omicron_generation, + ledger_generation, + zones, + }) + } +} + +/// This struct represents the combo of "what zone did you ask for" + "where did +/// we put it". +#[derive(Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] +struct ZoneRequest { + zone: ServiceZoneRequest, + #[schemars(with = "String")] + root: Utf8PathBuf, +} + +impl TryFrom for OmicronZoneConfigLocal { + type Error = anyhow::Error; + + fn try_from(input: ZoneRequest) -> Result { + Ok(OmicronZoneConfigLocal { + zone: OmicronZoneConfig::try_from(input.zone)?, + root: input.root, + }) + } +} + +/// Describes a request to create a zone running one or more services. +#[derive( + Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, +)] +struct ServiceZoneRequest { + // The UUID of the zone to be initialized. + id: Uuid, + // The type of the zone to be created. + zone_type: ZoneType, + // The addresses on which the service should listen for requests. + addresses: Vec, + // Datasets which should be managed by this service. + #[serde(default)] + dataset: Option, + // Services that should be run in the zone + services: Vec, +} + +impl TryFrom for OmicronZoneConfig { + type Error = anyhow::Error; + + fn try_from(input: ServiceZoneRequest) -> Result { + let error_context = || { + format!( + "zone {} (type {:?})", + input.id, + input.zone_type.to_string() + ) + }; + + // Historically, this type was used to describe two distinct kinds of + // thing: + // + // 1. an "Omicron" zone: Clickhouse, CockroachDb, Nexus, etc. We call + // these Omicron zones because they're managed by the control plane + // (Omicron). Nexus knows about these, stores information in + // CockroachDB about them, and is responsible for using Sled Agent + // APIs to configure these zones. + // + // 2. a "sled-local" zone. The only such zone is the "switch" zone. + // This is not really known to Nexus nor exposed outside Sled Agent. + // It's configured either based on Sled Agent's config file or else + // autodetection of whether this system _is_ a Scrimlet. + // + // All of the types in this file describe the ledgered configuration of + // the Omicron zones. We don't care about the switch zone here. Even + // for Omicron zones, the `ServiceZoneRequest` type is much more general + // than was strictly necessary to represent the kinds of zones we + // defined in practice. The more constrained schema is described by + // `OmicronZoneConfig`. This function verifies that the structures we + // find conform to that more constrained schema. + // + // Many of these properties were determined by code inspection. They + // could be wrong! But we've tried hard to make sure we're not wrong. + + match input.zone_type { + ZoneType::Clickhouse + | ZoneType::ClickhouseKeeper + | ZoneType::CockroachDb + | ZoneType::CruciblePantry + | ZoneType::Crucible + | ZoneType::ExternalDns + | ZoneType::InternalDns + | ZoneType::Nexus + | ZoneType::Ntp + | ZoneType::Oximeter => (), + ZoneType::Switch => { + return Err(anyhow!("unsupported zone type")) + .with_context(error_context) + } + } + + let id = input.id; + + // In production systems, Omicron zones only ever had exactly one + // address here. Multiple addresses were used for the "switch" zone, + // which cannot appear here. + if input.addresses.len() != 1 { + return Err(anyhow!( + "expected exactly one address, found {}", + input.addresses.len() + )) + .with_context(error_context); + } + + let underlay_address = input.addresses[0]; + + // In production systems, Omicron zones only ever had exactly one + // "service" inside them. (Multiple services were only supported for + // the "switch" zone and for Omicron zones in pre-release versions of + // Omicron, neither of which we expect to see here.) + if input.services.len() != 1 { + return Err(anyhow!( + "expected exactly one service, found {}", + input.services.len(), + )) + .with_context(error_context); + } + + let service = input.services.into_iter().next().unwrap(); + + // The id for the one service we found must match the overall request + // id. + if service.id != input.id { + return Err(anyhow!( + "expected service id ({}) to match id ({})", + service.id, + input.id, + )) + .with_context(error_context); + } + + // If there's a dataset, its id must match the overall request id. + let dataset_request = input + .dataset + .ok_or_else(|| anyhow!("missing dataset")) + .with_context(error_context); + let has_dataset = dataset_request.is_ok(); + if let Ok(dataset) = &dataset_request { + if dataset.id != input.id { + return Err(anyhow!( + "expected dataset id ({}) to match id ({})", + dataset.id, + input.id, + )) + .with_context(error_context); + } + } + + let zone_type = match service.details { + ServiceType::Nexus { + internal_address, + external_ip, + nic, + external_tls, + external_dns_servers, + } => OmicronZoneType::Nexus { + internal_address, + external_ip, + nic, + external_tls, + external_dns_servers, + }, + ServiceType::ExternalDns { http_address, dns_address, nic } => { + OmicronZoneType::ExternalDns { + dataset: dataset_request?.to_omicron_zone_dataset( + DatasetKind::ExternalDns, + http_address, + )?, + http_address, + dns_address, + nic, + } + } + ServiceType::InternalDns { + http_address, + dns_address, + gz_address, + gz_address_index, + } => OmicronZoneType::InternalDns { + dataset: dataset_request?.to_omicron_zone_dataset( + DatasetKind::InternalDns, + http_address, + )?, + http_address, + dns_address, + gz_address, + gz_address_index, + }, + ServiceType::Oximeter { address } => { + OmicronZoneType::Oximeter { address } + } + ServiceType::CruciblePantry { address } => { + OmicronZoneType::CruciblePantry { address } + } + ServiceType::BoundaryNtp { + address, + ntp_servers, + dns_servers, + domain, + nic, + snat_cfg, + } => OmicronZoneType::BoundaryNtp { + address, + ntp_servers, + dns_servers, + domain, + nic, + snat_cfg, + }, + ServiceType::InternalNtp { + address, + ntp_servers, + dns_servers, + domain, + } => OmicronZoneType::InternalNtp { + address, + ntp_servers, + dns_servers, + domain, + }, + ServiceType::Clickhouse { address } => { + OmicronZoneType::Clickhouse { + address, + dataset: dataset_request?.to_omicron_zone_dataset( + DatasetKind::Clickhouse, + address, + )?, + } + } + ServiceType::ClickhouseKeeper { address } => { + OmicronZoneType::ClickhouseKeeper { + address, + dataset: dataset_request?.to_omicron_zone_dataset( + DatasetKind::ClickhouseKeeper, + address, + )?, + } + } + ServiceType::CockroachDb { address } => { + OmicronZoneType::CockroachDb { + address, + dataset: dataset_request?.to_omicron_zone_dataset( + DatasetKind::CockroachDb, + address, + )?, + } + } + ServiceType::Crucible { address } => OmicronZoneType::Crucible { + address, + dataset: dataset_request? + .to_omicron_zone_dataset(DatasetKind::Crucible, address)?, + }, + }; + + if zone_type.dataset_name().is_none() && has_dataset { + // This indicates that the legacy form specified a dataset for a + // zone type that we do not (today) believe should have one. This + // should be impossible. If it happens, we need to re-evaluate our + // assumptions in designing `OmicronZoneType`. + return Err(anyhow!("found dataset that went unused")) + .with_context(error_context); + } + + Ok(OmicronZoneConfig { id, underlay_address, zone_type }) + } +} + +/// Used to request that the Sled initialize a single service. +#[derive( + Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, +)] +struct ServiceZoneService { + id: Uuid, + details: ServiceType, +} + +/// Describes service-specific parameters. +#[derive( + Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, +)] +#[serde(tag = "type", rename_all = "snake_case")] +enum ServiceType { + Nexus { + /// The address at which the internal nexus server is reachable. + internal_address: SocketAddrV6, + /// The address at which the external nexus server is reachable. + external_ip: IpAddr, + /// The service vNIC providing external connectivity using OPTE. + nic: NetworkInterface, + /// Whether Nexus's external endpoint should use TLS + external_tls: bool, + /// External DNS servers Nexus can use to resolve external hosts. + external_dns_servers: Vec, + }, + ExternalDns { + /// The address at which the external DNS server API is reachable. + http_address: SocketAddrV6, + /// The address at which the external DNS server is reachable. + dns_address: SocketAddr, + /// The service vNIC providing external connectivity using OPTE. + nic: NetworkInterface, + }, + InternalDns { + http_address: SocketAddrV6, + dns_address: SocketAddrV6, + /// The addresses in the global zone which should be created + /// + /// For the DNS service, which exists outside the sleds's typical subnet + /// - adding an address in the GZ is necessary to allow inter-zone + /// traffic routing. + gz_address: Ipv6Addr, + + /// The address is also identified with an auxiliary bit of information + /// to ensure that the created global zone address can have a unique + /// name. + gz_address_index: u32, + }, + Oximeter { + address: SocketAddrV6, + }, + CruciblePantry { + address: SocketAddrV6, + }, + BoundaryNtp { + address: SocketAddrV6, + ntp_servers: Vec, + dns_servers: Vec, + domain: Option, + /// The service vNIC providing outbound connectivity using OPTE. + nic: NetworkInterface, + /// The SNAT configuration for outbound connections. + snat_cfg: SourceNatConfig, + }, + InternalNtp { + address: SocketAddrV6, + ntp_servers: Vec, + dns_servers: Vec, + domain: Option, + }, + Clickhouse { + address: SocketAddrV6, + }, + ClickhouseKeeper { + address: SocketAddrV6, + }, + CockroachDb { + address: SocketAddrV6, + }, + Crucible { + address: SocketAddrV6, + }, +} + +/// Describes a request to provision a specific dataset +#[derive( + Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, +)] +struct DatasetRequest { + id: Uuid, + name: DatasetName, + service_address: SocketAddrV6, +} + +impl DatasetRequest { + fn to_omicron_zone_dataset( + self, + kind: DatasetKind, + service_address: SocketAddrV6, + ) -> Result { + ensure!( + kind == *self.name.dataset(), + "expected dataset kind {:?}, found {:?}", + kind, + self.name.dataset(), + ); + + ensure!( + self.service_address == service_address, + "expected dataset kind {:?} service address to be {}, found {}", + kind, + service_address, + self.service_address, + ); + + Ok(OmicronZoneDataset { pool_name: self.name.pool().clone() }) + } +} + +#[cfg(test)] +mod test { + use super::AllZoneRequests; + use crate::services::OmicronZonesConfigLocal; + use camino::Utf8PathBuf; + + /// Verifies that our understanding of this old-format ledger has not + /// changed. (If you need to change this for some reason, you must figure + /// out how that affects systems with old-format ledgers and update this + /// test accordingly.) + #[test] + fn test_all_services_requests_schema() { + let schema = schemars::schema_for!(AllZoneRequests); + expectorate::assert_contents( + "../schema/all-zone-requests.json", + &serde_json::to_string_pretty(&schema).unwrap(), + ); + } + + /// Verifies that we can successfully convert a corpus of known old-format + /// ledgers. These came from two racks operated by Oxide. In practice + /// there probably aren't many different configurations represented here but + /// it's easy enough to just check them all. + /// + /// In terms of verifying the output: all we have done by hand in + /// constructing this test is verify that the code successfully converts + /// them. The conversion code does some basic sanity checks as well, like + /// that we produced the same number of zones and datasets. + #[test] + fn test_convert_known_ledgers() { + let known_ledgers = &[ + /* rack2 */ + "rack2-sled8.json", + "rack2-sled9.json", + "rack2-sled10.json", + "rack2-sled11.json", + "rack2-sled12.json", + "rack2-sled14.json", + "rack2-sled16.json", + "rack2-sled17.json", + "rack2-sled21.json", + "rack2-sled23.json", + "rack2-sled25.json", + /* rack3 (no sled 10) */ + "rack3-sled0.json", + "rack3-sled1.json", + "rack3-sled2.json", + "rack3-sled3.json", + "rack3-sled4.json", + "rack3-sled5.json", + "rack3-sled6.json", + "rack3-sled7.json", + "rack3-sled8.json", + "rack3-sled9.json", + "rack3-sled11.json", + "rack3-sled12.json", + "rack3-sled13.json", + "rack3-sled14.json", + "rack3-sled15.json", + "rack3-sled16.json", + "rack3-sled17.json", + "rack3-sled18.json", + "rack3-sled19.json", + "rack3-sled20.json", + "rack3-sled21.json", + "rack3-sled22.json", + "rack3-sled23.json", + "rack3-sled24.json", + "rack3-sled25.json", + "rack3-sled26.json", + "rack3-sled27.json", + "rack3-sled28.json", + "rack3-sled29.json", + "rack3-sled30.json", + "rack3-sled31.json", + ]; + + let path = Utf8PathBuf::from("tests/old-service-ledgers"); + let out_path = Utf8PathBuf::from("tests/output/new-zones-ledgers"); + for ledger_basename in known_ledgers { + println!("checking {:?}", ledger_basename); + let contents = std::fs::read_to_string(path.join(ledger_basename)) + .expect("failed to read file"); + let parsed: AllZoneRequests = + serde_json::from_str(&contents).expect("failed to parse file"); + let converted = OmicronZonesConfigLocal::try_from(parsed) + .expect("failed to convert file"); + expectorate::assert_contents( + out_path.join(ledger_basename), + &serde_json::to_string_pretty(&converted).unwrap(), + ); + } + } +} diff --git a/sled-agent/src/sim/collection.rs b/sled-agent/src/sim/collection.rs index bd6ed4aa90..8dae31863c 100644 --- a/sled-agent/src/sim/collection.rs +++ b/sled-agent/src/sim/collection.rs @@ -777,7 +777,7 @@ mod test { let error = disk.transition(DiskStateRequested::Attached(id2)).unwrap_err(); if let Error::InvalidRequest { message } = error { - assert_eq!("disk is already attached", message); + assert_eq!("disk is already attached", message.external_message()); } else { panic!("unexpected error type"); } @@ -829,7 +829,10 @@ mod test { let error = disk.transition(DiskStateRequested::Attached(id)).unwrap_err(); if let Error::InvalidRequest { message } = error { - assert_eq!("cannot attach from detaching", message); + assert_eq!( + "cannot attach from detaching", + message.external_message() + ); } else { panic!("unexpected error type"); } diff --git a/sled-agent/src/sim/disk.rs b/sled-agent/src/sim/disk.rs index f131fd2bff..fc388f6ce2 100644 --- a/sled-agent/src/sim/disk.rs +++ b/sled-agent/src/sim/disk.rs @@ -169,7 +169,7 @@ impl SimDisk { let producer_address = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0); let server_info = ProducerEndpoint { id, - kind: Some(ProducerKind::SledAgent), + kind: ProducerKind::SledAgent, address: producer_address, base_route: "/collect".to_string(), interval: Duration::from_millis(200), diff --git a/sled-agent/src/sim/http_entrypoints_pantry.rs b/sled-agent/src/sim/http_entrypoints_pantry.rs index 64b26a83a4..8430dc0731 100644 --- a/sled-agent/src/sim/http_entrypoints_pantry.rs +++ b/sled-agent/src/sim/http_entrypoints_pantry.rs @@ -96,7 +96,7 @@ struct JobPollResponse { /// Poll to see if a Pantry background job is done #[endpoint { method = GET, - path = "/crucible/pantry/0/job/{id}/is_finished", + path = "/crucible/pantry/0/job/{id}/is-finished", }] async fn is_job_finished( rc: RequestContext>, @@ -139,6 +139,7 @@ async fn job_result_ok( } #[derive(Debug, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] pub enum ExpectedDigest { Sha256(String), } @@ -157,7 +158,7 @@ struct ImportFromUrlResponse { /// Import data from a URL into a volume #[endpoint { method = POST, - path = "/crucible/pantry/0/volume/{id}/import_from_url", + path = "/crucible/pantry/0/volume/{id}/import-from-url", }] async fn import_from_url( rc: RequestContext>, @@ -213,7 +214,7 @@ struct BulkWriteRequest { /// Bulk write data into a volume at a specified offset #[endpoint { method = POST, - path = "/crucible/pantry/0/volume/{id}/bulk_write", + path = "/crucible/pantry/0/volume/{id}/bulk-write", }] async fn bulk_write( rc: RequestContext>, diff --git a/sled-agent/src/sim/instance.rs b/sled-agent/src/sim/instance.rs index 15ff83c969..8b00adce60 100644 --- a/sled-agent/src/sim/instance.rs +++ b/sled-agent/src/sim/instance.rs @@ -362,13 +362,11 @@ impl SimInstanceInner { } if self.state.instance().gen != old_runtime.gen { - return Err(Error::InvalidRequest { - message: format!( - "wrong Propolis ID generation: expected {}, got {}", - self.state.instance().gen, - old_runtime.gen - ), - }); + return Err(Error::invalid_request(format!( + "wrong Propolis ID generation: expected {}, got {}", + self.state.instance().gen, + old_runtime.gen + ))); } self.state.set_migration_ids(ids, Utc::now()); diff --git a/sled-agent/src/sim/sled_agent.rs b/sled-agent/src/sim/sled_agent.rs index a7dce7730c..c06ae96f2e 100644 --- a/sled-agent/src/sim/sled_agent.rs +++ b/sled-agent/src/sim/sled_agent.rs @@ -651,10 +651,7 @@ impl SledAgent { &dropshot_log, ) .map_err(|error| { - Error::unavail_external(format!( - "initializing propolis-server: {}", - error - )) + Error::unavail(&format!("initializing propolis-server: {}", error)) })? .start(); let client = propolis_client::Client::new(&format!( diff --git a/sled-agent/src/sim/storage.rs b/sled-agent/src/sim/storage.rs index 2528a258d7..101228934d 100644 --- a/sled-agent/src/sim/storage.rs +++ b/sled-agent/src/sim/storage.rs @@ -40,6 +40,7 @@ struct CrucibleDataInner { running_snapshots: HashMap>, on_create: Option, region_creation_error: bool, + region_deletion_error: bool, creating_a_running_snapshot_should_fail: bool, next_port: u16, } @@ -53,6 +54,7 @@ impl CrucibleDataInner { running_snapshots: HashMap::new(), on_create: None, region_creation_error: false, + region_deletion_error: false, creating_a_running_snapshot_should_fail: false, next_port: crucible_port, } @@ -129,6 +131,10 @@ impl CrucibleDataInner { ); } + if self.region_deletion_error { + bail!("region deletion error!"); + } + let id = Uuid::from_str(&id.0).unwrap(); if let Some(region) = self.regions.get_mut(&id) { if region.state == State::Failed { @@ -229,6 +235,10 @@ impl CrucibleDataInner { self.region_creation_error = value; } + fn set_region_deletion_error(&mut self, value: bool) { + self.region_deletion_error = value; + } + fn create_running_snapshot( &mut self, id: &RegionId, @@ -391,6 +401,10 @@ impl CrucibleData { self.inner.lock().await.set_region_creation_error(value); } + pub async fn set_region_deletion_error(&self, value: bool) { + self.inner.lock().await.set_region_deletion_error(value); + } + pub async fn create_running_snapshot( &self, id: &RegionId, diff --git a/sled-agent/src/sled_agent.rs b/sled-agent/src/sled_agent.rs index f5b71106cd..90e9706198 100644 --- a/sled-agent/src/sled_agent.rs +++ b/sled-agent/src/sled_agent.rs @@ -17,7 +17,7 @@ use crate::nexus::{ConvertInto, NexusClientWithResolver, NexusRequestQueue}; use crate::params::{ DiskStateRequested, InstanceHardware, InstanceMigrationSourceParams, InstancePutStateResponse, InstanceStateRequested, - InstanceUnregisterResponse, ServiceEnsureBody, SledRole, TimeSync, + InstanceUnregisterResponse, OmicronZonesConfig, SledRole, TimeSync, VpcFirewallRule, ZoneBundleMetadata, Zpool, }; use crate::services::{self, ServiceManager}; @@ -507,7 +507,7 @@ impl SledAgent { // Nexus. This should not block progress here. let endpoint = ProducerEndpoint { id: request.body.id, - kind: Some(ProducerKind::SledAgent), + kind: ProducerKind::SledAgent, address: sled_address.into(), base_route: String::from("/metrics/collect"), interval: crate::metrics::METRIC_COLLECTION_INTERVAL, @@ -555,12 +555,11 @@ impl SledAgent { Ok(sled_agent) } - /// Load services for which we're responsible; only meaningful to call - /// during a cold boot. + /// Load services for which we're responsible. /// /// Blocks until all services have started, retrying indefinitely on /// failure. - pub(crate) async fn cold_boot_load_services(&self) { + pub(crate) async fn load_services(&self) { info!(self.log, "Loading cold boot services"); retry_notify( retry_policy_internal_service_aggressive(), @@ -803,36 +802,40 @@ impl SledAgent { self.inner.zone_bundler.cleanup().await.map_err(Error::from) } - /// Ensures that particular services should be initialized. - /// - /// These services will be instantiated by this function, will be recorded - /// to a local file to ensure they start automatically on next boot. - pub async fn services_ensure( + /// List the Omicron zone configuration that's currently running + pub async fn omicron_zones_list( &self, - requested_services: ServiceEnsureBody, - ) -> Result<(), Error> { - let datasets: Vec<_> = requested_services - .services - .iter() - .filter_map(|service| service.dataset.clone()) - .collect(); + ) -> Result { + Ok(self.inner.services.omicron_zones_list().await?) + } + /// Ensures that the specific set of Omicron zones are running as configured + /// (and that no other zones are running) + pub async fn omicron_zones_ensure( + &self, + requested_zones: OmicronZonesConfig, + ) -> Result<(), Error> { // TODO: // - If these are the set of filesystems, we should also consider // removing the ones which are not listed here. // - It's probably worth sending a bulk request to the storage system, // rather than requesting individual datasets. - for dataset in &datasets { + for zone in &requested_zones.zones { + let Some(dataset_name) = zone.dataset_name() else { + continue; + }; + // First, ensure the dataset exists + let dataset_id = zone.id; self.inner .storage - .upsert_filesystem(dataset.id, dataset.name.clone()) + .upsert_filesystem(dataset_id, dataset_name) .await?; } self.inner .services - .ensure_all_services_persistent(requested_services) + .ensure_all_omicron_zones_persistent(requested_zones) .await?; Ok(()) } diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled10.json b/sled-agent/tests/old-service-ledgers/rack2-sled10.json new file mode 100644 index 0000000000..b92a2bf4a0 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled10.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"04eef8aa-055c-42ab-bdb6-c982f63c9be0","zone_type":"crucible","addresses":["fd00:1122:3344:107::d"],"dataset":{"id":"04eef8aa-055c-42ab-bdb6-c982f63c9be0","name":{"pool_name":"oxp_845ff39a-3205-416f-8bda-e35829107c8a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::d]:32345"},"services":[{"id":"04eef8aa-055c-42ab-bdb6-c982f63c9be0","details":{"type":"crucible","address":"[fd00:1122:3344:107::d]:32345"}}]},"root":"/pool/ext/43efdd6d-7419-437a-a282-fc45bfafd042/crypt/zone"},{"zone":{"id":"8568c997-fbbb-46a8-8549-b78284530ffc","zone_type":"crucible","addresses":["fd00:1122:3344:107::5"],"dataset":{"id":"8568c997-fbbb-46a8-8549-b78284530ffc","name":{"pool_name":"oxp_0e485ad3-04e6-404b-b619-87d4fea9f5ae","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::5]:32345"},"services":[{"id":"8568c997-fbbb-46a8-8549-b78284530ffc","details":{"type":"crucible","address":"[fd00:1122:3344:107::5]:32345"}}]},"root":"/pool/ext/9b61d4b2-66f6-459f-86f4-13d0b8c5d6cf/crypt/zone"},{"zone":{"id":"6cec1d60-5c1a-4c1b-9632-2b4bc76bd37c","zone_type":"crucible","addresses":["fd00:1122:3344:107::e"],"dataset":{"id":"6cec1d60-5c1a-4c1b-9632-2b4bc76bd37c","name":{"pool_name":"oxp_62a4c68a-2073-42d0-8e49-01f5e8b90cd4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::e]:32345"},"services":[{"id":"6cec1d60-5c1a-4c1b-9632-2b4bc76bd37c","details":{"type":"crucible","address":"[fd00:1122:3344:107::e]:32345"}}]},"root":"/pool/ext/845ff39a-3205-416f-8bda-e35829107c8a/crypt/zone"},{"zone":{"id":"aa646c82-c6d7-4d0c-8401-150130927759","zone_type":"clickhouse","addresses":["fd00:1122:3344:107::4"],"dataset":{"id":"aa646c82-c6d7-4d0c-8401-150130927759","name":{"pool_name":"oxp_0e485ad3-04e6-404b-b619-87d4fea9f5ae","kind":{"type":"clickhouse"}},"service_address":"[fd00:1122:3344:107::4]:8123"},"services":[{"id":"aa646c82-c6d7-4d0c-8401-150130927759","details":{"type":"clickhouse","address":"[fd00:1122:3344:107::4]:8123"}}]},"root":"/pool/ext/fd82dcc7-00dd-4d01-826a-937a7d8238fb/crypt/zone"},{"zone":{"id":"2f294ca1-7a4f-468f-8966-2b7915804729","zone_type":"crucible","addresses":["fd00:1122:3344:107::7"],"dataset":{"id":"2f294ca1-7a4f-468f-8966-2b7915804729","name":{"pool_name":"oxp_43efdd6d-7419-437a-a282-fc45bfafd042","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::7]:32345"},"services":[{"id":"2f294ca1-7a4f-468f-8966-2b7915804729","details":{"type":"crucible","address":"[fd00:1122:3344:107::7]:32345"}}]},"root":"/pool/ext/fd82dcc7-00dd-4d01-826a-937a7d8238fb/crypt/zone"},{"zone":{"id":"1a77bd1d-4fd4-4d6c-a105-17f942d94ba6","zone_type":"crucible","addresses":["fd00:1122:3344:107::c"],"dataset":{"id":"1a77bd1d-4fd4-4d6c-a105-17f942d94ba6","name":{"pool_name":"oxp_b6bdfdaf-9c0d-4b74-926c-49ff3ed05562","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::c]:32345"},"services":[{"id":"1a77bd1d-4fd4-4d6c-a105-17f942d94ba6","details":{"type":"crucible","address":"[fd00:1122:3344:107::c]:32345"}}]},"root":"/pool/ext/9b61d4b2-66f6-459f-86f4-13d0b8c5d6cf/crypt/zone"},{"zone":{"id":"f65a6668-1aea-4deb-81ed-191fbe469328","zone_type":"crucible","addresses":["fd00:1122:3344:107::9"],"dataset":{"id":"f65a6668-1aea-4deb-81ed-191fbe469328","name":{"pool_name":"oxp_9b61d4b2-66f6-459f-86f4-13d0b8c5d6cf","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::9]:32345"},"services":[{"id":"f65a6668-1aea-4deb-81ed-191fbe469328","details":{"type":"crucible","address":"[fd00:1122:3344:107::9]:32345"}}]},"root":"/pool/ext/d0584f4a-20ba-436d-a75b-7709e80deb79/crypt/zone"},{"zone":{"id":"ee8bce67-8f8e-4221-97b0-85f1860d66d0","zone_type":"crucible","addresses":["fd00:1122:3344:107::8"],"dataset":{"id":"ee8bce67-8f8e-4221-97b0-85f1860d66d0","name":{"pool_name":"oxp_b252b176-3974-436a-915b-60382b21eb76","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::8]:32345"},"services":[{"id":"ee8bce67-8f8e-4221-97b0-85f1860d66d0","details":{"type":"crucible","address":"[fd00:1122:3344:107::8]:32345"}}]},"root":"/pool/ext/b6bdfdaf-9c0d-4b74-926c-49ff3ed05562/crypt/zone"},{"zone":{"id":"cf3b2d54-5e36-4c93-b44f-8bf36ac98071","zone_type":"crucible","addresses":["fd00:1122:3344:107::b"],"dataset":{"id":"cf3b2d54-5e36-4c93-b44f-8bf36ac98071","name":{"pool_name":"oxp_d0584f4a-20ba-436d-a75b-7709e80deb79","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::b]:32345"},"services":[{"id":"cf3b2d54-5e36-4c93-b44f-8bf36ac98071","details":{"type":"crucible","address":"[fd00:1122:3344:107::b]:32345"}}]},"root":"/pool/ext/4c157f35-865d-4310-9d81-c6259cb69293/crypt/zone"},{"zone":{"id":"5c8c244c-00dc-4b16-aa17-6d9eb4827fab","zone_type":"crucible","addresses":["fd00:1122:3344:107::a"],"dataset":{"id":"5c8c244c-00dc-4b16-aa17-6d9eb4827fab","name":{"pool_name":"oxp_4c157f35-865d-4310-9d81-c6259cb69293","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::a]:32345"},"services":[{"id":"5c8c244c-00dc-4b16-aa17-6d9eb4827fab","details":{"type":"crucible","address":"[fd00:1122:3344:107::a]:32345"}}]},"root":"/pool/ext/845ff39a-3205-416f-8bda-e35829107c8a/crypt/zone"},{"zone":{"id":"7d5e942b-926c-442d-937a-76cc4aa72bf3","zone_type":"crucible","addresses":["fd00:1122:3344:107::6"],"dataset":{"id":"7d5e942b-926c-442d-937a-76cc4aa72bf3","name":{"pool_name":"oxp_fd82dcc7-00dd-4d01-826a-937a7d8238fb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::6]:32345"},"services":[{"id":"7d5e942b-926c-442d-937a-76cc4aa72bf3","details":{"type":"crucible","address":"[fd00:1122:3344:107::6]:32345"}}]},"root":"/pool/ext/b252b176-3974-436a-915b-60382b21eb76/crypt/zone"},{"zone":{"id":"a3628a56-6f85-43b5-be50-71d8f0e04877","zone_type":"cockroach_db","addresses":["fd00:1122:3344:107::3"],"dataset":{"id":"a3628a56-6f85-43b5-be50-71d8f0e04877","name":{"pool_name":"oxp_0e485ad3-04e6-404b-b619-87d4fea9f5ae","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:107::3]:32221"},"services":[{"id":"a3628a56-6f85-43b5-be50-71d8f0e04877","details":{"type":"cockroach_db","address":"[fd00:1122:3344:107::3]:32221"}}]},"root":"/pool/ext/4c157f35-865d-4310-9d81-c6259cb69293/crypt/zone"},{"zone":{"id":"7529be1c-ca8b-441a-89aa-37166cc450df","zone_type":"ntp","addresses":["fd00:1122:3344:107::f"],"dataset":null,"services":[{"id":"7529be1c-ca8b-441a-89aa-37166cc450df","details":{"type":"internal_ntp","address":"[fd00:1122:3344:107::f]:123","ntp_servers":["c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal","6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/fd82dcc7-00dd-4d01-826a-937a7d8238fb/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled11.json b/sled-agent/tests/old-service-ledgers/rack2-sled11.json new file mode 100644 index 0000000000..3833bed5c9 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled11.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"605be8b9-c652-4a5f-94ca-068ec7a39472","zone_type":"crucible","addresses":["fd00:1122:3344:106::a"],"dataset":{"id":"605be8b9-c652-4a5f-94ca-068ec7a39472","name":{"pool_name":"oxp_cf14d1b9-b4db-4594-b3ab-a9957e770ce9","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::a]:32345"},"services":[{"id":"605be8b9-c652-4a5f-94ca-068ec7a39472","details":{"type":"crucible","address":"[fd00:1122:3344:106::a]:32345"}}]},"root":"/pool/ext/cf5f8849-0c5a-475b-8683-6d17da88d1d1/crypt/zone"},{"zone":{"id":"af8a8712-457c-4ea7-a8b6-aecb04761c1b","zone_type":"crucible","addresses":["fd00:1122:3344:106::9"],"dataset":{"id":"af8a8712-457c-4ea7-a8b6-aecb04761c1b","name":{"pool_name":"oxp_cf5f8849-0c5a-475b-8683-6d17da88d1d1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::9]:32345"},"services":[{"id":"af8a8712-457c-4ea7-a8b6-aecb04761c1b","details":{"type":"crucible","address":"[fd00:1122:3344:106::9]:32345"}}]},"root":"/pool/ext/7f778610-7328-4554-98f6-b17f74f551c7/crypt/zone"},{"zone":{"id":"0022703b-dcfc-44d4-897a-b42f6f53b433","zone_type":"crucible","addresses":["fd00:1122:3344:106::c"],"dataset":{"id":"0022703b-dcfc-44d4-897a-b42f6f53b433","name":{"pool_name":"oxp_025725fa-9e40-4b46-b018-c420408394ef","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::c]:32345"},"services":[{"id":"0022703b-dcfc-44d4-897a-b42f6f53b433","details":{"type":"crucible","address":"[fd00:1122:3344:106::c]:32345"}}]},"root":"/pool/ext/025725fa-9e40-4b46-b018-c420408394ef/crypt/zone"},{"zone":{"id":"fffddf56-10ca-4b62-9be3-5b3764a5f682","zone_type":"crucible","addresses":["fd00:1122:3344:106::d"],"dataset":{"id":"fffddf56-10ca-4b62-9be3-5b3764a5f682","name":{"pool_name":"oxp_4d2f5aaf-eb14-4b1e-aa99-ae38ec844605","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::d]:32345"},"services":[{"id":"fffddf56-10ca-4b62-9be3-5b3764a5f682","details":{"type":"crucible","address":"[fd00:1122:3344:106::d]:32345"}}]},"root":"/pool/ext/834c9aad-c53b-4357-bc3f-f422efa63848/crypt/zone"},{"zone":{"id":"9b8194ee-917d-4abc-a55c-94cea6cdaea1","zone_type":"crucible","addresses":["fd00:1122:3344:106::6"],"dataset":{"id":"9b8194ee-917d-4abc-a55c-94cea6cdaea1","name":{"pool_name":"oxp_d7665e0d-9354-4341-a76f-965d7c49f277","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::6]:32345"},"services":[{"id":"9b8194ee-917d-4abc-a55c-94cea6cdaea1","details":{"type":"crucible","address":"[fd00:1122:3344:106::6]:32345"}}]},"root":"/pool/ext/cf5f8849-0c5a-475b-8683-6d17da88d1d1/crypt/zone"},{"zone":{"id":"b369e133-485c-4d98-8fee-83542d1fd94d","zone_type":"crucible","addresses":["fd00:1122:3344:106::4"],"dataset":{"id":"b369e133-485c-4d98-8fee-83542d1fd94d","name":{"pool_name":"oxp_4366f80d-3902-4b93-8f2d-380008e805fc","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::4]:32345"},"services":[{"id":"b369e133-485c-4d98-8fee-83542d1fd94d","details":{"type":"crucible","address":"[fd00:1122:3344:106::4]:32345"}}]},"root":"/pool/ext/025725fa-9e40-4b46-b018-c420408394ef/crypt/zone"},{"zone":{"id":"edd99650-5df1-4241-815d-253e4ef2399c","zone_type":"external_dns","addresses":["fd00:1122:3344:106::3"],"dataset":{"id":"edd99650-5df1-4241-815d-253e4ef2399c","name":{"pool_name":"oxp_4366f80d-3902-4b93-8f2d-380008e805fc","kind":{"type":"external_dns"}},"service_address":"[fd00:1122:3344:106::3]:5353"},"services":[{"id":"edd99650-5df1-4241-815d-253e4ef2399c","details":{"type":"external_dns","http_address":"[fd00:1122:3344:106::3]:5353","dns_address":"172.20.26.1:53","nic":{"id":"99b759fc-8e2e-44b7-aca8-93c3b201974d","kind":{"type":"service","id":"edd99650-5df1-4241-815d-253e4ef2399c"},"name":"external-dns-edd99650-5df1-4241-815d-253e4ef2399c","ip":"172.30.1.5","mac":"A8:40:25:FF:B0:9C","subnet":"172.30.1.0/24","vni":100,"primary":true,"slot":0}}}]},"root":"/pool/ext/7f778610-7328-4554-98f6-b17f74f551c7/crypt/zone"},{"zone":{"id":"46d1afcc-cc3f-4b17-aafc-054dd4862d15","zone_type":"crucible","addresses":["fd00:1122:3344:106::5"],"dataset":{"id":"46d1afcc-cc3f-4b17-aafc-054dd4862d15","name":{"pool_name":"oxp_7f778610-7328-4554-98f6-b17f74f551c7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::5]:32345"},"services":[{"id":"46d1afcc-cc3f-4b17-aafc-054dd4862d15","details":{"type":"crucible","address":"[fd00:1122:3344:106::5]:32345"}}]},"root":"/pool/ext/cf5f8849-0c5a-475b-8683-6d17da88d1d1/crypt/zone"},{"zone":{"id":"12afe1c3-bfe6-4278-8240-91d401347d36","zone_type":"crucible","addresses":["fd00:1122:3344:106::8"],"dataset":{"id":"12afe1c3-bfe6-4278-8240-91d401347d36","name":{"pool_name":"oxp_534bcd4b-502f-4109-af6e-4b28a22c20f1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::8]:32345"},"services":[{"id":"12afe1c3-bfe6-4278-8240-91d401347d36","details":{"type":"crucible","address":"[fd00:1122:3344:106::8]:32345"}}]},"root":"/pool/ext/4366f80d-3902-4b93-8f2d-380008e805fc/crypt/zone"},{"zone":{"id":"c33b5912-9985-43ed-98f2-41297e2b796a","zone_type":"crucible","addresses":["fd00:1122:3344:106::b"],"dataset":{"id":"c33b5912-9985-43ed-98f2-41297e2b796a","name":{"pool_name":"oxp_834c9aad-c53b-4357-bc3f-f422efa63848","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::b]:32345"},"services":[{"id":"c33b5912-9985-43ed-98f2-41297e2b796a","details":{"type":"crucible","address":"[fd00:1122:3344:106::b]:32345"}}]},"root":"/pool/ext/d7665e0d-9354-4341-a76f-965d7c49f277/crypt/zone"},{"zone":{"id":"65b3db59-9361-4100-9cee-04e32a8c67d3","zone_type":"crucible","addresses":["fd00:1122:3344:106::7"],"dataset":{"id":"65b3db59-9361-4100-9cee-04e32a8c67d3","name":{"pool_name":"oxp_32b5303f-f667-4345-84d2-c7eec63b91b2","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::7]:32345"},"services":[{"id":"65b3db59-9361-4100-9cee-04e32a8c67d3","details":{"type":"crucible","address":"[fd00:1122:3344:106::7]:32345"}}]},"root":"/pool/ext/d7665e0d-9354-4341-a76f-965d7c49f277/crypt/zone"},{"zone":{"id":"82500cc9-f33d-4d59-9e6e-d70ea6133077","zone_type":"ntp","addresses":["fd00:1122:3344:106::e"],"dataset":null,"services":[{"id":"82500cc9-f33d-4d59-9e6e-d70ea6133077","details":{"type":"internal_ntp","address":"[fd00:1122:3344:106::e]:123","ntp_servers":["c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal","6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/cf14d1b9-b4db-4594-b3ab-a9957e770ce9/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled12.json b/sled-agent/tests/old-service-ledgers/rack2-sled12.json new file mode 100644 index 0000000000..5126c007f3 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled12.json @@ -0,0 +1 @@ +{"generation":5,"requests":[{"zone":{"id":"a76b3357-b690-43b8-8352-3300568ffc2b","zone_type":"crucible","addresses":["fd00:1122:3344:104::a"],"dataset":{"id":"a76b3357-b690-43b8-8352-3300568ffc2b","name":{"pool_name":"oxp_05715ad8-59a1-44ab-ad5f-0cdffb46baab","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::a]:32345"},"services":[{"id":"a76b3357-b690-43b8-8352-3300568ffc2b","details":{"type":"crucible","address":"[fd00:1122:3344:104::a]:32345"}}]},"root":"/pool/ext/2ec2a731-3340-4777-b1bb-4a906c598174/crypt/zone"},{"zone":{"id":"8d202759-ca06-4383-b50f-7f3ec4062bf7","zone_type":"crucible","addresses":["fd00:1122:3344:104::4"],"dataset":{"id":"8d202759-ca06-4383-b50f-7f3ec4062bf7","name":{"pool_name":"oxp_56e32a8f-0877-4437-9cab-94a4928b1495","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::4]:32345"},"services":[{"id":"8d202759-ca06-4383-b50f-7f3ec4062bf7","details":{"type":"crucible","address":"[fd00:1122:3344:104::4]:32345"}}]},"root":"/pool/ext/613b58fc-5a80-42dc-a61c-b143cf220fb5/crypt/zone"},{"zone":{"id":"fcdda266-fc6a-4518-89db-aec007a4b682","zone_type":"crucible","addresses":["fd00:1122:3344:104::b"],"dataset":{"id":"fcdda266-fc6a-4518-89db-aec007a4b682","name":{"pool_name":"oxp_7e1293ad-b903-4054-aeae-2182d5e4a785","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::b]:32345"},"services":[{"id":"fcdda266-fc6a-4518-89db-aec007a4b682","details":{"type":"crucible","address":"[fd00:1122:3344:104::b]:32345"}}]},"root":"/pool/ext/416fd29e-d3b5-4fdf-8101-d0d163fa0706/crypt/zone"},{"zone":{"id":"167cf6a2-ec51-4de2-bc6c-7785bbc0e436","zone_type":"crucible","addresses":["fd00:1122:3344:104::c"],"dataset":{"id":"167cf6a2-ec51-4de2-bc6c-7785bbc0e436","name":{"pool_name":"oxp_f96c8d49-fdf7-4bd6-84f6-c282202d1abc","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::c]:32345"},"services":[{"id":"167cf6a2-ec51-4de2-bc6c-7785bbc0e436","details":{"type":"crucible","address":"[fd00:1122:3344:104::c]:32345"}}]},"root":"/pool/ext/56e32a8f-0877-4437-9cab-94a4928b1495/crypt/zone"},{"zone":{"id":"c6fde82d-8dae-4ef0-b557-6c3d094d9454","zone_type":"crucible","addresses":["fd00:1122:3344:104::9"],"dataset":{"id":"c6fde82d-8dae-4ef0-b557-6c3d094d9454","name":{"pool_name":"oxp_416fd29e-d3b5-4fdf-8101-d0d163fa0706","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::9]:32345"},"services":[{"id":"c6fde82d-8dae-4ef0-b557-6c3d094d9454","details":{"type":"crucible","address":"[fd00:1122:3344:104::9]:32345"}}]},"root":"/pool/ext/3af01cc4-1f16-47d9-a489-abafcb91c2db/crypt/zone"},{"zone":{"id":"650f5da7-86a0-4ade-af0f-bc96e021ded0","zone_type":"crucible","addresses":["fd00:1122:3344:104::5"],"dataset":{"id":"650f5da7-86a0-4ade-af0f-bc96e021ded0","name":{"pool_name":"oxp_b4a71d3d-1ecd-418a-9a52-8d118f82082b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::5]:32345"},"services":[{"id":"650f5da7-86a0-4ade-af0f-bc96e021ded0","details":{"type":"crucible","address":"[fd00:1122:3344:104::5]:32345"}}]},"root":"/pool/ext/613b58fc-5a80-42dc-a61c-b143cf220fb5/crypt/zone"},{"zone":{"id":"7ce9a2c5-2d37-4188-b7b5-a9db819396c3","zone_type":"crucible","addresses":["fd00:1122:3344:104::d"],"dataset":{"id":"7ce9a2c5-2d37-4188-b7b5-a9db819396c3","name":{"pool_name":"oxp_c87d16b8-e814-4159-8562-f8d7fdd19d13","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::d]:32345"},"services":[{"id":"7ce9a2c5-2d37-4188-b7b5-a9db819396c3","details":{"type":"crucible","address":"[fd00:1122:3344:104::d]:32345"}}]},"root":"/pool/ext/416fd29e-d3b5-4fdf-8101-d0d163fa0706/crypt/zone"},{"zone":{"id":"23e1cf01-70ab-422f-997b-6216158965c3","zone_type":"crucible","addresses":["fd00:1122:3344:104::8"],"dataset":{"id":"23e1cf01-70ab-422f-997b-6216158965c3","name":{"pool_name":"oxp_3af01cc4-1f16-47d9-a489-abafcb91c2db","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::8]:32345"},"services":[{"id":"23e1cf01-70ab-422f-997b-6216158965c3","details":{"type":"crucible","address":"[fd00:1122:3344:104::8]:32345"}}]},"root":"/pool/ext/3af01cc4-1f16-47d9-a489-abafcb91c2db/crypt/zone"},{"zone":{"id":"50209816-89fb-48ed-9595-16899d114844","zone_type":"crucible","addresses":["fd00:1122:3344:104::6"],"dataset":{"id":"50209816-89fb-48ed-9595-16899d114844","name":{"pool_name":"oxp_2ec2a731-3340-4777-b1bb-4a906c598174","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::6]:32345"},"services":[{"id":"50209816-89fb-48ed-9595-16899d114844","details":{"type":"crucible","address":"[fd00:1122:3344:104::6]:32345"}}]},"root":"/pool/ext/416fd29e-d3b5-4fdf-8101-d0d163fa0706/crypt/zone"},{"zone":{"id":"20b100d0-84c3-4119-aa9b-0c632b0b6a3a","zone_type":"nexus","addresses":["fd00:1122:3344:104::3"],"dataset":null,"services":[{"id":"20b100d0-84c3-4119-aa9b-0c632b0b6a3a","details":{"type":"nexus","internal_address":"[fd00:1122:3344:104::3]:12221","external_ip":"172.20.26.4","nic":{"id":"364b0ecd-bf08-4cac-a993-bbf4a70564c7","kind":{"type":"service","id":"20b100d0-84c3-4119-aa9b-0c632b0b6a3a"},"name":"nexus-20b100d0-84c3-4119-aa9b-0c632b0b6a3a","ip":"172.30.2.6","mac":"A8:40:25:FF:B4:C1","subnet":"172.30.2.0/24","vni":100,"primary":true,"slot":0},"external_tls":true,"external_dns_servers":["1.1.1.1","9.9.9.9"]}}]},"root":"/pool/ext/c87d16b8-e814-4159-8562-f8d7fdd19d13/crypt/zone"},{"zone":{"id":"8bc0f29e-0c20-437e-b8ca-7b9844acda22","zone_type":"crucible","addresses":["fd00:1122:3344:104::7"],"dataset":{"id":"8bc0f29e-0c20-437e-b8ca-7b9844acda22","name":{"pool_name":"oxp_613b58fc-5a80-42dc-a61c-b143cf220fb5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::7]:32345"},"services":[{"id":"8bc0f29e-0c20-437e-b8ca-7b9844acda22","details":{"type":"crucible","address":"[fd00:1122:3344:104::7]:32345"}}]},"root":"/pool/ext/56e32a8f-0877-4437-9cab-94a4928b1495/crypt/zone"},{"zone":{"id":"c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55","zone_type":"ntp","addresses":["fd00:1122:3344:104::e"],"dataset":null,"services":[{"id":"c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55","details":{"type":"boundary_ntp","address":"[fd00:1122:3344:104::e]:123","ntp_servers":["ntp.eng.oxide.computer"],"dns_servers":["1.1.1.1","9.9.9.9"],"domain":null,"nic":{"id":"a4b9bacf-6c04-431a-81ad-9bf0302af96e","kind":{"type":"service","id":"c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55"},"name":"ntp-c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55","ip":"172.30.3.5","mac":"A8:40:25:FF:B2:52","subnet":"172.30.3.0/24","vni":100,"primary":true,"slot":0},"snat_cfg":{"ip":"172.20.26.6","first_port":0,"last_port":16383}}}]},"root":"/pool/ext/3af01cc4-1f16-47d9-a489-abafcb91c2db/crypt/zone"},{"zone":{"id":"51c9ad09-7814-4643-8ad4-689ccbe53fbd","zone_type":"internal_dns","addresses":["fd00:1122:3344:1::1"],"dataset":{"id":"51c9ad09-7814-4643-8ad4-689ccbe53fbd","name":{"pool_name":"oxp_56e32a8f-0877-4437-9cab-94a4928b1495","kind":{"type":"internal_dns"}},"service_address":"[fd00:1122:3344:1::1]:5353"},"services":[{"id":"51c9ad09-7814-4643-8ad4-689ccbe53fbd","details":{"type":"internal_dns","http_address":"[fd00:1122:3344:1::1]:5353","dns_address":"[fd00:1122:3344:1::1]:53","gz_address":"fd00:1122:3344:1::2","gz_address_index":0}}]},"root":"/pool/ext/3af01cc4-1f16-47d9-a489-abafcb91c2db/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled14.json b/sled-agent/tests/old-service-ledgers/rack2-sled14.json new file mode 100644 index 0000000000..421e21d84d --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled14.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"ee8b2cfa-87fe-46a6-98ef-23640b80a968","zone_type":"crucible","addresses":["fd00:1122:3344:10b::d"],"dataset":{"id":"ee8b2cfa-87fe-46a6-98ef-23640b80a968","name":{"pool_name":"oxp_4a624324-003a-4255-98e8-546a90b5b7fa","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::d]:32345"},"services":[{"id":"ee8b2cfa-87fe-46a6-98ef-23640b80a968","details":{"type":"crucible","address":"[fd00:1122:3344:10b::d]:32345"}}]},"root":"/pool/ext/6b9ec5f1-859f-459c-9c06-6a51ba87786f/crypt/zone"},{"zone":{"id":"9228f8ca-2a83-439f-9cb7-f2801b5fea27","zone_type":"crucible","addresses":["fd00:1122:3344:10b::6"],"dataset":{"id":"9228f8ca-2a83-439f-9cb7-f2801b5fea27","name":{"pool_name":"oxp_6b9ec5f1-859f-459c-9c06-6a51ba87786f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::6]:32345"},"services":[{"id":"9228f8ca-2a83-439f-9cb7-f2801b5fea27","details":{"type":"crucible","address":"[fd00:1122:3344:10b::6]:32345"}}]},"root":"/pool/ext/6b9ec5f1-859f-459c-9c06-6a51ba87786f/crypt/zone"},{"zone":{"id":"ee44cdde-7ac9-4469-9f1d-e8bcfeb5cc46","zone_type":"crucible","addresses":["fd00:1122:3344:10b::e"],"dataset":{"id":"ee44cdde-7ac9-4469-9f1d-e8bcfeb5cc46","name":{"pool_name":"oxp_11b02ce7-7e50-486f-86c2-de8af9575a45","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::e]:32345"},"services":[{"id":"ee44cdde-7ac9-4469-9f1d-e8bcfeb5cc46","details":{"type":"crucible","address":"[fd00:1122:3344:10b::e]:32345"}}]},"root":"/pool/ext/11b02ce7-7e50-486f-86c2-de8af9575a45/crypt/zone"},{"zone":{"id":"96bac0b1-8b34-4c81-9e76-6404d2c37630","zone_type":"crucible_pantry","addresses":["fd00:1122:3344:10b::4"],"dataset":null,"services":[{"id":"96bac0b1-8b34-4c81-9e76-6404d2c37630","details":{"type":"crucible_pantry","address":"[fd00:1122:3344:10b::4]:17000"}}]},"root":"/pool/ext/350b2814-7b7f-40f1-9bf6-9818a1ef49bb/crypt/zone"},{"zone":{"id":"d4e1e554-7b98-4413-809e-4a42561c3d0c","zone_type":"crucible","addresses":["fd00:1122:3344:10b::a"],"dataset":{"id":"d4e1e554-7b98-4413-809e-4a42561c3d0c","name":{"pool_name":"oxp_e6d2fe1d-c74d-40cd-8fae-bc7d06bdaac8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::a]:32345"},"services":[{"id":"d4e1e554-7b98-4413-809e-4a42561c3d0c","details":{"type":"crucible","address":"[fd00:1122:3344:10b::a]:32345"}}]},"root":"/pool/ext/6b9ec5f1-859f-459c-9c06-6a51ba87786f/crypt/zone"},{"zone":{"id":"1dd69b02-a032-46c3-8e2a-5012e8314455","zone_type":"crucible","addresses":["fd00:1122:3344:10b::b"],"dataset":{"id":"1dd69b02-a032-46c3-8e2a-5012e8314455","name":{"pool_name":"oxp_350b2814-7b7f-40f1-9bf6-9818a1ef49bb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::b]:32345"},"services":[{"id":"1dd69b02-a032-46c3-8e2a-5012e8314455","details":{"type":"crucible","address":"[fd00:1122:3344:10b::b]:32345"}}]},"root":"/pool/ext/350b2814-7b7f-40f1-9bf6-9818a1ef49bb/crypt/zone"},{"zone":{"id":"921f7752-d2f3-40df-a739-5cb1390abc2c","zone_type":"crucible","addresses":["fd00:1122:3344:10b::8"],"dataset":{"id":"921f7752-d2f3-40df-a739-5cb1390abc2c","name":{"pool_name":"oxp_2d1ebe24-6deb-4f81-8450-6842de28126c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::8]:32345"},"services":[{"id":"921f7752-d2f3-40df-a739-5cb1390abc2c","details":{"type":"crucible","address":"[fd00:1122:3344:10b::8]:32345"}}]},"root":"/pool/ext/91ea7bb6-2be7-4498-9b0d-a0521509ec00/crypt/zone"},{"zone":{"id":"609b25e8-9750-4308-ae6f-7202907a3675","zone_type":"crucible","addresses":["fd00:1122:3344:10b::9"],"dataset":{"id":"609b25e8-9750-4308-ae6f-7202907a3675","name":{"pool_name":"oxp_91ea7bb6-2be7-4498-9b0d-a0521509ec00","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::9]:32345"},"services":[{"id":"609b25e8-9750-4308-ae6f-7202907a3675","details":{"type":"crucible","address":"[fd00:1122:3344:10b::9]:32345"}}]},"root":"/pool/ext/2d1ebe24-6deb-4f81-8450-6842de28126c/crypt/zone"},{"zone":{"id":"a232eba2-e94f-4592-a5a6-ec23f9be3296","zone_type":"crucible","addresses":["fd00:1122:3344:10b::5"],"dataset":{"id":"a232eba2-e94f-4592-a5a6-ec23f9be3296","name":{"pool_name":"oxp_e12f29b8-1ab8-431e-bc96-1c1298947980","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::5]:32345"},"services":[{"id":"a232eba2-e94f-4592-a5a6-ec23f9be3296","details":{"type":"crucible","address":"[fd00:1122:3344:10b::5]:32345"}}]},"root":"/pool/ext/021afd19-2f87-4def-9284-ab7add1dd6ae/crypt/zone"},{"zone":{"id":"800d1758-9312-4b1a-8f02-dc6d644c2a9b","zone_type":"crucible","addresses":["fd00:1122:3344:10b::c"],"dataset":{"id":"800d1758-9312-4b1a-8f02-dc6d644c2a9b","name":{"pool_name":"oxp_b6932bb0-bab8-4876-914a-9c75a600e794","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::c]:32345"},"services":[{"id":"800d1758-9312-4b1a-8f02-dc6d644c2a9b","details":{"type":"crucible","address":"[fd00:1122:3344:10b::c]:32345"}}]},"root":"/pool/ext/b6932bb0-bab8-4876-914a-9c75a600e794/crypt/zone"},{"zone":{"id":"668a4d4a-96dc-4b45-866b-bed3d64c26ec","zone_type":"crucible","addresses":["fd00:1122:3344:10b::7"],"dataset":{"id":"668a4d4a-96dc-4b45-866b-bed3d64c26ec","name":{"pool_name":"oxp_021afd19-2f87-4def-9284-ab7add1dd6ae","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::7]:32345"},"services":[{"id":"668a4d4a-96dc-4b45-866b-bed3d64c26ec","details":{"type":"crucible","address":"[fd00:1122:3344:10b::7]:32345"}}]},"root":"/pool/ext/91ea7bb6-2be7-4498-9b0d-a0521509ec00/crypt/zone"},{"zone":{"id":"8bbea076-ff60-4330-8302-383e18140ef3","zone_type":"cockroach_db","addresses":["fd00:1122:3344:10b::3"],"dataset":{"id":"8bbea076-ff60-4330-8302-383e18140ef3","name":{"pool_name":"oxp_e12f29b8-1ab8-431e-bc96-1c1298947980","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:10b::3]:32221"},"services":[{"id":"8bbea076-ff60-4330-8302-383e18140ef3","details":{"type":"cockroach_db","address":"[fd00:1122:3344:10b::3]:32221"}}]},"root":"/pool/ext/4a624324-003a-4255-98e8-546a90b5b7fa/crypt/zone"},{"zone":{"id":"3ccea933-89f2-4ce5-8367-efb0afeffe97","zone_type":"ntp","addresses":["fd00:1122:3344:10b::f"],"dataset":null,"services":[{"id":"3ccea933-89f2-4ce5-8367-efb0afeffe97","details":{"type":"internal_ntp","address":"[fd00:1122:3344:10b::f]:123","ntp_servers":["c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal","6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/4a624324-003a-4255-98e8-546a90b5b7fa/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled16.json b/sled-agent/tests/old-service-ledgers/rack2-sled16.json new file mode 100644 index 0000000000..c928e004b2 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled16.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"b12aa520-a769-4eac-b56b-09960550a831","zone_type":"crucible","addresses":["fd00:1122:3344:108::7"],"dataset":{"id":"b12aa520-a769-4eac-b56b-09960550a831","name":{"pool_name":"oxp_34dadf3f-f60c-4acc-b82b-4b0c82224222","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::7]:32345"},"services":[{"id":"b12aa520-a769-4eac-b56b-09960550a831","details":{"type":"crucible","address":"[fd00:1122:3344:108::7]:32345"}}]},"root":"/pool/ext/8be8c577-23ac-452e-a205-6d9c95088f61/crypt/zone"},{"zone":{"id":"9bdc40ee-ccba-4d18-9efb-a30596e2d290","zone_type":"crucible","addresses":["fd00:1122:3344:108::d"],"dataset":{"id":"9bdc40ee-ccba-4d18-9efb-a30596e2d290","name":{"pool_name":"oxp_eb81728c-3b83-42fb-8133-ac32a0bdf70f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::d]:32345"},"services":[{"id":"9bdc40ee-ccba-4d18-9efb-a30596e2d290","details":{"type":"crucible","address":"[fd00:1122:3344:108::d]:32345"}}]},"root":"/pool/ext/8be8c577-23ac-452e-a205-6d9c95088f61/crypt/zone"},{"zone":{"id":"c9a367c7-64d7-48e4-b484-9ecb4e8faea7","zone_type":"crucible","addresses":["fd00:1122:3344:108::9"],"dataset":{"id":"c9a367c7-64d7-48e4-b484-9ecb4e8faea7","name":{"pool_name":"oxp_76ab5a67-e20f-4bf0-87b3-01fcc4144bd2","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::9]:32345"},"services":[{"id":"c9a367c7-64d7-48e4-b484-9ecb4e8faea7","details":{"type":"crucible","address":"[fd00:1122:3344:108::9]:32345"}}]},"root":"/pool/ext/34dadf3f-f60c-4acc-b82b-4b0c82224222/crypt/zone"},{"zone":{"id":"bc5124d8-65e8-4879-bfac-64d59003d482","zone_type":"crucible","addresses":["fd00:1122:3344:108::a"],"dataset":{"id":"bc5124d8-65e8-4879-bfac-64d59003d482","name":{"pool_name":"oxp_5fac7a1d-e855-46e1-b8c2-dd848ac4fee6","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::a]:32345"},"services":[{"id":"bc5124d8-65e8-4879-bfac-64d59003d482","details":{"type":"crucible","address":"[fd00:1122:3344:108::a]:32345"}}]},"root":"/pool/ext/0c4ef358-5533-43db-ad38-a8eff716e53a/crypt/zone"},{"zone":{"id":"5cc7c840-8e6b-48c8-ac4b-f4297f8cf61a","zone_type":"crucible","addresses":["fd00:1122:3344:108::c"],"dataset":{"id":"5cc7c840-8e6b-48c8-ac4b-f4297f8cf61a","name":{"pool_name":"oxp_0c4ef358-5533-43db-ad38-a8eff716e53a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::c]:32345"},"services":[{"id":"5cc7c840-8e6b-48c8-ac4b-f4297f8cf61a","details":{"type":"crucible","address":"[fd00:1122:3344:108::c]:32345"}}]},"root":"/pool/ext/6d3e9cc6-f03b-4055-9785-05711d5e4fdc/crypt/zone"},{"zone":{"id":"3b767edf-a72d-4d80-a0fc-65d6801ed0e0","zone_type":"crucible","addresses":["fd00:1122:3344:108::e"],"dataset":{"id":"3b767edf-a72d-4d80-a0fc-65d6801ed0e0","name":{"pool_name":"oxp_f522118c-5dcd-4116-8044-07f0cceec52e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::e]:32345"},"services":[{"id":"3b767edf-a72d-4d80-a0fc-65d6801ed0e0","details":{"type":"crucible","address":"[fd00:1122:3344:108::e]:32345"}}]},"root":"/pool/ext/5fac7a1d-e855-46e1-b8c2-dd848ac4fee6/crypt/zone"},{"zone":{"id":"f3c02ed6-fbc5-45c3-a030-409f74b450fd","zone_type":"crucible_pantry","addresses":["fd00:1122:3344:108::4"],"dataset":null,"services":[{"id":"f3c02ed6-fbc5-45c3-a030-409f74b450fd","details":{"type":"crucible_pantry","address":"[fd00:1122:3344:108::4]:17000"}}]},"root":"/pool/ext/eb81728c-3b83-42fb-8133-ac32a0bdf70f/crypt/zone"},{"zone":{"id":"85bd9bdb-1ec5-4a8d-badb-8b5d502546a1","zone_type":"crucible","addresses":["fd00:1122:3344:108::5"],"dataset":{"id":"85bd9bdb-1ec5-4a8d-badb-8b5d502546a1","name":{"pool_name":"oxp_416232c1-bc8f-403f-bacb-28403dd8fced","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::5]:32345"},"services":[{"id":"85bd9bdb-1ec5-4a8d-badb-8b5d502546a1","details":{"type":"crucible","address":"[fd00:1122:3344:108::5]:32345"}}]},"root":"/pool/ext/34dadf3f-f60c-4acc-b82b-4b0c82224222/crypt/zone"},{"zone":{"id":"d2f1c3df-d4e0-4469-b50e-f1871da86ebf","zone_type":"crucible","addresses":["fd00:1122:3344:108::6"],"dataset":{"id":"d2f1c3df-d4e0-4469-b50e-f1871da86ebf","name":{"pool_name":"oxp_6d3e9cc6-f03b-4055-9785-05711d5e4fdc","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::6]:32345"},"services":[{"id":"d2f1c3df-d4e0-4469-b50e-f1871da86ebf","details":{"type":"crucible","address":"[fd00:1122:3344:108::6]:32345"}}]},"root":"/pool/ext/34dadf3f-f60c-4acc-b82b-4b0c82224222/crypt/zone"},{"zone":{"id":"88fe3c12-4c55-47df-b4ee-ed26b795439d","zone_type":"crucible","addresses":["fd00:1122:3344:108::8"],"dataset":{"id":"88fe3c12-4c55-47df-b4ee-ed26b795439d","name":{"pool_name":"oxp_8be8c577-23ac-452e-a205-6d9c95088f61","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::8]:32345"},"services":[{"id":"88fe3c12-4c55-47df-b4ee-ed26b795439d","details":{"type":"crucible","address":"[fd00:1122:3344:108::8]:32345"}}]},"root":"/pool/ext/34dadf3f-f60c-4acc-b82b-4b0c82224222/crypt/zone"},{"zone":{"id":"4d20175a-588b-44b8-8b9c-b16c6c3a97a0","zone_type":"crucible","addresses":["fd00:1122:3344:108::b"],"dataset":{"id":"4d20175a-588b-44b8-8b9c-b16c6c3a97a0","name":{"pool_name":"oxp_a726cacd-fa35-4ed2-ade6-31ad928b24cb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::b]:32345"},"services":[{"id":"4d20175a-588b-44b8-8b9c-b16c6c3a97a0","details":{"type":"crucible","address":"[fd00:1122:3344:108::b]:32345"}}]},"root":"/pool/ext/0c4ef358-5533-43db-ad38-a8eff716e53a/crypt/zone"},{"zone":{"id":"e86845b5-eabd-49f5-9a10-6dfef9066209","zone_type":"cockroach_db","addresses":["fd00:1122:3344:108::3"],"dataset":{"id":"e86845b5-eabd-49f5-9a10-6dfef9066209","name":{"pool_name":"oxp_416232c1-bc8f-403f-bacb-28403dd8fced","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:108::3]:32221"},"services":[{"id":"e86845b5-eabd-49f5-9a10-6dfef9066209","details":{"type":"cockroach_db","address":"[fd00:1122:3344:108::3]:32221"}}]},"root":"/pool/ext/416232c1-bc8f-403f-bacb-28403dd8fced/crypt/zone"},{"zone":{"id":"209b6213-588b-43b6-a89b-19ee5c84ffba","zone_type":"ntp","addresses":["fd00:1122:3344:108::f"],"dataset":null,"services":[{"id":"209b6213-588b-43b6-a89b-19ee5c84ffba","details":{"type":"internal_ntp","address":"[fd00:1122:3344:108::f]:123","ntp_servers":["c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal","6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/416232c1-bc8f-403f-bacb-28403dd8fced/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled17.json b/sled-agent/tests/old-service-ledgers/rack2-sled17.json new file mode 100644 index 0000000000..93872adf13 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled17.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"90b53c3d-42fa-4ca9-bbfc-96fff245b508","zone_type":"crucible","addresses":["fd00:1122:3344:109::4"],"dataset":{"id":"90b53c3d-42fa-4ca9-bbfc-96fff245b508","name":{"pool_name":"oxp_ae56280b-17ce-4266-8573-e1da9db6c6bb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::4]:32345"},"services":[{"id":"90b53c3d-42fa-4ca9-bbfc-96fff245b508","details":{"type":"crucible","address":"[fd00:1122:3344:109::4]:32345"}}]},"root":"/pool/ext/b0e1a261-b932-47c4-81e9-1977275ae9d9/crypt/zone"},{"zone":{"id":"4f9f2e1d-be04-4e8b-a50b-ffb18557a650","zone_type":"crucible","addresses":["fd00:1122:3344:109::5"],"dataset":{"id":"4f9f2e1d-be04-4e8b-a50b-ffb18557a650","name":{"pool_name":"oxp_d5b07362-64db-4b18-a3e9-8d7cbabae2d5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::5]:32345"},"services":[{"id":"4f9f2e1d-be04-4e8b-a50b-ffb18557a650","details":{"type":"crucible","address":"[fd00:1122:3344:109::5]:32345"}}]},"root":"/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone"},{"zone":{"id":"2fa5671d-3109-4f11-ae70-1280f4fa3b89","zone_type":"crucible","addresses":["fd00:1122:3344:109::6"],"dataset":{"id":"2fa5671d-3109-4f11-ae70-1280f4fa3b89","name":{"pool_name":"oxp_9ba7bfbf-b9a2-4237-a142-94c1e68de984","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::6]:32345"},"services":[{"id":"2fa5671d-3109-4f11-ae70-1280f4fa3b89","details":{"type":"crucible","address":"[fd00:1122:3344:109::6]:32345"}}]},"root":"/pool/ext/3cafbb47-c194-4a42-99ff-34dfeab999ed/crypt/zone"},{"zone":{"id":"b63c6882-ca90-4156-b561-4781ab4a0962","zone_type":"crucible","addresses":["fd00:1122:3344:109::7"],"dataset":{"id":"b63c6882-ca90-4156-b561-4781ab4a0962","name":{"pool_name":"oxp_b0e1a261-b932-47c4-81e9-1977275ae9d9","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::7]:32345"},"services":[{"id":"b63c6882-ca90-4156-b561-4781ab4a0962","details":{"type":"crucible","address":"[fd00:1122:3344:109::7]:32345"}}]},"root":"/pool/ext/d5b07362-64db-4b18-a3e9-8d7cbabae2d5/crypt/zone"},{"zone":{"id":"f71344eb-f7e2-439d-82a0-9941e6868fb6","zone_type":"crucible","addresses":["fd00:1122:3344:109::9"],"dataset":{"id":"f71344eb-f7e2-439d-82a0-9941e6868fb6","name":{"pool_name":"oxp_027a82e8-daa3-4fa6-8205-ed03445e1086","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::9]:32345"},"services":[{"id":"f71344eb-f7e2-439d-82a0-9941e6868fb6","details":{"type":"crucible","address":"[fd00:1122:3344:109::9]:32345"}}]},"root":"/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone"},{"zone":{"id":"a60cf0d7-12d5-43cb-aa3f-7a9e84de08fb","zone_type":"crucible","addresses":["fd00:1122:3344:109::a"],"dataset":{"id":"a60cf0d7-12d5-43cb-aa3f-7a9e84de08fb","name":{"pool_name":"oxp_8736aaf9-4d72-42b1-8e4f-07644d999c8b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::a]:32345"},"services":[{"id":"a60cf0d7-12d5-43cb-aa3f-7a9e84de08fb","details":{"type":"crucible","address":"[fd00:1122:3344:109::a]:32345"}}]},"root":"/pool/ext/8736aaf9-4d72-42b1-8e4f-07644d999c8b/crypt/zone"},{"zone":{"id":"5d0e03b2-8958-4c43-8851-bf819f102958","zone_type":"crucible","addresses":["fd00:1122:3344:109::8"],"dataset":{"id":"5d0e03b2-8958-4c43-8851-bf819f102958","name":{"pool_name":"oxp_62426615-7832-49e7-9426-e39ffeb42c69","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::8]:32345"},"services":[{"id":"5d0e03b2-8958-4c43-8851-bf819f102958","details":{"type":"crucible","address":"[fd00:1122:3344:109::8]:32345"}}]},"root":"/pool/ext/07fc8ec9-1216-4d98-be34-c2970b585e61/crypt/zone"},{"zone":{"id":"accc05a2-ec80-4856-a825-ec6b7f700eaa","zone_type":"crucible","addresses":["fd00:1122:3344:109::d"],"dataset":{"id":"accc05a2-ec80-4856-a825-ec6b7f700eaa","name":{"pool_name":"oxp_dc083c53-7014-4482-8a79-f338ba2b0fb4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::d]:32345"},"services":[{"id":"accc05a2-ec80-4856-a825-ec6b7f700eaa","details":{"type":"crucible","address":"[fd00:1122:3344:109::d]:32345"}}]},"root":"/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone"},{"zone":{"id":"2e32fdcc-737a-4430-8290-cb7028ea4d50","zone_type":"crucible","addresses":["fd00:1122:3344:109::b"],"dataset":{"id":"2e32fdcc-737a-4430-8290-cb7028ea4d50","name":{"pool_name":"oxp_3cafbb47-c194-4a42-99ff-34dfeab999ed","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::b]:32345"},"services":[{"id":"2e32fdcc-737a-4430-8290-cb7028ea4d50","details":{"type":"crucible","address":"[fd00:1122:3344:109::b]:32345"}}]},"root":"/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone"},{"zone":{"id":"a97c6ae2-37f6-4d93-a66e-cb5cd3c6aaa2","zone_type":"crucible","addresses":["fd00:1122:3344:109::c"],"dataset":{"id":"a97c6ae2-37f6-4d93-a66e-cb5cd3c6aaa2","name":{"pool_name":"oxp_07fc8ec9-1216-4d98-be34-c2970b585e61","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::c]:32345"},"services":[{"id":"a97c6ae2-37f6-4d93-a66e-cb5cd3c6aaa2","details":{"type":"crucible","address":"[fd00:1122:3344:109::c]:32345"}}]},"root":"/pool/ext/07fc8ec9-1216-4d98-be34-c2970b585e61/crypt/zone"},{"zone":{"id":"3237a532-acaa-4ebe-bf11-dde794fea739","zone_type":"cockroach_db","addresses":["fd00:1122:3344:109::3"],"dataset":{"id":"3237a532-acaa-4ebe-bf11-dde794fea739","name":{"pool_name":"oxp_ae56280b-17ce-4266-8573-e1da9db6c6bb","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:109::3]:32221"},"services":[{"id":"3237a532-acaa-4ebe-bf11-dde794fea739","details":{"type":"cockroach_db","address":"[fd00:1122:3344:109::3]:32221"}}]},"root":"/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone"},{"zone":{"id":"83257100-5590-484a-b72a-a079389d8da6","zone_type":"ntp","addresses":["fd00:1122:3344:109::e"],"dataset":null,"services":[{"id":"83257100-5590-484a-b72a-a079389d8da6","details":{"type":"internal_ntp","address":"[fd00:1122:3344:109::e]:123","ntp_servers":["c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal","6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/3cafbb47-c194-4a42-99ff-34dfeab999ed/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled21.json b/sled-agent/tests/old-service-ledgers/rack2-sled21.json new file mode 100644 index 0000000000..78e003f79e --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled21.json @@ -0,0 +1 @@ +{"generation":5,"requests":[{"zone":{"id":"0437b69d-73a8-4231-86f9-6b5556e7e7ef","zone_type":"crucible","addresses":["fd00:1122:3344:102::5"],"dataset":{"id":"0437b69d-73a8-4231-86f9-6b5556e7e7ef","name":{"pool_name":"oxp_aa0ffe35-76db-42ab-adf2-ceb072bdf811","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::5]:32345"},"services":[{"id":"0437b69d-73a8-4231-86f9-6b5556e7e7ef","details":{"type":"crucible","address":"[fd00:1122:3344:102::5]:32345"}}]},"root":"/pool/ext/0d2805da-6d24-4e57-a700-0c3865c05544/crypt/zone"},{"zone":{"id":"47234ca5-305f-436a-9e9a-36bca9667680","zone_type":"crucible","addresses":["fd00:1122:3344:102::b"],"dataset":{"id":"47234ca5-305f-436a-9e9a-36bca9667680","name":{"pool_name":"oxp_0d2805da-6d24-4e57-a700-0c3865c05544","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::b]:32345"},"services":[{"id":"47234ca5-305f-436a-9e9a-36bca9667680","details":{"type":"crucible","address":"[fd00:1122:3344:102::b]:32345"}}]},"root":"/pool/ext/160691d8-33a1-4d7d-a48a-c3fd27d76822/crypt/zone"},{"zone":{"id":"2898657e-4141-4c05-851b-147bffc6bbbd","zone_type":"nexus","addresses":["fd00:1122:3344:102::3"],"dataset":null,"services":[{"id":"2898657e-4141-4c05-851b-147bffc6bbbd","details":{"type":"nexus","internal_address":"[fd00:1122:3344:102::3]:12221","external_ip":"172.20.26.5","nic":{"id":"2e9a412e-c79a-48fe-8fa4-f5a6afed1040","kind":{"type":"service","id":"2898657e-4141-4c05-851b-147bffc6bbbd"},"name":"nexus-2898657e-4141-4c05-851b-147bffc6bbbd","ip":"172.30.2.7","mac":"A8:40:25:FF:C6:59","subnet":"172.30.2.0/24","vni":100,"primary":true,"slot":0},"external_tls":true,"external_dns_servers":["1.1.1.1","9.9.9.9"]}}]},"root":"/pool/ext/c0b4ecc1-a145-443f-90d1-2e8136b007bc/crypt/zone"},{"zone":{"id":"cf98c4d6-4a7b-49c0-9b14-48a8adf52ce9","zone_type":"crucible","addresses":["fd00:1122:3344:102::c"],"dataset":{"id":"cf98c4d6-4a7b-49c0-9b14-48a8adf52ce9","name":{"pool_name":"oxp_c0b4ecc1-a145-443f-90d1-2e8136b007bc","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::c]:32345"},"services":[{"id":"cf98c4d6-4a7b-49c0-9b14-48a8adf52ce9","details":{"type":"crucible","address":"[fd00:1122:3344:102::c]:32345"}}]},"root":"/pool/ext/f6acd70a-d6cb-464d-a460-dd5c60301562/crypt/zone"},{"zone":{"id":"13c1e91e-bfcc-4eea-8185-412fc37fdea3","zone_type":"crucible","addresses":["fd00:1122:3344:102::9"],"dataset":{"id":"13c1e91e-bfcc-4eea-8185-412fc37fdea3","name":{"pool_name":"oxp_e9b0a2e4-8060-41bd-a3b5-d0642246d06d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::9]:32345"},"services":[{"id":"13c1e91e-bfcc-4eea-8185-412fc37fdea3","details":{"type":"crucible","address":"[fd00:1122:3344:102::9]:32345"}}]},"root":"/pool/ext/c0b4ecc1-a145-443f-90d1-2e8136b007bc/crypt/zone"},{"zone":{"id":"c9cb60af-9e0e-4b3b-b971-53138a9b8d27","zone_type":"crucible","addresses":["fd00:1122:3344:102::4"],"dataset":{"id":"c9cb60af-9e0e-4b3b-b971-53138a9b8d27","name":{"pool_name":"oxp_77749ec7-39a9-489d-904b-87f7223c4e3c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::4]:32345"},"services":[{"id":"c9cb60af-9e0e-4b3b-b971-53138a9b8d27","details":{"type":"crucible","address":"[fd00:1122:3344:102::4]:32345"}}]},"root":"/pool/ext/77749ec7-39a9-489d-904b-87f7223c4e3c/crypt/zone"},{"zone":{"id":"32995cfa-47ec-4b84-8514-7c1c8a86c19d","zone_type":"crucible","addresses":["fd00:1122:3344:102::8"],"dataset":{"id":"32995cfa-47ec-4b84-8514-7c1c8a86c19d","name":{"pool_name":"oxp_eac83f81-eb51-4f3e-874e-82f55dd952ba","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::8]:32345"},"services":[{"id":"32995cfa-47ec-4b84-8514-7c1c8a86c19d","details":{"type":"crucible","address":"[fd00:1122:3344:102::8]:32345"}}]},"root":"/pool/ext/0d2805da-6d24-4e57-a700-0c3865c05544/crypt/zone"},{"zone":{"id":"b93d2e2d-d54b-4503-85c3-9878e3cee9c7","zone_type":"crucible","addresses":["fd00:1122:3344:102::a"],"dataset":{"id":"b93d2e2d-d54b-4503-85c3-9878e3cee9c7","name":{"pool_name":"oxp_160691d8-33a1-4d7d-a48a-c3fd27d76822","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::a]:32345"},"services":[{"id":"b93d2e2d-d54b-4503-85c3-9878e3cee9c7","details":{"type":"crucible","address":"[fd00:1122:3344:102::a]:32345"}}]},"root":"/pool/ext/138663ad-a382-4595-baf0-08f6b0276a67/crypt/zone"},{"zone":{"id":"2ebbac4f-7b0f-43eb-99fd-dd6ff7f9e097","zone_type":"crucible","addresses":["fd00:1122:3344:102::6"],"dataset":{"id":"2ebbac4f-7b0f-43eb-99fd-dd6ff7f9e097","name":{"pool_name":"oxp_138663ad-a382-4595-baf0-08f6b0276a67","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::6]:32345"},"services":[{"id":"2ebbac4f-7b0f-43eb-99fd-dd6ff7f9e097","details":{"type":"crucible","address":"[fd00:1122:3344:102::6]:32345"}}]},"root":"/pool/ext/e9b0a2e4-8060-41bd-a3b5-d0642246d06d/crypt/zone"},{"zone":{"id":"d0eea3b2-e5ac-42bf-97b7-531b78fa06d1","zone_type":"crucible","addresses":["fd00:1122:3344:102::7"],"dataset":{"id":"d0eea3b2-e5ac-42bf-97b7-531b78fa06d1","name":{"pool_name":"oxp_69f0b863-f73f-42b2-9822-b2cb99f09003","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::7]:32345"},"services":[{"id":"d0eea3b2-e5ac-42bf-97b7-531b78fa06d1","details":{"type":"crucible","address":"[fd00:1122:3344:102::7]:32345"}}]},"root":"/pool/ext/138663ad-a382-4595-baf0-08f6b0276a67/crypt/zone"},{"zone":{"id":"2b34cd1d-ea7d-41a1-82b9-75550fdf6eb0","zone_type":"crucible","addresses":["fd00:1122:3344:102::d"],"dataset":{"id":"2b34cd1d-ea7d-41a1-82b9-75550fdf6eb0","name":{"pool_name":"oxp_f6acd70a-d6cb-464d-a460-dd5c60301562","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::d]:32345"},"services":[{"id":"2b34cd1d-ea7d-41a1-82b9-75550fdf6eb0","details":{"type":"crucible","address":"[fd00:1122:3344:102::d]:32345"}}]},"root":"/pool/ext/c0b4ecc1-a145-443f-90d1-2e8136b007bc/crypt/zone"},{"zone":{"id":"6ea2684c-115e-48a6-8453-ab52d1cecd73","zone_type":"ntp","addresses":["fd00:1122:3344:102::e"],"dataset":null,"services":[{"id":"6ea2684c-115e-48a6-8453-ab52d1cecd73","details":{"type":"boundary_ntp","address":"[fd00:1122:3344:102::e]:123","ntp_servers":["ntp.eng.oxide.computer"],"dns_servers":["1.1.1.1","9.9.9.9"],"domain":null,"nic":{"id":"4effd079-ed4e-4cf6-8545-bb9574f516d2","kind":{"type":"service","id":"6ea2684c-115e-48a6-8453-ab52d1cecd73"},"name":"ntp-6ea2684c-115e-48a6-8453-ab52d1cecd73","ip":"172.30.3.6","mac":"A8:40:25:FF:A0:F9","subnet":"172.30.3.0/24","vni":100,"primary":true,"slot":0},"snat_cfg":{"ip":"172.20.26.7","first_port":16384,"last_port":32767}}}]},"root":"/pool/ext/aa0ffe35-76db-42ab-adf2-ceb072bdf811/crypt/zone"},{"zone":{"id":"3a1ea15f-06a4-4afd-959a-c3a00b2bdd80","zone_type":"internal_dns","addresses":["fd00:1122:3344:2::1"],"dataset":{"id":"3a1ea15f-06a4-4afd-959a-c3a00b2bdd80","name":{"pool_name":"oxp_77749ec7-39a9-489d-904b-87f7223c4e3c","kind":{"type":"internal_dns"}},"service_address":"[fd00:1122:3344:2::1]:5353"},"services":[{"id":"3a1ea15f-06a4-4afd-959a-c3a00b2bdd80","details":{"type":"internal_dns","http_address":"[fd00:1122:3344:2::1]:5353","dns_address":"[fd00:1122:3344:2::1]:53","gz_address":"fd00:1122:3344:2::2","gz_address_index":1}}]},"root":"/pool/ext/69f0b863-f73f-42b2-9822-b2cb99f09003/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled23.json b/sled-agent/tests/old-service-ledgers/rack2-sled23.json new file mode 100644 index 0000000000..29b8c455d3 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled23.json @@ -0,0 +1 @@ +{"generation":5,"requests":[{"zone":{"id":"1876cdcf-b2e7-4b79-ad2e-67df716e1860","zone_type":"crucible","addresses":["fd00:1122:3344:10a::8"],"dataset":{"id":"1876cdcf-b2e7-4b79-ad2e-67df716e1860","name":{"pool_name":"oxp_d4c6bdc6-5e99-4f6c-b57a-9bfcb9a76be4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::8]:32345"},"services":[{"id":"1876cdcf-b2e7-4b79-ad2e-67df716e1860","details":{"type":"crucible","address":"[fd00:1122:3344:10a::8]:32345"}}]},"root":"/pool/ext/86c58ea3-1413-4af3-9aff-9c0a3d758459/crypt/zone"},{"zone":{"id":"0e708ee3-b7a6-4993-a88a-4489add33e29","zone_type":"crucible","addresses":["fd00:1122:3344:10a::d"],"dataset":{"id":"0e708ee3-b7a6-4993-a88a-4489add33e29","name":{"pool_name":"oxp_718ad834-b415-4abb-934d-9f987cde0a96","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::d]:32345"},"services":[{"id":"0e708ee3-b7a6-4993-a88a-4489add33e29","details":{"type":"crucible","address":"[fd00:1122:3344:10a::d]:32345"}}]},"root":"/pool/ext/30f7d236-c835-46cc-bc27-9099a6826f67/crypt/zone"},{"zone":{"id":"4e1b9a65-848f-4649-b360-1df0d135b44d","zone_type":"crucible","addresses":["fd00:1122:3344:10a::c"],"dataset":{"id":"4e1b9a65-848f-4649-b360-1df0d135b44d","name":{"pool_name":"oxp_88ee08c6-1c0f-44c2-9110-b8d5a7589ebb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::c]:32345"},"services":[{"id":"4e1b9a65-848f-4649-b360-1df0d135b44d","details":{"type":"crucible","address":"[fd00:1122:3344:10a::c]:32345"}}]},"root":"/pool/ext/30f7d236-c835-46cc-bc27-9099a6826f67/crypt/zone"},{"zone":{"id":"da510a57-3af1-4d2b-b2ed-2e8849f27d8b","zone_type":"oximeter","addresses":["fd00:1122:3344:10a::3"],"dataset":null,"services":[{"id":"da510a57-3af1-4d2b-b2ed-2e8849f27d8b","details":{"type":"oximeter","address":"[fd00:1122:3344:10a::3]:12223"}}]},"root":"/pool/ext/718ad834-b415-4abb-934d-9f987cde0a96/crypt/zone"},{"zone":{"id":"d4d9acc8-3e0b-4fab-a0a2-d21920fabd7e","zone_type":"crucible","addresses":["fd00:1122:3344:10a::6"],"dataset":{"id":"d4d9acc8-3e0b-4fab-a0a2-d21920fabd7e","name":{"pool_name":"oxp_9dfe424f-cba6-4bfb-a3dd-e8bd7fdea57d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::6]:32345"},"services":[{"id":"d4d9acc8-3e0b-4fab-a0a2-d21920fabd7e","details":{"type":"crucible","address":"[fd00:1122:3344:10a::6]:32345"}}]},"root":"/pool/ext/30f7d236-c835-46cc-bc27-9099a6826f67/crypt/zone"},{"zone":{"id":"fcb75972-836b-4f55-ba21-9722832cf5c2","zone_type":"crucible","addresses":["fd00:1122:3344:10a::7"],"dataset":{"id":"fcb75972-836b-4f55-ba21-9722832cf5c2","name":{"pool_name":"oxp_9005671f-3d90-4ed1-be15-ad65b9a65bd5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::7]:32345"},"services":[{"id":"fcb75972-836b-4f55-ba21-9722832cf5c2","details":{"type":"crucible","address":"[fd00:1122:3344:10a::7]:32345"}}]},"root":"/pool/ext/d4c6bdc6-5e99-4f6c-b57a-9bfcb9a76be4/crypt/zone"},{"zone":{"id":"624beba0-7dcd-4d55-af05-4670c6fcb1fb","zone_type":"crucible","addresses":["fd00:1122:3344:10a::4"],"dataset":{"id":"624beba0-7dcd-4d55-af05-4670c6fcb1fb","name":{"pool_name":"oxp_93867156-a43d-4c03-a899-1535e566c8bd","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::4]:32345"},"services":[{"id":"624beba0-7dcd-4d55-af05-4670c6fcb1fb","details":{"type":"crucible","address":"[fd00:1122:3344:10a::4]:32345"}}]},"root":"/pool/ext/93867156-a43d-4c03-a899-1535e566c8bd/crypt/zone"},{"zone":{"id":"26fb3830-898e-4086-afaf-8f9654716b8c","zone_type":"crucible","addresses":["fd00:1122:3344:10a::b"],"dataset":{"id":"26fb3830-898e-4086-afaf-8f9654716b8c","name":{"pool_name":"oxp_86c58ea3-1413-4af3-9aff-9c0a3d758459","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::b]:32345"},"services":[{"id":"26fb3830-898e-4086-afaf-8f9654716b8c","details":{"type":"crucible","address":"[fd00:1122:3344:10a::b]:32345"}}]},"root":"/pool/ext/93867156-a43d-4c03-a899-1535e566c8bd/crypt/zone"},{"zone":{"id":"a3ef7eba-c08e-48ef-ae7a-89e2fcb49b66","zone_type":"crucible","addresses":["fd00:1122:3344:10a::a"],"dataset":{"id":"a3ef7eba-c08e-48ef-ae7a-89e2fcb49b66","name":{"pool_name":"oxp_cd3fdbae-a9d9-4db7-866a-bca36f6dd634","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::a]:32345"},"services":[{"id":"a3ef7eba-c08e-48ef-ae7a-89e2fcb49b66","details":{"type":"crucible","address":"[fd00:1122:3344:10a::a]:32345"}}]},"root":"/pool/ext/718ad834-b415-4abb-934d-9f987cde0a96/crypt/zone"},{"zone":{"id":"5c1d4a02-f33b-433a-81f5-5c149e3433bd","zone_type":"crucible","addresses":["fd00:1122:3344:10a::5"],"dataset":{"id":"5c1d4a02-f33b-433a-81f5-5c149e3433bd","name":{"pool_name":"oxp_9adfc865-2eef-4880-a6e3-9d2f88c8efd0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::5]:32345"},"services":[{"id":"5c1d4a02-f33b-433a-81f5-5c149e3433bd","details":{"type":"crucible","address":"[fd00:1122:3344:10a::5]:32345"}}]},"root":"/pool/ext/cd3fdbae-a9d9-4db7-866a-bca36f6dd634/crypt/zone"},{"zone":{"id":"ee77efe9-81d0-4395-a237-15e30c2c2d04","zone_type":"crucible","addresses":["fd00:1122:3344:10a::9"],"dataset":{"id":"ee77efe9-81d0-4395-a237-15e30c2c2d04","name":{"pool_name":"oxp_30f7d236-c835-46cc-bc27-9099a6826f67","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::9]:32345"},"services":[{"id":"ee77efe9-81d0-4395-a237-15e30c2c2d04","details":{"type":"crucible","address":"[fd00:1122:3344:10a::9]:32345"}}]},"root":"/pool/ext/88ee08c6-1c0f-44c2-9110-b8d5a7589ebb/crypt/zone"},{"zone":{"id":"71ab91b7-48d4-4d31-b47e-59f29f419116","zone_type":"ntp","addresses":["fd00:1122:3344:10a::e"],"dataset":null,"services":[{"id":"71ab91b7-48d4-4d31-b47e-59f29f419116","details":{"type":"internal_ntp","address":"[fd00:1122:3344:10a::e]:123","ntp_servers":["c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal","6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/cd3fdbae-a9d9-4db7-866a-bca36f6dd634/crypt/zone"},{"zone":{"id":"46ccd8fe-4e3c-4307-97ae-1f7ac505082a","zone_type":"internal_dns","addresses":["fd00:1122:3344:3::1"],"dataset":{"id":"46ccd8fe-4e3c-4307-97ae-1f7ac505082a","name":{"pool_name":"oxp_93867156-a43d-4c03-a899-1535e566c8bd","kind":{"type":"internal_dns"}},"service_address":"[fd00:1122:3344:3::1]:5353"},"services":[{"id":"46ccd8fe-4e3c-4307-97ae-1f7ac505082a","details":{"type":"internal_dns","http_address":"[fd00:1122:3344:3::1]:5353","dns_address":"[fd00:1122:3344:3::1]:53","gz_address":"fd00:1122:3344:3::2","gz_address_index":2}}]},"root":"/pool/ext/9dfe424f-cba6-4bfb-a3dd-e8bd7fdea57d/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled25.json b/sled-agent/tests/old-service-ledgers/rack2-sled25.json new file mode 100644 index 0000000000..e48ef68faa --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled25.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"180d466d-eb36-4546-8922-e52c4c076823","zone_type":"crucible","addresses":["fd00:1122:3344:101::5"],"dataset":{"id":"180d466d-eb36-4546-8922-e52c4c076823","name":{"pool_name":"oxp_ac789935-fa42-4d00-8967-df0d96dbb74e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::5]:32345"},"services":[{"id":"180d466d-eb36-4546-8922-e52c4c076823","details":{"type":"crucible","address":"[fd00:1122:3344:101::5]:32345"}}]},"root":"/pool/ext/d732addc-cfe8-4c2c-8028-72eb4481b04e/crypt/zone"},{"zone":{"id":"b5af0303-bc03-40a3-b733-0396d705dfbf","zone_type":"crucible","addresses":["fd00:1122:3344:101::7"],"dataset":{"id":"b5af0303-bc03-40a3-b733-0396d705dfbf","name":{"pool_name":"oxp_d732addc-cfe8-4c2c-8028-72eb4481b04e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::7]:32345"},"services":[{"id":"b5af0303-bc03-40a3-b733-0396d705dfbf","details":{"type":"crucible","address":"[fd00:1122:3344:101::7]:32345"}}]},"root":"/pool/ext/677b0057-3a80-461b-aca8-c2cb501a7278/crypt/zone"},{"zone":{"id":"9c7c805a-f5ed-4e48-86e3-7aa81a718881","zone_type":"crucible","addresses":["fd00:1122:3344:101::c"],"dataset":{"id":"9c7c805a-f5ed-4e48-86e3-7aa81a718881","name":{"pool_name":"oxp_923c930c-80f8-448d-8321-cebfc6c41760","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::c]:32345"},"services":[{"id":"9c7c805a-f5ed-4e48-86e3-7aa81a718881","details":{"type":"crucible","address":"[fd00:1122:3344:101::c]:32345"}}]},"root":"/pool/ext/ac789935-fa42-4d00-8967-df0d96dbb74e/crypt/zone"},{"zone":{"id":"4e49c83c-2d4a-491a-91ac-4ab022026dcf","zone_type":"crucible","addresses":["fd00:1122:3344:101::4"],"dataset":{"id":"4e49c83c-2d4a-491a-91ac-4ab022026dcf","name":{"pool_name":"oxp_c99e6032-1d4f-47d2-9efe-ae2b2479554e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::4]:32345"},"services":[{"id":"4e49c83c-2d4a-491a-91ac-4ab022026dcf","details":{"type":"crucible","address":"[fd00:1122:3344:101::4]:32345"}}]},"root":"/pool/ext/653065d2-ab70-47c9-b832-34238fdc95ef/crypt/zone"},{"zone":{"id":"0e38475e-b8b2-4813-bf80-3c170081081a","zone_type":"crucible","addresses":["fd00:1122:3344:101::d"],"dataset":{"id":"0e38475e-b8b2-4813-bf80-3c170081081a","name":{"pool_name":"oxp_653065d2-ab70-47c9-b832-34238fdc95ef","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::d]:32345"},"services":[{"id":"0e38475e-b8b2-4813-bf80-3c170081081a","details":{"type":"crucible","address":"[fd00:1122:3344:101::d]:32345"}}]},"root":"/pool/ext/4c7ad252-55c2-4a1a-9d93-9dfcdfdfacca/crypt/zone"},{"zone":{"id":"75123e60-1116-4b8d-a466-7302220127da","zone_type":"crucible","addresses":["fd00:1122:3344:101::8"],"dataset":{"id":"75123e60-1116-4b8d-a466-7302220127da","name":{"pool_name":"oxp_c764a8ae-6862-4eec-9db0-cc6ea478e4a7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::8]:32345"},"services":[{"id":"75123e60-1116-4b8d-a466-7302220127da","details":{"type":"crucible","address":"[fd00:1122:3344:101::8]:32345"}}]},"root":"/pool/ext/c764a8ae-6862-4eec-9db0-cc6ea478e4a7/crypt/zone"},{"zone":{"id":"fbd0379c-97fa-49ea-8980-17ae30ffff3c","zone_type":"crucible","addresses":["fd00:1122:3344:101::b"],"dataset":{"id":"fbd0379c-97fa-49ea-8980-17ae30ffff3c","name":{"pool_name":"oxp_fcb0e4c7-e046-4cf5-ad35-3ad90e1eb90c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::b]:32345"},"services":[{"id":"fbd0379c-97fa-49ea-8980-17ae30ffff3c","details":{"type":"crucible","address":"[fd00:1122:3344:101::b]:32345"}}]},"root":"/pool/ext/4c7ad252-55c2-4a1a-9d93-9dfcdfdfacca/crypt/zone"},{"zone":{"id":"ec635326-cd1d-4f73-b8e6-c3a36a7020db","zone_type":"crucible","addresses":["fd00:1122:3344:101::a"],"dataset":{"id":"ec635326-cd1d-4f73-b8e6-c3a36a7020db","name":{"pool_name":"oxp_6bfb4120-488d-4f3d-90ef-e9bfa523b388","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::a]:32345"},"services":[{"id":"ec635326-cd1d-4f73-b8e6-c3a36a7020db","details":{"type":"crucible","address":"[fd00:1122:3344:101::a]:32345"}}]},"root":"/pool/ext/c99e6032-1d4f-47d2-9efe-ae2b2479554e/crypt/zone"},{"zone":{"id":"f500d564-c40a-4eca-ac8a-a26b435f2037","zone_type":"external_dns","addresses":["fd00:1122:3344:101::3"],"dataset":{"id":"f500d564-c40a-4eca-ac8a-a26b435f2037","name":{"pool_name":"oxp_c99e6032-1d4f-47d2-9efe-ae2b2479554e","kind":{"type":"external_dns"}},"service_address":"[fd00:1122:3344:101::3]:5353"},"services":[{"id":"f500d564-c40a-4eca-ac8a-a26b435f2037","details":{"type":"external_dns","http_address":"[fd00:1122:3344:101::3]:5353","dns_address":"172.20.26.2:53","nic":{"id":"b0b42776-3914-4a69-889f-4831dc72327c","kind":{"type":"service","id":"f500d564-c40a-4eca-ac8a-a26b435f2037"},"name":"external-dns-f500d564-c40a-4eca-ac8a-a26b435f2037","ip":"172.30.1.6","mac":"A8:40:25:FF:D0:B4","subnet":"172.30.1.0/24","vni":100,"primary":true,"slot":0}}}]},"root":"/pool/ext/ac789935-fa42-4d00-8967-df0d96dbb74e/crypt/zone"},{"zone":{"id":"56d4dbcc-3b4a-4ed0-8795-7734aadcc4c0","zone_type":"crucible","addresses":["fd00:1122:3344:101::9"],"dataset":{"id":"56d4dbcc-3b4a-4ed0-8795-7734aadcc4c0","name":{"pool_name":"oxp_4c7ad252-55c2-4a1a-9d93-9dfcdfdfacca","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::9]:32345"},"services":[{"id":"56d4dbcc-3b4a-4ed0-8795-7734aadcc4c0","details":{"type":"crucible","address":"[fd00:1122:3344:101::9]:32345"}}]},"root":"/pool/ext/4c7ad252-55c2-4a1a-9d93-9dfcdfdfacca/crypt/zone"},{"zone":{"id":"0d3a1bd5-f6fe-49cb-807a-190dabc90103","zone_type":"crucible","addresses":["fd00:1122:3344:101::6"],"dataset":{"id":"0d3a1bd5-f6fe-49cb-807a-190dabc90103","name":{"pool_name":"oxp_677b0057-3a80-461b-aca8-c2cb501a7278","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::6]:32345"},"services":[{"id":"0d3a1bd5-f6fe-49cb-807a-190dabc90103","details":{"type":"crucible","address":"[fd00:1122:3344:101::6]:32345"}}]},"root":"/pool/ext/6bfb4120-488d-4f3d-90ef-e9bfa523b388/crypt/zone"},{"zone":{"id":"d34c7184-5d4e-4cb5-8f91-df74a343ffbc","zone_type":"ntp","addresses":["fd00:1122:3344:101::e"],"dataset":null,"services":[{"id":"d34c7184-5d4e-4cb5-8f91-df74a343ffbc","details":{"type":"internal_ntp","address":"[fd00:1122:3344:101::e]:123","ntp_servers":["c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal","6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/ac789935-fa42-4d00-8967-df0d96dbb74e/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled8.json b/sled-agent/tests/old-service-ledgers/rack2-sled8.json new file mode 100644 index 0000000000..7d52980d9f --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled8.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"7153983f-8fd7-4fb9-92ac-0f07a07798b4","zone_type":"crucible","addresses":["fd00:1122:3344:103::a"],"dataset":{"id":"7153983f-8fd7-4fb9-92ac-0f07a07798b4","name":{"pool_name":"oxp_bf428719-1b16-4503-99f4-ad95846d916f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::a]:32345"},"services":[{"id":"7153983f-8fd7-4fb9-92ac-0f07a07798b4","details":{"type":"crucible","address":"[fd00:1122:3344:103::a]:32345"}}]},"root":"/pool/ext/26e698bb-006d-4208-94b9-d1bc279111fa/crypt/zone"},{"zone":{"id":"7d44ba36-4a69-490a-bc40-f6f90a4208d4","zone_type":"crucible","addresses":["fd00:1122:3344:103::c"],"dataset":{"id":"7d44ba36-4a69-490a-bc40-f6f90a4208d4","name":{"pool_name":"oxp_414e235b-55c3-4dc1-a568-8adf4ea1a052","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::c]:32345"},"services":[{"id":"7d44ba36-4a69-490a-bc40-f6f90a4208d4","details":{"type":"crucible","address":"[fd00:1122:3344:103::c]:32345"}}]},"root":"/pool/ext/cf940e15-dbc5-481b-866a-4de4b018898e/crypt/zone"},{"zone":{"id":"65a11c18-7f59-41ac-b9e7-680627f996e7","zone_type":"nexus","addresses":["fd00:1122:3344:103::3"],"dataset":null,"services":[{"id":"65a11c18-7f59-41ac-b9e7-680627f996e7","details":{"type":"nexus","internal_address":"[fd00:1122:3344:103::3]:12221","external_ip":"172.20.26.3","nic":{"id":"a3e13dde-a2bc-4170-ad84-aad8085b6034","kind":{"type":"service","id":"65a11c18-7f59-41ac-b9e7-680627f996e7"},"name":"nexus-65a11c18-7f59-41ac-b9e7-680627f996e7","ip":"172.30.2.5","mac":"A8:40:25:FF:A6:83","subnet":"172.30.2.0/24","vni":100,"primary":true,"slot":0},"external_tls":true,"external_dns_servers":["1.1.1.1","9.9.9.9"]}}]},"root":"/pool/ext/e126ddcc-8bee-46ba-8199-2a74df0ba040/crypt/zone"},{"zone":{"id":"072fdae8-2adf-4fd2-94ce-e9b0663b91e7","zone_type":"crucible","addresses":["fd00:1122:3344:103::b"],"dataset":{"id":"072fdae8-2adf-4fd2-94ce-e9b0663b91e7","name":{"pool_name":"oxp_26e698bb-006d-4208-94b9-d1bc279111fa","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::b]:32345"},"services":[{"id":"072fdae8-2adf-4fd2-94ce-e9b0663b91e7","details":{"type":"crucible","address":"[fd00:1122:3344:103::b]:32345"}}]},"root":"/pool/ext/bf428719-1b16-4503-99f4-ad95846d916f/crypt/zone"},{"zone":{"id":"01f93020-7e7d-4185-93fb-6ca234056c82","zone_type":"crucible","addresses":["fd00:1122:3344:103::5"],"dataset":{"id":"01f93020-7e7d-4185-93fb-6ca234056c82","name":{"pool_name":"oxp_7b24095a-72df-45e3-984f-2b795e052ac7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::5]:32345"},"services":[{"id":"01f93020-7e7d-4185-93fb-6ca234056c82","details":{"type":"crucible","address":"[fd00:1122:3344:103::5]:32345"}}]},"root":"/pool/ext/7b24095a-72df-45e3-984f-2b795e052ac7/crypt/zone"},{"zone":{"id":"e238116d-e5cc-43d4-9c8a-6f138ae8a15d","zone_type":"crucible","addresses":["fd00:1122:3344:103::6"],"dataset":{"id":"e238116d-e5cc-43d4-9c8a-6f138ae8a15d","name":{"pool_name":"oxp_e126ddcc-8bee-46ba-8199-2a74df0ba040","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::6]:32345"},"services":[{"id":"e238116d-e5cc-43d4-9c8a-6f138ae8a15d","details":{"type":"crucible","address":"[fd00:1122:3344:103::6]:32345"}}]},"root":"/pool/ext/7b24095a-72df-45e3-984f-2b795e052ac7/crypt/zone"},{"zone":{"id":"585cd8c5-c41e-4be4-beb8-bfbef9b53856","zone_type":"crucible","addresses":["fd00:1122:3344:103::7"],"dataset":{"id":"585cd8c5-c41e-4be4-beb8-bfbef9b53856","name":{"pool_name":"oxp_6340805e-c5af-418d-8bd1-fc0085667f33","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::7]:32345"},"services":[{"id":"585cd8c5-c41e-4be4-beb8-bfbef9b53856","details":{"type":"crucible","address":"[fd00:1122:3344:103::7]:32345"}}]},"root":"/pool/ext/414e235b-55c3-4dc1-a568-8adf4ea1a052/crypt/zone"},{"zone":{"id":"0b41c560-3b20-42f4-82ad-92f5bb575d6b","zone_type":"crucible","addresses":["fd00:1122:3344:103::9"],"dataset":{"id":"0b41c560-3b20-42f4-82ad-92f5bb575d6b","name":{"pool_name":"oxp_b93f880e-c55b-4d6c-9a16-939d84b628fc","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::9]:32345"},"services":[{"id":"0b41c560-3b20-42f4-82ad-92f5bb575d6b","details":{"type":"crucible","address":"[fd00:1122:3344:103::9]:32345"}}]},"root":"/pool/ext/6340805e-c5af-418d-8bd1-fc0085667f33/crypt/zone"},{"zone":{"id":"0ccf27c0-e32d-4b52-a2c5-6db0c64a26f9","zone_type":"crucible","addresses":["fd00:1122:3344:103::d"],"dataset":{"id":"0ccf27c0-e32d-4b52-a2c5-6db0c64a26f9","name":{"pool_name":"oxp_2115b084-be0f-4fba-941b-33a659798a9e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::d]:32345"},"services":[{"id":"0ccf27c0-e32d-4b52-a2c5-6db0c64a26f9","details":{"type":"crucible","address":"[fd00:1122:3344:103::d]:32345"}}]},"root":"/pool/ext/414e235b-55c3-4dc1-a568-8adf4ea1a052/crypt/zone"},{"zone":{"id":"a6ba8273-0320-4dab-b801-281f041b0c50","zone_type":"crucible","addresses":["fd00:1122:3344:103::4"],"dataset":{"id":"a6ba8273-0320-4dab-b801-281f041b0c50","name":{"pool_name":"oxp_8a199f12-4f5c-483a-8aca-f97856658a35","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::4]:32345"},"services":[{"id":"a6ba8273-0320-4dab-b801-281f041b0c50","details":{"type":"crucible","address":"[fd00:1122:3344:103::4]:32345"}}]},"root":"/pool/ext/b93f880e-c55b-4d6c-9a16-939d84b628fc/crypt/zone"},{"zone":{"id":"b9b7b4c2-284a-4ec1-80ea-75b7a43b71c4","zone_type":"crucible","addresses":["fd00:1122:3344:103::8"],"dataset":{"id":"b9b7b4c2-284a-4ec1-80ea-75b7a43b71c4","name":{"pool_name":"oxp_cf940e15-dbc5-481b-866a-4de4b018898e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::8]:32345"},"services":[{"id":"b9b7b4c2-284a-4ec1-80ea-75b7a43b71c4","details":{"type":"crucible","address":"[fd00:1122:3344:103::8]:32345"}}]},"root":"/pool/ext/cf940e15-dbc5-481b-866a-4de4b018898e/crypt/zone"},{"zone":{"id":"7a85d50e-b524-41c1-a052-118027eb77db","zone_type":"ntp","addresses":["fd00:1122:3344:103::e"],"dataset":null,"services":[{"id":"7a85d50e-b524-41c1-a052-118027eb77db","details":{"type":"internal_ntp","address":"[fd00:1122:3344:103::e]:123","ntp_servers":["c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal","6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/b93f880e-c55b-4d6c-9a16-939d84b628fc/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack2-sled9.json b/sled-agent/tests/old-service-ledgers/rack2-sled9.json new file mode 100644 index 0000000000..36af68759b --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack2-sled9.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"912346a2-d7e6-427e-b373-e8dcbe4fcea9","zone_type":"crucible","addresses":["fd00:1122:3344:105::5"],"dataset":{"id":"912346a2-d7e6-427e-b373-e8dcbe4fcea9","name":{"pool_name":"oxp_b358fb1e-f52a-4a63-9aab-170225509b37","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::5]:32345"},"services":[{"id":"912346a2-d7e6-427e-b373-e8dcbe4fcea9","details":{"type":"crucible","address":"[fd00:1122:3344:105::5]:32345"}}]},"root":"/pool/ext/0ae29053-29a2-489e-a1e6-6aec0ecd05f8/crypt/zone"},{"zone":{"id":"3d420dff-c616-4c7d-bab1-0f9c2b5396bf","zone_type":"crucible","addresses":["fd00:1122:3344:105::a"],"dataset":{"id":"3d420dff-c616-4c7d-bab1-0f9c2b5396bf","name":{"pool_name":"oxp_4eb2e4eb-41d8-496c-9a5a-687d7e004aa4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::a]:32345"},"services":[{"id":"3d420dff-c616-4c7d-bab1-0f9c2b5396bf","details":{"type":"crucible","address":"[fd00:1122:3344:105::a]:32345"}}]},"root":"/pool/ext/eb1234a5-fdf7-4977-94d5-2eef25ce56a1/crypt/zone"},{"zone":{"id":"9c5d88c9-8ff1-4f23-9438-7b81322eaf68","zone_type":"crucible","addresses":["fd00:1122:3344:105::b"],"dataset":{"id":"9c5d88c9-8ff1-4f23-9438-7b81322eaf68","name":{"pool_name":"oxp_aadf48eb-6ff0-40b5-a092-1fdd06c03e11","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::b]:32345"},"services":[{"id":"9c5d88c9-8ff1-4f23-9438-7b81322eaf68","details":{"type":"crucible","address":"[fd00:1122:3344:105::b]:32345"}}]},"root":"/pool/ext/4358f47f-f21e-4cc8-829e-0c7fc2400a59/crypt/zone"},{"zone":{"id":"f9c1deca-1898-429e-8c93-254c7aa7bae6","zone_type":"crucible","addresses":["fd00:1122:3344:105::8"],"dataset":{"id":"f9c1deca-1898-429e-8c93-254c7aa7bae6","name":{"pool_name":"oxp_d1cb6b7d-2b92-4b7d-8a4d-551987f0277e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::8]:32345"},"services":[{"id":"f9c1deca-1898-429e-8c93-254c7aa7bae6","details":{"type":"crucible","address":"[fd00:1122:3344:105::8]:32345"}}]},"root":"/pool/ext/f8b11629-ced6-412a-9c3f-d169b99ee996/crypt/zone"},{"zone":{"id":"ce8563f3-4a93-45ff-b727-cbfbee6aa413","zone_type":"crucible","addresses":["fd00:1122:3344:105::9"],"dataset":{"id":"ce8563f3-4a93-45ff-b727-cbfbee6aa413","name":{"pool_name":"oxp_4358f47f-f21e-4cc8-829e-0c7fc2400a59","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::9]:32345"},"services":[{"id":"ce8563f3-4a93-45ff-b727-cbfbee6aa413","details":{"type":"crucible","address":"[fd00:1122:3344:105::9]:32345"}}]},"root":"/pool/ext/eb1234a5-fdf7-4977-94d5-2eef25ce56a1/crypt/zone"},{"zone":{"id":"9470ea7d-1920-4b4b-8fca-e7659a1ef733","zone_type":"crucible","addresses":["fd00:1122:3344:105::c"],"dataset":{"id":"9470ea7d-1920-4b4b-8fca-e7659a1ef733","name":{"pool_name":"oxp_17eff217-f0b1-4353-b133-0f68bbd5ceaa","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::c]:32345"},"services":[{"id":"9470ea7d-1920-4b4b-8fca-e7659a1ef733","details":{"type":"crucible","address":"[fd00:1122:3344:105::c]:32345"}}]},"root":"/pool/ext/eb1234a5-fdf7-4977-94d5-2eef25ce56a1/crypt/zone"},{"zone":{"id":"375296e5-0a23-466c-b605-4204080f8103","zone_type":"crucible_pantry","addresses":["fd00:1122:3344:105::4"],"dataset":null,"services":[{"id":"375296e5-0a23-466c-b605-4204080f8103","details":{"type":"crucible_pantry","address":"[fd00:1122:3344:105::4]:17000"}}]},"root":"/pool/ext/4eb2e4eb-41d8-496c-9a5a-687d7e004aa4/crypt/zone"},{"zone":{"id":"f9940969-b0e8-4e8c-86c7-4bc49cd15a5f","zone_type":"crucible","addresses":["fd00:1122:3344:105::7"],"dataset":{"id":"f9940969-b0e8-4e8c-86c7-4bc49cd15a5f","name":{"pool_name":"oxp_f8b11629-ced6-412a-9c3f-d169b99ee996","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::7]:32345"},"services":[{"id":"f9940969-b0e8-4e8c-86c7-4bc49cd15a5f","details":{"type":"crucible","address":"[fd00:1122:3344:105::7]:32345"}}]},"root":"/pool/ext/17eff217-f0b1-4353-b133-0f68bbd5ceaa/crypt/zone"},{"zone":{"id":"23dca27d-c79b-4930-a817-392e8aeaa4c1","zone_type":"crucible","addresses":["fd00:1122:3344:105::e"],"dataset":{"id":"23dca27d-c79b-4930-a817-392e8aeaa4c1","name":{"pool_name":"oxp_57650e05-36ff-4de8-865f-b9562bdb67f5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::e]:32345"},"services":[{"id":"23dca27d-c79b-4930-a817-392e8aeaa4c1","details":{"type":"crucible","address":"[fd00:1122:3344:105::e]:32345"}}]},"root":"/pool/ext/0ae29053-29a2-489e-a1e6-6aec0ecd05f8/crypt/zone"},{"zone":{"id":"92d3e4e9-0768-4772-83c1-23cce52190e9","zone_type":"crucible","addresses":["fd00:1122:3344:105::6"],"dataset":{"id":"92d3e4e9-0768-4772-83c1-23cce52190e9","name":{"pool_name":"oxp_eb1234a5-fdf7-4977-94d5-2eef25ce56a1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::6]:32345"},"services":[{"id":"92d3e4e9-0768-4772-83c1-23cce52190e9","details":{"type":"crucible","address":"[fd00:1122:3344:105::6]:32345"}}]},"root":"/pool/ext/b358fb1e-f52a-4a63-9aab-170225509b37/crypt/zone"},{"zone":{"id":"b3e9fee2-24d2-44e7-8539-a6918e85cf2b","zone_type":"crucible","addresses":["fd00:1122:3344:105::d"],"dataset":{"id":"b3e9fee2-24d2-44e7-8539-a6918e85cf2b","name":{"pool_name":"oxp_0ae29053-29a2-489e-a1e6-6aec0ecd05f8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::d]:32345"},"services":[{"id":"b3e9fee2-24d2-44e7-8539-a6918e85cf2b","details":{"type":"crucible","address":"[fd00:1122:3344:105::d]:32345"}}]},"root":"/pool/ext/eb1234a5-fdf7-4977-94d5-2eef25ce56a1/crypt/zone"},{"zone":{"id":"4c3ef132-ec83-4b1b-9574-7c7d3035f9e9","zone_type":"cockroach_db","addresses":["fd00:1122:3344:105::3"],"dataset":{"id":"4c3ef132-ec83-4b1b-9574-7c7d3035f9e9","name":{"pool_name":"oxp_b358fb1e-f52a-4a63-9aab-170225509b37","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:105::3]:32221"},"services":[{"id":"4c3ef132-ec83-4b1b-9574-7c7d3035f9e9","details":{"type":"cockroach_db","address":"[fd00:1122:3344:105::3]:32221"}}]},"root":"/pool/ext/d1cb6b7d-2b92-4b7d-8a4d-551987f0277e/crypt/zone"},{"zone":{"id":"76b79b96-eaa2-4341-9aba-e77cfc92e0a9","zone_type":"ntp","addresses":["fd00:1122:3344:105::f"],"dataset":null,"services":[{"id":"76b79b96-eaa2-4341-9aba-e77cfc92e0a9","details":{"type":"internal_ntp","address":"[fd00:1122:3344:105::f]:123","ntp_servers":["c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal","6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/0ae29053-29a2-489e-a1e6-6aec0ecd05f8/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled0.json b/sled-agent/tests/old-service-ledgers/rack3-sled0.json new file mode 100644 index 0000000000..a853a525bc --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled0.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"0710ecea-dbc4-417f-a6f7-1b97c3045db1","zone_type":"crucible","addresses":["fd00:1122:3344:116::6"],"dataset":{"id":"0710ecea-dbc4-417f-a6f7-1b97c3045db1","name":{"pool_name":"oxp_d5313ef5-019c-4c47-bc5e-63794107a1bb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::6]:32345"},"services":[{"id":"0710ecea-dbc4-417f-a6f7-1b97c3045db1","details":{"type":"crucible","address":"[fd00:1122:3344:116::6]:32345"}}]},"root":"/pool/ext/904e93a9-d175-4a20-9006-8c1e847aecf7/crypt/zone"},{"zone":{"id":"28b29d14-d55f-4b55-bbc1-f66e46ae3e70","zone_type":"crucible","addresses":["fd00:1122:3344:116::9"],"dataset":{"id":"28b29d14-d55f-4b55-bbc1-f66e46ae3e70","name":{"pool_name":"oxp_60755ffe-e9ee-4619-a751-8b3ea6405e67","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::9]:32345"},"services":[{"id":"28b29d14-d55f-4b55-bbc1-f66e46ae3e70","details":{"type":"crucible","address":"[fd00:1122:3344:116::9]:32345"}}]},"root":"/pool/ext/d5313ef5-019c-4c47-bc5e-63794107a1bb/crypt/zone"},{"zone":{"id":"6f8f9fd2-b139-4069-a7e2-8d40efd58f6c","zone_type":"crucible","addresses":["fd00:1122:3344:116::d"],"dataset":{"id":"6f8f9fd2-b139-4069-a7e2-8d40efd58f6c","name":{"pool_name":"oxp_ccd2cb0b-782f-4026-a160-6d1192f04ca3","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::d]:32345"},"services":[{"id":"6f8f9fd2-b139-4069-a7e2-8d40efd58f6c","details":{"type":"crucible","address":"[fd00:1122:3344:116::d]:32345"}}]},"root":"/pool/ext/d5313ef5-019c-4c47-bc5e-63794107a1bb/crypt/zone"},{"zone":{"id":"450308ad-bf4d-40ff-ba62-f3290f7fffaf","zone_type":"crucible","addresses":["fd00:1122:3344:116::4"],"dataset":{"id":"450308ad-bf4d-40ff-ba62-f3290f7fffaf","name":{"pool_name":"oxp_46b09442-65ba-4d59-9121-9803fe3b724b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::4]:32345"},"services":[{"id":"450308ad-bf4d-40ff-ba62-f3290f7fffaf","details":{"type":"crucible","address":"[fd00:1122:3344:116::4]:32345"}}]},"root":"/pool/ext/54d901cc-f75e-417d-8a9f-24363136d0ef/crypt/zone"},{"zone":{"id":"9a22bbaa-eab4-4a32-8546-9882dc029483","zone_type":"crucible","addresses":["fd00:1122:3344:116::8"],"dataset":{"id":"9a22bbaa-eab4-4a32-8546-9882dc029483","name":{"pool_name":"oxp_93e3f350-75a0-4af0-bdac-baf9b423926f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::8]:32345"},"services":[{"id":"9a22bbaa-eab4-4a32-8546-9882dc029483","details":{"type":"crucible","address":"[fd00:1122:3344:116::8]:32345"}}]},"root":"/pool/ext/d5313ef5-019c-4c47-bc5e-63794107a1bb/crypt/zone"},{"zone":{"id":"63a9dc49-0b5b-4483-95ed-553b545dc202","zone_type":"crucible","addresses":["fd00:1122:3344:116::a"],"dataset":{"id":"63a9dc49-0b5b-4483-95ed-553b545dc202","name":{"pool_name":"oxp_e3532845-76c0-42a9-903b-a07f7992e937","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::a]:32345"},"services":[{"id":"63a9dc49-0b5b-4483-95ed-553b545dc202","details":{"type":"crucible","address":"[fd00:1122:3344:116::a]:32345"}}]},"root":"/pool/ext/60755ffe-e9ee-4619-a751-8b3ea6405e67/crypt/zone"},{"zone":{"id":"1fef5b6c-78e4-4ad9-9973-9d8c78f1e232","zone_type":"crucible","addresses":["fd00:1122:3344:116::7"],"dataset":{"id":"1fef5b6c-78e4-4ad9-9973-9d8c78f1e232","name":{"pool_name":"oxp_54d901cc-f75e-417d-8a9f-24363136d0ef","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::7]:32345"},"services":[{"id":"1fef5b6c-78e4-4ad9-9973-9d8c78f1e232","details":{"type":"crucible","address":"[fd00:1122:3344:116::7]:32345"}}]},"root":"/pool/ext/90d7b6f9-3e28-48b0-86ac-0486728075cf/crypt/zone"},{"zone":{"id":"b2aab21a-cccd-4aa9-977f-a32090e6eaa7","zone_type":"crucible","addresses":["fd00:1122:3344:116::5"],"dataset":{"id":"b2aab21a-cccd-4aa9-977f-a32090e6eaa7","name":{"pool_name":"oxp_90d7b6f9-3e28-48b0-86ac-0486728075cf","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::5]:32345"},"services":[{"id":"b2aab21a-cccd-4aa9-977f-a32090e6eaa7","details":{"type":"crucible","address":"[fd00:1122:3344:116::5]:32345"}}]},"root":"/pool/ext/46b09442-65ba-4d59-9121-9803fe3b724b/crypt/zone"},{"zone":{"id":"fc1bbf28-24f3-4c1f-b367-2bc8231eb7d4","zone_type":"crucible","addresses":["fd00:1122:3344:116::b"],"dataset":{"id":"fc1bbf28-24f3-4c1f-b367-2bc8231eb7d4","name":{"pool_name":"oxp_0a7bb0d3-408b-42b1-8846-76cf106a9580","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::b]:32345"},"services":[{"id":"fc1bbf28-24f3-4c1f-b367-2bc8231eb7d4","details":{"type":"crucible","address":"[fd00:1122:3344:116::b]:32345"}}]},"root":"/pool/ext/e3532845-76c0-42a9-903b-a07f7992e937/crypt/zone"},{"zone":{"id":"bcb7617a-f76a-4912-8ccc-802d2a697e3c","zone_type":"crucible","addresses":["fd00:1122:3344:116::c"],"dataset":{"id":"bcb7617a-f76a-4912-8ccc-802d2a697e3c","name":{"pool_name":"oxp_904e93a9-d175-4a20-9006-8c1e847aecf7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:116::c]:32345"},"services":[{"id":"bcb7617a-f76a-4912-8ccc-802d2a697e3c","details":{"type":"crucible","address":"[fd00:1122:3344:116::c]:32345"}}]},"root":"/pool/ext/ccd2cb0b-782f-4026-a160-6d1192f04ca3/crypt/zone"},{"zone":{"id":"371fba3a-658b-469b-b675-c90cc0d39254","zone_type":"cockroach_db","addresses":["fd00:1122:3344:116::3"],"dataset":{"id":"371fba3a-658b-469b-b675-c90cc0d39254","name":{"pool_name":"oxp_46b09442-65ba-4d59-9121-9803fe3b724b","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:116::3]:32221"},"services":[{"id":"371fba3a-658b-469b-b675-c90cc0d39254","details":{"type":"cockroach_db","address":"[fd00:1122:3344:116::3]:32221"}}]},"root":"/pool/ext/46b09442-65ba-4d59-9121-9803fe3b724b/crypt/zone"},{"zone":{"id":"5a4d89f5-49e0-4566-a99c-342d1bb26b1c","zone_type":"ntp","addresses":["fd00:1122:3344:116::e"],"dataset":null,"services":[{"id":"5a4d89f5-49e0-4566-a99c-342d1bb26b1c","details":{"type":"internal_ntp","address":"[fd00:1122:3344:116::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/60755ffe-e9ee-4619-a751-8b3ea6405e67/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled1.json b/sled-agent/tests/old-service-ledgers/rack3-sled1.json new file mode 100644 index 0000000000..bd735e5e64 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled1.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"f401d06c-46fc-42f8-aa51-7515a51355ce","zone_type":"crucible","addresses":["fd00:1122:3344:11c::8"],"dataset":{"id":"f401d06c-46fc-42f8-aa51-7515a51355ce","name":{"pool_name":"oxp_8a88768a-2dd5-43b7-bd40-0db77be4d3a8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::8]:32345"},"services":[{"id":"f401d06c-46fc-42f8-aa51-7515a51355ce","details":{"type":"crucible","address":"[fd00:1122:3344:11c::8]:32345"}}]},"root":"/pool/ext/19d23d27-6a33-4203-b8c1-4b0df4ac791f/crypt/zone"},{"zone":{"id":"721c96ea-08d4-4c89-828f-600e7e344916","zone_type":"crucible","addresses":["fd00:1122:3344:11c::6"],"dataset":{"id":"721c96ea-08d4-4c89-828f-600e7e344916","name":{"pool_name":"oxp_15259003-fb04-4547-b4a9-b4511893c0fd","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::6]:32345"},"services":[{"id":"721c96ea-08d4-4c89-828f-600e7e344916","details":{"type":"crucible","address":"[fd00:1122:3344:11c::6]:32345"}}]},"root":"/pool/ext/d2a8ed82-22ef-46d8-ad40-e1cb2cecebee/crypt/zone"},{"zone":{"id":"ca17bdf9-51c5-4e1e-b822-856609070ec6","zone_type":"crucible","addresses":["fd00:1122:3344:11c::5"],"dataset":{"id":"ca17bdf9-51c5-4e1e-b822-856609070ec6","name":{"pool_name":"oxp_d2a8ed82-22ef-46d8-ad40-e1cb2cecebee","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::5]:32345"},"services":[{"id":"ca17bdf9-51c5-4e1e-b822-856609070ec6","details":{"type":"crucible","address":"[fd00:1122:3344:11c::5]:32345"}}]},"root":"/pool/ext/15259003-fb04-4547-b4a9-b4511893c0fd/crypt/zone"},{"zone":{"id":"5825447e-1b5b-4960-b202-e75853d3d250","zone_type":"crucible","addresses":["fd00:1122:3344:11c::9"],"dataset":{"id":"5825447e-1b5b-4960-b202-e75853d3d250","name":{"pool_name":"oxp_04e94454-cbd4-4cee-ad69-42372bcbabd5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::9]:32345"},"services":[{"id":"5825447e-1b5b-4960-b202-e75853d3d250","details":{"type":"crucible","address":"[fd00:1122:3344:11c::9]:32345"}}]},"root":"/pool/ext/542e0fb3-552c-4d3b-b853-da1f13b581a0/crypt/zone"},{"zone":{"id":"b937d3f0-1352-47a2-b9d1-a9ccf9c82b16","zone_type":"crucible","addresses":["fd00:1122:3344:11c::c"],"dataset":{"id":"b937d3f0-1352-47a2-b9d1-a9ccf9c82b16","name":{"pool_name":"oxp_542e0fb3-552c-4d3b-b853-da1f13b581a0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::c]:32345"},"services":[{"id":"b937d3f0-1352-47a2-b9d1-a9ccf9c82b16","details":{"type":"crucible","address":"[fd00:1122:3344:11c::c]:32345"}}]},"root":"/pool/ext/eedd1d58-4892-456f-aaf7-9d650c7921ca/crypt/zone"},{"zone":{"id":"d63a677b-8dac-44ee-89a2-cc4cb151254d","zone_type":"crucible","addresses":["fd00:1122:3344:11c::3"],"dataset":{"id":"d63a677b-8dac-44ee-89a2-cc4cb151254d","name":{"pool_name":"oxp_45b5f1ee-7b66-4d74-8364-54fa0c73775f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::3]:32345"},"services":[{"id":"d63a677b-8dac-44ee-89a2-cc4cb151254d","details":{"type":"crucible","address":"[fd00:1122:3344:11c::3]:32345"}}]},"root":"/pool/ext/8a88768a-2dd5-43b7-bd40-0db77be4d3a8/crypt/zone"},{"zone":{"id":"abcb92ea-9f17-4cd8-897b-9d0d1ef7903a","zone_type":"crucible","addresses":["fd00:1122:3344:11c::4"],"dataset":{"id":"abcb92ea-9f17-4cd8-897b-9d0d1ef7903a","name":{"pool_name":"oxp_341d49db-c06a-416d-90e1-b0a3426ed02e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::4]:32345"},"services":[{"id":"abcb92ea-9f17-4cd8-897b-9d0d1ef7903a","details":{"type":"crucible","address":"[fd00:1122:3344:11c::4]:32345"}}]},"root":"/pool/ext/eedd1d58-4892-456f-aaf7-9d650c7921ca/crypt/zone"},{"zone":{"id":"000ac89d-db07-47ae-83cf-d9cafef013de","zone_type":"crucible","addresses":["fd00:1122:3344:11c::b"],"dataset":{"id":"000ac89d-db07-47ae-83cf-d9cafef013de","name":{"pool_name":"oxp_eedd1d58-4892-456f-aaf7-9d650c7921ca","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::b]:32345"},"services":[{"id":"000ac89d-db07-47ae-83cf-d9cafef013de","details":{"type":"crucible","address":"[fd00:1122:3344:11c::b]:32345"}}]},"root":"/pool/ext/04e94454-cbd4-4cee-ad69-42372bcbabd5/crypt/zone"},{"zone":{"id":"29e1e2e4-695e-4c05-8f0c-c16a0a61d390","zone_type":"crucible","addresses":["fd00:1122:3344:11c::7"],"dataset":{"id":"29e1e2e4-695e-4c05-8f0c-c16a0a61d390","name":{"pool_name":"oxp_19d23d27-6a33-4203-b8c1-4b0df4ac791f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::7]:32345"},"services":[{"id":"29e1e2e4-695e-4c05-8f0c-c16a0a61d390","details":{"type":"crucible","address":"[fd00:1122:3344:11c::7]:32345"}}]},"root":"/pool/ext/d2a8ed82-22ef-46d8-ad40-e1cb2cecebee/crypt/zone"},{"zone":{"id":"9fa7d7be-a6de-4d36-b56b-d1cc5ca7c82c","zone_type":"crucible","addresses":["fd00:1122:3344:11c::a"],"dataset":{"id":"9fa7d7be-a6de-4d36-b56b-d1cc5ca7c82c","name":{"pool_name":"oxp_0fd7a0b1-ed4b-4dc6-8c44-a49c9628c7e1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11c::a]:32345"},"services":[{"id":"9fa7d7be-a6de-4d36-b56b-d1cc5ca7c82c","details":{"type":"crucible","address":"[fd00:1122:3344:11c::a]:32345"}}]},"root":"/pool/ext/d2a8ed82-22ef-46d8-ad40-e1cb2cecebee/crypt/zone"},{"zone":{"id":"249db5f1-45e2-4a5c-a91f-cc51dbd87040","zone_type":"ntp","addresses":["fd00:1122:3344:11c::d"],"dataset":null,"services":[{"id":"249db5f1-45e2-4a5c-a91f-cc51dbd87040","details":{"type":"internal_ntp","address":"[fd00:1122:3344:11c::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/542e0fb3-552c-4d3b-b853-da1f13b581a0/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled11.json b/sled-agent/tests/old-service-ledgers/rack3-sled11.json new file mode 100644 index 0000000000..2918c74c4b --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled11.json @@ -0,0 +1 @@ +{"generation":5,"requests":[{"zone":{"id":"7ddd0738-59df-4b67-a41e-7f0de9827187","zone_type":"crucible","addresses":["fd00:1122:3344:11e::4"],"dataset":{"id":"7ddd0738-59df-4b67-a41e-7f0de9827187","name":{"pool_name":"oxp_09af632a-6b1b-4a18-8c91-d392da38b02f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::4]:32345"},"services":[{"id":"7ddd0738-59df-4b67-a41e-7f0de9827187","details":{"type":"crucible","address":"[fd00:1122:3344:11e::4]:32345"}}]},"root":"/pool/ext/09af632a-6b1b-4a18-8c91-d392da38b02f/crypt/zone"},{"zone":{"id":"9706189f-713a-4394-b5dc-45dcf67dc46e","zone_type":"crucible","addresses":["fd00:1122:3344:11e::9"],"dataset":{"id":"9706189f-713a-4394-b5dc-45dcf67dc46e","name":{"pool_name":"oxp_4e1837c8-91ab-4d1d-abfd-f5144d88535e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::9]:32345"},"services":[{"id":"9706189f-713a-4394-b5dc-45dcf67dc46e","details":{"type":"crucible","address":"[fd00:1122:3344:11e::9]:32345"}}]},"root":"/pool/ext/2f0d47cb-28d1-4350-8656-60c6121f773b/crypt/zone"},{"zone":{"id":"7bdd841b-5e34-4c19-9066-b12578651446","zone_type":"crucible","addresses":["fd00:1122:3344:11e::a"],"dataset":{"id":"7bdd841b-5e34-4c19-9066-b12578651446","name":{"pool_name":"oxp_78d1e7f7-8d11-4fed-8b1e-be58908aea2f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::a]:32345"},"services":[{"id":"7bdd841b-5e34-4c19-9066-b12578651446","details":{"type":"crucible","address":"[fd00:1122:3344:11e::a]:32345"}}]},"root":"/pool/ext/62c23f4b-8e7b-4cd8-9055-19c1d8bd5ac8/crypt/zone"},{"zone":{"id":"74c0f60b-de5f-4456-a85f-f992a6e10424","zone_type":"crucible","addresses":["fd00:1122:3344:11e::b"],"dataset":{"id":"74c0f60b-de5f-4456-a85f-f992a6e10424","name":{"pool_name":"oxp_3b81d709-bf10-4dd7-a2c0-759d8acc2da0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::b]:32345"},"services":[{"id":"74c0f60b-de5f-4456-a85f-f992a6e10424","details":{"type":"crucible","address":"[fd00:1122:3344:11e::b]:32345"}}]},"root":"/pool/ext/09af632a-6b1b-4a18-8c91-d392da38b02f/crypt/zone"},{"zone":{"id":"da81ce6f-bd38-440e-b966-8a743092fa21","zone_type":"crucible","addresses":["fd00:1122:3344:11e::6"],"dataset":{"id":"da81ce6f-bd38-440e-b966-8a743092fa21","name":{"pool_name":"oxp_62c23f4b-8e7b-4cd8-9055-19c1d8bd5ac8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::6]:32345"},"services":[{"id":"da81ce6f-bd38-440e-b966-8a743092fa21","details":{"type":"crucible","address":"[fd00:1122:3344:11e::6]:32345"}}]},"root":"/pool/ext/215dd02b-0de6-488a-9e65-5e588cd079fb/crypt/zone"},{"zone":{"id":"febbca37-5279-400f-a2e9-6b5271b2d2fc","zone_type":"crucible","addresses":["fd00:1122:3344:11e::7"],"dataset":{"id":"febbca37-5279-400f-a2e9-6b5271b2d2fc","name":{"pool_name":"oxp_fb33e773-fb93-41a0-8078-b653b9078dda","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::7]:32345"},"services":[{"id":"febbca37-5279-400f-a2e9-6b5271b2d2fc","details":{"type":"crucible","address":"[fd00:1122:3344:11e::7]:32345"}}]},"root":"/pool/ext/2f0d47cb-28d1-4350-8656-60c6121f773b/crypt/zone"},{"zone":{"id":"5100e222-5ea4-4e67-9040-679137e666c8","zone_type":"crucible","addresses":["fd00:1122:3344:11e::5"],"dataset":{"id":"5100e222-5ea4-4e67-9040-679137e666c8","name":{"pool_name":"oxp_23767587-2253-431b-8944-18b9bfefcb3d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::5]:32345"},"services":[{"id":"5100e222-5ea4-4e67-9040-679137e666c8","details":{"type":"crucible","address":"[fd00:1122:3344:11e::5]:32345"}}]},"root":"/pool/ext/3b81d709-bf10-4dd7-a2c0-759d8acc2da0/crypt/zone"},{"zone":{"id":"c7ec3bc8-08ca-4901-a45e-0d68db72c6a7","zone_type":"crucible","addresses":["fd00:1122:3344:11e::3"],"dataset":{"id":"c7ec3bc8-08ca-4901-a45e-0d68db72c6a7","name":{"pool_name":"oxp_2f0d47cb-28d1-4350-8656-60c6121f773b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::3]:32345"},"services":[{"id":"c7ec3bc8-08ca-4901-a45e-0d68db72c6a7","details":{"type":"crucible","address":"[fd00:1122:3344:11e::3]:32345"}}]},"root":"/pool/ext/215dd02b-0de6-488a-9e65-5e588cd079fb/crypt/zone"},{"zone":{"id":"1fc80dd3-0fd9-4403-96bd-5bbf9eb0f15a","zone_type":"crucible","addresses":["fd00:1122:3344:11e::c"],"dataset":{"id":"1fc80dd3-0fd9-4403-96bd-5bbf9eb0f15a","name":{"pool_name":"oxp_2c932d54-41fb-4ffe-a57f-0479b9e5841e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::c]:32345"},"services":[{"id":"1fc80dd3-0fd9-4403-96bd-5bbf9eb0f15a","details":{"type":"crucible","address":"[fd00:1122:3344:11e::c]:32345"}}]},"root":"/pool/ext/3b81d709-bf10-4dd7-a2c0-759d8acc2da0/crypt/zone"},{"zone":{"id":"4eacc68d-5699-440a-ab33-c75f259e4cc3","zone_type":"crucible","addresses":["fd00:1122:3344:11e::8"],"dataset":{"id":"4eacc68d-5699-440a-ab33-c75f259e4cc3","name":{"pool_name":"oxp_215dd02b-0de6-488a-9e65-5e588cd079fb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11e::8]:32345"},"services":[{"id":"4eacc68d-5699-440a-ab33-c75f259e4cc3","details":{"type":"crucible","address":"[fd00:1122:3344:11e::8]:32345"}}]},"root":"/pool/ext/4e1837c8-91ab-4d1d-abfd-f5144d88535e/crypt/zone"},{"zone":{"id":"cb901d3e-8811-4c4c-a274-a44130501ecf","zone_type":"ntp","addresses":["fd00:1122:3344:11e::d"],"dataset":null,"services":[{"id":"cb901d3e-8811-4c4c-a274-a44130501ecf","details":{"type":"boundary_ntp","address":"[fd00:1122:3344:11e::d]:123","ntp_servers":["time.cloudflare.com"],"dns_servers":["1.1.1.1","8.8.8.8"],"domain":null,"nic":{"id":"bcf9d9eb-b4ba-4fd5-91e0-55a3414ae049","kind":{"type":"service","id":"cb901d3e-8811-4c4c-a274-a44130501ecf"},"name":"ntp-cb901d3e-8811-4c4c-a274-a44130501ecf","ip":"172.30.3.6","mac":"A8:40:25:FF:D5:2F","subnet":"172.30.3.0/24","vni":100,"primary":true,"slot":0},"snat_cfg":{"ip":"45.154.216.39","first_port":16384,"last_port":32767}}}]},"root":"/pool/ext/23767587-2253-431b-8944-18b9bfefcb3d/crypt/zone"},{"zone":{"id":"be4aada9-d160-401d-a630-a0764c039702","zone_type":"internal_dns","addresses":["fd00:1122:3344:2::1"],"dataset":{"id":"be4aada9-d160-401d-a630-a0764c039702","name":{"pool_name":"oxp_2f0d47cb-28d1-4350-8656-60c6121f773b","kind":{"type":"internal_dns"}},"service_address":"[fd00:1122:3344:2::1]:5353"},"services":[{"id":"be4aada9-d160-401d-a630-a0764c039702","details":{"type":"internal_dns","http_address":"[fd00:1122:3344:2::1]:5353","dns_address":"[fd00:1122:3344:2::1]:53","gz_address":"fd00:1122:3344:2::2","gz_address_index":1}}]},"root":"/pool/ext/78d1e7f7-8d11-4fed-8b1e-be58908aea2f/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled12.json b/sled-agent/tests/old-service-ledgers/rack3-sled12.json new file mode 100644 index 0000000000..c81f586e01 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled12.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"d8f1b9d2-fa2e-4f03-bbea-2039448d7792","zone_type":"crucible","addresses":["fd00:1122:3344:112::5"],"dataset":{"id":"d8f1b9d2-fa2e-4f03-bbea-2039448d7792","name":{"pool_name":"oxp_7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::5]:32345"},"services":[{"id":"d8f1b9d2-fa2e-4f03-bbea-2039448d7792","details":{"type":"crucible","address":"[fd00:1122:3344:112::5]:32345"}}]},"root":"/pool/ext/78d9f0ae-8e7f-450e-abc2-76b983efa5cd/crypt/zone"},{"zone":{"id":"2074a935-c0b3-4c4f-aae5-a29adae3e1ac","zone_type":"crucible","addresses":["fd00:1122:3344:112::8"],"dataset":{"id":"2074a935-c0b3-4c4f-aae5-a29adae3e1ac","name":{"pool_name":"oxp_ac663368-45fb-447c-811e-561c68e37bdd","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::8]:32345"},"services":[{"id":"2074a935-c0b3-4c4f-aae5-a29adae3e1ac","details":{"type":"crucible","address":"[fd00:1122:3344:112::8]:32345"}}]},"root":"/pool/ext/ac663368-45fb-447c-811e-561c68e37bdd/crypt/zone"},{"zone":{"id":"2885d3c7-ad7d-445c-8630-dc6c81f8caa0","zone_type":"crucible","addresses":["fd00:1122:3344:112::a"],"dataset":{"id":"2885d3c7-ad7d-445c-8630-dc6c81f8caa0","name":{"pool_name":"oxp_8e82e8da-e1c5-4867-bc1c-b5441f9c1010","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::a]:32345"},"services":[{"id":"2885d3c7-ad7d-445c-8630-dc6c81f8caa0","details":{"type":"crucible","address":"[fd00:1122:3344:112::a]:32345"}}]},"root":"/pool/ext/8e82e8da-e1c5-4867-bc1c-b5441f9c1010/crypt/zone"},{"zone":{"id":"1eca241b-6868-4c59-876b-58356654f3b5","zone_type":"crucible","addresses":["fd00:1122:3344:112::c"],"dataset":{"id":"1eca241b-6868-4c59-876b-58356654f3b5","name":{"pool_name":"oxp_fde16c69-aa47-4a15-bb3f-3a5861ae45bd","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::c]:32345"},"services":[{"id":"1eca241b-6868-4c59-876b-58356654f3b5","details":{"type":"crucible","address":"[fd00:1122:3344:112::c]:32345"}}]},"root":"/pool/ext/7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1/crypt/zone"},{"zone":{"id":"cc656f2e-8542-4986-8524-2f55984939c1","zone_type":"crucible","addresses":["fd00:1122:3344:112::d"],"dataset":{"id":"cc656f2e-8542-4986-8524-2f55984939c1","name":{"pool_name":"oxp_21e6d0f9-887e-4d6f-9a00-4cd61139eea6","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::d]:32345"},"services":[{"id":"cc656f2e-8542-4986-8524-2f55984939c1","details":{"type":"crucible","address":"[fd00:1122:3344:112::d]:32345"}}]},"root":"/pool/ext/21e6d0f9-887e-4d6f-9a00-4cd61139eea6/crypt/zone"},{"zone":{"id":"dfb1ebce-a4c7-4b50-9435-9a79b884c1af","zone_type":"clickhouse","addresses":["fd00:1122:3344:112::3"],"dataset":{"id":"dfb1ebce-a4c7-4b50-9435-9a79b884c1af","name":{"pool_name":"oxp_4f045315-de51-46ed-a011-16496615278f","kind":{"type":"clickhouse"}},"service_address":"[fd00:1122:3344:112::3]:8123"},"services":[{"id":"dfb1ebce-a4c7-4b50-9435-9a79b884c1af","details":{"type":"clickhouse","address":"[fd00:1122:3344:112::3]:8123"}}]},"root":"/pool/ext/7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1/crypt/zone"},{"zone":{"id":"a95d90ed-b2b1-4a5d-8d0d-4195b34bc764","zone_type":"crucible","addresses":["fd00:1122:3344:112::6"],"dataset":{"id":"a95d90ed-b2b1-4a5d-8d0d-4195b34bc764","name":{"pool_name":"oxp_d2c77c69-14d7-442e-8b47-a0d7af5a0e7e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::6]:32345"},"services":[{"id":"a95d90ed-b2b1-4a5d-8d0d-4195b34bc764","details":{"type":"crucible","address":"[fd00:1122:3344:112::6]:32345"}}]},"root":"/pool/ext/fad56ff1-ad9f-4215-b584-522eab18cf7b/crypt/zone"},{"zone":{"id":"1d3ebc90-d5a5-4cb0-ae90-50bb2163ae13","zone_type":"crucible","addresses":["fd00:1122:3344:112::b"],"dataset":{"id":"1d3ebc90-d5a5-4cb0-ae90-50bb2163ae13","name":{"pool_name":"oxp_fad56ff1-ad9f-4215-b584-522eab18cf7b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::b]:32345"},"services":[{"id":"1d3ebc90-d5a5-4cb0-ae90-50bb2163ae13","details":{"type":"crucible","address":"[fd00:1122:3344:112::b]:32345"}}]},"root":"/pool/ext/7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1/crypt/zone"},{"zone":{"id":"7af9f38b-0c7a-402e-8db3-7c7fb50b4665","zone_type":"crucible","addresses":["fd00:1122:3344:112::9"],"dataset":{"id":"7af9f38b-0c7a-402e-8db3-7c7fb50b4665","name":{"pool_name":"oxp_d0693580-5c5a-449f-803f-ce7188ebc580","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::9]:32345"},"services":[{"id":"7af9f38b-0c7a-402e-8db3-7c7fb50b4665","details":{"type":"crucible","address":"[fd00:1122:3344:112::9]:32345"}}]},"root":"/pool/ext/d2c77c69-14d7-442e-8b47-a0d7af5a0e7e/crypt/zone"},{"zone":{"id":"94d9bb0a-ecd2-4501-b960-60982f55ad12","zone_type":"crucible","addresses":["fd00:1122:3344:112::7"],"dataset":{"id":"94d9bb0a-ecd2-4501-b960-60982f55ad12","name":{"pool_name":"oxp_78d9f0ae-8e7f-450e-abc2-76b983efa5cd","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::7]:32345"},"services":[{"id":"94d9bb0a-ecd2-4501-b960-60982f55ad12","details":{"type":"crucible","address":"[fd00:1122:3344:112::7]:32345"}}]},"root":"/pool/ext/ac663368-45fb-447c-811e-561c68e37bdd/crypt/zone"},{"zone":{"id":"277c1105-576e-4ec1-8e2c-cbae2f5ac9f6","zone_type":"crucible","addresses":["fd00:1122:3344:112::4"],"dataset":{"id":"277c1105-576e-4ec1-8e2c-cbae2f5ac9f6","name":{"pool_name":"oxp_4f045315-de51-46ed-a011-16496615278f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:112::4]:32345"},"services":[{"id":"277c1105-576e-4ec1-8e2c-cbae2f5ac9f6","details":{"type":"crucible","address":"[fd00:1122:3344:112::4]:32345"}}]},"root":"/pool/ext/7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1/crypt/zone"},{"zone":{"id":"555c3407-a76c-4ea4-a17a-a670d85a59b0","zone_type":"ntp","addresses":["fd00:1122:3344:112::e"],"dataset":null,"services":[{"id":"555c3407-a76c-4ea4-a17a-a670d85a59b0","details":{"type":"internal_ntp","address":"[fd00:1122:3344:112::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/8e82e8da-e1c5-4867-bc1c-b5441f9c1010/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled13.json b/sled-agent/tests/old-service-ledgers/rack3-sled13.json new file mode 100644 index 0000000000..ab151a828e --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled13.json @@ -0,0 +1 @@ +{"generation":5,"requests":[{"zone":{"id":"fbcf51c9-a732-4a03-8c19-cfb5b819cb7a","zone_type":"crucible","addresses":["fd00:1122:3344:104::5"],"dataset":{"id":"fbcf51c9-a732-4a03-8c19-cfb5b819cb7a","name":{"pool_name":"oxp_382a2961-cd27-4a9c-901d-468a45ff5708","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::5]:32345"},"services":[{"id":"fbcf51c9-a732-4a03-8c19-cfb5b819cb7a","details":{"type":"crucible","address":"[fd00:1122:3344:104::5]:32345"}}]},"root":"/pool/ext/e99994ae-61ca-4742-a02c-eb0a8a5b69ff/crypt/zone"},{"zone":{"id":"7f8a5026-1f1d-4ab3-8c04-077bfda2f815","zone_type":"crucible","addresses":["fd00:1122:3344:104::4"],"dataset":{"id":"7f8a5026-1f1d-4ab3-8c04-077bfda2f815","name":{"pool_name":"oxp_9c99b9b6-8018-455e-a58a-c048ddd3e11b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::4]:32345"},"services":[{"id":"7f8a5026-1f1d-4ab3-8c04-077bfda2f815","details":{"type":"crucible","address":"[fd00:1122:3344:104::4]:32345"}}]},"root":"/pool/ext/22c79e54-37ef-4ad2-a6cb-a7ee3e4f7167/crypt/zone"},{"zone":{"id":"6d45d856-0e49-4eb7-ad76-989a9ae636a2","zone_type":"crucible","addresses":["fd00:1122:3344:104::3"],"dataset":{"id":"6d45d856-0e49-4eb7-ad76-989a9ae636a2","name":{"pool_name":"oxp_b74a84fa-b4c8-4c5f-92f4-f4e62a0a311d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::3]:32345"},"services":[{"id":"6d45d856-0e49-4eb7-ad76-989a9ae636a2","details":{"type":"crucible","address":"[fd00:1122:3344:104::3]:32345"}}]},"root":"/pool/ext/9c99b9b6-8018-455e-a58a-c048ddd3e11b/crypt/zone"},{"zone":{"id":"c8dc7fff-72c8-49eb-a552-d605f8655134","zone_type":"crucible","addresses":["fd00:1122:3344:104::6"],"dataset":{"id":"c8dc7fff-72c8-49eb-a552-d605f8655134","name":{"pool_name":"oxp_22c79e54-37ef-4ad2-a6cb-a7ee3e4f7167","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::6]:32345"},"services":[{"id":"c8dc7fff-72c8-49eb-a552-d605f8655134","details":{"type":"crucible","address":"[fd00:1122:3344:104::6]:32345"}}]},"root":"/pool/ext/22c79e54-37ef-4ad2-a6cb-a7ee3e4f7167/crypt/zone"},{"zone":{"id":"128a90f5-8889-4665-8343-2c7098f2922c","zone_type":"crucible","addresses":["fd00:1122:3344:104::7"],"dataset":{"id":"128a90f5-8889-4665-8343-2c7098f2922c","name":{"pool_name":"oxp_8b3d0b51-c6a5-4d2c-827a-0d0d1471136d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::7]:32345"},"services":[{"id":"128a90f5-8889-4665-8343-2c7098f2922c","details":{"type":"crucible","address":"[fd00:1122:3344:104::7]:32345"}}]},"root":"/pool/ext/29cd042b-e772-4d26-ac85-ef16009950bd/crypt/zone"},{"zone":{"id":"a72f1878-3b03-4267-9024-5df5ebae69de","zone_type":"crucible","addresses":["fd00:1122:3344:104::a"],"dataset":{"id":"a72f1878-3b03-4267-9024-5df5ebae69de","name":{"pool_name":"oxp_e99994ae-61ca-4742-a02c-eb0a8a5b69ff","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::a]:32345"},"services":[{"id":"a72f1878-3b03-4267-9024-5df5ebae69de","details":{"type":"crucible","address":"[fd00:1122:3344:104::a]:32345"}}]},"root":"/pool/ext/8b3d0b51-c6a5-4d2c-827a-0d0d1471136d/crypt/zone"},{"zone":{"id":"6a9165a2-9b66-485a-aaf0-70d89d60bb6c","zone_type":"crucible","addresses":["fd00:1122:3344:104::b"],"dataset":{"id":"6a9165a2-9b66-485a-aaf0-70d89d60bb6c","name":{"pool_name":"oxp_6a02f05f-e400-4c80-8df8-89aaecb6c12b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::b]:32345"},"services":[{"id":"6a9165a2-9b66-485a-aaf0-70d89d60bb6c","details":{"type":"crucible","address":"[fd00:1122:3344:104::b]:32345"}}]},"root":"/pool/ext/9c99b9b6-8018-455e-a58a-c048ddd3e11b/crypt/zone"},{"zone":{"id":"9677c4ed-96bc-4dcb-ae74-f7a3e9d2b5e2","zone_type":"crucible","addresses":["fd00:1122:3344:104::c"],"dataset":{"id":"9677c4ed-96bc-4dcb-ae74-f7a3e9d2b5e2","name":{"pool_name":"oxp_7c30978f-ee87-4e53-8fdf-3455e5e851b7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::c]:32345"},"services":[{"id":"9677c4ed-96bc-4dcb-ae74-f7a3e9d2b5e2","details":{"type":"crucible","address":"[fd00:1122:3344:104::c]:32345"}}]},"root":"/pool/ext/29cd042b-e772-4d26-ac85-ef16009950bd/crypt/zone"},{"zone":{"id":"179039e7-3ffd-4b76-9379-bef41d42a5ff","zone_type":"crucible","addresses":["fd00:1122:3344:104::8"],"dataset":{"id":"179039e7-3ffd-4b76-9379-bef41d42a5ff","name":{"pool_name":"oxp_4db7e002-e112-4bfc-a41e-8ae26991b01e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::8]:32345"},"services":[{"id":"179039e7-3ffd-4b76-9379-bef41d42a5ff","details":{"type":"crucible","address":"[fd00:1122:3344:104::8]:32345"}}]},"root":"/pool/ext/8b3d0b51-c6a5-4d2c-827a-0d0d1471136d/crypt/zone"},{"zone":{"id":"6067e31e-b6a3-4114-9e49-0296adc8e7af","zone_type":"crucible","addresses":["fd00:1122:3344:104::9"],"dataset":{"id":"6067e31e-b6a3-4114-9e49-0296adc8e7af","name":{"pool_name":"oxp_29cd042b-e772-4d26-ac85-ef16009950bd","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:104::9]:32345"},"services":[{"id":"6067e31e-b6a3-4114-9e49-0296adc8e7af","details":{"type":"crucible","address":"[fd00:1122:3344:104::9]:32345"}}]},"root":"/pool/ext/9c99b9b6-8018-455e-a58a-c048ddd3e11b/crypt/zone"},{"zone":{"id":"440dd615-e11f-4a5d-aeb4-dcf88bb314de","zone_type":"ntp","addresses":["fd00:1122:3344:104::d"],"dataset":null,"services":[{"id":"440dd615-e11f-4a5d-aeb4-dcf88bb314de","details":{"type":"boundary_ntp","address":"[fd00:1122:3344:104::d]:123","ntp_servers":["time.cloudflare.com"],"dns_servers":["1.1.1.1","8.8.8.8"],"domain":null,"nic":{"id":"0b52fe1b-f4cc-43b1-9ac3-4ebb4ab60133","kind":{"type":"service","id":"440dd615-e11f-4a5d-aeb4-dcf88bb314de"},"name":"ntp-440dd615-e11f-4a5d-aeb4-dcf88bb314de","ip":"172.30.3.5","mac":"A8:40:25:FF:85:1E","subnet":"172.30.3.0/24","vni":100,"primary":true,"slot":0},"snat_cfg":{"ip":"45.154.216.38","first_port":0,"last_port":16383}}}]},"root":"/pool/ext/382a2961-cd27-4a9c-901d-468a45ff5708/crypt/zone"},{"zone":{"id":"06e2de03-bd92-404c-a8ea-a13185539d24","zone_type":"internal_dns","addresses":["fd00:1122:3344:1::1"],"dataset":{"id":"06e2de03-bd92-404c-a8ea-a13185539d24","name":{"pool_name":"oxp_b74a84fa-b4c8-4c5f-92f4-f4e62a0a311d","kind":{"type":"internal_dns"}},"service_address":"[fd00:1122:3344:1::1]:5353"},"services":[{"id":"06e2de03-bd92-404c-a8ea-a13185539d24","details":{"type":"internal_dns","http_address":"[fd00:1122:3344:1::1]:5353","dns_address":"[fd00:1122:3344:1::1]:53","gz_address":"fd00:1122:3344:1::2","gz_address_index":0}}]},"root":"/pool/ext/e99994ae-61ca-4742-a02c-eb0a8a5b69ff/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled14.json b/sled-agent/tests/old-service-ledgers/rack3-sled14.json new file mode 100644 index 0000000000..89c12a015f --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled14.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"ac35afab-a312-43c3-a42d-04b8e99fcbde","zone_type":"crucible","addresses":["fd00:1122:3344:111::4"],"dataset":{"id":"ac35afab-a312-43c3-a42d-04b8e99fcbde","name":{"pool_name":"oxp_6601065c-c172-4118-81b4-16adde7e9401","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::4]:32345"},"services":[{"id":"ac35afab-a312-43c3-a42d-04b8e99fcbde","details":{"type":"crucible","address":"[fd00:1122:3344:111::4]:32345"}}]},"root":"/pool/ext/24d7e250-9fc6-459e-8155-30f8e8ccb28c/crypt/zone"},{"zone":{"id":"6cd94da2-35b9-4683-a931-29ad4a5ed0ef","zone_type":"crucible","addresses":["fd00:1122:3344:111::c"],"dataset":{"id":"6cd94da2-35b9-4683-a931-29ad4a5ed0ef","name":{"pool_name":"oxp_58276eba-a53c-4ef3-b374-4cdcde4d6e12","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::c]:32345"},"services":[{"id":"6cd94da2-35b9-4683-a931-29ad4a5ed0ef","details":{"type":"crucible","address":"[fd00:1122:3344:111::c]:32345"}}]},"root":"/pool/ext/24d7e250-9fc6-459e-8155-30f8e8ccb28c/crypt/zone"},{"zone":{"id":"41f07d39-fcc0-4796-8b7c-7cfcd9135f78","zone_type":"crucible","addresses":["fd00:1122:3344:111::9"],"dataset":{"id":"41f07d39-fcc0-4796-8b7c-7cfcd9135f78","name":{"pool_name":"oxp_4b90abdc-3348-4158-bedc-5bcd56e281d8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::9]:32345"},"services":[{"id":"41f07d39-fcc0-4796-8b7c-7cfcd9135f78","details":{"type":"crucible","address":"[fd00:1122:3344:111::9]:32345"}}]},"root":"/pool/ext/8e955f54-fbef-4021-9eec-457825468813/crypt/zone"},{"zone":{"id":"44c35566-dd64-4e4a-896e-c50aaa3df14f","zone_type":"nexus","addresses":["fd00:1122:3344:111::3"],"dataset":null,"services":[{"id":"44c35566-dd64-4e4a-896e-c50aaa3df14f","details":{"type":"nexus","internal_address":"[fd00:1122:3344:111::3]:12221","external_ip":"45.154.216.37","nic":{"id":"6f824d20-6ce0-4e8b-9ce3-b12dd2b59913","kind":{"type":"service","id":"44c35566-dd64-4e4a-896e-c50aaa3df14f"},"name":"nexus-44c35566-dd64-4e4a-896e-c50aaa3df14f","ip":"172.30.2.7","mac":"A8:40:25:FF:E8:5F","subnet":"172.30.2.0/24","vni":100,"primary":true,"slot":0},"external_tls":true,"external_dns_servers":["1.1.1.1","8.8.8.8"]}}]},"root":"/pool/ext/435d7a1b-2865-4d49-903f-a68f464ade4d/crypt/zone"},{"zone":{"id":"e5020d24-8652-456b-bf92-cd7d255a34c5","zone_type":"crucible","addresses":["fd00:1122:3344:111::6"],"dataset":{"id":"e5020d24-8652-456b-bf92-cd7d255a34c5","name":{"pool_name":"oxp_f6925045-363d-4e18-9bde-ee2987b33d21","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::6]:32345"},"services":[{"id":"e5020d24-8652-456b-bf92-cd7d255a34c5","details":{"type":"crucible","address":"[fd00:1122:3344:111::6]:32345"}}]},"root":"/pool/ext/6601065c-c172-4118-81b4-16adde7e9401/crypt/zone"},{"zone":{"id":"8f25f258-afd7-4351-83e4-24220ec0c251","zone_type":"crucible","addresses":["fd00:1122:3344:111::8"],"dataset":{"id":"8f25f258-afd7-4351-83e4-24220ec0c251","name":{"pool_name":"oxp_8e955f54-fbef-4021-9eec-457825468813","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::8]:32345"},"services":[{"id":"8f25f258-afd7-4351-83e4-24220ec0c251","details":{"type":"crucible","address":"[fd00:1122:3344:111::8]:32345"}}]},"root":"/pool/ext/6601065c-c172-4118-81b4-16adde7e9401/crypt/zone"},{"zone":{"id":"26aa50ec-d70a-47ea-85fc-e55c62a2e0c6","zone_type":"crucible","addresses":["fd00:1122:3344:111::5"],"dataset":{"id":"26aa50ec-d70a-47ea-85fc-e55c62a2e0c6","name":{"pool_name":"oxp_24d7e250-9fc6-459e-8155-30f8e8ccb28c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::5]:32345"},"services":[{"id":"26aa50ec-d70a-47ea-85fc-e55c62a2e0c6","details":{"type":"crucible","address":"[fd00:1122:3344:111::5]:32345"}}]},"root":"/pool/ext/435d7a1b-2865-4d49-903f-a68f464ade4d/crypt/zone"},{"zone":{"id":"68dc212f-a96a-420f-8334-b11ee5d7cb95","zone_type":"crucible","addresses":["fd00:1122:3344:111::7"],"dataset":{"id":"68dc212f-a96a-420f-8334-b11ee5d7cb95","name":{"pool_name":"oxp_4353b00b-937e-4d07-aea6-014c57b6f12c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::7]:32345"},"services":[{"id":"68dc212f-a96a-420f-8334-b11ee5d7cb95","details":{"type":"crucible","address":"[fd00:1122:3344:111::7]:32345"}}]},"root":"/pool/ext/24d7e250-9fc6-459e-8155-30f8e8ccb28c/crypt/zone"},{"zone":{"id":"475140fa-a5dc-4ec1-876d-751c48adfc37","zone_type":"crucible","addresses":["fd00:1122:3344:111::a"],"dataset":{"id":"475140fa-a5dc-4ec1-876d-751c48adfc37","name":{"pool_name":"oxp_ee55b053-6874-4e20-86b5-2e105e64c068","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::a]:32345"},"services":[{"id":"475140fa-a5dc-4ec1-876d-751c48adfc37","details":{"type":"crucible","address":"[fd00:1122:3344:111::a]:32345"}}]},"root":"/pool/ext/ee55b053-6874-4e20-86b5-2e105e64c068/crypt/zone"},{"zone":{"id":"09d5a8c9-00db-4914-a2c6-7ae3d2da4558","zone_type":"crucible","addresses":["fd00:1122:3344:111::d"],"dataset":{"id":"09d5a8c9-00db-4914-a2c6-7ae3d2da4558","name":{"pool_name":"oxp_9ab5aba5-47dc-4bc4-8f6d-7cbe0f98a9a2","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::d]:32345"},"services":[{"id":"09d5a8c9-00db-4914-a2c6-7ae3d2da4558","details":{"type":"crucible","address":"[fd00:1122:3344:111::d]:32345"}}]},"root":"/pool/ext/8e955f54-fbef-4021-9eec-457825468813/crypt/zone"},{"zone":{"id":"014f6a39-ad64-4f0a-9fef-01ca0d184cbf","zone_type":"crucible","addresses":["fd00:1122:3344:111::b"],"dataset":{"id":"014f6a39-ad64-4f0a-9fef-01ca0d184cbf","name":{"pool_name":"oxp_435d7a1b-2865-4d49-903f-a68f464ade4d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:111::b]:32345"},"services":[{"id":"014f6a39-ad64-4f0a-9fef-01ca0d184cbf","details":{"type":"crucible","address":"[fd00:1122:3344:111::b]:32345"}}]},"root":"/pool/ext/f6925045-363d-4e18-9bde-ee2987b33d21/crypt/zone"},{"zone":{"id":"aceaf348-ba07-4965-a543-63a800826fe8","zone_type":"ntp","addresses":["fd00:1122:3344:111::e"],"dataset":null,"services":[{"id":"aceaf348-ba07-4965-a543-63a800826fe8","details":{"type":"internal_ntp","address":"[fd00:1122:3344:111::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/8e955f54-fbef-4021-9eec-457825468813/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled15.json b/sled-agent/tests/old-service-ledgers/rack3-sled15.json new file mode 100644 index 0000000000..880f29409e --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled15.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"09a9ecee-1e7c-4819-b27a-73bb61099ce7","zone_type":"external_dns","addresses":["fd00:1122:3344:114::3"],"dataset":{"id":"09a9ecee-1e7c-4819-b27a-73bb61099ce7","name":{"pool_name":"oxp_b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e","kind":{"type":"external_dns"}},"service_address":"[fd00:1122:3344:114::3]:5353"},"services":[{"id":"09a9ecee-1e7c-4819-b27a-73bb61099ce7","details":{"type":"external_dns","http_address":"[fd00:1122:3344:114::3]:5353","dns_address":"45.154.216.33:53","nic":{"id":"400ca77b-7fee-47d5-8f17-1f4b9c729f27","kind":{"type":"service","id":"09a9ecee-1e7c-4819-b27a-73bb61099ce7"},"name":"external-dns-09a9ecee-1e7c-4819-b27a-73bb61099ce7","ip":"172.30.1.5","mac":"A8:40:25:FF:B7:C7","subnet":"172.30.1.0/24","vni":100,"primary":true,"slot":0}}}]},"root":"/pool/ext/9e878b1e-bf92-4155-8162-640851c2f5d5/crypt/zone"},{"zone":{"id":"1792e003-55f7-49b8-906c-4160db91bc23","zone_type":"crucible","addresses":["fd00:1122:3344:114::5"],"dataset":{"id":"1792e003-55f7-49b8-906c-4160db91bc23","name":{"pool_name":"oxp_7f3a760f-a4c0-456f-8a22-2d06ecac1022","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::5]:32345"},"services":[{"id":"1792e003-55f7-49b8-906c-4160db91bc23","details":{"type":"crucible","address":"[fd00:1122:3344:114::5]:32345"}}]},"root":"/pool/ext/76f09ad5-c96c-4748-bbe4-71afaea7bc5e/crypt/zone"},{"zone":{"id":"73bc7c0e-1034-449f-8920-4a1f418653ff","zone_type":"crucible","addresses":["fd00:1122:3344:114::8"],"dataset":{"id":"73bc7c0e-1034-449f-8920-4a1f418653ff","name":{"pool_name":"oxp_e87037be-1cdf-4c6e-a8a3-c27b830eaef9","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::8]:32345"},"services":[{"id":"73bc7c0e-1034-449f-8920-4a1f418653ff","details":{"type":"crucible","address":"[fd00:1122:3344:114::8]:32345"}}]},"root":"/pool/ext/b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e/crypt/zone"},{"zone":{"id":"06dc6619-6251-4543-9a10-da1698af49d5","zone_type":"crucible","addresses":["fd00:1122:3344:114::9"],"dataset":{"id":"06dc6619-6251-4543-9a10-da1698af49d5","name":{"pool_name":"oxp_ee34c530-ce70-4f1a-8c97-d0ebb77ccfc8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::9]:32345"},"services":[{"id":"06dc6619-6251-4543-9a10-da1698af49d5","details":{"type":"crucible","address":"[fd00:1122:3344:114::9]:32345"}}]},"root":"/pool/ext/9e878b1e-bf92-4155-8162-640851c2f5d5/crypt/zone"},{"zone":{"id":"0d796c52-37ca-490d-b42f-dcc22fe5fd6b","zone_type":"crucible","addresses":["fd00:1122:3344:114::c"],"dataset":{"id":"0d796c52-37ca-490d-b42f-dcc22fe5fd6b","name":{"pool_name":"oxp_9ec2b893-d486-4b24-a077-1a297f9eb15f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::c]:32345"},"services":[{"id":"0d796c52-37ca-490d-b42f-dcc22fe5fd6b","details":{"type":"crucible","address":"[fd00:1122:3344:114::c]:32345"}}]},"root":"/pool/ext/9e72c0e2-4895-4791-b606-2f18e432fb69/crypt/zone"},{"zone":{"id":"91d0011f-de44-4823-bc26-a447affa39bc","zone_type":"crucible","addresses":["fd00:1122:3344:114::a"],"dataset":{"id":"91d0011f-de44-4823-bc26-a447affa39bc","name":{"pool_name":"oxp_85e81a14-031d-4a63-a91f-981c64e91f60","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::a]:32345"},"services":[{"id":"91d0011f-de44-4823-bc26-a447affa39bc","details":{"type":"crucible","address":"[fd00:1122:3344:114::a]:32345"}}]},"root":"/pool/ext/b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e/crypt/zone"},{"zone":{"id":"0c44a2f1-559a-459c-9931-e0e7964d41c6","zone_type":"crucible","addresses":["fd00:1122:3344:114::b"],"dataset":{"id":"0c44a2f1-559a-459c-9931-e0e7964d41c6","name":{"pool_name":"oxp_76f09ad5-c96c-4748-bbe4-71afaea7bc5e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::b]:32345"},"services":[{"id":"0c44a2f1-559a-459c-9931-e0e7964d41c6","details":{"type":"crucible","address":"[fd00:1122:3344:114::b]:32345"}}]},"root":"/pool/ext/e87037be-1cdf-4c6e-a8a3-c27b830eaef9/crypt/zone"},{"zone":{"id":"ea363819-96f6-4fb6-a203-f18414f1c60e","zone_type":"crucible","addresses":["fd00:1122:3344:114::4"],"dataset":{"id":"ea363819-96f6-4fb6-a203-f18414f1c60e","name":{"pool_name":"oxp_b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::4]:32345"},"services":[{"id":"ea363819-96f6-4fb6-a203-f18414f1c60e","details":{"type":"crucible","address":"[fd00:1122:3344:114::4]:32345"}}]},"root":"/pool/ext/b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e/crypt/zone"},{"zone":{"id":"21592c39-da6b-4527-842e-edeeceffafa1","zone_type":"crucible","addresses":["fd00:1122:3344:114::6"],"dataset":{"id":"21592c39-da6b-4527-842e-edeeceffafa1","name":{"pool_name":"oxp_9e72c0e2-4895-4791-b606-2f18e432fb69","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::6]:32345"},"services":[{"id":"21592c39-da6b-4527-842e-edeeceffafa1","details":{"type":"crucible","address":"[fd00:1122:3344:114::6]:32345"}}]},"root":"/pool/ext/7aff8429-b65d-4a53-a796-7221ac7581a9/crypt/zone"},{"zone":{"id":"f33b1263-f1b2-43a6-a8aa-5f8570dd4e72","zone_type":"crucible","addresses":["fd00:1122:3344:114::7"],"dataset":{"id":"f33b1263-f1b2-43a6-a8aa-5f8570dd4e72","name":{"pool_name":"oxp_9e878b1e-bf92-4155-8162-640851c2f5d5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::7]:32345"},"services":[{"id":"f33b1263-f1b2-43a6-a8aa-5f8570dd4e72","details":{"type":"crucible","address":"[fd00:1122:3344:114::7]:32345"}}]},"root":"/pool/ext/7f3a760f-a4c0-456f-8a22-2d06ecac1022/crypt/zone"},{"zone":{"id":"6f42b469-5a36-4048-a152-e884f7e8a206","zone_type":"crucible","addresses":["fd00:1122:3344:114::d"],"dataset":{"id":"6f42b469-5a36-4048-a152-e884f7e8a206","name":{"pool_name":"oxp_7aff8429-b65d-4a53-a796-7221ac7581a9","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:114::d]:32345"},"services":[{"id":"6f42b469-5a36-4048-a152-e884f7e8a206","details":{"type":"crucible","address":"[fd00:1122:3344:114::d]:32345"}}]},"root":"/pool/ext/9e72c0e2-4895-4791-b606-2f18e432fb69/crypt/zone"},{"zone":{"id":"ad77d594-8f78-4d33-a5e4-59887060178e","zone_type":"ntp","addresses":["fd00:1122:3344:114::e"],"dataset":null,"services":[{"id":"ad77d594-8f78-4d33-a5e4-59887060178e","details":{"type":"internal_ntp","address":"[fd00:1122:3344:114::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/85e81a14-031d-4a63-a91f-981c64e91f60/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled16.json b/sled-agent/tests/old-service-ledgers/rack3-sled16.json new file mode 100644 index 0000000000..3a1cbeb411 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled16.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"dcb9a4ae-2c89-4a74-905b-b7936ff49c19","zone_type":"crucible","addresses":["fd00:1122:3344:11f::9"],"dataset":{"id":"dcb9a4ae-2c89-4a74-905b-b7936ff49c19","name":{"pool_name":"oxp_af509039-d27f-4095-bc9d-cecbc5c606db","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11f::9]:32345"},"services":[{"id":"dcb9a4ae-2c89-4a74-905b-b7936ff49c19","details":{"type":"crucible","address":"[fd00:1122:3344:11f::9]:32345"}}]},"root":"/pool/ext/44ee0fb4-6034-44e8-b3de-b3a44457ffca/crypt/zone"},{"zone":{"id":"dbd46f71-ec39-4b72-a77d-9d281ccb37e0","zone_type":"crucible","addresses":["fd00:1122:3344:11f::b"],"dataset":{"id":"dbd46f71-ec39-4b72-a77d-9d281ccb37e0","name":{"pool_name":"oxp_44ee0fb4-6034-44e8-b3de-b3a44457ffca","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11f::b]:32345"},"services":[{"id":"dbd46f71-ec39-4b72-a77d-9d281ccb37e0","details":{"type":"crucible","address":"[fd00:1122:3344:11f::b]:32345"}}]},"root":"/pool/ext/5e32c0a3-1210-402b-91fb-256946eeac2b/crypt/zone"},{"zone":{"id":"a1f30569-a5c6-4a6d-922e-241966aea142","zone_type":"crucible","addresses":["fd00:1122:3344:11f::6"],"dataset":{"id":"a1f30569-a5c6-4a6d-922e-241966aea142","name":{"pool_name":"oxp_d2133e8b-51cc-455e-89d0-5454fd4fe109","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11f::6]:32345"},"services":[{"id":"a1f30569-a5c6-4a6d-922e-241966aea142","details":{"type":"crucible","address":"[fd00:1122:3344:11f::6]:32345"}}]},"root":"/pool/ext/3f57835b-1469-499a-8757-7cc56acc5d49/crypt/zone"},{"zone":{"id":"a33e25ae-4e41-40f4-843d-3d12f62d8cb6","zone_type":"crucible","addresses":["fd00:1122:3344:11f::8"],"dataset":{"id":"a33e25ae-4e41-40f4-843d-3d12f62d8cb6","name":{"pool_name":"oxp_c8e4a7f4-1ae6-4683-8397-ea53475a53e8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11f::8]:32345"},"services":[{"id":"a33e25ae-4e41-40f4-843d-3d12f62d8cb6","details":{"type":"crucible","address":"[fd00:1122:3344:11f::8]:32345"}}]},"root":"/pool/ext/5e32c0a3-1210-402b-91fb-256946eeac2b/crypt/zone"},{"zone":{"id":"65ed75c2-2d80-4de5-a6f6-adfa6516c7cf","zone_type":"crucible","addresses":["fd00:1122:3344:11f::c"],"dataset":{"id":"65ed75c2-2d80-4de5-a6f6-adfa6516c7cf","name":{"pool_name":"oxp_3f57835b-1469-499a-8757-7cc56acc5d49","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11f::c]:32345"},"services":[{"id":"65ed75c2-2d80-4de5-a6f6-adfa6516c7cf","details":{"type":"crucible","address":"[fd00:1122:3344:11f::c]:32345"}}]},"root":"/pool/ext/cd8cd75c-632b-4527-889a-7ca0c080fe2c/crypt/zone"},{"zone":{"id":"bc6ccf18-6b9b-4687-8b70-c7917d972ae0","zone_type":"crucible","addresses":["fd00:1122:3344:11f::a"],"dataset":{"id":"bc6ccf18-6b9b-4687-8b70-c7917d972ae0","name":{"pool_name":"oxp_cd8cd75c-632b-4527-889a-7ca0c080fe2c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11f::a]:32345"},"services":[{"id":"bc6ccf18-6b9b-4687-8b70-c7917d972ae0","details":{"type":"crucible","address":"[fd00:1122:3344:11f::a]:32345"}}]},"root":"/pool/ext/5e32c0a3-1210-402b-91fb-256946eeac2b/crypt/zone"},{"zone":{"id":"06233bfe-a857-4819-aefe-212af9eeb90f","zone_type":"crucible","addresses":["fd00:1122:3344:11f::5"],"dataset":{"id":"06233bfe-a857-4819-aefe-212af9eeb90f","name":{"pool_name":"oxp_c8a1aaf1-d27c-45fd-9f8d-80ac6bf6865d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11f::5]:32345"},"services":[{"id":"06233bfe-a857-4819-aefe-212af9eeb90f","details":{"type":"crucible","address":"[fd00:1122:3344:11f::5]:32345"}}]},"root":"/pool/ext/af509039-d27f-4095-bc9d-cecbc5c606db/crypt/zone"},{"zone":{"id":"0bbfef71-9eae-43b6-b5e7-0060ce9269dd","zone_type":"crucible","addresses":["fd00:1122:3344:11f::4"],"dataset":{"id":"0bbfef71-9eae-43b6-b5e7-0060ce9269dd","name":{"pool_name":"oxp_5e32c0a3-1210-402b-91fb-256946eeac2b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11f::4]:32345"},"services":[{"id":"0bbfef71-9eae-43b6-b5e7-0060ce9269dd","details":{"type":"crucible","address":"[fd00:1122:3344:11f::4]:32345"}}]},"root":"/pool/ext/af509039-d27f-4095-bc9d-cecbc5c606db/crypt/zone"},{"zone":{"id":"550e10ee-24d1-444f-80be-2744dd321e0f","zone_type":"crucible","addresses":["fd00:1122:3344:11f::7"],"dataset":{"id":"550e10ee-24d1-444f-80be-2744dd321e0f","name":{"pool_name":"oxp_f437ce0e-eb45-4be8-b1fe-33ed2656eb01","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11f::7]:32345"},"services":[{"id":"550e10ee-24d1-444f-80be-2744dd321e0f","details":{"type":"crucible","address":"[fd00:1122:3344:11f::7]:32345"}}]},"root":"/pool/ext/44ee0fb4-6034-44e8-b3de-b3a44457ffca/crypt/zone"},{"zone":{"id":"86d768f3-ece2-4956-983f-999bdb23a983","zone_type":"cockroach_db","addresses":["fd00:1122:3344:11f::3"],"dataset":{"id":"86d768f3-ece2-4956-983f-999bdb23a983","name":{"pool_name":"oxp_5e32c0a3-1210-402b-91fb-256946eeac2b","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:11f::3]:32221"},"services":[{"id":"86d768f3-ece2-4956-983f-999bdb23a983","details":{"type":"cockroach_db","address":"[fd00:1122:3344:11f::3]:32221"}}]},"root":"/pool/ext/c8a1aaf1-d27c-45fd-9f8d-80ac6bf6865d/crypt/zone"},{"zone":{"id":"2f358812-f72c-4838-a5ea-7d78d0954be0","zone_type":"ntp","addresses":["fd00:1122:3344:11f::d"],"dataset":null,"services":[{"id":"2f358812-f72c-4838-a5ea-7d78d0954be0","details":{"type":"internal_ntp","address":"[fd00:1122:3344:11f::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/f437ce0e-eb45-4be8-b1fe-33ed2656eb01/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled17.json b/sled-agent/tests/old-service-ledgers/rack3-sled17.json new file mode 100644 index 0000000000..4063fed2e2 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled17.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"525a19a2-d4ac-418d-bdcf-2ce26e7abe70","zone_type":"crucible","addresses":["fd00:1122:3344:107::a"],"dataset":{"id":"525a19a2-d4ac-418d-bdcf-2ce26e7abe70","name":{"pool_name":"oxp_cb774d2f-ff86-4fd7-866b-17a6b10e61f0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::a]:32345"},"services":[{"id":"525a19a2-d4ac-418d-bdcf-2ce26e7abe70","details":{"type":"crucible","address":"[fd00:1122:3344:107::a]:32345"}}]},"root":"/pool/ext/e17b68b5-f50c-4fc3-b55a-80d284c6c32d/crypt/zone"},{"zone":{"id":"7af188e1-6175-4769-9e4f-2ca7a98b76f6","zone_type":"crucible","addresses":["fd00:1122:3344:107::4"],"dataset":{"id":"7af188e1-6175-4769-9e4f-2ca7a98b76f6","name":{"pool_name":"oxp_0cbbcf22-770d-4e75-9148-e6109b129093","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::4]:32345"},"services":[{"id":"7af188e1-6175-4769-9e4f-2ca7a98b76f6","details":{"type":"crucible","address":"[fd00:1122:3344:107::4]:32345"}}]},"root":"/pool/ext/b998e8df-ea69-4bdd-84cb-b7f17075b060/crypt/zone"},{"zone":{"id":"2544540f-6ffc-46c0-84bf-f42a110c02d7","zone_type":"crucible","addresses":["fd00:1122:3344:107::6"],"dataset":{"id":"2544540f-6ffc-46c0-84bf-f42a110c02d7","name":{"pool_name":"oxp_e17b68b5-f50c-4fc3-b55a-80d284c6c32d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::6]:32345"},"services":[{"id":"2544540f-6ffc-46c0-84bf-f42a110c02d7","details":{"type":"crucible","address":"[fd00:1122:3344:107::6]:32345"}}]},"root":"/pool/ext/521fa477-4d83-49a8-a5cf-c267b7f0c409/crypt/zone"},{"zone":{"id":"cfc20f72-cac2-4681-a6d8-e5a0accafbb7","zone_type":"crucible","addresses":["fd00:1122:3344:107::7"],"dataset":{"id":"cfc20f72-cac2-4681-a6d8-e5a0accafbb7","name":{"pool_name":"oxp_b998e8df-ea69-4bdd-84cb-b7f17075b060","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::7]:32345"},"services":[{"id":"cfc20f72-cac2-4681-a6d8-e5a0accafbb7","details":{"type":"crucible","address":"[fd00:1122:3344:107::7]:32345"}}]},"root":"/pool/ext/0cbbcf22-770d-4e75-9148-e6109b129093/crypt/zone"},{"zone":{"id":"e24be791-5773-425e-a3df-e35ca81570c7","zone_type":"crucible","addresses":["fd00:1122:3344:107::9"],"dataset":{"id":"e24be791-5773-425e-a3df-e35ca81570c7","name":{"pool_name":"oxp_7849c221-dc7f-43ac-ac47-bc51864e083b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::9]:32345"},"services":[{"id":"e24be791-5773-425e-a3df-e35ca81570c7","details":{"type":"crucible","address":"[fd00:1122:3344:107::9]:32345"}}]},"root":"/pool/ext/7849c221-dc7f-43ac-ac47-bc51864e083b/crypt/zone"},{"zone":{"id":"170856ee-21cf-4780-8903-175d558bc7cc","zone_type":"crucible","addresses":["fd00:1122:3344:107::3"],"dataset":{"id":"170856ee-21cf-4780-8903-175d558bc7cc","name":{"pool_name":"oxp_618e21e5-77d4-40ba-9f8e-7960e9ad92e2","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::3]:32345"},"services":[{"id":"170856ee-21cf-4780-8903-175d558bc7cc","details":{"type":"crucible","address":"[fd00:1122:3344:107::3]:32345"}}]},"root":"/pool/ext/aa7a37fb-2f03-4d5c-916b-db3a4fc269ac/crypt/zone"},{"zone":{"id":"604278ff-525a-4d41-82ff-07aef3174d38","zone_type":"crucible","addresses":["fd00:1122:3344:107::5"],"dataset":{"id":"604278ff-525a-4d41-82ff-07aef3174d38","name":{"pool_name":"oxp_521fa477-4d83-49a8-a5cf-c267b7f0c409","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::5]:32345"},"services":[{"id":"604278ff-525a-4d41-82ff-07aef3174d38","details":{"type":"crucible","address":"[fd00:1122:3344:107::5]:32345"}}]},"root":"/pool/ext/0cbbcf22-770d-4e75-9148-e6109b129093/crypt/zone"},{"zone":{"id":"d0d4fcc0-6ed0-410a-99c7-5daf34014421","zone_type":"crucible","addresses":["fd00:1122:3344:107::b"],"dataset":{"id":"d0d4fcc0-6ed0-410a-99c7-5daf34014421","name":{"pool_name":"oxp_aa7a37fb-2f03-4d5c-916b-db3a4fc269ac","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::b]:32345"},"services":[{"id":"d0d4fcc0-6ed0-410a-99c7-5daf34014421","details":{"type":"crucible","address":"[fd00:1122:3344:107::b]:32345"}}]},"root":"/pool/ext/aa7a37fb-2f03-4d5c-916b-db3a4fc269ac/crypt/zone"},{"zone":{"id":"c935df7b-2629-48ee-bc10-20508301905d","zone_type":"crucible","addresses":["fd00:1122:3344:107::c"],"dataset":{"id":"c935df7b-2629-48ee-bc10-20508301905d","name":{"pool_name":"oxp_793fd018-5fdc-4e54-9c45-f8023fa3ea18","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::c]:32345"},"services":[{"id":"c935df7b-2629-48ee-bc10-20508301905d","details":{"type":"crucible","address":"[fd00:1122:3344:107::c]:32345"}}]},"root":"/pool/ext/7849c221-dc7f-43ac-ac47-bc51864e083b/crypt/zone"},{"zone":{"id":"4ba5f3b6-8be5-4a85-bc57-a5e3b0b867d8","zone_type":"crucible","addresses":["fd00:1122:3344:107::8"],"dataset":{"id":"4ba5f3b6-8be5-4a85-bc57-a5e3b0b867d8","name":{"pool_name":"oxp_e80e7996-c572-481e-8c22-61c16c6e47f4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:107::8]:32345"},"services":[{"id":"4ba5f3b6-8be5-4a85-bc57-a5e3b0b867d8","details":{"type":"crucible","address":"[fd00:1122:3344:107::8]:32345"}}]},"root":"/pool/ext/e17b68b5-f50c-4fc3-b55a-80d284c6c32d/crypt/zone"},{"zone":{"id":"395c9d6e-3bd0-445e-9269-46c3260edb83","zone_type":"ntp","addresses":["fd00:1122:3344:107::d"],"dataset":null,"services":[{"id":"395c9d6e-3bd0-445e-9269-46c3260edb83","details":{"type":"internal_ntp","address":"[fd00:1122:3344:107::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/0cbbcf22-770d-4e75-9148-e6109b129093/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled18.json b/sled-agent/tests/old-service-ledgers/rack3-sled18.json new file mode 100644 index 0000000000..f47e912424 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled18.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"c7096dd4-e429-4a6f-9725-041a77ef2513","zone_type":"crucible","addresses":["fd00:1122:3344:11a::6"],"dataset":{"id":"c7096dd4-e429-4a6f-9725-041a77ef2513","name":{"pool_name":"oxp_dcf62af6-c0f9-4eb5-9b23-9424ef8f3d32","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::6]:32345"},"services":[{"id":"c7096dd4-e429-4a6f-9725-041a77ef2513","details":{"type":"crucible","address":"[fd00:1122:3344:11a::6]:32345"}}]},"root":"/pool/ext/b869e463-c8b9-4c12-a6b9-13175b3896dd/crypt/zone"},{"zone":{"id":"09dd367f-b32f-43f3-aa53-11ccec1cd0c9","zone_type":"crucible","addresses":["fd00:1122:3344:11a::9"],"dataset":{"id":"09dd367f-b32f-43f3-aa53-11ccec1cd0c9","name":{"pool_name":"oxp_d7d00317-42c7-4d1e-a04c-85491fb230cd","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::9]:32345"},"services":[{"id":"09dd367f-b32f-43f3-aa53-11ccec1cd0c9","details":{"type":"crucible","address":"[fd00:1122:3344:11a::9]:32345"}}]},"root":"/pool/ext/d7d00317-42c7-4d1e-a04c-85491fb230cd/crypt/zone"},{"zone":{"id":"fb2f85f1-05b3-432f-9bb5-63fb27a762b1","zone_type":"crucible","addresses":["fd00:1122:3344:11a::5"],"dataset":{"id":"fb2f85f1-05b3-432f-9bb5-63fb27a762b1","name":{"pool_name":"oxp_db4a9949-68da-4c1c-9a1c-49083eba14fe","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::5]:32345"},"services":[{"id":"fb2f85f1-05b3-432f-9bb5-63fb27a762b1","details":{"type":"crucible","address":"[fd00:1122:3344:11a::5]:32345"}}]},"root":"/pool/ext/db4a9949-68da-4c1c-9a1c-49083eba14fe/crypt/zone"},{"zone":{"id":"5b89425e-69e4-4305-8f33-dc5768a1849e","zone_type":"crucible","addresses":["fd00:1122:3344:11a::a"],"dataset":{"id":"5b89425e-69e4-4305-8f33-dc5768a1849e","name":{"pool_name":"oxp_64a1bad7-d1b1-4e39-a3f3-9b8d73c4709e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::a]:32345"},"services":[{"id":"5b89425e-69e4-4305-8f33-dc5768a1849e","details":{"type":"crucible","address":"[fd00:1122:3344:11a::a]:32345"}}]},"root":"/pool/ext/64a1bad7-d1b1-4e39-a3f3-9b8d73c4709e/crypt/zone"},{"zone":{"id":"a5156db4-273a-4f8b-b8d8-df77062a6c63","zone_type":"crucible","addresses":["fd00:1122:3344:11a::4"],"dataset":{"id":"a5156db4-273a-4f8b-b8d8-df77062a6c63","name":{"pool_name":"oxp_b869e463-c8b9-4c12-a6b9-13175b3896dd","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::4]:32345"},"services":[{"id":"a5156db4-273a-4f8b-b8d8-df77062a6c63","details":{"type":"crucible","address":"[fd00:1122:3344:11a::4]:32345"}}]},"root":"/pool/ext/dcf62af6-c0f9-4eb5-9b23-9424ef8f3d32/crypt/zone"},{"zone":{"id":"1f2d2f86-b69b-4130-bb9b-e62ba0cb6802","zone_type":"crucible","addresses":["fd00:1122:3344:11a::b"],"dataset":{"id":"1f2d2f86-b69b-4130-bb9b-e62ba0cb6802","name":{"pool_name":"oxp_153ffee4-5d7a-4786-ad33-d5567b434fe0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::b]:32345"},"services":[{"id":"1f2d2f86-b69b-4130-bb9b-e62ba0cb6802","details":{"type":"crucible","address":"[fd00:1122:3344:11a::b]:32345"}}]},"root":"/pool/ext/174a067d-1c5a-49f7-a29f-1e62ab1c3796/crypt/zone"},{"zone":{"id":"1e249cc9-52e7-4d66-b713-8ace1392e991","zone_type":"crucible","addresses":["fd00:1122:3344:11a::7"],"dataset":{"id":"1e249cc9-52e7-4d66-b713-8ace1392e991","name":{"pool_name":"oxp_04b6215e-9651-4a3c-ba1b-b8a1e67b3d89","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::7]:32345"},"services":[{"id":"1e249cc9-52e7-4d66-b713-8ace1392e991","details":{"type":"crucible","address":"[fd00:1122:3344:11a::7]:32345"}}]},"root":"/pool/ext/db4a9949-68da-4c1c-9a1c-49083eba14fe/crypt/zone"},{"zone":{"id":"eb779538-2b1b-4d1d-8c7e-b15f04db6e53","zone_type":"crucible","addresses":["fd00:1122:3344:11a::3"],"dataset":{"id":"eb779538-2b1b-4d1d-8c7e-b15f04db6e53","name":{"pool_name":"oxp_aacb8524-3562-4f97-a616-9023230d6efa","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::3]:32345"},"services":[{"id":"eb779538-2b1b-4d1d-8c7e-b15f04db6e53","details":{"type":"crucible","address":"[fd00:1122:3344:11a::3]:32345"}}]},"root":"/pool/ext/174a067d-1c5a-49f7-a29f-1e62ab1c3796/crypt/zone"},{"zone":{"id":"b575d52d-be7d-46af-814b-91e6d18f3464","zone_type":"crucible","addresses":["fd00:1122:3344:11a::8"],"dataset":{"id":"b575d52d-be7d-46af-814b-91e6d18f3464","name":{"pool_name":"oxp_174a067d-1c5a-49f7-a29f-1e62ab1c3796","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::8]:32345"},"services":[{"id":"b575d52d-be7d-46af-814b-91e6d18f3464","details":{"type":"crucible","address":"[fd00:1122:3344:11a::8]:32345"}}]},"root":"/pool/ext/64a1bad7-d1b1-4e39-a3f3-9b8d73c4709e/crypt/zone"},{"zone":{"id":"274200bc-eac7-47d7-8a57-4b7be794caba","zone_type":"crucible","addresses":["fd00:1122:3344:11a::c"],"dataset":{"id":"274200bc-eac7-47d7-8a57-4b7be794caba","name":{"pool_name":"oxp_2e7644e4-7d46-42bf-8e7a-9c3f39085b3f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11a::c]:32345"},"services":[{"id":"274200bc-eac7-47d7-8a57-4b7be794caba","details":{"type":"crucible","address":"[fd00:1122:3344:11a::c]:32345"}}]},"root":"/pool/ext/2e7644e4-7d46-42bf-8e7a-9c3f39085b3f/crypt/zone"},{"zone":{"id":"bc20ba3a-df62-4a62-97c2-75b5653f84b4","zone_type":"ntp","addresses":["fd00:1122:3344:11a::d"],"dataset":null,"services":[{"id":"bc20ba3a-df62-4a62-97c2-75b5653f84b4","details":{"type":"internal_ntp","address":"[fd00:1122:3344:11a::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/04b6215e-9651-4a3c-ba1b-b8a1e67b3d89/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled19.json b/sled-agent/tests/old-service-ledgers/rack3-sled19.json new file mode 100644 index 0000000000..c450320a73 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled19.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"9c73abb9-edb8-4aa2-835b-c25ebe4466d9","zone_type":"crucible","addresses":["fd00:1122:3344:109::7"],"dataset":{"id":"9c73abb9-edb8-4aa2-835b-c25ebe4466d9","name":{"pool_name":"oxp_b7a3032f-7b8c-4a6a-9fa2-e5773bfdbc94","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::7]:32345"},"services":[{"id":"9c73abb9-edb8-4aa2-835b-c25ebe4466d9","details":{"type":"crucible","address":"[fd00:1122:3344:109::7]:32345"}}]},"root":"/pool/ext/46d21f3d-23be-4361-b5c5-9d0f6ece5b8c/crypt/zone"},{"zone":{"id":"ca576bda-cbdd-4bb9-9d75-ce06d569e926","zone_type":"crucible","addresses":["fd00:1122:3344:109::a"],"dataset":{"id":"ca576bda-cbdd-4bb9-9d75-ce06d569e926","name":{"pool_name":"oxp_863c4bc4-9c7e-453c-99d8-a3d509f49f3e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::a]:32345"},"services":[{"id":"ca576bda-cbdd-4bb9-9d75-ce06d569e926","details":{"type":"crucible","address":"[fd00:1122:3344:109::a]:32345"}}]},"root":"/pool/ext/7e67cb32-0c00-4090-9647-eb7bae75deeb/crypt/zone"},{"zone":{"id":"f010978d-346e-49cd-b265-7607a25685f9","zone_type":"crucible","addresses":["fd00:1122:3344:109::c"],"dataset":{"id":"f010978d-346e-49cd-b265-7607a25685f9","name":{"pool_name":"oxp_9bc1dab8-2d2a-4f92-bdfb-94ebca7881f1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::c]:32345"},"services":[{"id":"f010978d-346e-49cd-b265-7607a25685f9","details":{"type":"crucible","address":"[fd00:1122:3344:109::c]:32345"}}]},"root":"/pool/ext/9bc1dab8-2d2a-4f92-bdfb-94ebca7881f1/crypt/zone"},{"zone":{"id":"daff4162-cc81-4586-a457-91d767b8f1d9","zone_type":"crucible","addresses":["fd00:1122:3344:109::6"],"dataset":{"id":"daff4162-cc81-4586-a457-91d767b8f1d9","name":{"pool_name":"oxp_b9b5b50c-e823-41ae-9585-01b818883521","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::6]:32345"},"services":[{"id":"daff4162-cc81-4586-a457-91d767b8f1d9","details":{"type":"crucible","address":"[fd00:1122:3344:109::6]:32345"}}]},"root":"/pool/ext/de682b18-afaf-4d53-b62e-934f6bd4a1f8/crypt/zone"},{"zone":{"id":"9f300d3d-e698-4cc8-be4c-1f81ac8c927f","zone_type":"crucible","addresses":["fd00:1122:3344:109::d"],"dataset":{"id":"9f300d3d-e698-4cc8-be4c-1f81ac8c927f","name":{"pool_name":"oxp_f1d82c22-ad7d-4cda-9ab0-8f5f496d90ce","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::d]:32345"},"services":[{"id":"9f300d3d-e698-4cc8-be4c-1f81ac8c927f","details":{"type":"crucible","address":"[fd00:1122:3344:109::d]:32345"}}]},"root":"/pool/ext/de682b18-afaf-4d53-b62e-934f6bd4a1f8/crypt/zone"},{"zone":{"id":"8db7c7be-da40-4a1c-9681-4d02606a7eb7","zone_type":"crucible","addresses":["fd00:1122:3344:109::9"],"dataset":{"id":"8db7c7be-da40-4a1c-9681-4d02606a7eb7","name":{"pool_name":"oxp_46d21f3d-23be-4361-b5c5-9d0f6ece5b8c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::9]:32345"},"services":[{"id":"8db7c7be-da40-4a1c-9681-4d02606a7eb7","details":{"type":"crucible","address":"[fd00:1122:3344:109::9]:32345"}}]},"root":"/pool/ext/b7a3032f-7b8c-4a6a-9fa2-e5773bfdbc94/crypt/zone"},{"zone":{"id":"b990911b-805a-4f9d-bd83-e977f5b19a35","zone_type":"crucible","addresses":["fd00:1122:3344:109::4"],"dataset":{"id":"b990911b-805a-4f9d-bd83-e977f5b19a35","name":{"pool_name":"oxp_7e67cb32-0c00-4090-9647-eb7bae75deeb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::4]:32345"},"services":[{"id":"b990911b-805a-4f9d-bd83-e977f5b19a35","details":{"type":"crucible","address":"[fd00:1122:3344:109::4]:32345"}}]},"root":"/pool/ext/de682b18-afaf-4d53-b62e-934f6bd4a1f8/crypt/zone"},{"zone":{"id":"c99392f5-8f30-41ac-9eeb-12d7f4b707f1","zone_type":"crucible","addresses":["fd00:1122:3344:109::b"],"dataset":{"id":"c99392f5-8f30-41ac-9eeb-12d7f4b707f1","name":{"pool_name":"oxp_de682b18-afaf-4d53-b62e-934f6bd4a1f8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::b]:32345"},"services":[{"id":"c99392f5-8f30-41ac-9eeb-12d7f4b707f1","details":{"type":"crucible","address":"[fd00:1122:3344:109::b]:32345"}}]},"root":"/pool/ext/46d21f3d-23be-4361-b5c5-9d0f6ece5b8c/crypt/zone"},{"zone":{"id":"7f6cb339-9eb1-4866-8a4f-383bad25b36f","zone_type":"crucible","addresses":["fd00:1122:3344:109::5"],"dataset":{"id":"7f6cb339-9eb1-4866-8a4f-383bad25b36f","name":{"pool_name":"oxp_458cbfa3-3752-415d-8a3b-fb64e88468e1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::5]:32345"},"services":[{"id":"7f6cb339-9eb1-4866-8a4f-383bad25b36f","details":{"type":"crucible","address":"[fd00:1122:3344:109::5]:32345"}}]},"root":"/pool/ext/b9b5b50c-e823-41ae-9585-01b818883521/crypt/zone"},{"zone":{"id":"11946372-f253-4648-b00c-c7874a7b2888","zone_type":"crucible","addresses":["fd00:1122:3344:109::8"],"dataset":{"id":"11946372-f253-4648-b00c-c7874a7b2888","name":{"pool_name":"oxp_d73332f5-b2a5-46c0-94cf-c5c5712abfe8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:109::8]:32345"},"services":[{"id":"11946372-f253-4648-b00c-c7874a7b2888","details":{"type":"crucible","address":"[fd00:1122:3344:109::8]:32345"}}]},"root":"/pool/ext/b9b5b50c-e823-41ae-9585-01b818883521/crypt/zone"},{"zone":{"id":"58ece9e1-387f-4d2f-a42f-69cd34f9f380","zone_type":"cockroach_db","addresses":["fd00:1122:3344:109::3"],"dataset":{"id":"58ece9e1-387f-4d2f-a42f-69cd34f9f380","name":{"pool_name":"oxp_7e67cb32-0c00-4090-9647-eb7bae75deeb","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:109::3]:32221"},"services":[{"id":"58ece9e1-387f-4d2f-a42f-69cd34f9f380","details":{"type":"cockroach_db","address":"[fd00:1122:3344:109::3]:32221"}}]},"root":"/pool/ext/b9b5b50c-e823-41ae-9585-01b818883521/crypt/zone"},{"zone":{"id":"f016a25a-deb5-4f20-bdb0-2425c00d41a6","zone_type":"ntp","addresses":["fd00:1122:3344:109::e"],"dataset":null,"services":[{"id":"f016a25a-deb5-4f20-bdb0-2425c00d41a6","details":{"type":"internal_ntp","address":"[fd00:1122:3344:109::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/b9b5b50c-e823-41ae-9585-01b818883521/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled2.json b/sled-agent/tests/old-service-ledgers/rack3-sled2.json new file mode 100644 index 0000000000..6c420c989d --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled2.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"dd799dd4-03f9-451d-85e2-844155753a03","zone_type":"crucible","addresses":["fd00:1122:3344:10a::7"],"dataset":{"id":"dd799dd4-03f9-451d-85e2-844155753a03","name":{"pool_name":"oxp_7dcf3acc-bde9-4306-bb46-4c6a6cbbb7ba","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::7]:32345"},"services":[{"id":"dd799dd4-03f9-451d-85e2-844155753a03","details":{"type":"crucible","address":"[fd00:1122:3344:10a::7]:32345"}}]},"root":"/pool/ext/7dcf3acc-bde9-4306-bb46-4c6a6cbbb7ba/crypt/zone"},{"zone":{"id":"dbf9346d-b46d-4402-bb44-92ce20fb5290","zone_type":"crucible","addresses":["fd00:1122:3344:10a::9"],"dataset":{"id":"dbf9346d-b46d-4402-bb44-92ce20fb5290","name":{"pool_name":"oxp_9275d50f-da2c-4f84-9775-598a364309ad","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::9]:32345"},"services":[{"id":"dbf9346d-b46d-4402-bb44-92ce20fb5290","details":{"type":"crucible","address":"[fd00:1122:3344:10a::9]:32345"}}]},"root":"/pool/ext/d83e36ef-dd7a-4cc2-be19-379b1114c031/crypt/zone"},{"zone":{"id":"9a55ebdd-eeef-4954-b0a1-e32b04837f14","zone_type":"crucible","addresses":["fd00:1122:3344:10a::4"],"dataset":{"id":"9a55ebdd-eeef-4954-b0a1-e32b04837f14","name":{"pool_name":"oxp_7f30f77e-5998-4676-a226-b433b5940e77","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::4]:32345"},"services":[{"id":"9a55ebdd-eeef-4954-b0a1-e32b04837f14","details":{"type":"crucible","address":"[fd00:1122:3344:10a::4]:32345"}}]},"root":"/pool/ext/9275d50f-da2c-4f84-9775-598a364309ad/crypt/zone"},{"zone":{"id":"bc2935f8-e4fa-4015-968e-f90985533a6a","zone_type":"crucible","addresses":["fd00:1122:3344:10a::6"],"dataset":{"id":"bc2935f8-e4fa-4015-968e-f90985533a6a","name":{"pool_name":"oxp_022c9d58-e91f-480d-bda6-0cf32ce3b1f5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::6]:32345"},"services":[{"id":"bc2935f8-e4fa-4015-968e-f90985533a6a","details":{"type":"crucible","address":"[fd00:1122:3344:10a::6]:32345"}}]},"root":"/pool/ext/c395dcc3-6ece-4b3f-b143-e111a54ef7da/crypt/zone"},{"zone":{"id":"63f8c861-fa1d-4121-92d9-7efa5ef7f5a0","zone_type":"crucible","addresses":["fd00:1122:3344:10a::a"],"dataset":{"id":"63f8c861-fa1d-4121-92d9-7efa5ef7f5a0","name":{"pool_name":"oxp_3c805784-f403-4d01-9eb0-4f77d0821980","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::a]:32345"},"services":[{"id":"63f8c861-fa1d-4121-92d9-7efa5ef7f5a0","details":{"type":"crucible","address":"[fd00:1122:3344:10a::a]:32345"}}]},"root":"/pool/ext/9275d50f-da2c-4f84-9775-598a364309ad/crypt/zone"},{"zone":{"id":"4996dcf9-78de-4f69-94fa-c09cc86a8d3c","zone_type":"crucible","addresses":["fd00:1122:3344:10a::b"],"dataset":{"id":"4996dcf9-78de-4f69-94fa-c09cc86a8d3c","name":{"pool_name":"oxp_f9fe9ce6-be0d-4974-bc30-78a8f1330496","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::b]:32345"},"services":[{"id":"4996dcf9-78de-4f69-94fa-c09cc86a8d3c","details":{"type":"crucible","address":"[fd00:1122:3344:10a::b]:32345"}}]},"root":"/pool/ext/9275d50f-da2c-4f84-9775-598a364309ad/crypt/zone"},{"zone":{"id":"36b9a4bf-7b30-4fe7-903d-3b722c79fa86","zone_type":"crucible","addresses":["fd00:1122:3344:10a::c"],"dataset":{"id":"36b9a4bf-7b30-4fe7-903d-3b722c79fa86","name":{"pool_name":"oxp_cb1052e0-4c70-4d37-b979-dd55e6a25f08","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::c]:32345"},"services":[{"id":"36b9a4bf-7b30-4fe7-903d-3b722c79fa86","details":{"type":"crucible","address":"[fd00:1122:3344:10a::c]:32345"}}]},"root":"/pool/ext/3c805784-f403-4d01-9eb0-4f77d0821980/crypt/zone"},{"zone":{"id":"a109a902-6a27-41b6-a881-c353e28e5389","zone_type":"crucible","addresses":["fd00:1122:3344:10a::8"],"dataset":{"id":"a109a902-6a27-41b6-a881-c353e28e5389","name":{"pool_name":"oxp_d83e36ef-dd7a-4cc2-be19-379b1114c031","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::8]:32345"},"services":[{"id":"a109a902-6a27-41b6-a881-c353e28e5389","details":{"type":"crucible","address":"[fd00:1122:3344:10a::8]:32345"}}]},"root":"/pool/ext/d83e36ef-dd7a-4cc2-be19-379b1114c031/crypt/zone"},{"zone":{"id":"d2a9a0bc-ea12-44e3-ac4a-904c76120d11","zone_type":"crucible","addresses":["fd00:1122:3344:10a::3"],"dataset":{"id":"d2a9a0bc-ea12-44e3-ac4a-904c76120d11","name":{"pool_name":"oxp_c395dcc3-6ece-4b3f-b143-e111a54ef7da","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::3]:32345"},"services":[{"id":"d2a9a0bc-ea12-44e3-ac4a-904c76120d11","details":{"type":"crucible","address":"[fd00:1122:3344:10a::3]:32345"}}]},"root":"/pool/ext/9898a289-2f0d-43a6-b053-850f6e784e9a/crypt/zone"},{"zone":{"id":"b3c3e53b-d9ec-4dd8-bd2c-bd811319aa44","zone_type":"crucible","addresses":["fd00:1122:3344:10a::5"],"dataset":{"id":"b3c3e53b-d9ec-4dd8-bd2c-bd811319aa44","name":{"pool_name":"oxp_9898a289-2f0d-43a6-b053-850f6e784e9a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10a::5]:32345"},"services":[{"id":"b3c3e53b-d9ec-4dd8-bd2c-bd811319aa44","details":{"type":"crucible","address":"[fd00:1122:3344:10a::5]:32345"}}]},"root":"/pool/ext/9275d50f-da2c-4f84-9775-598a364309ad/crypt/zone"},{"zone":{"id":"7b445d3b-fd25-4538-ac3f-f439c66d1223","zone_type":"ntp","addresses":["fd00:1122:3344:10a::d"],"dataset":null,"services":[{"id":"7b445d3b-fd25-4538-ac3f-f439c66d1223","details":{"type":"internal_ntp","address":"[fd00:1122:3344:10a::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/f9fe9ce6-be0d-4974-bc30-78a8f1330496/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled20.json b/sled-agent/tests/old-service-ledgers/rack3-sled20.json new file mode 100644 index 0000000000..20c9d60624 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled20.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"4b49e669-264d-4bfb-8ab1-555b520b679c","zone_type":"crucible","addresses":["fd00:1122:3344:108::c"],"dataset":{"id":"4b49e669-264d-4bfb-8ab1-555b520b679c","name":{"pool_name":"oxp_799a1c86-9e1a-4626-91e2-a19f7ff5356e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::c]:32345"},"services":[{"id":"4b49e669-264d-4bfb-8ab1-555b520b679c","details":{"type":"crucible","address":"[fd00:1122:3344:108::c]:32345"}}]},"root":"/pool/ext/d2478613-b7c9-4bd3-856f-1fe8e9c903c2/crypt/zone"},{"zone":{"id":"d802baae-9c3f-437a-85fe-cd72653b6db1","zone_type":"crucible","addresses":["fd00:1122:3344:108::5"],"dataset":{"id":"d802baae-9c3f-437a-85fe-cd72653b6db1","name":{"pool_name":"oxp_d2478613-b7c9-4bd3-856f-1fe8e9c903c2","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::5]:32345"},"services":[{"id":"d802baae-9c3f-437a-85fe-cd72653b6db1","details":{"type":"crucible","address":"[fd00:1122:3344:108::5]:32345"}}]},"root":"/pool/ext/116f216c-e151-410f-82bf-8913904cf7b4/crypt/zone"},{"zone":{"id":"e5f69e60-3421-49a4-8c1d-2db8cbb6a5e9","zone_type":"crucible","addresses":["fd00:1122:3344:108::b"],"dataset":{"id":"e5f69e60-3421-49a4-8c1d-2db8cbb6a5e9","name":{"pool_name":"oxp_116f216c-e151-410f-82bf-8913904cf7b4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::b]:32345"},"services":[{"id":"e5f69e60-3421-49a4-8c1d-2db8cbb6a5e9","details":{"type":"crucible","address":"[fd00:1122:3344:108::b]:32345"}}]},"root":"/pool/ext/eea15142-4635-4e40-b0b4-b0c4f13eca3c/crypt/zone"},{"zone":{"id":"3e598962-ef8c-4cb6-bdfe-ec8563939d6a","zone_type":"crucible","addresses":["fd00:1122:3344:108::4"],"dataset":{"id":"3e598962-ef8c-4cb6-bdfe-ec8563939d6a","name":{"pool_name":"oxp_ababce44-01d1-4c50-b389-f60464c5dde9","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::4]:32345"},"services":[{"id":"3e598962-ef8c-4cb6-bdfe-ec8563939d6a","details":{"type":"crucible","address":"[fd00:1122:3344:108::4]:32345"}}]},"root":"/pool/ext/ababce44-01d1-4c50-b389-f60464c5dde9/crypt/zone"},{"zone":{"id":"25355c9f-cc2b-4b24-8eaa-65190f8936a8","zone_type":"crucible","addresses":["fd00:1122:3344:108::d"],"dataset":{"id":"25355c9f-cc2b-4b24-8eaa-65190f8936a8","name":{"pool_name":"oxp_fed46d41-136d-4462-8782-359014efba59","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::d]:32345"},"services":[{"id":"25355c9f-cc2b-4b24-8eaa-65190f8936a8","details":{"type":"crucible","address":"[fd00:1122:3344:108::d]:32345"}}]},"root":"/pool/ext/eea15142-4635-4e40-b0b4-b0c4f13eca3c/crypt/zone"},{"zone":{"id":"efb2f16c-ebad-4192-b575-dcb4d9b1d5cd","zone_type":"crucible","addresses":["fd00:1122:3344:108::a"],"dataset":{"id":"efb2f16c-ebad-4192-b575-dcb4d9b1d5cd","name":{"pool_name":"oxp_bf509067-0165-456d-98ae-72c86378e626","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::a]:32345"},"services":[{"id":"efb2f16c-ebad-4192-b575-dcb4d9b1d5cd","details":{"type":"crucible","address":"[fd00:1122:3344:108::a]:32345"}}]},"root":"/pool/ext/95220093-e3b8-4f7f-9f5a-cb32cb75180a/crypt/zone"},{"zone":{"id":"89191f0d-4e0b-47fa-9a9e-fbe2a6db1385","zone_type":"crucible","addresses":["fd00:1122:3344:108::8"],"dataset":{"id":"89191f0d-4e0b-47fa-9a9e-fbe2a6db1385","name":{"pool_name":"oxp_eea15142-4635-4e40-b0b4-b0c4f13eca3c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::8]:32345"},"services":[{"id":"89191f0d-4e0b-47fa-9a9e-fbe2a6db1385","details":{"type":"crucible","address":"[fd00:1122:3344:108::8]:32345"}}]},"root":"/pool/ext/eea15142-4635-4e40-b0b4-b0c4f13eca3c/crypt/zone"},{"zone":{"id":"e4589324-c528-49c7-9141-35e0a7af6947","zone_type":"crucible","addresses":["fd00:1122:3344:108::6"],"dataset":{"id":"e4589324-c528-49c7-9141-35e0a7af6947","name":{"pool_name":"oxp_95220093-e3b8-4f7f-9f5a-cb32cb75180a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::6]:32345"},"services":[{"id":"e4589324-c528-49c7-9141-35e0a7af6947","details":{"type":"crucible","address":"[fd00:1122:3344:108::6]:32345"}}]},"root":"/pool/ext/ababce44-01d1-4c50-b389-f60464c5dde9/crypt/zone"},{"zone":{"id":"95ebe94d-0e68-421d-9260-c30bd7fe4bd6","zone_type":"nexus","addresses":["fd00:1122:3344:108::3"],"dataset":null,"services":[{"id":"95ebe94d-0e68-421d-9260-c30bd7fe4bd6","details":{"type":"nexus","internal_address":"[fd00:1122:3344:108::3]:12221","external_ip":"45.154.216.35","nic":{"id":"301aa595-f072-4da3-a533-99647b44a66a","kind":{"type":"service","id":"95ebe94d-0e68-421d-9260-c30bd7fe4bd6"},"name":"nexus-95ebe94d-0e68-421d-9260-c30bd7fe4bd6","ip":"172.30.2.5","mac":"A8:40:25:FF:F1:30","subnet":"172.30.2.0/24","vni":100,"primary":true,"slot":0},"external_tls":true,"external_dns_servers":["1.1.1.1","8.8.8.8"]}}]},"root":"/pool/ext/eea15142-4635-4e40-b0b4-b0c4f13eca3c/crypt/zone"},{"zone":{"id":"4b7a7052-f8e8-4196-8d6b-315943986ce6","zone_type":"crucible","addresses":["fd00:1122:3344:108::7"],"dataset":{"id":"4b7a7052-f8e8-4196-8d6b-315943986ce6","name":{"pool_name":"oxp_a549421c-2f12-45cc-b691-202f0a9bfa8b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::7]:32345"},"services":[{"id":"4b7a7052-f8e8-4196-8d6b-315943986ce6","details":{"type":"crucible","address":"[fd00:1122:3344:108::7]:32345"}}]},"root":"/pool/ext/bf509067-0165-456d-98ae-72c86378e626/crypt/zone"},{"zone":{"id":"71b8ff53-c781-47bb-8ddc-2c7129680542","zone_type":"crucible","addresses":["fd00:1122:3344:108::9"],"dataset":{"id":"71b8ff53-c781-47bb-8ddc-2c7129680542","name":{"pool_name":"oxp_9d19f891-a3d9-4c6e-b1e1-6b0b085a9440","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:108::9]:32345"},"services":[{"id":"71b8ff53-c781-47bb-8ddc-2c7129680542","details":{"type":"crucible","address":"[fd00:1122:3344:108::9]:32345"}}]},"root":"/pool/ext/fed46d41-136d-4462-8782-359014efba59/crypt/zone"},{"zone":{"id":"eaf7bf77-f4c2-4016-9909-4b88a27e9d9a","zone_type":"ntp","addresses":["fd00:1122:3344:108::e"],"dataset":null,"services":[{"id":"eaf7bf77-f4c2-4016-9909-4b88a27e9d9a","details":{"type":"internal_ntp","address":"[fd00:1122:3344:108::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/ababce44-01d1-4c50-b389-f60464c5dde9/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled21.json b/sled-agent/tests/old-service-ledgers/rack3-sled21.json new file mode 100644 index 0000000000..4f69e01c7f --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled21.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"a91e4af3-5d18-4b08-8cb6-0583db8f8842","zone_type":"crucible","addresses":["fd00:1122:3344:117::a"],"dataset":{"id":"a91e4af3-5d18-4b08-8cb6-0583db8f8842","name":{"pool_name":"oxp_4b2896b8-5f0e-42fb-a474-658b28421e65","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::a]:32345"},"services":[{"id":"a91e4af3-5d18-4b08-8cb6-0583db8f8842","details":{"type":"crucible","address":"[fd00:1122:3344:117::a]:32345"}}]},"root":"/pool/ext/23393ed9-acee-4686-861f-7fc825af1249/crypt/zone"},{"zone":{"id":"1ce74512-ce3a-4125-95f1-12c86e0275d5","zone_type":"crucible","addresses":["fd00:1122:3344:117::8"],"dataset":{"id":"1ce74512-ce3a-4125-95f1-12c86e0275d5","name":{"pool_name":"oxp_46ece76f-ef00-4dd0-9f73-326c63959470","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::8]:32345"},"services":[{"id":"1ce74512-ce3a-4125-95f1-12c86e0275d5","details":{"type":"crucible","address":"[fd00:1122:3344:117::8]:32345"}}]},"root":"/pool/ext/1bd5955e-14a9-463f-adeb-f12bcb45a6c1/crypt/zone"},{"zone":{"id":"fef5d35f-9622-4dee-8635-d26e9f7f6869","zone_type":"crucible","addresses":["fd00:1122:3344:117::4"],"dataset":{"id":"fef5d35f-9622-4dee-8635-d26e9f7f6869","name":{"pool_name":"oxp_e4d7c2e8-016b-4617-afb5-38a2d9c1b508","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::4]:32345"},"services":[{"id":"fef5d35f-9622-4dee-8635-d26e9f7f6869","details":{"type":"crucible","address":"[fd00:1122:3344:117::4]:32345"}}]},"root":"/pool/ext/e372bba3-ef60-466f-b819-a3d5b9acbe77/crypt/zone"},{"zone":{"id":"4f024a31-cd38-4219-8381-9f1af70d1d54","zone_type":"crucible","addresses":["fd00:1122:3344:117::c"],"dataset":{"id":"4f024a31-cd38-4219-8381-9f1af70d1d54","name":{"pool_name":"oxp_7cb2a3c2-9d33-4c6a-af57-669f251cf4cf","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::c]:32345"},"services":[{"id":"4f024a31-cd38-4219-8381-9f1af70d1d54","details":{"type":"crucible","address":"[fd00:1122:3344:117::c]:32345"}}]},"root":"/pool/ext/cfbd185d-e185-4aaa-a598-9216124ceec4/crypt/zone"},{"zone":{"id":"d00e1d0b-e12f-420a-a4df-21e4cac176f6","zone_type":"crucible","addresses":["fd00:1122:3344:117::b"],"dataset":{"id":"d00e1d0b-e12f-420a-a4df-21e4cac176f6","name":{"pool_name":"oxp_e372bba3-ef60-466f-b819-a3d5b9acbe77","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::b]:32345"},"services":[{"id":"d00e1d0b-e12f-420a-a4df-21e4cac176f6","details":{"type":"crucible","address":"[fd00:1122:3344:117::b]:32345"}}]},"root":"/pool/ext/cfbd185d-e185-4aaa-a598-9216124ceec4/crypt/zone"},{"zone":{"id":"1598058a-6064-449e-b39c-1e3d345ed793","zone_type":"crucible","addresses":["fd00:1122:3344:117::5"],"dataset":{"id":"1598058a-6064-449e-b39c-1e3d345ed793","name":{"pool_name":"oxp_022a8d67-1e00-49f3-81ed-a0a1bc187cfa","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::5]:32345"},"services":[{"id":"1598058a-6064-449e-b39c-1e3d345ed793","details":{"type":"crucible","address":"[fd00:1122:3344:117::5]:32345"}}]},"root":"/pool/ext/022a8d67-1e00-49f3-81ed-a0a1bc187cfa/crypt/zone"},{"zone":{"id":"c723c4b8-3031-4b25-8c16-fe08bc0b5f00","zone_type":"crucible","addresses":["fd00:1122:3344:117::7"],"dataset":{"id":"c723c4b8-3031-4b25-8c16-fe08bc0b5f00","name":{"pool_name":"oxp_23393ed9-acee-4686-861f-7fc825af1249","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::7]:32345"},"services":[{"id":"c723c4b8-3031-4b25-8c16-fe08bc0b5f00","details":{"type":"crucible","address":"[fd00:1122:3344:117::7]:32345"}}]},"root":"/pool/ext/1bd5955e-14a9-463f-adeb-f12bcb45a6c1/crypt/zone"},{"zone":{"id":"7751b307-888f-46c8-8787-75d2f3fdaef3","zone_type":"crucible","addresses":["fd00:1122:3344:117::9"],"dataset":{"id":"7751b307-888f-46c8-8787-75d2f3fdaef3","name":{"pool_name":"oxp_e54e53d4-f68f-4b19-b8c1-9d5ab42e51c1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::9]:32345"},"services":[{"id":"7751b307-888f-46c8-8787-75d2f3fdaef3","details":{"type":"crucible","address":"[fd00:1122:3344:117::9]:32345"}}]},"root":"/pool/ext/e372bba3-ef60-466f-b819-a3d5b9acbe77/crypt/zone"},{"zone":{"id":"89413ff1-d5de-4931-8389-e84e7ea321af","zone_type":"crucible","addresses":["fd00:1122:3344:117::6"],"dataset":{"id":"89413ff1-d5de-4931-8389-e84e7ea321af","name":{"pool_name":"oxp_1bd5955e-14a9-463f-adeb-f12bcb45a6c1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::6]:32345"},"services":[{"id":"89413ff1-d5de-4931-8389-e84e7ea321af","details":{"type":"crucible","address":"[fd00:1122:3344:117::6]:32345"}}]},"root":"/pool/ext/1bd5955e-14a9-463f-adeb-f12bcb45a6c1/crypt/zone"},{"zone":{"id":"287b0b24-72aa-41b5-a597-8523d84225ef","zone_type":"crucible","addresses":["fd00:1122:3344:117::3"],"dataset":{"id":"287b0b24-72aa-41b5-a597-8523d84225ef","name":{"pool_name":"oxp_cfbd185d-e185-4aaa-a598-9216124ceec4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:117::3]:32345"},"services":[{"id":"287b0b24-72aa-41b5-a597-8523d84225ef","details":{"type":"crucible","address":"[fd00:1122:3344:117::3]:32345"}}]},"root":"/pool/ext/cfbd185d-e185-4aaa-a598-9216124ceec4/crypt/zone"},{"zone":{"id":"4728253e-c534-4a5b-b707-c64ac9a8eb8c","zone_type":"ntp","addresses":["fd00:1122:3344:117::d"],"dataset":null,"services":[{"id":"4728253e-c534-4a5b-b707-c64ac9a8eb8c","details":{"type":"internal_ntp","address":"[fd00:1122:3344:117::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/cfbd185d-e185-4aaa-a598-9216124ceec4/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled22.json b/sled-agent/tests/old-service-ledgers/rack3-sled22.json new file mode 100644 index 0000000000..dc98c0390c --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled22.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"49f20cd1-a8a3-4fa8-9209-59da60cd8f9b","zone_type":"crucible","addresses":["fd00:1122:3344:103::5"],"dataset":{"id":"49f20cd1-a8a3-4fa8-9209-59da60cd8f9b","name":{"pool_name":"oxp_13a9ef4a-f33a-4781-8f83-712c07a79b1f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::5]:32345"},"services":[{"id":"49f20cd1-a8a3-4fa8-9209-59da60cd8f9b","details":{"type":"crucible","address":"[fd00:1122:3344:103::5]:32345"}}]},"root":"/pool/ext/711eff4e-736c-478e-83aa-ae86f5efbf1d/crypt/zone"},{"zone":{"id":"896fd564-f94e-496b-9fcf-ddfbfcfac9f7","zone_type":"crucible","addresses":["fd00:1122:3344:103::c"],"dataset":{"id":"896fd564-f94e-496b-9fcf-ddfbfcfac9f7","name":{"pool_name":"oxp_0944c0a2-0fb7-4f51-bced-52cc257cd2f6","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::c]:32345"},"services":[{"id":"896fd564-f94e-496b-9fcf-ddfbfcfac9f7","details":{"type":"crucible","address":"[fd00:1122:3344:103::c]:32345"}}]},"root":"/pool/ext/bc54d8c5-955d-429d-84e0-a20a4e5e27a3/crypt/zone"},{"zone":{"id":"911fb8b3-05c2-4af7-8974-6c74a61d94ad","zone_type":"crucible","addresses":["fd00:1122:3344:103::9"],"dataset":{"id":"911fb8b3-05c2-4af7-8974-6c74a61d94ad","name":{"pool_name":"oxp_29f59fce-a867-4571-9d2e-b03fa5c13510","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::9]:32345"},"services":[{"id":"911fb8b3-05c2-4af7-8974-6c74a61d94ad","details":{"type":"crucible","address":"[fd00:1122:3344:103::9]:32345"}}]},"root":"/pool/ext/711eff4e-736c-478e-83aa-ae86f5efbf1d/crypt/zone"},{"zone":{"id":"682b34db-0b06-4770-a8fe-74437cf184d6","zone_type":"crucible","addresses":["fd00:1122:3344:103::6"],"dataset":{"id":"682b34db-0b06-4770-a8fe-74437cf184d6","name":{"pool_name":"oxp_094d11d2-8049-4138-bcf4-562f5f8e77c0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::6]:32345"},"services":[{"id":"682b34db-0b06-4770-a8fe-74437cf184d6","details":{"type":"crucible","address":"[fd00:1122:3344:103::6]:32345"}}]},"root":"/pool/ext/0944c0a2-0fb7-4f51-bced-52cc257cd2f6/crypt/zone"},{"zone":{"id":"d8d20365-ecd3-4fd5-9495-c0670e3bd5d9","zone_type":"crucible","addresses":["fd00:1122:3344:103::a"],"dataset":{"id":"d8d20365-ecd3-4fd5-9495-c0670e3bd5d9","name":{"pool_name":"oxp_fb97ff7b-0225-400c-a137-3b38a786c0a0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::a]:32345"},"services":[{"id":"d8d20365-ecd3-4fd5-9495-c0670e3bd5d9","details":{"type":"crucible","address":"[fd00:1122:3344:103::a]:32345"}}]},"root":"/pool/ext/094d11d2-8049-4138-bcf4-562f5f8e77c0/crypt/zone"},{"zone":{"id":"673620b6-44d9-4310-8e17-3024ac84e708","zone_type":"crucible","addresses":["fd00:1122:3344:103::7"],"dataset":{"id":"673620b6-44d9-4310-8e17-3024ac84e708","name":{"pool_name":"oxp_711eff4e-736c-478e-83aa-ae86f5efbf1d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::7]:32345"},"services":[{"id":"673620b6-44d9-4310-8e17-3024ac84e708","details":{"type":"crucible","address":"[fd00:1122:3344:103::7]:32345"}}]},"root":"/pool/ext/fb97ff7b-0225-400c-a137-3b38a786c0a0/crypt/zone"},{"zone":{"id":"bf6dfc04-4d4c-41b6-a011-40ffc3bc5080","zone_type":"crucible","addresses":["fd00:1122:3344:103::8"],"dataset":{"id":"bf6dfc04-4d4c-41b6-a011-40ffc3bc5080","name":{"pool_name":"oxp_f815f1b6-48ef-436d-8768-eb08227e2386","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::8]:32345"},"services":[{"id":"bf6dfc04-4d4c-41b6-a011-40ffc3bc5080","details":{"type":"crucible","address":"[fd00:1122:3344:103::8]:32345"}}]},"root":"/pool/ext/13a9ef4a-f33a-4781-8f83-712c07a79b1f/crypt/zone"},{"zone":{"id":"ac8a82a8-fb6f-4635-a9a9-d98617eab390","zone_type":"crucible","addresses":["fd00:1122:3344:103::3"],"dataset":{"id":"ac8a82a8-fb6f-4635-a9a9-d98617eab390","name":{"pool_name":"oxp_97d6c860-4e2f-496e-974b-2e293fee6af9","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::3]:32345"},"services":[{"id":"ac8a82a8-fb6f-4635-a9a9-d98617eab390","details":{"type":"crucible","address":"[fd00:1122:3344:103::3]:32345"}}]},"root":"/pool/ext/0944c0a2-0fb7-4f51-bced-52cc257cd2f6/crypt/zone"},{"zone":{"id":"4ed66558-4815-4b85-9b94-9edf3ee69ead","zone_type":"crucible","addresses":["fd00:1122:3344:103::4"],"dataset":{"id":"4ed66558-4815-4b85-9b94-9edf3ee69ead","name":{"pool_name":"oxp_bc54d8c5-955d-429d-84e0-a20a4e5e27a3","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::4]:32345"},"services":[{"id":"4ed66558-4815-4b85-9b94-9edf3ee69ead","details":{"type":"crucible","address":"[fd00:1122:3344:103::4]:32345"}}]},"root":"/pool/ext/13a9ef4a-f33a-4781-8f83-712c07a79b1f/crypt/zone"},{"zone":{"id":"8a71c6ee-b08d-4c3d-b13c-c9cebc4c328a","zone_type":"crucible","addresses":["fd00:1122:3344:103::b"],"dataset":{"id":"8a71c6ee-b08d-4c3d-b13c-c9cebc4c328a","name":{"pool_name":"oxp_2bdfa429-09bd-4fa1-aa20-eea99f0d2b85","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:103::b]:32345"},"services":[{"id":"8a71c6ee-b08d-4c3d-b13c-c9cebc4c328a","details":{"type":"crucible","address":"[fd00:1122:3344:103::b]:32345"}}]},"root":"/pool/ext/29f59fce-a867-4571-9d2e-b03fa5c13510/crypt/zone"},{"zone":{"id":"7e6b8962-7a1e-4d7b-b7ea-49e64a51d98d","zone_type":"ntp","addresses":["fd00:1122:3344:103::d"],"dataset":null,"services":[{"id":"7e6b8962-7a1e-4d7b-b7ea-49e64a51d98d","details":{"type":"internal_ntp","address":"[fd00:1122:3344:103::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/2bdfa429-09bd-4fa1-aa20-eea99f0d2b85/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled23.json b/sled-agent/tests/old-service-ledgers/rack3-sled23.json new file mode 100644 index 0000000000..ade2144287 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled23.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"6b7e931d-4b91-4dc6-9a7b-4c19ac669e5d","zone_type":"crucible","addresses":["fd00:1122:3344:105::4"],"dataset":{"id":"6b7e931d-4b91-4dc6-9a7b-4c19ac669e5d","name":{"pool_name":"oxp_24dab7f5-164a-47f3-a878-f32ab1e68cce","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::4]:32345"},"services":[{"id":"6b7e931d-4b91-4dc6-9a7b-4c19ac669e5d","details":{"type":"crucible","address":"[fd00:1122:3344:105::4]:32345"}}]},"root":"/pool/ext/ad493851-2d11-4c2d-8d75-989579d9616a/crypt/zone"},{"zone":{"id":"6c58e7aa-71e1-4868-9d4b-e12c7ef40303","zone_type":"crucible","addresses":["fd00:1122:3344:105::a"],"dataset":{"id":"6c58e7aa-71e1-4868-9d4b-e12c7ef40303","name":{"pool_name":"oxp_d664c9e8-bc81-4225-a618-a8ae2d057186","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::a]:32345"},"services":[{"id":"6c58e7aa-71e1-4868-9d4b-e12c7ef40303","details":{"type":"crucible","address":"[fd00:1122:3344:105::a]:32345"}}]},"root":"/pool/ext/ad493851-2d11-4c2d-8d75-989579d9616a/crypt/zone"},{"zone":{"id":"51c6dc8d-b1a4-454a-9b19-01e45eb0b599","zone_type":"crucible","addresses":["fd00:1122:3344:105::d"],"dataset":{"id":"51c6dc8d-b1a4-454a-9b19-01e45eb0b599","name":{"pool_name":"oxp_f5f85537-eb25-4d0e-8e94-b775c41abd73","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::d]:32345"},"services":[{"id":"51c6dc8d-b1a4-454a-9b19-01e45eb0b599","details":{"type":"crucible","address":"[fd00:1122:3344:105::d]:32345"}}]},"root":"/pool/ext/4f1eafe9-b28d-49d3-83e2-ceac8721d6b5/crypt/zone"},{"zone":{"id":"8cbffa61-0bd0-4ad2-bd7d-30fe0dd57469","zone_type":"crucible","addresses":["fd00:1122:3344:105::9"],"dataset":{"id":"8cbffa61-0bd0-4ad2-bd7d-30fe0dd57469","name":{"pool_name":"oxp_88abca38-3f61-4d4b-80a1-4ea3e4827f84","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::9]:32345"},"services":[{"id":"8cbffa61-0bd0-4ad2-bd7d-30fe0dd57469","details":{"type":"crucible","address":"[fd00:1122:3344:105::9]:32345"}}]},"root":"/pool/ext/88abca38-3f61-4d4b-80a1-4ea3e4827f84/crypt/zone"},{"zone":{"id":"2177f37f-2ac9-4e66-bf74-a10bd91f4d33","zone_type":"crucible","addresses":["fd00:1122:3344:105::6"],"dataset":{"id":"2177f37f-2ac9-4e66-bf74-a10bd91f4d33","name":{"pool_name":"oxp_59e20871-4670-40d6-8ff4-aa97899fc991","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::6]:32345"},"services":[{"id":"2177f37f-2ac9-4e66-bf74-a10bd91f4d33","details":{"type":"crucible","address":"[fd00:1122:3344:105::6]:32345"}}]},"root":"/pool/ext/4f1eafe9-b28d-49d3-83e2-ceac8721d6b5/crypt/zone"},{"zone":{"id":"e4e43855-4879-4910-a2ba-40f625c1cc2d","zone_type":"crucible","addresses":["fd00:1122:3344:105::b"],"dataset":{"id":"e4e43855-4879-4910-a2ba-40f625c1cc2d","name":{"pool_name":"oxp_967d2f05-b141-44f5-837d-9b2aa67ee128","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::b]:32345"},"services":[{"id":"e4e43855-4879-4910-a2ba-40f625c1cc2d","details":{"type":"crucible","address":"[fd00:1122:3344:105::b]:32345"}}]},"root":"/pool/ext/6b6f34cd-6d3d-4832-a4e6-3df112c97133/crypt/zone"},{"zone":{"id":"8d2517e1-f9ad-40f2-abb9-2f5122839910","zone_type":"crucible","addresses":["fd00:1122:3344:105::7"],"dataset":{"id":"8d2517e1-f9ad-40f2-abb9-2f5122839910","name":{"pool_name":"oxp_ad493851-2d11-4c2d-8d75-989579d9616a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::7]:32345"},"services":[{"id":"8d2517e1-f9ad-40f2-abb9-2f5122839910","details":{"type":"crucible","address":"[fd00:1122:3344:105::7]:32345"}}]},"root":"/pool/ext/88abca38-3f61-4d4b-80a1-4ea3e4827f84/crypt/zone"},{"zone":{"id":"44cb3698-a7b1-4388-9165-ac76082ec8bc","zone_type":"crucible","addresses":["fd00:1122:3344:105::5"],"dataset":{"id":"44cb3698-a7b1-4388-9165-ac76082ec8bc","name":{"pool_name":"oxp_4292a83c-8c1f-4b2e-9120-72e0c510bf3c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::5]:32345"},"services":[{"id":"44cb3698-a7b1-4388-9165-ac76082ec8bc","details":{"type":"crucible","address":"[fd00:1122:3344:105::5]:32345"}}]},"root":"/pool/ext/24dab7f5-164a-47f3-a878-f32ab1e68cce/crypt/zone"},{"zone":{"id":"931b5c86-9d72-4518-bfd6-97863152ac65","zone_type":"crucible","addresses":["fd00:1122:3344:105::c"],"dataset":{"id":"931b5c86-9d72-4518-bfd6-97863152ac65","name":{"pool_name":"oxp_6b6f34cd-6d3d-4832-a4e6-3df112c97133","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::c]:32345"},"services":[{"id":"931b5c86-9d72-4518-bfd6-97863152ac65","details":{"type":"crucible","address":"[fd00:1122:3344:105::c]:32345"}}]},"root":"/pool/ext/ad493851-2d11-4c2d-8d75-989579d9616a/crypt/zone"},{"zone":{"id":"ac568073-1889-463e-8cc4-cfed16ce2a34","zone_type":"crucible","addresses":["fd00:1122:3344:105::8"],"dataset":{"id":"ac568073-1889-463e-8cc4-cfed16ce2a34","name":{"pool_name":"oxp_4f1eafe9-b28d-49d3-83e2-ceac8721d6b5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:105::8]:32345"},"services":[{"id":"ac568073-1889-463e-8cc4-cfed16ce2a34","details":{"type":"crucible","address":"[fd00:1122:3344:105::8]:32345"}}]},"root":"/pool/ext/4292a83c-8c1f-4b2e-9120-72e0c510bf3c/crypt/zone"},{"zone":{"id":"e8f86fbb-864e-4d5a-961c-b50b54ae853e","zone_type":"cockroach_db","addresses":["fd00:1122:3344:105::3"],"dataset":{"id":"e8f86fbb-864e-4d5a-961c-b50b54ae853e","name":{"pool_name":"oxp_24dab7f5-164a-47f3-a878-f32ab1e68cce","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:105::3]:32221"},"services":[{"id":"e8f86fbb-864e-4d5a-961c-b50b54ae853e","details":{"type":"cockroach_db","address":"[fd00:1122:3344:105::3]:32221"}}]},"root":"/pool/ext/4f1eafe9-b28d-49d3-83e2-ceac8721d6b5/crypt/zone"},{"zone":{"id":"c79caea0-37b1-49d6-ae6e-8cf849d91374","zone_type":"ntp","addresses":["fd00:1122:3344:105::e"],"dataset":null,"services":[{"id":"c79caea0-37b1-49d6-ae6e-8cf849d91374","details":{"type":"internal_ntp","address":"[fd00:1122:3344:105::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/24dab7f5-164a-47f3-a878-f32ab1e68cce/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled24.json b/sled-agent/tests/old-service-ledgers/rack3-sled24.json new file mode 100644 index 0000000000..e7bd3050d6 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled24.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"d2b1e468-bc3c-4d08-b855-ae3327465375","zone_type":"crucible","addresses":["fd00:1122:3344:106::3"],"dataset":{"id":"d2b1e468-bc3c-4d08-b855-ae3327465375","name":{"pool_name":"oxp_9db196bf-828d-4e55-a2c1-dd9d579d3908","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::3]:32345"},"services":[{"id":"d2b1e468-bc3c-4d08-b855-ae3327465375","details":{"type":"crucible","address":"[fd00:1122:3344:106::3]:32345"}}]},"root":"/pool/ext/74df4c92-edbb-4431-a770-1d015110e66b/crypt/zone"},{"zone":{"id":"61f94a16-79fd-42e3-b225-a4dc67228437","zone_type":"crucible","addresses":["fd00:1122:3344:106::6"],"dataset":{"id":"61f94a16-79fd-42e3-b225-a4dc67228437","name":{"pool_name":"oxp_d77d5b08-5f70-496a-997b-b38804dc3b8a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::6]:32345"},"services":[{"id":"61f94a16-79fd-42e3-b225-a4dc67228437","details":{"type":"crucible","address":"[fd00:1122:3344:106::6]:32345"}}]},"root":"/pool/ext/daf9e3cd-5a40-4eba-a0f6-4f94dab37dae/crypt/zone"},{"zone":{"id":"7d32ef34-dec5-4fd8-899e-20bbc473a3ee","zone_type":"crucible","addresses":["fd00:1122:3344:106::7"],"dataset":{"id":"7d32ef34-dec5-4fd8-899e-20bbc473a3ee","name":{"pool_name":"oxp_50c1b653-6231-41fe-b3cf-b7ba709a0746","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::7]:32345"},"services":[{"id":"7d32ef34-dec5-4fd8-899e-20bbc473a3ee","details":{"type":"crucible","address":"[fd00:1122:3344:106::7]:32345"}}]},"root":"/pool/ext/9db196bf-828d-4e55-a2c1-dd9d579d3908/crypt/zone"},{"zone":{"id":"c34b7ae5-26b9-4651-a3c4-20bba2bd0d2c","zone_type":"crucible","addresses":["fd00:1122:3344:106::5"],"dataset":{"id":"c34b7ae5-26b9-4651-a3c4-20bba2bd0d2c","name":{"pool_name":"oxp_88aea92c-ab92-44c1-9471-eb8e30e075d3","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::5]:32345"},"services":[{"id":"c34b7ae5-26b9-4651-a3c4-20bba2bd0d2c","details":{"type":"crucible","address":"[fd00:1122:3344:106::5]:32345"}}]},"root":"/pool/ext/8da316d4-6b18-4980-a0a8-6e76e72cc40d/crypt/zone"},{"zone":{"id":"36472be8-9a70-4c14-bd02-439b725cec1a","zone_type":"crucible","addresses":["fd00:1122:3344:106::8"],"dataset":{"id":"36472be8-9a70-4c14-bd02-439b725cec1a","name":{"pool_name":"oxp_54544b3a-1513-4db2-911e-7c1eb4b12385","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::8]:32345"},"services":[{"id":"36472be8-9a70-4c14-bd02-439b725cec1a","details":{"type":"crucible","address":"[fd00:1122:3344:106::8]:32345"}}]},"root":"/pool/ext/54544b3a-1513-4db2-911e-7c1eb4b12385/crypt/zone"},{"zone":{"id":"2548f8ab-5255-4334-a1fb-5d7d95213129","zone_type":"crucible","addresses":["fd00:1122:3344:106::9"],"dataset":{"id":"2548f8ab-5255-4334-a1fb-5d7d95213129","name":{"pool_name":"oxp_08050450-967f-431c-9a12-0d051aff020e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::9]:32345"},"services":[{"id":"2548f8ab-5255-4334-a1fb-5d7d95213129","details":{"type":"crucible","address":"[fd00:1122:3344:106::9]:32345"}}]},"root":"/pool/ext/08050450-967f-431c-9a12-0d051aff020e/crypt/zone"},{"zone":{"id":"1455c069-853c-49cd-853a-3ea81b89acd4","zone_type":"crucible","addresses":["fd00:1122:3344:106::c"],"dataset":{"id":"1455c069-853c-49cd-853a-3ea81b89acd4","name":{"pool_name":"oxp_8da316d4-6b18-4980-a0a8-6e76e72cc40d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::c]:32345"},"services":[{"id":"1455c069-853c-49cd-853a-3ea81b89acd4","details":{"type":"crucible","address":"[fd00:1122:3344:106::c]:32345"}}]},"root":"/pool/ext/08050450-967f-431c-9a12-0d051aff020e/crypt/zone"},{"zone":{"id":"27c0244b-f91a-46c3-bc96-e8eec009371e","zone_type":"crucible","addresses":["fd00:1122:3344:106::b"],"dataset":{"id":"27c0244b-f91a-46c3-bc96-e8eec009371e","name":{"pool_name":"oxp_daf9e3cd-5a40-4eba-a0f6-4f94dab37dae","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::b]:32345"},"services":[{"id":"27c0244b-f91a-46c3-bc96-e8eec009371e","details":{"type":"crucible","address":"[fd00:1122:3344:106::b]:32345"}}]},"root":"/pool/ext/74df4c92-edbb-4431-a770-1d015110e66b/crypt/zone"},{"zone":{"id":"9e46d837-1e0f-42b6-a352-84e6946b8734","zone_type":"crucible","addresses":["fd00:1122:3344:106::4"],"dataset":{"id":"9e46d837-1e0f-42b6-a352-84e6946b8734","name":{"pool_name":"oxp_74df4c92-edbb-4431-a770-1d015110e66b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::4]:32345"},"services":[{"id":"9e46d837-1e0f-42b6-a352-84e6946b8734","details":{"type":"crucible","address":"[fd00:1122:3344:106::4]:32345"}}]},"root":"/pool/ext/15f94c39-d48c-41f6-a913-cc1d04aef1a2/crypt/zone"},{"zone":{"id":"b972fcd4-c1b3-4b3c-9e24-f59c7a7cb192","zone_type":"crucible","addresses":["fd00:1122:3344:106::a"],"dataset":{"id":"b972fcd4-c1b3-4b3c-9e24-f59c7a7cb192","name":{"pool_name":"oxp_15f94c39-d48c-41f6-a913-cc1d04aef1a2","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:106::a]:32345"},"services":[{"id":"b972fcd4-c1b3-4b3c-9e24-f59c7a7cb192","details":{"type":"crucible","address":"[fd00:1122:3344:106::a]:32345"}}]},"root":"/pool/ext/74df4c92-edbb-4431-a770-1d015110e66b/crypt/zone"},{"zone":{"id":"e1c8c655-1950-42d5-ae1f-a4ce84854bbc","zone_type":"ntp","addresses":["fd00:1122:3344:106::d"],"dataset":null,"services":[{"id":"e1c8c655-1950-42d5-ae1f-a4ce84854bbc","details":{"type":"internal_ntp","address":"[fd00:1122:3344:106::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/15f94c39-d48c-41f6-a913-cc1d04aef1a2/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled25.json b/sled-agent/tests/old-service-ledgers/rack3-sled25.json new file mode 100644 index 0000000000..642657bbce --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled25.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"10b80058-9b2e-4d6c-8a1a-a61a8258c12f","zone_type":"crucible","addresses":["fd00:1122:3344:118::9"],"dataset":{"id":"10b80058-9b2e-4d6c-8a1a-a61a8258c12f","name":{"pool_name":"oxp_953c19bb-9fff-4488-8a7b-29de9994a948","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::9]:32345"},"services":[{"id":"10b80058-9b2e-4d6c-8a1a-a61a8258c12f","details":{"type":"crucible","address":"[fd00:1122:3344:118::9]:32345"}}]},"root":"/pool/ext/a78caf97-6145-4908-83b5-a03a6d2e0ac4/crypt/zone"},{"zone":{"id":"f58fef96-7b5e-40c2-9482-669088a19209","zone_type":"crucible","addresses":["fd00:1122:3344:118::d"],"dataset":{"id":"f58fef96-7b5e-40c2-9482-669088a19209","name":{"pool_name":"oxp_d7976706-d6ed-4465-8b04-450c96d8feec","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::d]:32345"},"services":[{"id":"f58fef96-7b5e-40c2-9482-669088a19209","details":{"type":"crucible","address":"[fd00:1122:3344:118::d]:32345"}}]},"root":"/pool/ext/d7976706-d6ed-4465-8b04-450c96d8feec/crypt/zone"},{"zone":{"id":"624f1168-47b6-4aa1-84da-e20a0d74d783","zone_type":"crucible","addresses":["fd00:1122:3344:118::b"],"dataset":{"id":"624f1168-47b6-4aa1-84da-e20a0d74d783","name":{"pool_name":"oxp_a78caf97-6145-4908-83b5-a03a6d2e0ac4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::b]:32345"},"services":[{"id":"624f1168-47b6-4aa1-84da-e20a0d74d783","details":{"type":"crucible","address":"[fd00:1122:3344:118::b]:32345"}}]},"root":"/pool/ext/a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d/crypt/zone"},{"zone":{"id":"8ea85412-19b4-45c1-a53c-027ddd629296","zone_type":"crucible","addresses":["fd00:1122:3344:118::6"],"dataset":{"id":"8ea85412-19b4-45c1-a53c-027ddd629296","name":{"pool_name":"oxp_d5f4c903-155a-4c91-aadd-6039a4f64821","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::6]:32345"},"services":[{"id":"8ea85412-19b4-45c1-a53c-027ddd629296","details":{"type":"crucible","address":"[fd00:1122:3344:118::6]:32345"}}]},"root":"/pool/ext/7d2a7685-c1c9-4d2d-a2bb-df65d96ea3e2/crypt/zone"},{"zone":{"id":"fd226b82-71d7-4719-b32c-a6c7abe28a2a","zone_type":"external_dns","addresses":["fd00:1122:3344:118::3"],"dataset":{"id":"fd226b82-71d7-4719-b32c-a6c7abe28a2a","name":{"pool_name":"oxp_84a80b58-70e9-439c-9558-5b343d9a4b53","kind":{"type":"external_dns"}},"service_address":"[fd00:1122:3344:118::3]:5353"},"services":[{"id":"fd226b82-71d7-4719-b32c-a6c7abe28a2a","details":{"type":"external_dns","http_address":"[fd00:1122:3344:118::3]:5353","dns_address":"45.154.216.34:53","nic":{"id":"7f72b6fd-1120-44dc-b3a7-f727502ba47c","kind":{"type":"service","id":"fd226b82-71d7-4719-b32c-a6c7abe28a2a"},"name":"external-dns-fd226b82-71d7-4719-b32c-a6c7abe28a2a","ip":"172.30.1.6","mac":"A8:40:25:FF:9E:D1","subnet":"172.30.1.0/24","vni":100,"primary":true,"slot":0}}}]},"root":"/pool/ext/a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d/crypt/zone"},{"zone":{"id":"08d0c38d-f0d9-45b9-856d-b85059fe5f07","zone_type":"crucible","addresses":["fd00:1122:3344:118::4"],"dataset":{"id":"08d0c38d-f0d9-45b9-856d-b85059fe5f07","name":{"pool_name":"oxp_84a80b58-70e9-439c-9558-5b343d9a4b53","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::4]:32345"},"services":[{"id":"08d0c38d-f0d9-45b9-856d-b85059fe5f07","details":{"type":"crucible","address":"[fd00:1122:3344:118::4]:32345"}}]},"root":"/pool/ext/a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d/crypt/zone"},{"zone":{"id":"5de7d3fd-4a3f-4fdd-b6b2-d1186e16dce5","zone_type":"crucible","addresses":["fd00:1122:3344:118::7"],"dataset":{"id":"5de7d3fd-4a3f-4fdd-b6b2-d1186e16dce5","name":{"pool_name":"oxp_d76e058f-2d1e-4b15-b3a0-e5509a246876","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::7]:32345"},"services":[{"id":"5de7d3fd-4a3f-4fdd-b6b2-d1186e16dce5","details":{"type":"crucible","address":"[fd00:1122:3344:118::7]:32345"}}]},"root":"/pool/ext/a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d/crypt/zone"},{"zone":{"id":"5d0f5cad-10b3-497c-903b-eeeabce920e2","zone_type":"crucible","addresses":["fd00:1122:3344:118::8"],"dataset":{"id":"5d0f5cad-10b3-497c-903b-eeeabce920e2","name":{"pool_name":"oxp_3a3ad639-8800-4951-bc2a-201d269e47a2","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::8]:32345"},"services":[{"id":"5d0f5cad-10b3-497c-903b-eeeabce920e2","details":{"type":"crucible","address":"[fd00:1122:3344:118::8]:32345"}}]},"root":"/pool/ext/3a3ad639-8800-4951-bc2a-201d269e47a2/crypt/zone"},{"zone":{"id":"39f9cefa-801c-4843-9fb9-05446ffbdd1a","zone_type":"crucible","addresses":["fd00:1122:3344:118::a"],"dataset":{"id":"39f9cefa-801c-4843-9fb9-05446ffbdd1a","name":{"pool_name":"oxp_7d2a7685-c1c9-4d2d-a2bb-df65d96ea3e2","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::a]:32345"},"services":[{"id":"39f9cefa-801c-4843-9fb9-05446ffbdd1a","details":{"type":"crucible","address":"[fd00:1122:3344:118::a]:32345"}}]},"root":"/pool/ext/a78caf97-6145-4908-83b5-a03a6d2e0ac4/crypt/zone"},{"zone":{"id":"0711e710-7fdd-4e68-94c8-294b8677e804","zone_type":"crucible","addresses":["fd00:1122:3344:118::5"],"dataset":{"id":"0711e710-7fdd-4e68-94c8-294b8677e804","name":{"pool_name":"oxp_a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::5]:32345"},"services":[{"id":"0711e710-7fdd-4e68-94c8-294b8677e804","details":{"type":"crucible","address":"[fd00:1122:3344:118::5]:32345"}}]},"root":"/pool/ext/3a3ad639-8800-4951-bc2a-201d269e47a2/crypt/zone"},{"zone":{"id":"318a62cc-5c6c-4805-9fb6-c0f6a75ce31c","zone_type":"crucible","addresses":["fd00:1122:3344:118::c"],"dataset":{"id":"318a62cc-5c6c-4805-9fb6-c0f6a75ce31c","name":{"pool_name":"oxp_1d5f0ba3-6b31-4cea-a9a9-2065a538887d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:118::c]:32345"},"services":[{"id":"318a62cc-5c6c-4805-9fb6-c0f6a75ce31c","details":{"type":"crucible","address":"[fd00:1122:3344:118::c]:32345"}}]},"root":"/pool/ext/d7976706-d6ed-4465-8b04-450c96d8feec/crypt/zone"},{"zone":{"id":"463d0498-85b9-40eb-af96-d99af58a587c","zone_type":"ntp","addresses":["fd00:1122:3344:118::e"],"dataset":null,"services":[{"id":"463d0498-85b9-40eb-af96-d99af58a587c","details":{"type":"internal_ntp","address":"[fd00:1122:3344:118::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/d5f4c903-155a-4c91-aadd-6039a4f64821/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled26.json b/sled-agent/tests/old-service-ledgers/rack3-sled26.json new file mode 100644 index 0000000000..0978cb9e45 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled26.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"d8b3de97-cc79-48f6-83ad-02017c21223b","zone_type":"crucible_pantry","addresses":["fd00:1122:3344:119::3"],"dataset":null,"services":[{"id":"d8b3de97-cc79-48f6-83ad-02017c21223b","details":{"type":"crucible_pantry","address":"[fd00:1122:3344:119::3]:17000"}}]},"root":"/pool/ext/e0faea44-8b5c-40b0-bb75-a1aec1a10377/crypt/zone"},{"zone":{"id":"adba1a3b-5bac-44d5-aa5a-879dc6eadb5f","zone_type":"crucible","addresses":["fd00:1122:3344:119::c"],"dataset":{"id":"adba1a3b-5bac-44d5-aa5a-879dc6eadb5f","name":{"pool_name":"oxp_21c339c3-6461-4bdb-8b0e-c0f9f08ee10b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::c]:32345"},"services":[{"id":"adba1a3b-5bac-44d5-aa5a-879dc6eadb5f","details":{"type":"crucible","address":"[fd00:1122:3344:119::c]:32345"}}]},"root":"/pool/ext/f5c73c28-2168-4321-b737-4ca6663155c9/crypt/zone"},{"zone":{"id":"42bb9833-5c39-4aba-b2c4-da2ca1287728","zone_type":"crucible","addresses":["fd00:1122:3344:119::a"],"dataset":{"id":"42bb9833-5c39-4aba-b2c4-da2ca1287728","name":{"pool_name":"oxp_1f91451d-a466-4c9a-a6e6-0abd7985595f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::a]:32345"},"services":[{"id":"42bb9833-5c39-4aba-b2c4-da2ca1287728","details":{"type":"crucible","address":"[fd00:1122:3344:119::a]:32345"}}]},"root":"/pool/ext/21c339c3-6461-4bdb-8b0e-c0f9f08ee10b/crypt/zone"},{"zone":{"id":"197695e1-d949-4982-b679-6e5c9ab4bcc7","zone_type":"crucible","addresses":["fd00:1122:3344:119::b"],"dataset":{"id":"197695e1-d949-4982-b679-6e5c9ab4bcc7","name":{"pool_name":"oxp_e0faea44-8b5c-40b0-bb75-a1aec1a10377","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::b]:32345"},"services":[{"id":"197695e1-d949-4982-b679-6e5c9ab4bcc7","details":{"type":"crucible","address":"[fd00:1122:3344:119::b]:32345"}}]},"root":"/pool/ext/b31e1815-cae0-4145-940c-874fff63bdd5/crypt/zone"},{"zone":{"id":"bf99d4f8-edf1-4de5-98d4-8e6a24965005","zone_type":"crucible","addresses":["fd00:1122:3344:119::8"],"dataset":{"id":"bf99d4f8-edf1-4de5-98d4-8e6a24965005","name":{"pool_name":"oxp_ef2c3afb-6962-4f6b-b567-14766bbd9ec0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::8]:32345"},"services":[{"id":"bf99d4f8-edf1-4de5-98d4-8e6a24965005","details":{"type":"crucible","address":"[fd00:1122:3344:119::8]:32345"}}]},"root":"/pool/ext/21c339c3-6461-4bdb-8b0e-c0f9f08ee10b/crypt/zone"},{"zone":{"id":"390d1853-8be9-4987-b8b6-f022999bf4e7","zone_type":"crucible","addresses":["fd00:1122:3344:119::7"],"dataset":{"id":"390d1853-8be9-4987-b8b6-f022999bf4e7","name":{"pool_name":"oxp_06eed00a-d8d3-4b9d-84c9-23fce535f63e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::7]:32345"},"services":[{"id":"390d1853-8be9-4987-b8b6-f022999bf4e7","details":{"type":"crucible","address":"[fd00:1122:3344:119::7]:32345"}}]},"root":"/pool/ext/ef2c3afb-6962-4f6b-b567-14766bbd9ec0/crypt/zone"},{"zone":{"id":"76fe2161-90df-41b5-9c94-067de9c29db1","zone_type":"crucible","addresses":["fd00:1122:3344:119::4"],"dataset":{"id":"76fe2161-90df-41b5-9c94-067de9c29db1","name":{"pool_name":"oxp_f5c73c28-2168-4321-b737-4ca6663155c9","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::4]:32345"},"services":[{"id":"76fe2161-90df-41b5-9c94-067de9c29db1","details":{"type":"crucible","address":"[fd00:1122:3344:119::4]:32345"}}]},"root":"/pool/ext/ef2c3afb-6962-4f6b-b567-14766bbd9ec0/crypt/zone"},{"zone":{"id":"f49dc522-2b13-4055-964c-8315671096aa","zone_type":"crucible","addresses":["fd00:1122:3344:119::d"],"dataset":{"id":"f49dc522-2b13-4055-964c-8315671096aa","name":{"pool_name":"oxp_662c278b-7f5f-4c7e-91ff-70207e8a307b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::d]:32345"},"services":[{"id":"f49dc522-2b13-4055-964c-8315671096aa","details":{"type":"crucible","address":"[fd00:1122:3344:119::d]:32345"}}]},"root":"/pool/ext/1f91451d-a466-4c9a-a6e6-0abd7985595f/crypt/zone"},{"zone":{"id":"08cc7bd6-368e-4d16-a619-28b17eff35af","zone_type":"crucible","addresses":["fd00:1122:3344:119::9"],"dataset":{"id":"08cc7bd6-368e-4d16-a619-28b17eff35af","name":{"pool_name":"oxp_5516b9ac-b139-40da-aa3b-f094568ba095","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::9]:32345"},"services":[{"id":"08cc7bd6-368e-4d16-a619-28b17eff35af","details":{"type":"crucible","address":"[fd00:1122:3344:119::9]:32345"}}]},"root":"/pool/ext/06eed00a-d8d3-4b9d-84c9-23fce535f63e/crypt/zone"},{"zone":{"id":"74b0613f-bce8-4922-93e0-b5bfccfc8443","zone_type":"crucible","addresses":["fd00:1122:3344:119::5"],"dataset":{"id":"74b0613f-bce8-4922-93e0-b5bfccfc8443","name":{"pool_name":"oxp_b31e1815-cae0-4145-940c-874fff63bdd5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::5]:32345"},"services":[{"id":"74b0613f-bce8-4922-93e0-b5bfccfc8443","details":{"type":"crucible","address":"[fd00:1122:3344:119::5]:32345"}}]},"root":"/pool/ext/21c339c3-6461-4bdb-8b0e-c0f9f08ee10b/crypt/zone"},{"zone":{"id":"55fcfc62-8435-475f-a2aa-29373901b993","zone_type":"crucible","addresses":["fd00:1122:3344:119::6"],"dataset":{"id":"55fcfc62-8435-475f-a2aa-29373901b993","name":{"pool_name":"oxp_eadf6a03-1028-4d48-ac0d-0d27ef2c8c0f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:119::6]:32345"},"services":[{"id":"55fcfc62-8435-475f-a2aa-29373901b993","details":{"type":"crucible","address":"[fd00:1122:3344:119::6]:32345"}}]},"root":"/pool/ext/1f91451d-a466-4c9a-a6e6-0abd7985595f/crypt/zone"},{"zone":{"id":"d52ccea3-6d7f-43a6-a19f-e0409f4e9cdc","zone_type":"ntp","addresses":["fd00:1122:3344:119::e"],"dataset":null,"services":[{"id":"d52ccea3-6d7f-43a6-a19f-e0409f4e9cdc","details":{"type":"internal_ntp","address":"[fd00:1122:3344:119::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/f5c73c28-2168-4321-b737-4ca6663155c9/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled27.json b/sled-agent/tests/old-service-ledgers/rack3-sled27.json new file mode 100644 index 0000000000..0b2db29c4a --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled27.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"095e612f-e218-4a16-aa6e-98c3d69a470a","zone_type":"crucible","addresses":["fd00:1122:3344:10d::a"],"dataset":{"id":"095e612f-e218-4a16-aa6e-98c3d69a470a","name":{"pool_name":"oxp_9f657858-623f-4d78-9841-6e620b5ede30","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::a]:32345"},"services":[{"id":"095e612f-e218-4a16-aa6e-98c3d69a470a","details":{"type":"crucible","address":"[fd00:1122:3344:10d::a]:32345"}}]},"root":"/pool/ext/2d086b51-2b77-4bc7-adc6-43586ea38ce9/crypt/zone"},{"zone":{"id":"de818730-0e3b-4567-94e7-344bd9b6f564","zone_type":"crucible","addresses":["fd00:1122:3344:10d::3"],"dataset":{"id":"de818730-0e3b-4567-94e7-344bd9b6f564","name":{"pool_name":"oxp_ba6ab301-07e1-4d35-80ac-59612f2c2bdb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::3]:32345"},"services":[{"id":"de818730-0e3b-4567-94e7-344bd9b6f564","details":{"type":"crucible","address":"[fd00:1122:3344:10d::3]:32345"}}]},"root":"/pool/ext/7cee2806-e898-47d8-b568-e276a6e271f8/crypt/zone"},{"zone":{"id":"6a21dc3c-3a9d-4520-9a91-7d8f2737bcd4","zone_type":"crucible","addresses":["fd00:1122:3344:10d::4"],"dataset":{"id":"6a21dc3c-3a9d-4520-9a91-7d8f2737bcd4","name":{"pool_name":"oxp_7cee2806-e898-47d8-b568-e276a6e271f8","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::4]:32345"},"services":[{"id":"6a21dc3c-3a9d-4520-9a91-7d8f2737bcd4","details":{"type":"crucible","address":"[fd00:1122:3344:10d::4]:32345"}}]},"root":"/pool/ext/cef23d87-31ed-40d5-99b8-12d7be8e46e7/crypt/zone"},{"zone":{"id":"e01b7f45-b8d7-4944-ba5b-41fb699889a9","zone_type":"crucible","addresses":["fd00:1122:3344:10d::b"],"dataset":{"id":"e01b7f45-b8d7-4944-ba5b-41fb699889a9","name":{"pool_name":"oxp_d9af8878-50bd-4425-95d9-e6556ce92cfa","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::b]:32345"},"services":[{"id":"e01b7f45-b8d7-4944-ba5b-41fb699889a9","details":{"type":"crucible","address":"[fd00:1122:3344:10d::b]:32345"}}]},"root":"/pool/ext/6fe9bcaa-88cb-451d-b086-24a3ad53fa22/crypt/zone"},{"zone":{"id":"4271ef62-d319-4e80-b157-915321cec8c7","zone_type":"crucible","addresses":["fd00:1122:3344:10d::c"],"dataset":{"id":"4271ef62-d319-4e80-b157-915321cec8c7","name":{"pool_name":"oxp_ba8ee7dd-cdfb-48bd-92ce-4dc45e070930","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::c]:32345"},"services":[{"id":"4271ef62-d319-4e80-b157-915321cec8c7","details":{"type":"crucible","address":"[fd00:1122:3344:10d::c]:32345"}}]},"root":"/pool/ext/9f657858-623f-4d78-9841-6e620b5ede30/crypt/zone"},{"zone":{"id":"6bdcc159-aeb9-4903-9486-dd8b43a3dc16","zone_type":"crucible","addresses":["fd00:1122:3344:10d::8"],"dataset":{"id":"6bdcc159-aeb9-4903-9486-dd8b43a3dc16","name":{"pool_name":"oxp_5b03a5dc-bb5a-4bf4-bc21-0af849cd1dab","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::8]:32345"},"services":[{"id":"6bdcc159-aeb9-4903-9486-dd8b43a3dc16","details":{"type":"crucible","address":"[fd00:1122:3344:10d::8]:32345"}}]},"root":"/pool/ext/d9af8878-50bd-4425-95d9-e6556ce92cfa/crypt/zone"},{"zone":{"id":"85540e54-cdd7-4baa-920c-5cf54cbc1f83","zone_type":"crucible","addresses":["fd00:1122:3344:10d::7"],"dataset":{"id":"85540e54-cdd7-4baa-920c-5cf54cbc1f83","name":{"pool_name":"oxp_ee24f9a6-84ab-49a5-a28f-e394abfcaa95","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::7]:32345"},"services":[{"id":"85540e54-cdd7-4baa-920c-5cf54cbc1f83","details":{"type":"crucible","address":"[fd00:1122:3344:10d::7]:32345"}}]},"root":"/pool/ext/9f657858-623f-4d78-9841-6e620b5ede30/crypt/zone"},{"zone":{"id":"750d1a0b-6a14-46c5-9a0b-a504caefb198","zone_type":"crucible","addresses":["fd00:1122:3344:10d::9"],"dataset":{"id":"750d1a0b-6a14-46c5-9a0b-a504caefb198","name":{"pool_name":"oxp_cef23d87-31ed-40d5-99b8-12d7be8e46e7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::9]:32345"},"services":[{"id":"750d1a0b-6a14-46c5-9a0b-a504caefb198","details":{"type":"crucible","address":"[fd00:1122:3344:10d::9]:32345"}}]},"root":"/pool/ext/ba8ee7dd-cdfb-48bd-92ce-4dc45e070930/crypt/zone"},{"zone":{"id":"b5996893-1a9a-434e-a257-d702694f058b","zone_type":"crucible","addresses":["fd00:1122:3344:10d::6"],"dataset":{"id":"b5996893-1a9a-434e-a257-d702694f058b","name":{"pool_name":"oxp_2d086b51-2b77-4bc7-adc6-43586ea38ce9","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::6]:32345"},"services":[{"id":"b5996893-1a9a-434e-a257-d702694f058b","details":{"type":"crucible","address":"[fd00:1122:3344:10d::6]:32345"}}]},"root":"/pool/ext/7cee2806-e898-47d8-b568-e276a6e271f8/crypt/zone"},{"zone":{"id":"8b36686a-b98d-451a-9124-a3583000a83a","zone_type":"crucible","addresses":["fd00:1122:3344:10d::5"],"dataset":{"id":"8b36686a-b98d-451a-9124-a3583000a83a","name":{"pool_name":"oxp_6fe9bcaa-88cb-451d-b086-24a3ad53fa22","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10d::5]:32345"},"services":[{"id":"8b36686a-b98d-451a-9124-a3583000a83a","details":{"type":"crucible","address":"[fd00:1122:3344:10d::5]:32345"}}]},"root":"/pool/ext/9f657858-623f-4d78-9841-6e620b5ede30/crypt/zone"},{"zone":{"id":"88d695a2-c8c1-41af-85b0-77424f4d650d","zone_type":"ntp","addresses":["fd00:1122:3344:10d::d"],"dataset":null,"services":[{"id":"88d695a2-c8c1-41af-85b0-77424f4d650d","details":{"type":"internal_ntp","address":"[fd00:1122:3344:10d::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/ba6ab301-07e1-4d35-80ac-59612f2c2bdb/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled28.json b/sled-agent/tests/old-service-ledgers/rack3-sled28.json new file mode 100644 index 0000000000..ec137c18fa --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled28.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"a126365d-f459-43bf-9f99-dbe1c4cdecf8","zone_type":"crucible","addresses":["fd00:1122:3344:113::4"],"dataset":{"id":"a126365d-f459-43bf-9f99-dbe1c4cdecf8","name":{"pool_name":"oxp_c99eabb2-6815-416a-9660-87e2609b357a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::4]:32345"},"services":[{"id":"a126365d-f459-43bf-9f99-dbe1c4cdecf8","details":{"type":"crucible","address":"[fd00:1122:3344:113::4]:32345"}}]},"root":"/pool/ext/6461a450-f043-4d1e-bc03-4a68ed5fe94a/crypt/zone"},{"zone":{"id":"52f57ef8-546a-43bd-a0f3-8c42b99c37a6","zone_type":"crucible","addresses":["fd00:1122:3344:113::3"],"dataset":{"id":"52f57ef8-546a-43bd-a0f3-8c42b99c37a6","name":{"pool_name":"oxp_f6530e9c-6d64-44fa-93d5-ae427916fbf1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::3]:32345"},"services":[{"id":"52f57ef8-546a-43bd-a0f3-8c42b99c37a6","details":{"type":"crucible","address":"[fd00:1122:3344:113::3]:32345"}}]},"root":"/pool/ext/97662260-6b62-450f-9d7e-42f7dee5d568/crypt/zone"},{"zone":{"id":"3ee87855-9423-43ff-800a-fa4fdbf1d956","zone_type":"crucible","addresses":["fd00:1122:3344:113::a"],"dataset":{"id":"3ee87855-9423-43ff-800a-fa4fdbf1d956","name":{"pool_name":"oxp_6461a450-f043-4d1e-bc03-4a68ed5fe94a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::a]:32345"},"services":[{"id":"3ee87855-9423-43ff-800a-fa4fdbf1d956","details":{"type":"crucible","address":"[fd00:1122:3344:113::a]:32345"}}]},"root":"/pool/ext/9515dc86-fe62-4d4f-b38d-b3461cc042fc/crypt/zone"},{"zone":{"id":"55d0ddf9-9b24-4a7a-b97f-248e240f9ba6","zone_type":"crucible","addresses":["fd00:1122:3344:113::5"],"dataset":{"id":"55d0ddf9-9b24-4a7a-b97f-248e240f9ba6","name":{"pool_name":"oxp_97662260-6b62-450f-9d7e-42f7dee5d568","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::5]:32345"},"services":[{"id":"55d0ddf9-9b24-4a7a-b97f-248e240f9ba6","details":{"type":"crucible","address":"[fd00:1122:3344:113::5]:32345"}}]},"root":"/pool/ext/9515dc86-fe62-4d4f-b38d-b3461cc042fc/crypt/zone"},{"zone":{"id":"014cad37-56a7-4b2a-9c9e-505b15b4de85","zone_type":"crucible","addresses":["fd00:1122:3344:113::b"],"dataset":{"id":"014cad37-56a7-4b2a-9c9e-505b15b4de85","name":{"pool_name":"oxp_8529ce8e-21d2-4b23-b9fd-6b90c7ae4f90","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::b]:32345"},"services":[{"id":"014cad37-56a7-4b2a-9c9e-505b15b4de85","details":{"type":"crucible","address":"[fd00:1122:3344:113::b]:32345"}}]},"root":"/pool/ext/6461a450-f043-4d1e-bc03-4a68ed5fe94a/crypt/zone"},{"zone":{"id":"e14fb192-aaab-42ab-aa86-c85f13955940","zone_type":"crucible","addresses":["fd00:1122:3344:113::6"],"dataset":{"id":"e14fb192-aaab-42ab-aa86-c85f13955940","name":{"pool_name":"oxp_5a9455ca-fb01-4549-9a70-7579c031779d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::6]:32345"},"services":[{"id":"e14fb192-aaab-42ab-aa86-c85f13955940","details":{"type":"crucible","address":"[fd00:1122:3344:113::6]:32345"}}]},"root":"/pool/ext/f6530e9c-6d64-44fa-93d5-ae427916fbf1/crypt/zone"},{"zone":{"id":"14540609-9371-442b-8486-88c244e97cd4","zone_type":"crucible","addresses":["fd00:1122:3344:113::8"],"dataset":{"id":"14540609-9371-442b-8486-88c244e97cd4","name":{"pool_name":"oxp_2916d6f3-8775-4887-a6d3-f9723982756f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::8]:32345"},"services":[{"id":"14540609-9371-442b-8486-88c244e97cd4","details":{"type":"crucible","address":"[fd00:1122:3344:113::8]:32345"}}]},"root":"/pool/ext/8529ce8e-21d2-4b23-b9fd-6b90c7ae4f90/crypt/zone"},{"zone":{"id":"97a6b35f-0af9-41eb-93a1-f8bc5dbba357","zone_type":"crucible","addresses":["fd00:1122:3344:113::7"],"dataset":{"id":"97a6b35f-0af9-41eb-93a1-f8bc5dbba357","name":{"pool_name":"oxp_9515dc86-fe62-4d4f-b38d-b3461cc042fc","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::7]:32345"},"services":[{"id":"97a6b35f-0af9-41eb-93a1-f8bc5dbba357","details":{"type":"crucible","address":"[fd00:1122:3344:113::7]:32345"}}]},"root":"/pool/ext/8529ce8e-21d2-4b23-b9fd-6b90c7ae4f90/crypt/zone"},{"zone":{"id":"5734aa24-cb66-4b0a-9eb2-564646f8d729","zone_type":"crucible","addresses":["fd00:1122:3344:113::9"],"dataset":{"id":"5734aa24-cb66-4b0a-9eb2-564646f8d729","name":{"pool_name":"oxp_9f889a6c-17b1-4edd-9659-458d91439dc1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::9]:32345"},"services":[{"id":"5734aa24-cb66-4b0a-9eb2-564646f8d729","details":{"type":"crucible","address":"[fd00:1122:3344:113::9]:32345"}}]},"root":"/pool/ext/a5074e7f-8d3b-40e0-a79e-dbd9af9d5693/crypt/zone"},{"zone":{"id":"ba86eca1-1427-4540-b4a6-1d9a0e1bc656","zone_type":"crucible","addresses":["fd00:1122:3344:113::c"],"dataset":{"id":"ba86eca1-1427-4540-b4a6-1d9a0e1bc656","name":{"pool_name":"oxp_a5074e7f-8d3b-40e0-a79e-dbd9af9d5693","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:113::c]:32345"},"services":[{"id":"ba86eca1-1427-4540-b4a6-1d9a0e1bc656","details":{"type":"crucible","address":"[fd00:1122:3344:113::c]:32345"}}]},"root":"/pool/ext/2916d6f3-8775-4887-a6d3-f9723982756f/crypt/zone"},{"zone":{"id":"6634dbc4-d22f-40a4-8cd3-4f271d781fa1","zone_type":"ntp","addresses":["fd00:1122:3344:113::d"],"dataset":null,"services":[{"id":"6634dbc4-d22f-40a4-8cd3-4f271d781fa1","details":{"type":"internal_ntp","address":"[fd00:1122:3344:113::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/a5074e7f-8d3b-40e0-a79e-dbd9af9d5693/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled29.json b/sled-agent/tests/old-service-ledgers/rack3-sled29.json new file mode 100644 index 0000000000..2618364e4f --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled29.json @@ -0,0 +1 @@ +{"generation":5,"requests":[{"zone":{"id":"1cdd1ebf-9321-4f2d-914c-1e617f60b41a","zone_type":"crucible","addresses":["fd00:1122:3344:120::8"],"dataset":{"id":"1cdd1ebf-9321-4f2d-914c-1e617f60b41a","name":{"pool_name":"oxp_74046573-78a2-46b4-86dc-40bb2ee29dd5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::8]:32345"},"services":[{"id":"1cdd1ebf-9321-4f2d-914c-1e617f60b41a","details":{"type":"crucible","address":"[fd00:1122:3344:120::8]:32345"}}]},"root":"/pool/ext/c1f0a9e4-ea10-4fd9-8b6d-79a2bacfec5e/crypt/zone"},{"zone":{"id":"720a0d08-d1c0-43ba-af86-f2dac1a53639","zone_type":"crucible","addresses":["fd00:1122:3344:120::c"],"dataset":{"id":"720a0d08-d1c0-43ba-af86-f2dac1a53639","name":{"pool_name":"oxp_068d2790-1044-41ed-97a5-b493490b14d1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::c]:32345"},"services":[{"id":"720a0d08-d1c0-43ba-af86-f2dac1a53639","details":{"type":"crucible","address":"[fd00:1122:3344:120::c]:32345"}}]},"root":"/pool/ext/86cd16cf-d00d-40bc-b14a-8220b1e11476/crypt/zone"},{"zone":{"id":"d9f0b97b-2cef-4155-b45f-7db89263e4cf","zone_type":"crucible","addresses":["fd00:1122:3344:120::9"],"dataset":{"id":"d9f0b97b-2cef-4155-b45f-7db89263e4cf","name":{"pool_name":"oxp_8171bf0d-e61e-43f9-87d6-ec8833b80102","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::9]:32345"},"services":[{"id":"d9f0b97b-2cef-4155-b45f-7db89263e4cf","details":{"type":"crucible","address":"[fd00:1122:3344:120::9]:32345"}}]},"root":"/pool/ext/86cd16cf-d00d-40bc-b14a-8220b1e11476/crypt/zone"},{"zone":{"id":"018edff1-0d95-45a3-9a01-39c419bec55a","zone_type":"crucible","addresses":["fd00:1122:3344:120::b"],"dataset":{"id":"018edff1-0d95-45a3-9a01-39c419bec55a","name":{"pool_name":"oxp_0b11e026-f265-49a0-935f-7b234c19c789","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::b]:32345"},"services":[{"id":"018edff1-0d95-45a3-9a01-39c419bec55a","details":{"type":"crucible","address":"[fd00:1122:3344:120::b]:32345"}}]},"root":"/pool/ext/35db8700-d6a7-498c-9d2c-08eb9ab41b7c/crypt/zone"},{"zone":{"id":"f8cc1c1e-a556-436c-836d-42052101c38a","zone_type":"crucible","addresses":["fd00:1122:3344:120::3"],"dataset":{"id":"f8cc1c1e-a556-436c-836d-42052101c38a","name":{"pool_name":"oxp_ed8e5a26-5591-405a-b792-408f5b16e444","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::3]:32345"},"services":[{"id":"f8cc1c1e-a556-436c-836d-42052101c38a","details":{"type":"crucible","address":"[fd00:1122:3344:120::3]:32345"}}]},"root":"/pool/ext/1069bdee-fe5a-4164-a856-ff8ae56c07fb/crypt/zone"},{"zone":{"id":"f9600313-fac0-45a1-a1b5-02dd6af468b9","zone_type":"crucible","addresses":["fd00:1122:3344:120::4"],"dataset":{"id":"f9600313-fac0-45a1-a1b5-02dd6af468b9","name":{"pool_name":"oxp_c1f0a9e4-ea10-4fd9-8b6d-79a2bacfec5e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::4]:32345"},"services":[{"id":"f9600313-fac0-45a1-a1b5-02dd6af468b9","details":{"type":"crucible","address":"[fd00:1122:3344:120::4]:32345"}}]},"root":"/pool/ext/74046573-78a2-46b4-86dc-40bb2ee29dd5/crypt/zone"},{"zone":{"id":"869e4f7c-5312-4b98-bacc-1508f236bf5a","zone_type":"crucible","addresses":["fd00:1122:3344:120::6"],"dataset":{"id":"869e4f7c-5312-4b98-bacc-1508f236bf5a","name":{"pool_name":"oxp_04aea8dc-4316-432f-a13a-d7d9b2efa3f2","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::6]:32345"},"services":[{"id":"869e4f7c-5312-4b98-bacc-1508f236bf5a","details":{"type":"crucible","address":"[fd00:1122:3344:120::6]:32345"}}]},"root":"/pool/ext/0b11e026-f265-49a0-935f-7b234c19c789/crypt/zone"},{"zone":{"id":"31ed5a0c-7caf-4825-b730-85ee94fe27f1","zone_type":"crucible","addresses":["fd00:1122:3344:120::a"],"dataset":{"id":"31ed5a0c-7caf-4825-b730-85ee94fe27f1","name":{"pool_name":"oxp_86cd16cf-d00d-40bc-b14a-8220b1e11476","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::a]:32345"},"services":[{"id":"31ed5a0c-7caf-4825-b730-85ee94fe27f1","details":{"type":"crucible","address":"[fd00:1122:3344:120::a]:32345"}}]},"root":"/pool/ext/04aea8dc-4316-432f-a13a-d7d9b2efa3f2/crypt/zone"},{"zone":{"id":"7e5a3c39-152a-4270-b01e-9e144cca4aaa","zone_type":"crucible","addresses":["fd00:1122:3344:120::5"],"dataset":{"id":"7e5a3c39-152a-4270-b01e-9e144cca4aaa","name":{"pool_name":"oxp_1069bdee-fe5a-4164-a856-ff8ae56c07fb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::5]:32345"},"services":[{"id":"7e5a3c39-152a-4270-b01e-9e144cca4aaa","details":{"type":"crucible","address":"[fd00:1122:3344:120::5]:32345"}}]},"root":"/pool/ext/04aea8dc-4316-432f-a13a-d7d9b2efa3f2/crypt/zone"},{"zone":{"id":"9a03a386-7304-4a86-bee8-153ef643195e","zone_type":"crucible","addresses":["fd00:1122:3344:120::7"],"dataset":{"id":"9a03a386-7304-4a86-bee8-153ef643195e","name":{"pool_name":"oxp_35db8700-d6a7-498c-9d2c-08eb9ab41b7c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:120::7]:32345"},"services":[{"id":"9a03a386-7304-4a86-bee8-153ef643195e","details":{"type":"crucible","address":"[fd00:1122:3344:120::7]:32345"}}]},"root":"/pool/ext/068d2790-1044-41ed-97a5-b493490b14d1/crypt/zone"},{"zone":{"id":"a800d0a7-1020-481c-8be8-ecfd28b7a2be","zone_type":"ntp","addresses":["fd00:1122:3344:120::d"],"dataset":null,"services":[{"id":"a800d0a7-1020-481c-8be8-ecfd28b7a2be","details":{"type":"internal_ntp","address":"[fd00:1122:3344:120::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/c1f0a9e4-ea10-4fd9-8b6d-79a2bacfec5e/crypt/zone"},{"zone":{"id":"be469efd-8e07-4b8e-bcee-6fd33373cdef","zone_type":"internal_dns","addresses":["fd00:1122:3344:3::1"],"dataset":{"id":"be469efd-8e07-4b8e-bcee-6fd33373cdef","name":{"pool_name":"oxp_ed8e5a26-5591-405a-b792-408f5b16e444","kind":{"type":"internal_dns"}},"service_address":"[fd00:1122:3344:3::1]:5353"},"services":[{"id":"be469efd-8e07-4b8e-bcee-6fd33373cdef","details":{"type":"internal_dns","http_address":"[fd00:1122:3344:3::1]:5353","dns_address":"[fd00:1122:3344:3::1]:53","gz_address":"fd00:1122:3344:3::2","gz_address_index":2}}]},"root":"/pool/ext/068d2790-1044-41ed-97a5-b493490b14d1/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled3.json b/sled-agent/tests/old-service-ledgers/rack3-sled3.json new file mode 100644 index 0000000000..6bcb626cf6 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled3.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"19d091b8-e005-4ff4-97e1-026de95e3667","zone_type":"crucible","addresses":["fd00:1122:3344:10f::c"],"dataset":{"id":"19d091b8-e005-4ff4-97e1-026de95e3667","name":{"pool_name":"oxp_11a63469-4f57-4976-8620-0055bf82dc97","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::c]:32345"},"services":[{"id":"19d091b8-e005-4ff4-97e1-026de95e3667","details":{"type":"crucible","address":"[fd00:1122:3344:10f::c]:32345"}}]},"root":"/pool/ext/6a73a62c-c636-4557-af45-042cb287aee6/crypt/zone"},{"zone":{"id":"57d77171-104e-4977-b2f9-9b529ee7f8a0","zone_type":"crucible","addresses":["fd00:1122:3344:10f::8"],"dataset":{"id":"57d77171-104e-4977-b2f9-9b529ee7f8a0","name":{"pool_name":"oxp_7f3060af-058f-4f52-ab80-902bd13e7ef4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::8]:32345"},"services":[{"id":"57d77171-104e-4977-b2f9-9b529ee7f8a0","details":{"type":"crucible","address":"[fd00:1122:3344:10f::8]:32345"}}]},"root":"/pool/ext/7f3060af-058f-4f52-ab80-902bd13e7ef4/crypt/zone"},{"zone":{"id":"b0371ccf-67da-4562-baf2-eaabe5243e9b","zone_type":"crucible","addresses":["fd00:1122:3344:10f::7"],"dataset":{"id":"b0371ccf-67da-4562-baf2-eaabe5243e9b","name":{"pool_name":"oxp_58ae04cb-26ff-4e30-a20d-9f847bafba4d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::7]:32345"},"services":[{"id":"b0371ccf-67da-4562-baf2-eaabe5243e9b","details":{"type":"crucible","address":"[fd00:1122:3344:10f::7]:32345"}}]},"root":"/pool/ext/125ddcda-f94b-46bc-a10a-94e9acf40265/crypt/zone"},{"zone":{"id":"ae3791ff-2657-4252-bd61-58ec5dc237cd","zone_type":"crucible","addresses":["fd00:1122:3344:10f::9"],"dataset":{"id":"ae3791ff-2657-4252-bd61-58ec5dc237cd","name":{"pool_name":"oxp_125ddcda-f94b-46bc-a10a-94e9acf40265","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::9]:32345"},"services":[{"id":"ae3791ff-2657-4252-bd61-58ec5dc237cd","details":{"type":"crucible","address":"[fd00:1122:3344:10f::9]:32345"}}]},"root":"/pool/ext/58ae04cb-26ff-4e30-a20d-9f847bafba4d/crypt/zone"},{"zone":{"id":"73f865dc-5db7-48c6-9dc4-dff56dd8c045","zone_type":"crucible_pantry","addresses":["fd00:1122:3344:10f::3"],"dataset":null,"services":[{"id":"73f865dc-5db7-48c6-9dc4-dff56dd8c045","details":{"type":"crucible_pantry","address":"[fd00:1122:3344:10f::3]:17000"}}]},"root":"/pool/ext/11a63469-4f57-4976-8620-0055bf82dc97/crypt/zone"},{"zone":{"id":"e5d0170a-0d60-4c51-8f72-4c301979690e","zone_type":"crucible","addresses":["fd00:1122:3344:10f::6"],"dataset":{"id":"e5d0170a-0d60-4c51-8f72-4c301979690e","name":{"pool_name":"oxp_efe4cbab-2a39-4d7d-ae6c-83eb3ab8d4b5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::6]:32345"},"services":[{"id":"e5d0170a-0d60-4c51-8f72-4c301979690e","details":{"type":"crucible","address":"[fd00:1122:3344:10f::6]:32345"}}]},"root":"/pool/ext/6a73a62c-c636-4557-af45-042cb287aee6/crypt/zone"},{"zone":{"id":"ea6894de-c575-43bc-86e9-65b8a58499ff","zone_type":"crucible","addresses":["fd00:1122:3344:10f::a"],"dataset":{"id":"ea6894de-c575-43bc-86e9-65b8a58499ff","name":{"pool_name":"oxp_a87dc882-8b88-4a99-9628-5db79072cffa","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::a]:32345"},"services":[{"id":"ea6894de-c575-43bc-86e9-65b8a58499ff","details":{"type":"crucible","address":"[fd00:1122:3344:10f::a]:32345"}}]},"root":"/pool/ext/11a63469-4f57-4976-8620-0055bf82dc97/crypt/zone"},{"zone":{"id":"3081dc99-4fa9-4238-adfa-b9ca381c1f7b","zone_type":"crucible","addresses":["fd00:1122:3344:10f::b"],"dataset":{"id":"3081dc99-4fa9-4238-adfa-b9ca381c1f7b","name":{"pool_name":"oxp_6a73a62c-c636-4557-af45-042cb287aee6","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::b]:32345"},"services":[{"id":"3081dc99-4fa9-4238-adfa-b9ca381c1f7b","details":{"type":"crucible","address":"[fd00:1122:3344:10f::b]:32345"}}]},"root":"/pool/ext/a87dc882-8b88-4a99-9628-5db79072cffa/crypt/zone"},{"zone":{"id":"b4a3d7c8-487d-4d76-ae4e-a6a51595a5a6","zone_type":"crucible","addresses":["fd00:1122:3344:10f::d"],"dataset":{"id":"b4a3d7c8-487d-4d76-ae4e-a6a51595a5a6","name":{"pool_name":"oxp_a12f87ee-9918-4269-9de4-4bad4fb41caa","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::d]:32345"},"services":[{"id":"b4a3d7c8-487d-4d76-ae4e-a6a51595a5a6","details":{"type":"crucible","address":"[fd00:1122:3344:10f::d]:32345"}}]},"root":"/pool/ext/a12f87ee-9918-4269-9de4-4bad4fb41caa/crypt/zone"},{"zone":{"id":"5ebcee26-f76c-4206-8d81-584ac138d3b9","zone_type":"crucible","addresses":["fd00:1122:3344:10f::4"],"dataset":{"id":"5ebcee26-f76c-4206-8d81-584ac138d3b9","name":{"pool_name":"oxp_27f1917e-fb69-496a-9d40-8ef0d0c0ee55","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::4]:32345"},"services":[{"id":"5ebcee26-f76c-4206-8d81-584ac138d3b9","details":{"type":"crucible","address":"[fd00:1122:3344:10f::4]:32345"}}]},"root":"/pool/ext/58ae04cb-26ff-4e30-a20d-9f847bafba4d/crypt/zone"},{"zone":{"id":"90b2bc57-3a2a-4117-bb6d-7eda7542329a","zone_type":"crucible","addresses":["fd00:1122:3344:10f::5"],"dataset":{"id":"90b2bc57-3a2a-4117-bb6d-7eda7542329a","name":{"pool_name":"oxp_a222e405-40f6-4fdd-9146-94f7d94ed08a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10f::5]:32345"},"services":[{"id":"90b2bc57-3a2a-4117-bb6d-7eda7542329a","details":{"type":"crucible","address":"[fd00:1122:3344:10f::5]:32345"}}]},"root":"/pool/ext/a12f87ee-9918-4269-9de4-4bad4fb41caa/crypt/zone"},{"zone":{"id":"0fb540af-58d3-4abc-bfad-e49765c2b1ee","zone_type":"ntp","addresses":["fd00:1122:3344:10f::e"],"dataset":null,"services":[{"id":"0fb540af-58d3-4abc-bfad-e49765c2b1ee","details":{"type":"internal_ntp","address":"[fd00:1122:3344:10f::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/58ae04cb-26ff-4e30-a20d-9f847bafba4d/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled30.json b/sled-agent/tests/old-service-ledgers/rack3-sled30.json new file mode 100644 index 0000000000..e919de3488 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled30.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"dda0f1c6-84a5-472c-b350-a799c8d3d0eb","zone_type":"crucible","addresses":["fd00:1122:3344:115::8"],"dataset":{"id":"dda0f1c6-84a5-472c-b350-a799c8d3d0eb","name":{"pool_name":"oxp_028b6c9e-5a0e-43d2-a8ed-a5946cf62924","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::8]:32345"},"services":[{"id":"dda0f1c6-84a5-472c-b350-a799c8d3d0eb","details":{"type":"crucible","address":"[fd00:1122:3344:115::8]:32345"}}]},"root":"/pool/ext/b8d84b9c-a65e-4c86-8196-69da5317ae63/crypt/zone"},{"zone":{"id":"157672f9-113f-48b7-9808-dff3c3e67dcd","zone_type":"crucible","addresses":["fd00:1122:3344:115::a"],"dataset":{"id":"157672f9-113f-48b7-9808-dff3c3e67dcd","name":{"pool_name":"oxp_4fdca201-b37e-4072-a1cc-3cb7705954eb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::a]:32345"},"services":[{"id":"157672f9-113f-48b7-9808-dff3c3e67dcd","details":{"type":"crucible","address":"[fd00:1122:3344:115::a]:32345"}}]},"root":"/pool/ext/b8d84b9c-a65e-4c86-8196-69da5317ae63/crypt/zone"},{"zone":{"id":"5a7d4f67-a70f-4d8b-8d35-4dc600991fb5","zone_type":"crucible","addresses":["fd00:1122:3344:115::5"],"dataset":{"id":"5a7d4f67-a70f-4d8b-8d35-4dc600991fb5","name":{"pool_name":"oxp_11a991e5-19a9-48b0-8186-34249ef67957","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::5]:32345"},"services":[{"id":"5a7d4f67-a70f-4d8b-8d35-4dc600991fb5","details":{"type":"crucible","address":"[fd00:1122:3344:115::5]:32345"}}]},"root":"/pool/ext/1e9c9764-aaa4-4681-b110-a937b4c52748/crypt/zone"},{"zone":{"id":"c7036645-b680-4816-834f-8ae1af24c159","zone_type":"crucible","addresses":["fd00:1122:3344:115::b"],"dataset":{"id":"c7036645-b680-4816-834f-8ae1af24c159","name":{"pool_name":"oxp_0780be56-c13d-4c6a-a1ac-37753a0da820","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::b]:32345"},"services":[{"id":"c7036645-b680-4816-834f-8ae1af24c159","details":{"type":"crucible","address":"[fd00:1122:3344:115::b]:32345"}}]},"root":"/pool/ext/80a8d756-ee22-4c88-8b5b-4a46f7eca249/crypt/zone"},{"zone":{"id":"45e47e4b-708f-40b5-a8c8-fbfd73696d45","zone_type":"crucible","addresses":["fd00:1122:3344:115::7"],"dataset":{"id":"45e47e4b-708f-40b5-a8c8-fbfd73696d45","name":{"pool_name":"oxp_80a8d756-ee22-4c88-8b5b-4a46f7eca249","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::7]:32345"},"services":[{"id":"45e47e4b-708f-40b5-a8c8-fbfd73696d45","details":{"type":"crucible","address":"[fd00:1122:3344:115::7]:32345"}}]},"root":"/pool/ext/4fdca201-b37e-4072-a1cc-3cb7705954eb/crypt/zone"},{"zone":{"id":"e805b0c1-3f80-49da-8dc1-caaf843e5003","zone_type":"crucible","addresses":["fd00:1122:3344:115::c"],"dataset":{"id":"e805b0c1-3f80-49da-8dc1-caaf843e5003","name":{"pool_name":"oxp_d54e1ed7-e589-4413-a487-6e9a257104e7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::c]:32345"},"services":[{"id":"e805b0c1-3f80-49da-8dc1-caaf843e5003","details":{"type":"crucible","address":"[fd00:1122:3344:115::c]:32345"}}]},"root":"/pool/ext/d54e1ed7-e589-4413-a487-6e9a257104e7/crypt/zone"},{"zone":{"id":"e47d3f81-3df6-4c35-bec6-41277bc74c07","zone_type":"crucible","addresses":["fd00:1122:3344:115::4"],"dataset":{"id":"e47d3f81-3df6-4c35-bec6-41277bc74c07","name":{"pool_name":"oxp_b8d84b9c-a65e-4c86-8196-69da5317ae63","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::4]:32345"},"services":[{"id":"e47d3f81-3df6-4c35-bec6-41277bc74c07","details":{"type":"crucible","address":"[fd00:1122:3344:115::4]:32345"}}]},"root":"/pool/ext/772b3aaa-3501-4dc7-9b3d-048b8b1f7970/crypt/zone"},{"zone":{"id":"2a796a69-b061-44c7-b2df-35bc611f10f5","zone_type":"crucible","addresses":["fd00:1122:3344:115::6"],"dataset":{"id":"2a796a69-b061-44c7-b2df-35bc611f10f5","name":{"pool_name":"oxp_73abe9e0-d38e-48fc-bdec-b094bfa5670d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::6]:32345"},"services":[{"id":"2a796a69-b061-44c7-b2df-35bc611f10f5","details":{"type":"crucible","address":"[fd00:1122:3344:115::6]:32345"}}]},"root":"/pool/ext/028b6c9e-5a0e-43d2-a8ed-a5946cf62924/crypt/zone"},{"zone":{"id":"4e1d2af1-8ef4-4762-aa80-b08da08b45bb","zone_type":"crucible","addresses":["fd00:1122:3344:115::3"],"dataset":{"id":"4e1d2af1-8ef4-4762-aa80-b08da08b45bb","name":{"pool_name":"oxp_772b3aaa-3501-4dc7-9b3d-048b8b1f7970","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::3]:32345"},"services":[{"id":"4e1d2af1-8ef4-4762-aa80-b08da08b45bb","details":{"type":"crucible","address":"[fd00:1122:3344:115::3]:32345"}}]},"root":"/pool/ext/d54e1ed7-e589-4413-a487-6e9a257104e7/crypt/zone"},{"zone":{"id":"fb1b10d5-b7cb-416d-98fc-b5d3bc02d495","zone_type":"crucible","addresses":["fd00:1122:3344:115::9"],"dataset":{"id":"fb1b10d5-b7cb-416d-98fc-b5d3bc02d495","name":{"pool_name":"oxp_1e9c9764-aaa4-4681-b110-a937b4c52748","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:115::9]:32345"},"services":[{"id":"fb1b10d5-b7cb-416d-98fc-b5d3bc02d495","details":{"type":"crucible","address":"[fd00:1122:3344:115::9]:32345"}}]},"root":"/pool/ext/b8d84b9c-a65e-4c86-8196-69da5317ae63/crypt/zone"},{"zone":{"id":"5155463c-8a09-45a5-ad1b-817f2e93b284","zone_type":"ntp","addresses":["fd00:1122:3344:115::d"],"dataset":null,"services":[{"id":"5155463c-8a09-45a5-ad1b-817f2e93b284","details":{"type":"internal_ntp","address":"[fd00:1122:3344:115::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/772b3aaa-3501-4dc7-9b3d-048b8b1f7970/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled31.json b/sled-agent/tests/old-service-ledgers/rack3-sled31.json new file mode 100644 index 0000000000..d984227227 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled31.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"a0eae689-8e6b-4297-bb3d-8b7ffc5c4a07","zone_type":"crucible","addresses":["fd00:1122:3344:102::c"],"dataset":{"id":"a0eae689-8e6b-4297-bb3d-8b7ffc5c4a07","name":{"pool_name":"oxp_274cb567-fd74-4e00-b9c7-6ca367b3fda4","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::c]:32345"},"services":[{"id":"a0eae689-8e6b-4297-bb3d-8b7ffc5c4a07","details":{"type":"crucible","address":"[fd00:1122:3344:102::c]:32345"}}]},"root":"/pool/ext/1443b190-de16-42b0-b881-e87e875dd507/crypt/zone"},{"zone":{"id":"9cea406d-451e-4328-9052-b58487f799a5","zone_type":"crucible","addresses":["fd00:1122:3344:102::b"],"dataset":{"id":"9cea406d-451e-4328-9052-b58487f799a5","name":{"pool_name":"oxp_89c7f72e-632c-462b-a515-01cd80683711","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::b]:32345"},"services":[{"id":"9cea406d-451e-4328-9052-b58487f799a5","details":{"type":"crucible","address":"[fd00:1122:3344:102::b]:32345"}}]},"root":"/pool/ext/274cb567-fd74-4e00-b9c7-6ca367b3fda4/crypt/zone"},{"zone":{"id":"9c7dad7e-7f60-4bf4-8efc-0883a17e7cf6","zone_type":"crucible","addresses":["fd00:1122:3344:102::6"],"dataset":{"id":"9c7dad7e-7f60-4bf4-8efc-0883a17e7cf6","name":{"pool_name":"oxp_2c8e5637-b989-4b8f-82ac-ff2e9102b560","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::6]:32345"},"services":[{"id":"9c7dad7e-7f60-4bf4-8efc-0883a17e7cf6","details":{"type":"crucible","address":"[fd00:1122:3344:102::6]:32345"}}]},"root":"/pool/ext/1443b190-de16-42b0-b881-e87e875dd507/crypt/zone"},{"zone":{"id":"73015cba-79c6-4a67-97d8-fa0819cbf750","zone_type":"crucible","addresses":["fd00:1122:3344:102::a"],"dataset":{"id":"73015cba-79c6-4a67-97d8-fa0819cbf750","name":{"pool_name":"oxp_fa62108e-f7bb-4f6d-86f3-8094a1ea8352","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::a]:32345"},"services":[{"id":"73015cba-79c6-4a67-97d8-fa0819cbf750","details":{"type":"crucible","address":"[fd00:1122:3344:102::a]:32345"}}]},"root":"/pool/ext/2c8e5637-b989-4b8f-82ac-ff2e9102b560/crypt/zone"},{"zone":{"id":"f9ca3097-072e-4e7f-9f50-eb7c7ae39b6f","zone_type":"crucible","addresses":["fd00:1122:3344:102::5"],"dataset":{"id":"f9ca3097-072e-4e7f-9f50-eb7c7ae39b6f","name":{"pool_name":"oxp_42c6602c-2ccf-48ce-8344-693c832fd693","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::5]:32345"},"services":[{"id":"f9ca3097-072e-4e7f-9f50-eb7c7ae39b6f","details":{"type":"crucible","address":"[fd00:1122:3344:102::5]:32345"}}]},"root":"/pool/ext/2c8e5637-b989-4b8f-82ac-ff2e9102b560/crypt/zone"},{"zone":{"id":"e7855e05-a125-4a80-ac2c-8a2db96e1bf8","zone_type":"crucible","addresses":["fd00:1122:3344:102::7"],"dataset":{"id":"e7855e05-a125-4a80-ac2c-8a2db96e1bf8","name":{"pool_name":"oxp_1f72afd3-d2aa-46a8-b81a-54dbcc2f6317","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::7]:32345"},"services":[{"id":"e7855e05-a125-4a80-ac2c-8a2db96e1bf8","details":{"type":"crucible","address":"[fd00:1122:3344:102::7]:32345"}}]},"root":"/pool/ext/42c6602c-2ccf-48ce-8344-693c832fd693/crypt/zone"},{"zone":{"id":"e5de9bc9-e996-4fea-8318-ad7a8a6be4a3","zone_type":"crucible","addresses":["fd00:1122:3344:102::4"],"dataset":{"id":"e5de9bc9-e996-4fea-8318-ad7a8a6be4a3","name":{"pool_name":"oxp_1443b190-de16-42b0-b881-e87e875dd507","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::4]:32345"},"services":[{"id":"e5de9bc9-e996-4fea-8318-ad7a8a6be4a3","details":{"type":"crucible","address":"[fd00:1122:3344:102::4]:32345"}}]},"root":"/pool/ext/89c7f72e-632c-462b-a515-01cd80683711/crypt/zone"},{"zone":{"id":"cd0d0aac-44ff-4566-9260-a64ae6cecef4","zone_type":"crucible","addresses":["fd00:1122:3344:102::8"],"dataset":{"id":"cd0d0aac-44ff-4566-9260-a64ae6cecef4","name":{"pool_name":"oxp_92c0d1f6-cb4d-4ddb-b5ba-979fb3491812","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::8]:32345"},"services":[{"id":"cd0d0aac-44ff-4566-9260-a64ae6cecef4","details":{"type":"crucible","address":"[fd00:1122:3344:102::8]:32345"}}]},"root":"/pool/ext/89c7f72e-632c-462b-a515-01cd80683711/crypt/zone"},{"zone":{"id":"a8230592-0e7a-46c8-a653-7587a27f05bf","zone_type":"crucible","addresses":["fd00:1122:3344:102::9"],"dataset":{"id":"a8230592-0e7a-46c8-a653-7587a27f05bf","name":{"pool_name":"oxp_1b7873de-99fd-454f-b576-bff695524133","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::9]:32345"},"services":[{"id":"a8230592-0e7a-46c8-a653-7587a27f05bf","details":{"type":"crucible","address":"[fd00:1122:3344:102::9]:32345"}}]},"root":"/pool/ext/92c0d1f6-cb4d-4ddb-b5ba-979fb3491812/crypt/zone"},{"zone":{"id":"c19ffbb1-4dc1-4825-a3cf-080e9b543b16","zone_type":"crucible","addresses":["fd00:1122:3344:102::d"],"dataset":{"id":"c19ffbb1-4dc1-4825-a3cf-080e9b543b16","name":{"pool_name":"oxp_67823df7-511c-4984-b98c-7a8f5c40c22d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:102::d]:32345"},"services":[{"id":"c19ffbb1-4dc1-4825-a3cf-080e9b543b16","details":{"type":"crucible","address":"[fd00:1122:3344:102::d]:32345"}}]},"root":"/pool/ext/1443b190-de16-42b0-b881-e87e875dd507/crypt/zone"},{"zone":{"id":"ff30fe7c-51f3-43b9-a788-d8f94a7bb028","zone_type":"cockroach_db","addresses":["fd00:1122:3344:102::3"],"dataset":{"id":"ff30fe7c-51f3-43b9-a788-d8f94a7bb028","name":{"pool_name":"oxp_1443b190-de16-42b0-b881-e87e875dd507","kind":{"type":"cockroach_db"}},"service_address":"[fd00:1122:3344:102::3]:32221"},"services":[{"id":"ff30fe7c-51f3-43b9-a788-d8f94a7bb028","details":{"type":"cockroach_db","address":"[fd00:1122:3344:102::3]:32221"}}]},"root":"/pool/ext/fa62108e-f7bb-4f6d-86f3-8094a1ea8352/crypt/zone"},{"zone":{"id":"16b50c55-8117-4efd-aabf-0273677b89d5","zone_type":"ntp","addresses":["fd00:1122:3344:102::e"],"dataset":null,"services":[{"id":"16b50c55-8117-4efd-aabf-0273677b89d5","details":{"type":"internal_ntp","address":"[fd00:1122:3344:102::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/fa62108e-f7bb-4f6d-86f3-8094a1ea8352/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled4.json b/sled-agent/tests/old-service-ledgers/rack3-sled4.json new file mode 100644 index 0000000000..e9e5ce5569 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled4.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"22452953-ee80-4659-a555-8e027bf205b0","zone_type":"crucible","addresses":["fd00:1122:3344:10c::4"],"dataset":{"id":"22452953-ee80-4659-a555-8e027bf205b0","name":{"pool_name":"oxp_92ba1667-a6f7-4913-9b00-14825384c7bf","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::4]:32345"},"services":[{"id":"22452953-ee80-4659-a555-8e027bf205b0","details":{"type":"crucible","address":"[fd00:1122:3344:10c::4]:32345"}}]},"root":"/pool/ext/ab62b941-5f84-42c7-929d-295b20efffe7/crypt/zone"},{"zone":{"id":"9a5a2fcf-44a0-4468-979a-a71686cef627","zone_type":"crucible","addresses":["fd00:1122:3344:10c::3"],"dataset":{"id":"9a5a2fcf-44a0-4468-979a-a71686cef627","name":{"pool_name":"oxp_dbfdc981-1b81-4d7d-9449-9530890b199a","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::3]:32345"},"services":[{"id":"9a5a2fcf-44a0-4468-979a-a71686cef627","details":{"type":"crucible","address":"[fd00:1122:3344:10c::3]:32345"}}]},"root":"/pool/ext/74ac4da9-cdae-4c08-8431-11211184aa09/crypt/zone"},{"zone":{"id":"a014f12e-2636-4258-af76-e01d9b8d1c1f","zone_type":"crucible","addresses":["fd00:1122:3344:10c::b"],"dataset":{"id":"a014f12e-2636-4258-af76-e01d9b8d1c1f","name":{"pool_name":"oxp_ab62b941-5f84-42c7-929d-295b20efffe7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::b]:32345"},"services":[{"id":"a014f12e-2636-4258-af76-e01d9b8d1c1f","details":{"type":"crucible","address":"[fd00:1122:3344:10c::b]:32345"}}]},"root":"/pool/ext/a624a843-1c4e-41c3-a1d2-4be7a6c57e9b/crypt/zone"},{"zone":{"id":"431768b8-26ba-4ab4-b616-9e183bb79b8b","zone_type":"crucible","addresses":["fd00:1122:3344:10c::7"],"dataset":{"id":"431768b8-26ba-4ab4-b616-9e183bb79b8b","name":{"pool_name":"oxp_7c121177-3210-4457-9b42-3657add6e166","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::7]:32345"},"services":[{"id":"431768b8-26ba-4ab4-b616-9e183bb79b8b","details":{"type":"crucible","address":"[fd00:1122:3344:10c::7]:32345"}}]},"root":"/pool/ext/74ac4da9-cdae-4c08-8431-11211184aa09/crypt/zone"},{"zone":{"id":"22992c56-bd5a-4d0f-86c5-d6f8e87b7bbb","zone_type":"crucible","addresses":["fd00:1122:3344:10c::9"],"dataset":{"id":"22992c56-bd5a-4d0f-86c5-d6f8e87b7bbb","name":{"pool_name":"oxp_842bdd28-196e-4b18-83db-68bd81176a44","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::9]:32345"},"services":[{"id":"22992c56-bd5a-4d0f-86c5-d6f8e87b7bbb","details":{"type":"crucible","address":"[fd00:1122:3344:10c::9]:32345"}}]},"root":"/pool/ext/74ac4da9-cdae-4c08-8431-11211184aa09/crypt/zone"},{"zone":{"id":"de376149-aa45-4660-9ae6-15e8ba4a4233","zone_type":"crucible","addresses":["fd00:1122:3344:10c::5"],"dataset":{"id":"de376149-aa45-4660-9ae6-15e8ba4a4233","name":{"pool_name":"oxp_25856a84-6707-4b94-81d1-b43d5bc990d7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::5]:32345"},"services":[{"id":"de376149-aa45-4660-9ae6-15e8ba4a4233","details":{"type":"crucible","address":"[fd00:1122:3344:10c::5]:32345"}}]},"root":"/pool/ext/7c121177-3210-4457-9b42-3657add6e166/crypt/zone"},{"zone":{"id":"ceeba69d-8c0a-47df-a37b-7f1b90f23016","zone_type":"crucible","addresses":["fd00:1122:3344:10c::a"],"dataset":{"id":"ceeba69d-8c0a-47df-a37b-7f1b90f23016","name":{"pool_name":"oxp_a624a843-1c4e-41c3-a1d2-4be7a6c57e9b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::a]:32345"},"services":[{"id":"ceeba69d-8c0a-47df-a37b-7f1b90f23016","details":{"type":"crucible","address":"[fd00:1122:3344:10c::a]:32345"}}]},"root":"/pool/ext/74ac4da9-cdae-4c08-8431-11211184aa09/crypt/zone"},{"zone":{"id":"65293ce4-2e63-4336-9207-3c61f58667f9","zone_type":"crucible","addresses":["fd00:1122:3344:10c::c"],"dataset":{"id":"65293ce4-2e63-4336-9207-3c61f58667f9","name":{"pool_name":"oxp_74ac4da9-cdae-4c08-8431-11211184aa09","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::c]:32345"},"services":[{"id":"65293ce4-2e63-4336-9207-3c61f58667f9","details":{"type":"crucible","address":"[fd00:1122:3344:10c::c]:32345"}}]},"root":"/pool/ext/842bdd28-196e-4b18-83db-68bd81176a44/crypt/zone"},{"zone":{"id":"e8f55a5d-65f9-436c-bc25-1d1a7070e876","zone_type":"crucible","addresses":["fd00:1122:3344:10c::6"],"dataset":{"id":"e8f55a5d-65f9-436c-bc25-1d1a7070e876","name":{"pool_name":"oxp_9bfe385c-16dd-4209-bc0b-f28ae75d58e3","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::6]:32345"},"services":[{"id":"e8f55a5d-65f9-436c-bc25-1d1a7070e876","details":{"type":"crucible","address":"[fd00:1122:3344:10c::6]:32345"}}]},"root":"/pool/ext/92ba1667-a6f7-4913-9b00-14825384c7bf/crypt/zone"},{"zone":{"id":"2dfbd4c6-afbf-4c8c-bf40-764f02727852","zone_type":"crucible","addresses":["fd00:1122:3344:10c::8"],"dataset":{"id":"2dfbd4c6-afbf-4c8c-bf40-764f02727852","name":{"pool_name":"oxp_55eb093d-6b6f-418c-9767-09afe4c51fff","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10c::8]:32345"},"services":[{"id":"2dfbd4c6-afbf-4c8c-bf40-764f02727852","details":{"type":"crucible","address":"[fd00:1122:3344:10c::8]:32345"}}]},"root":"/pool/ext/dbfdc981-1b81-4d7d-9449-9530890b199a/crypt/zone"},{"zone":{"id":"8c73baf7-1a58-4e2c-b4d1-966c89a18d03","zone_type":"ntp","addresses":["fd00:1122:3344:10c::d"],"dataset":null,"services":[{"id":"8c73baf7-1a58-4e2c-b4d1-966c89a18d03","details":{"type":"internal_ntp","address":"[fd00:1122:3344:10c::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/842bdd28-196e-4b18-83db-68bd81176a44/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled5.json b/sled-agent/tests/old-service-ledgers/rack3-sled5.json new file mode 100644 index 0000000000..ea7b5ec40a --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled5.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"2f488e7b-fd93-48a6-8b2b-61f6e8336268","zone_type":"crucible","addresses":["fd00:1122:3344:101::b"],"dataset":{"id":"2f488e7b-fd93-48a6-8b2b-61f6e8336268","name":{"pool_name":"oxp_5840a3b7-f765-45d3-8a41-7f543f936bee","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::b]:32345"},"services":[{"id":"2f488e7b-fd93-48a6-8b2b-61f6e8336268","details":{"type":"crucible","address":"[fd00:1122:3344:101::b]:32345"}}]},"root":"/pool/ext/dd084b76-1130-4ad3-9196-6b02be607fe9/crypt/zone"},{"zone":{"id":"1ed5fd3f-933a-4921-a91f-5c286823f8d4","zone_type":"crucible","addresses":["fd00:1122:3344:101::a"],"dataset":{"id":"1ed5fd3f-933a-4921-a91f-5c286823f8d4","name":{"pool_name":"oxp_c1e807e7-b64a-4dbd-b845-ffed0b9a54f1","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::a]:32345"},"services":[{"id":"1ed5fd3f-933a-4921-a91f-5c286823f8d4","details":{"type":"crucible","address":"[fd00:1122:3344:101::a]:32345"}}]},"root":"/pool/ext/be06ea9c-df86-4fec-b5dd-8809710893af/crypt/zone"},{"zone":{"id":"0f8f1013-465d-4b49-b55d-f0b9bf6f789a","zone_type":"crucible","addresses":["fd00:1122:3344:101::6"],"dataset":{"id":"0f8f1013-465d-4b49-b55d-f0b9bf6f789a","name":{"pool_name":"oxp_4dfa7003-0305-47f5-b23d-88a228c1e12e","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::6]:32345"},"services":[{"id":"0f8f1013-465d-4b49-b55d-f0b9bf6f789a","details":{"type":"crucible","address":"[fd00:1122:3344:101::6]:32345"}}]},"root":"/pool/ext/be06ea9c-df86-4fec-b5dd-8809710893af/crypt/zone"},{"zone":{"id":"2e4ef017-6c62-40bc-bab5-f2e01addad22","zone_type":"crucible","addresses":["fd00:1122:3344:101::7"],"dataset":{"id":"2e4ef017-6c62-40bc-bab5-f2e01addad22","name":{"pool_name":"oxp_d94e9c58-e6d1-444b-b7d8-19ac17dea042","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::7]:32345"},"services":[{"id":"2e4ef017-6c62-40bc-bab5-f2e01addad22","details":{"type":"crucible","address":"[fd00:1122:3344:101::7]:32345"}}]},"root":"/pool/ext/c1e807e7-b64a-4dbd-b845-ffed0b9a54f1/crypt/zone"},{"zone":{"id":"6a0baf13-a80b-4778-a0ab-a69cd851de2d","zone_type":"crucible","addresses":["fd00:1122:3344:101::9"],"dataset":{"id":"6a0baf13-a80b-4778-a0ab-a69cd851de2d","name":{"pool_name":"oxp_be06ea9c-df86-4fec-b5dd-8809710893af","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::9]:32345"},"services":[{"id":"6a0baf13-a80b-4778-a0ab-a69cd851de2d","details":{"type":"crucible","address":"[fd00:1122:3344:101::9]:32345"}}]},"root":"/pool/ext/a9d419d4-5915-4a40-baa3-3512785de034/crypt/zone"},{"zone":{"id":"391ec257-fd47-4cc8-9bfa-49a0747a9a67","zone_type":"crucible","addresses":["fd00:1122:3344:101::8"],"dataset":{"id":"391ec257-fd47-4cc8-9bfa-49a0747a9a67","name":{"pool_name":"oxp_a9d419d4-5915-4a40-baa3-3512785de034","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::8]:32345"},"services":[{"id":"391ec257-fd47-4cc8-9bfa-49a0747a9a67","details":{"type":"crucible","address":"[fd00:1122:3344:101::8]:32345"}}]},"root":"/pool/ext/709d5d04-5dff-4558-8b5d-fbc2a7d83036/crypt/zone"},{"zone":{"id":"fd8e615a-f170-4da9-b8d0-2a5a123d8682","zone_type":"crucible_pantry","addresses":["fd00:1122:3344:101::3"],"dataset":null,"services":[{"id":"fd8e615a-f170-4da9-b8d0-2a5a123d8682","details":{"type":"crucible_pantry","address":"[fd00:1122:3344:101::3]:17000"}}]},"root":"/pool/ext/dd084b76-1130-4ad3-9196-6b02be607fe9/crypt/zone"},{"zone":{"id":"f8a793f4-cd08-49ec-8fee-6bcd37092fdc","zone_type":"crucible","addresses":["fd00:1122:3344:101::c"],"dataset":{"id":"f8a793f4-cd08-49ec-8fee-6bcd37092fdc","name":{"pool_name":"oxp_709d5d04-5dff-4558-8b5d-fbc2a7d83036","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::c]:32345"},"services":[{"id":"f8a793f4-cd08-49ec-8fee-6bcd37092fdc","details":{"type":"crucible","address":"[fd00:1122:3344:101::c]:32345"}}]},"root":"/pool/ext/d94e9c58-e6d1-444b-b7d8-19ac17dea042/crypt/zone"},{"zone":{"id":"c67d44be-d6b8-4a08-a7e0-3ab300749ad6","zone_type":"crucible","addresses":["fd00:1122:3344:101::4"],"dataset":{"id":"c67d44be-d6b8-4a08-a7e0-3ab300749ad6","name":{"pool_name":"oxp_231cd696-2839-4a9a-ae42-6d875a98a797","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::4]:32345"},"services":[{"id":"c67d44be-d6b8-4a08-a7e0-3ab300749ad6","details":{"type":"crucible","address":"[fd00:1122:3344:101::4]:32345"}}]},"root":"/pool/ext/709d5d04-5dff-4558-8b5d-fbc2a7d83036/crypt/zone"},{"zone":{"id":"e91b4957-8165-451d-9fa5-090c3a39f199","zone_type":"crucible","addresses":["fd00:1122:3344:101::d"],"dataset":{"id":"e91b4957-8165-451d-9fa5-090c3a39f199","name":{"pool_name":"oxp_dd084b76-1130-4ad3-9196-6b02be607fe9","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::d]:32345"},"services":[{"id":"e91b4957-8165-451d-9fa5-090c3a39f199","details":{"type":"crucible","address":"[fd00:1122:3344:101::d]:32345"}}]},"root":"/pool/ext/5840a3b7-f765-45d3-8a41-7f543f936bee/crypt/zone"},{"zone":{"id":"5e737b6e-d33d-4a2c-b8c0-3cad9d05a68f","zone_type":"crucible","addresses":["fd00:1122:3344:101::5"],"dataset":{"id":"5e737b6e-d33d-4a2c-b8c0-3cad9d05a68f","name":{"pool_name":"oxp_8fa4f837-c6f3-4c65-88d4-21eb3cd7ffee","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:101::5]:32345"},"services":[{"id":"5e737b6e-d33d-4a2c-b8c0-3cad9d05a68f","details":{"type":"crucible","address":"[fd00:1122:3344:101::5]:32345"}}]},"root":"/pool/ext/dd084b76-1130-4ad3-9196-6b02be607fe9/crypt/zone"},{"zone":{"id":"7e6b7816-b1a6-40f3-894a-a5d5c0571dbb","zone_type":"ntp","addresses":["fd00:1122:3344:101::e"],"dataset":null,"services":[{"id":"7e6b7816-b1a6-40f3-894a-a5d5c0571dbb","details":{"type":"internal_ntp","address":"[fd00:1122:3344:101::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/be06ea9c-df86-4fec-b5dd-8809710893af/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled6.json b/sled-agent/tests/old-service-ledgers/rack3-sled6.json new file mode 100644 index 0000000000..2c499813cd --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled6.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"eafffae7-69fd-49e1-9541-7cf237ab12b3","zone_type":"crucible","addresses":["fd00:1122:3344:110::3"],"dataset":{"id":"eafffae7-69fd-49e1-9541-7cf237ab12b3","name":{"pool_name":"oxp_929404cd-2522-4440-b21c-91d466a9a7e0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::3]:32345"},"services":[{"id":"eafffae7-69fd-49e1-9541-7cf237ab12b3","details":{"type":"crucible","address":"[fd00:1122:3344:110::3]:32345"}}]},"root":"/pool/ext/aff390ed-8d70-49fa-9000-5420b54ab118/crypt/zone"},{"zone":{"id":"f4bccf15-d69f-402d-9bd2-7959a4cb2823","zone_type":"crucible","addresses":["fd00:1122:3344:110::9"],"dataset":{"id":"f4bccf15-d69f-402d-9bd2-7959a4cb2823","name":{"pool_name":"oxp_f80f96be-a3d7-490a-96a7-faf7da80a579","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::9]:32345"},"services":[{"id":"f4bccf15-d69f-402d-9bd2-7959a4cb2823","details":{"type":"crucible","address":"[fd00:1122:3344:110::9]:32345"}}]},"root":"/pool/ext/6bcd54c8-d4a8-429d-8f17-cf02615eb063/crypt/zone"},{"zone":{"id":"82e51c9d-c187-4baa-8307-e46eeafc5ff2","zone_type":"crucible","addresses":["fd00:1122:3344:110::5"],"dataset":{"id":"82e51c9d-c187-4baa-8307-e46eeafc5ff2","name":{"pool_name":"oxp_37d86199-6834-49d9-888a-88ff6f281b29","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::5]:32345"},"services":[{"id":"82e51c9d-c187-4baa-8307-e46eeafc5ff2","details":{"type":"crucible","address":"[fd00:1122:3344:110::5]:32345"}}]},"root":"/pool/ext/d2e27e2a-2deb-42ae-84a7-c2d06f3aeb4f/crypt/zone"},{"zone":{"id":"cf667caf-304c-40c4-acce-f0eb05d011ef","zone_type":"crucible","addresses":["fd00:1122:3344:110::8"],"dataset":{"id":"cf667caf-304c-40c4-acce-f0eb05d011ef","name":{"pool_name":"oxp_625c0110-644e-4d63-8321-b85ab5642260","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::8]:32345"},"services":[{"id":"cf667caf-304c-40c4-acce-f0eb05d011ef","details":{"type":"crucible","address":"[fd00:1122:3344:110::8]:32345"}}]},"root":"/pool/ext/d2e27e2a-2deb-42ae-84a7-c2d06f3aeb4f/crypt/zone"},{"zone":{"id":"14e60912-108e-4dd3-984e-2332a183b346","zone_type":"crucible","addresses":["fd00:1122:3344:110::b"],"dataset":{"id":"14e60912-108e-4dd3-984e-2332a183b346","name":{"pool_name":"oxp_fa6470f5-0a4c-4fef-b0b1-57c8749c6cca","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::b]:32345"},"services":[{"id":"14e60912-108e-4dd3-984e-2332a183b346","details":{"type":"crucible","address":"[fd00:1122:3344:110::b]:32345"}}]},"root":"/pool/ext/6c5ab641-3bd4-4d8c-96f4-4f56c1045142/crypt/zone"},{"zone":{"id":"1aacf923-c96f-4bab-acb0-63f28e86eef6","zone_type":"crucible","addresses":["fd00:1122:3344:110::c"],"dataset":{"id":"1aacf923-c96f-4bab-acb0-63f28e86eef6","name":{"pool_name":"oxp_21b0f3ed-d27f-4996-968b-bf2b494d9308","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::c]:32345"},"services":[{"id":"1aacf923-c96f-4bab-acb0-63f28e86eef6","details":{"type":"crucible","address":"[fd00:1122:3344:110::c]:32345"}}]},"root":"/pool/ext/625c0110-644e-4d63-8321-b85ab5642260/crypt/zone"},{"zone":{"id":"b9db0845-04d3-4dc1-84ba-224749562a6c","zone_type":"crucible","addresses":["fd00:1122:3344:110::6"],"dataset":{"id":"b9db0845-04d3-4dc1-84ba-224749562a6c","name":{"pool_name":"oxp_d2e27e2a-2deb-42ae-84a7-c2d06f3aeb4f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::6]:32345"},"services":[{"id":"b9db0845-04d3-4dc1-84ba-224749562a6c","details":{"type":"crucible","address":"[fd00:1122:3344:110::6]:32345"}}]},"root":"/pool/ext/aff390ed-8d70-49fa-9000-5420b54ab118/crypt/zone"},{"zone":{"id":"38b51865-ee80-4e1b-a40b-3452951f9022","zone_type":"crucible","addresses":["fd00:1122:3344:110::7"],"dataset":{"id":"38b51865-ee80-4e1b-a40b-3452951f9022","name":{"pool_name":"oxp_6bcd54c8-d4a8-429d-8f17-cf02615eb063","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::7]:32345"},"services":[{"id":"38b51865-ee80-4e1b-a40b-3452951f9022","details":{"type":"crucible","address":"[fd00:1122:3344:110::7]:32345"}}]},"root":"/pool/ext/37d86199-6834-49d9-888a-88ff6f281b29/crypt/zone"},{"zone":{"id":"4bc441f6-f7e5-4d68-8751-53ef1e251c47","zone_type":"crucible","addresses":["fd00:1122:3344:110::a"],"dataset":{"id":"4bc441f6-f7e5-4d68-8751-53ef1e251c47","name":{"pool_name":"oxp_6c5ab641-3bd4-4d8c-96f4-4f56c1045142","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::a]:32345"},"services":[{"id":"4bc441f6-f7e5-4d68-8751-53ef1e251c47","details":{"type":"crucible","address":"[fd00:1122:3344:110::a]:32345"}}]},"root":"/pool/ext/21b0f3ed-d27f-4996-968b-bf2b494d9308/crypt/zone"},{"zone":{"id":"d2c20cf8-ed4c-4815-add9-45996364f721","zone_type":"crucible","addresses":["fd00:1122:3344:110::4"],"dataset":{"id":"d2c20cf8-ed4c-4815-add9-45996364f721","name":{"pool_name":"oxp_aff390ed-8d70-49fa-9000-5420b54ab118","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:110::4]:32345"},"services":[{"id":"d2c20cf8-ed4c-4815-add9-45996364f721","details":{"type":"crucible","address":"[fd00:1122:3344:110::4]:32345"}}]},"root":"/pool/ext/6c5ab641-3bd4-4d8c-96f4-4f56c1045142/crypt/zone"},{"zone":{"id":"1bb548cb-889a-411e-8c67-d1b785225180","zone_type":"ntp","addresses":["fd00:1122:3344:110::d"],"dataset":null,"services":[{"id":"1bb548cb-889a-411e-8c67-d1b785225180","details":{"type":"internal_ntp","address":"[fd00:1122:3344:110::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/6bcd54c8-d4a8-429d-8f17-cf02615eb063/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled7.json b/sled-agent/tests/old-service-ledgers/rack3-sled7.json new file mode 100644 index 0000000000..fb701a2bdb --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled7.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"2eb74fa3-71ec-484c-8ffa-3daeab0e4c78","zone_type":"crucible","addresses":["fd00:1122:3344:11d::3"],"dataset":{"id":"2eb74fa3-71ec-484c-8ffa-3daeab0e4c78","name":{"pool_name":"oxp_c6b63fea-e3e2-4806-b8dc-bdfe7b5c3d89","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::3]:32345"},"services":[{"id":"2eb74fa3-71ec-484c-8ffa-3daeab0e4c78","details":{"type":"crucible","address":"[fd00:1122:3344:11d::3]:32345"}}]},"root":"/pool/ext/9f20cbae-7a63-4c31-9386-2ac3cbe12030/crypt/zone"},{"zone":{"id":"9f92bfcf-7435-44a6-8e77-0597f93cd0b4","zone_type":"crucible","addresses":["fd00:1122:3344:11d::7"],"dataset":{"id":"9f92bfcf-7435-44a6-8e77-0597f93cd0b4","name":{"pool_name":"oxp_9fa336f1-2b69-4ebf-9553-e3bab7e3e6ef","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::7]:32345"},"services":[{"id":"9f92bfcf-7435-44a6-8e77-0597f93cd0b4","details":{"type":"crucible","address":"[fd00:1122:3344:11d::7]:32345"}}]},"root":"/pool/ext/e05a6264-63f2-4961-bc14-57b4f65614c0/crypt/zone"},{"zone":{"id":"1bf9aed4-9fd3-4d87-b8e7-7f066d25ec1d","zone_type":"crucible","addresses":["fd00:1122:3344:11d::b"],"dataset":{"id":"1bf9aed4-9fd3-4d87-b8e7-7f066d25ec1d","name":{"pool_name":"oxp_a5a52f47-9c9a-4519-83dc-abc56619495d","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::b]:32345"},"services":[{"id":"1bf9aed4-9fd3-4d87-b8e7-7f066d25ec1d","details":{"type":"crucible","address":"[fd00:1122:3344:11d::b]:32345"}}]},"root":"/pool/ext/cbcad26e-5e52-41b7-9875-1a84d30d8a15/crypt/zone"},{"zone":{"id":"2a722aa7-cd8a-445d-83fe-57fc9b9a8249","zone_type":"crucible","addresses":["fd00:1122:3344:11d::8"],"dataset":{"id":"2a722aa7-cd8a-445d-83fe-57fc9b9a8249","name":{"pool_name":"oxp_1f4b71eb-505f-4706-912c-b13dd3f2eafb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::8]:32345"},"services":[{"id":"2a722aa7-cd8a-445d-83fe-57fc9b9a8249","details":{"type":"crucible","address":"[fd00:1122:3344:11d::8]:32345"}}]},"root":"/pool/ext/a5a52f47-9c9a-4519-83dc-abc56619495d/crypt/zone"},{"zone":{"id":"76af5b23-d833-435c-b848-2a09d9fad9a1","zone_type":"crucible","addresses":["fd00:1122:3344:11d::c"],"dataset":{"id":"76af5b23-d833-435c-b848-2a09d9fad9a1","name":{"pool_name":"oxp_cbcad26e-5e52-41b7-9875-1a84d30d8a15","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::c]:32345"},"services":[{"id":"76af5b23-d833-435c-b848-2a09d9fad9a1","details":{"type":"crucible","address":"[fd00:1122:3344:11d::c]:32345"}}]},"root":"/pool/ext/9f20cbae-7a63-4c31-9386-2ac3cbe12030/crypt/zone"},{"zone":{"id":"3a412bf4-a385-4e66-9ada-a87f6536d6ca","zone_type":"crucible","addresses":["fd00:1122:3344:11d::4"],"dataset":{"id":"3a412bf4-a385-4e66-9ada-a87f6536d6ca","name":{"pool_name":"oxp_e05a6264-63f2-4961-bc14-57b4f65614c0","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::4]:32345"},"services":[{"id":"3a412bf4-a385-4e66-9ada-a87f6536d6ca","details":{"type":"crucible","address":"[fd00:1122:3344:11d::4]:32345"}}]},"root":"/pool/ext/e05a6264-63f2-4961-bc14-57b4f65614c0/crypt/zone"},{"zone":{"id":"99a25fa7-8231-4a46-a6ec-ffc5281db1f8","zone_type":"crucible","addresses":["fd00:1122:3344:11d::5"],"dataset":{"id":"99a25fa7-8231-4a46-a6ec-ffc5281db1f8","name":{"pool_name":"oxp_722494ab-9a2b-481b-ac11-292fded682a5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::5]:32345"},"services":[{"id":"99a25fa7-8231-4a46-a6ec-ffc5281db1f8","details":{"type":"crucible","address":"[fd00:1122:3344:11d::5]:32345"}}]},"root":"/pool/ext/e05a6264-63f2-4961-bc14-57b4f65614c0/crypt/zone"},{"zone":{"id":"06c7ddc8-9b3e-48ef-9874-0c40874e9877","zone_type":"crucible","addresses":["fd00:1122:3344:11d::a"],"dataset":{"id":"06c7ddc8-9b3e-48ef-9874-0c40874e9877","name":{"pool_name":"oxp_8c3972d1-5b17-4479-88cc-1c33e4344160","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::a]:32345"},"services":[{"id":"06c7ddc8-9b3e-48ef-9874-0c40874e9877","details":{"type":"crucible","address":"[fd00:1122:3344:11d::a]:32345"}}]},"root":"/pool/ext/8c3972d1-5b17-4479-88cc-1c33e4344160/crypt/zone"},{"zone":{"id":"1212b2dc-157d-4bd3-94af-fb5db1d91f24","zone_type":"crucible","addresses":["fd00:1122:3344:11d::9"],"dataset":{"id":"1212b2dc-157d-4bd3-94af-fb5db1d91f24","name":{"pool_name":"oxp_9f20cbae-7a63-4c31-9386-2ac3cbe12030","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::9]:32345"},"services":[{"id":"1212b2dc-157d-4bd3-94af-fb5db1d91f24","details":{"type":"crucible","address":"[fd00:1122:3344:11d::9]:32345"}}]},"root":"/pool/ext/977aa6c3-2026-4178-9948-e09f78008575/crypt/zone"},{"zone":{"id":"b1fb5f2e-b20d-4f4c-9f6f-bbeb1a98dd50","zone_type":"crucible","addresses":["fd00:1122:3344:11d::6"],"dataset":{"id":"b1fb5f2e-b20d-4f4c-9f6f-bbeb1a98dd50","name":{"pool_name":"oxp_977aa6c3-2026-4178-9948-e09f78008575","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:11d::6]:32345"},"services":[{"id":"b1fb5f2e-b20d-4f4c-9f6f-bbeb1a98dd50","details":{"type":"crucible","address":"[fd00:1122:3344:11d::6]:32345"}}]},"root":"/pool/ext/722494ab-9a2b-481b-ac11-292fded682a5/crypt/zone"},{"zone":{"id":"e68dde0f-0647-46db-ae1c-711835c13e25","zone_type":"ntp","addresses":["fd00:1122:3344:11d::d"],"dataset":null,"services":[{"id":"e68dde0f-0647-46db-ae1c-711835c13e25","details":{"type":"internal_ntp","address":"[fd00:1122:3344:11d::d]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/1f4b71eb-505f-4706-912c-b13dd3f2eafb/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled8.json b/sled-agent/tests/old-service-ledgers/rack3-sled8.json new file mode 100644 index 0000000000..cf96f8ae81 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled8.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"85c18b7c-a100-458c-b18d-ecfdacaefac4","zone_type":"crucible","addresses":["fd00:1122:3344:10e::5"],"dataset":{"id":"85c18b7c-a100-458c-b18d-ecfdacaefac4","name":{"pool_name":"oxp_07b266bc-86c3-4a76-9522-8b34ba1ae78c","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::5]:32345"},"services":[{"id":"85c18b7c-a100-458c-b18d-ecfdacaefac4","details":{"type":"crucible","address":"[fd00:1122:3344:10e::5]:32345"}}]},"root":"/pool/ext/5b88e44e-f886-4de8-8a6b-48ea5ed9d70b/crypt/zone"},{"zone":{"id":"db303465-7879-4d86-8da8-a0c7162e5184","zone_type":"crucible","addresses":["fd00:1122:3344:10e::4"],"dataset":{"id":"db303465-7879-4d86-8da8-a0c7162e5184","name":{"pool_name":"oxp_e9488a32-880d-44a2-8948-db0b7e3a35b5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::4]:32345"},"services":[{"id":"db303465-7879-4d86-8da8-a0c7162e5184","details":{"type":"crucible","address":"[fd00:1122:3344:10e::4]:32345"}}]},"root":"/pool/ext/8d798756-7200-4db4-9faf-f41b75106a63/crypt/zone"},{"zone":{"id":"c44ce6be-512d-4104-9260-a5b8fe373937","zone_type":"crucible","addresses":["fd00:1122:3344:10e::9"],"dataset":{"id":"c44ce6be-512d-4104-9260-a5b8fe373937","name":{"pool_name":"oxp_025dfc06-5aeb-407f-adc8-ba18dc9bba35","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::9]:32345"},"services":[{"id":"c44ce6be-512d-4104-9260-a5b8fe373937","details":{"type":"crucible","address":"[fd00:1122:3344:10e::9]:32345"}}]},"root":"/pool/ext/1544ce68-3544-4cba-b3b6-1927d08b78a5/crypt/zone"},{"zone":{"id":"1cfdb5b6-e568-436a-a85f-7fecf1b8eef2","zone_type":"nexus","addresses":["fd00:1122:3344:10e::3"],"dataset":null,"services":[{"id":"1cfdb5b6-e568-436a-a85f-7fecf1b8eef2","details":{"type":"nexus","internal_address":"[fd00:1122:3344:10e::3]:12221","external_ip":"45.154.216.36","nic":{"id":"569754a2-a5e0-4aa8-90a7-2fa65f43b667","kind":{"type":"service","id":"1cfdb5b6-e568-436a-a85f-7fecf1b8eef2"},"name":"nexus-1cfdb5b6-e568-436a-a85f-7fecf1b8eef2","ip":"172.30.2.6","mac":"A8:40:25:FF:EC:6B","subnet":"172.30.2.0/24","vni":100,"primary":true,"slot":0},"external_tls":true,"external_dns_servers":["1.1.1.1","8.8.8.8"]}}]},"root":"/pool/ext/025dfc06-5aeb-407f-adc8-ba18dc9bba35/crypt/zone"},{"zone":{"id":"44a68792-ca14-442e-b7a9-11970d50ba0e","zone_type":"crucible","addresses":["fd00:1122:3344:10e::a"],"dataset":{"id":"44a68792-ca14-442e-b7a9-11970d50ba0e","name":{"pool_name":"oxp_2a492098-7df3-4409-9466-561edb7aa99b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::a]:32345"},"services":[{"id":"44a68792-ca14-442e-b7a9-11970d50ba0e","details":{"type":"crucible","address":"[fd00:1122:3344:10e::a]:32345"}}]},"root":"/pool/ext/1544ce68-3544-4cba-b3b6-1927d08b78a5/crypt/zone"},{"zone":{"id":"514cf0ca-6d23-434e-9785-446b83b2f029","zone_type":"crucible","addresses":["fd00:1122:3344:10e::7"],"dataset":{"id":"514cf0ca-6d23-434e-9785-446b83b2f029","name":{"pool_name":"oxp_5b88e44e-f886-4de8-8a6b-48ea5ed9d70b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::7]:32345"},"services":[{"id":"514cf0ca-6d23-434e-9785-446b83b2f029","details":{"type":"crucible","address":"[fd00:1122:3344:10e::7]:32345"}}]},"root":"/pool/ext/5b88e44e-f886-4de8-8a6b-48ea5ed9d70b/crypt/zone"},{"zone":{"id":"bc6d8347-8f64-4031-912c-932349df07fe","zone_type":"crucible","addresses":["fd00:1122:3344:10e::6"],"dataset":{"id":"bc6d8347-8f64-4031-912c-932349df07fe","name":{"pool_name":"oxp_1544ce68-3544-4cba-b3b6-1927d08b78a5","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::6]:32345"},"services":[{"id":"bc6d8347-8f64-4031-912c-932349df07fe","details":{"type":"crucible","address":"[fd00:1122:3344:10e::6]:32345"}}]},"root":"/pool/ext/1544ce68-3544-4cba-b3b6-1927d08b78a5/crypt/zone"},{"zone":{"id":"1ab0a4f5-99ad-4341-8c89-7fd03e5ccb08","zone_type":"crucible","addresses":["fd00:1122:3344:10e::b"],"dataset":{"id":"1ab0a4f5-99ad-4341-8c89-7fd03e5ccb08","name":{"pool_name":"oxp_033eb462-968f-42ce-9c29-377bd40a3014","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::b]:32345"},"services":[{"id":"1ab0a4f5-99ad-4341-8c89-7fd03e5ccb08","details":{"type":"crucible","address":"[fd00:1122:3344:10e::b]:32345"}}]},"root":"/pool/ext/9e1a0803-7453-4eac-91c9-d7891ecd634f/crypt/zone"},{"zone":{"id":"d6f2520b-3d04-44d9-bd46-6ffccfcb46d2","zone_type":"crucible","addresses":["fd00:1122:3344:10e::8"],"dataset":{"id":"d6f2520b-3d04-44d9-bd46-6ffccfcb46d2","name":{"pool_name":"oxp_36e8d29c-1e88-4c2b-8f59-f312201067c3","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::8]:32345"},"services":[{"id":"d6f2520b-3d04-44d9-bd46-6ffccfcb46d2","details":{"type":"crucible","address":"[fd00:1122:3344:10e::8]:32345"}}]},"root":"/pool/ext/1544ce68-3544-4cba-b3b6-1927d08b78a5/crypt/zone"},{"zone":{"id":"d6da9d13-bfcf-469d-a99e-faeb5e30be32","zone_type":"crucible","addresses":["fd00:1122:3344:10e::c"],"dataset":{"id":"d6da9d13-bfcf-469d-a99e-faeb5e30be32","name":{"pool_name":"oxp_9e1a0803-7453-4eac-91c9-d7891ecd634f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::c]:32345"},"services":[{"id":"d6da9d13-bfcf-469d-a99e-faeb5e30be32","details":{"type":"crucible","address":"[fd00:1122:3344:10e::c]:32345"}}]},"root":"/pool/ext/8d798756-7200-4db4-9faf-f41b75106a63/crypt/zone"},{"zone":{"id":"a1dc59c2-5883-4fb8-83be-ac2d95d255d1","zone_type":"crucible","addresses":["fd00:1122:3344:10e::d"],"dataset":{"id":"a1dc59c2-5883-4fb8-83be-ac2d95d255d1","name":{"pool_name":"oxp_8d798756-7200-4db4-9faf-f41b75106a63","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10e::d]:32345"},"services":[{"id":"a1dc59c2-5883-4fb8-83be-ac2d95d255d1","details":{"type":"crucible","address":"[fd00:1122:3344:10e::d]:32345"}}]},"root":"/pool/ext/36e8d29c-1e88-4c2b-8f59-f312201067c3/crypt/zone"},{"zone":{"id":"48f25dba-7392-44ce-9bb0-28489ebc44bc","zone_type":"ntp","addresses":["fd00:1122:3344:10e::e"],"dataset":null,"services":[{"id":"48f25dba-7392-44ce-9bb0-28489ebc44bc","details":{"type":"internal_ntp","address":"[fd00:1122:3344:10e::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/5b88e44e-f886-4de8-8a6b-48ea5ed9d70b/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/old-service-ledgers/rack3-sled9.json b/sled-agent/tests/old-service-ledgers/rack3-sled9.json new file mode 100644 index 0000000000..c225f50081 --- /dev/null +++ b/sled-agent/tests/old-service-ledgers/rack3-sled9.json @@ -0,0 +1 @@ +{"generation":4,"requests":[{"zone":{"id":"b452e5e1-ab4c-4994-9679-ef21b3b4fee9","zone_type":"crucible","addresses":["fd00:1122:3344:10b::6"],"dataset":{"id":"b452e5e1-ab4c-4994-9679-ef21b3b4fee9","name":{"pool_name":"oxp_d63a297d-ae6a-4072-9dca-dda404044989","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::6]:32345"},"services":[{"id":"b452e5e1-ab4c-4994-9679-ef21b3b4fee9","details":{"type":"crucible","address":"[fd00:1122:3344:10b::6]:32345"}}]},"root":"/pool/ext/7c204111-31df-4c32-9a3e-780411f700fd/crypt/zone"},{"zone":{"id":"e9826cdc-6d3a-4eff-b1b5-ec4364ebe6b9","zone_type":"oximeter","addresses":["fd00:1122:3344:10b::3"],"dataset":null,"services":[{"id":"e9826cdc-6d3a-4eff-b1b5-ec4364ebe6b9","details":{"type":"oximeter","address":"[fd00:1122:3344:10b::3]:12223"}}]},"root":"/pool/ext/7c204111-31df-4c32-9a3e-780411f700fd/crypt/zone"},{"zone":{"id":"b0cde4a8-f27c-46e8-8355-756be9045afc","zone_type":"crucible","addresses":["fd00:1122:3344:10b::b"],"dataset":{"id":"b0cde4a8-f27c-46e8-8355-756be9045afc","name":{"pool_name":"oxp_07c1a8e7-51f5-4f12-a43d-734719fef92b","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::b]:32345"},"services":[{"id":"b0cde4a8-f27c-46e8-8355-756be9045afc","details":{"type":"crucible","address":"[fd00:1122:3344:10b::b]:32345"}}]},"root":"/pool/ext/1f6adf64-c9b9-4ed7-b3e2-37fb25624646/crypt/zone"},{"zone":{"id":"e2f70cf6-e285-4212-9b01-77ebf2ca9219","zone_type":"crucible","addresses":["fd00:1122:3344:10b::d"],"dataset":{"id":"e2f70cf6-e285-4212-9b01-77ebf2ca9219","name":{"pool_name":"oxp_a809f28a-7f25-4362-bc56-0cbdd72af2cb","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::d]:32345"},"services":[{"id":"e2f70cf6-e285-4212-9b01-77ebf2ca9219","details":{"type":"crucible","address":"[fd00:1122:3344:10b::d]:32345"}}]},"root":"/pool/ext/92a1bd39-6e8a-4226-b9d0-e3e8a9b8504f/crypt/zone"},{"zone":{"id":"b0949c9d-4aa1-4bc4-9cb3-5875b9166885","zone_type":"crucible","addresses":["fd00:1122:3344:10b::a"],"dataset":{"id":"b0949c9d-4aa1-4bc4-9cb3-5875b9166885","name":{"pool_name":"oxp_af0cc12b-43c5-473a-89a7-28351fbbb430","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::a]:32345"},"services":[{"id":"b0949c9d-4aa1-4bc4-9cb3-5875b9166885","details":{"type":"crucible","address":"[fd00:1122:3344:10b::a]:32345"}}]},"root":"/pool/ext/cf1594ed-7c0c-467c-b0af-a689dcb427a3/crypt/zone"},{"zone":{"id":"7cea4d59-a8ca-4826-901d-8d5bd935dc09","zone_type":"crucible","addresses":["fd00:1122:3344:10b::9"],"dataset":{"id":"7cea4d59-a8ca-4826-901d-8d5bd935dc09","name":{"pool_name":"oxp_d75dae09-4992-4a61-ab7d-5ae1d2b068ba","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::9]:32345"},"services":[{"id":"7cea4d59-a8ca-4826-901d-8d5bd935dc09","details":{"type":"crucible","address":"[fd00:1122:3344:10b::9]:32345"}}]},"root":"/pool/ext/a809f28a-7f25-4362-bc56-0cbdd72af2cb/crypt/zone"},{"zone":{"id":"08adaeee-c3b5-4cd8-8fbd-ac371b3101c9","zone_type":"crucible","addresses":["fd00:1122:3344:10b::4"],"dataset":{"id":"08adaeee-c3b5-4cd8-8fbd-ac371b3101c9","name":{"pool_name":"oxp_d9f23187-fbf9-4ea5-a103-bc112263a9a7","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::4]:32345"},"services":[{"id":"08adaeee-c3b5-4cd8-8fbd-ac371b3101c9","details":{"type":"crucible","address":"[fd00:1122:3344:10b::4]:32345"}}]},"root":"/pool/ext/7c204111-31df-4c32-9a3e-780411f700fd/crypt/zone"},{"zone":{"id":"3da1ade5-3fcb-4e64-aa08-81ee8a9ef723","zone_type":"crucible","addresses":["fd00:1122:3344:10b::8"],"dataset":{"id":"3da1ade5-3fcb-4e64-aa08-81ee8a9ef723","name":{"pool_name":"oxp_1f6adf64-c9b9-4ed7-b3e2-37fb25624646","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::8]:32345"},"services":[{"id":"3da1ade5-3fcb-4e64-aa08-81ee8a9ef723","details":{"type":"crucible","address":"[fd00:1122:3344:10b::8]:32345"}}]},"root":"/pool/ext/07c1a8e7-51f5-4f12-a43d-734719fef92b/crypt/zone"},{"zone":{"id":"816f26a7-4c28-4a39-b9ad-a036678520ab","zone_type":"crucible","addresses":["fd00:1122:3344:10b::7"],"dataset":{"id":"816f26a7-4c28-4a39-b9ad-a036678520ab","name":{"pool_name":"oxp_92a1bd39-6e8a-4226-b9d0-e3e8a9b8504f","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::7]:32345"},"services":[{"id":"816f26a7-4c28-4a39-b9ad-a036678520ab","details":{"type":"crucible","address":"[fd00:1122:3344:10b::7]:32345"}}]},"root":"/pool/ext/d9f23187-fbf9-4ea5-a103-bc112263a9a7/crypt/zone"},{"zone":{"id":"839f9839-409f-45d3-b8a6-7085507b90f6","zone_type":"crucible","addresses":["fd00:1122:3344:10b::c"],"dataset":{"id":"839f9839-409f-45d3-b8a6-7085507b90f6","name":{"pool_name":"oxp_7c204111-31df-4c32-9a3e-780411f700fd","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::c]:32345"},"services":[{"id":"839f9839-409f-45d3-b8a6-7085507b90f6","details":{"type":"crucible","address":"[fd00:1122:3344:10b::c]:32345"}}]},"root":"/pool/ext/af0cc12b-43c5-473a-89a7-28351fbbb430/crypt/zone"},{"zone":{"id":"c717c81f-a228-4412-a34e-90f8c491d847","zone_type":"crucible","addresses":["fd00:1122:3344:10b::5"],"dataset":{"id":"c717c81f-a228-4412-a34e-90f8c491d847","name":{"pool_name":"oxp_cf1594ed-7c0c-467c-b0af-a689dcb427a3","kind":{"type":"crucible"}},"service_address":"[fd00:1122:3344:10b::5]:32345"},"services":[{"id":"c717c81f-a228-4412-a34e-90f8c491d847","details":{"type":"crucible","address":"[fd00:1122:3344:10b::5]:32345"}}]},"root":"/pool/ext/d63a297d-ae6a-4072-9dca-dda404044989/crypt/zone"},{"zone":{"id":"e1fa2023-6c86-40a4-ae59-a0de112cf7a9","zone_type":"ntp","addresses":["fd00:1122:3344:10b::e"],"dataset":null,"services":[{"id":"e1fa2023-6c86-40a4-ae59-a0de112cf7a9","details":{"type":"internal_ntp","address":"[fd00:1122:3344:10b::e]:123","ntp_servers":["440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal","cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal"],"dns_servers":["fd00:1122:3344:1::1","fd00:1122:3344:2::1","fd00:1122:3344:3::1"],"domain":null}}]},"root":"/pool/ext/d9f23187-fbf9-4ea5-a103-bc112263a9a7/crypt/zone"}]} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled10.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled10.json new file mode 100644 index 0000000000..c00a65e8ea --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled10.json @@ -0,0 +1,195 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "04eef8aa-055c-42ab-bdb6-c982f63c9be0", + "underlay_address": "fd00:1122:3344:107::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::d]:32345", + "dataset": { + "pool_name": "oxp_845ff39a-3205-416f-8bda-e35829107c8a" + } + } + }, + "root": "/pool/ext/43efdd6d-7419-437a-a282-fc45bfafd042/crypt/zone" + }, + { + "zone": { + "id": "8568c997-fbbb-46a8-8549-b78284530ffc", + "underlay_address": "fd00:1122:3344:107::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::5]:32345", + "dataset": { + "pool_name": "oxp_0e485ad3-04e6-404b-b619-87d4fea9f5ae" + } + } + }, + "root": "/pool/ext/9b61d4b2-66f6-459f-86f4-13d0b8c5d6cf/crypt/zone" + }, + { + "zone": { + "id": "6cec1d60-5c1a-4c1b-9632-2b4bc76bd37c", + "underlay_address": "fd00:1122:3344:107::e", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::e]:32345", + "dataset": { + "pool_name": "oxp_62a4c68a-2073-42d0-8e49-01f5e8b90cd4" + } + } + }, + "root": "/pool/ext/845ff39a-3205-416f-8bda-e35829107c8a/crypt/zone" + }, + { + "zone": { + "id": "aa646c82-c6d7-4d0c-8401-150130927759", + "underlay_address": "fd00:1122:3344:107::4", + "zone_type": { + "type": "clickhouse", + "address": "[fd00:1122:3344:107::4]:8123", + "dataset": { + "pool_name": "oxp_0e485ad3-04e6-404b-b619-87d4fea9f5ae" + } + } + }, + "root": "/pool/ext/fd82dcc7-00dd-4d01-826a-937a7d8238fb/crypt/zone" + }, + { + "zone": { + "id": "2f294ca1-7a4f-468f-8966-2b7915804729", + "underlay_address": "fd00:1122:3344:107::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::7]:32345", + "dataset": { + "pool_name": "oxp_43efdd6d-7419-437a-a282-fc45bfafd042" + } + } + }, + "root": "/pool/ext/fd82dcc7-00dd-4d01-826a-937a7d8238fb/crypt/zone" + }, + { + "zone": { + "id": "1a77bd1d-4fd4-4d6c-a105-17f942d94ba6", + "underlay_address": "fd00:1122:3344:107::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::c]:32345", + "dataset": { + "pool_name": "oxp_b6bdfdaf-9c0d-4b74-926c-49ff3ed05562" + } + } + }, + "root": "/pool/ext/9b61d4b2-66f6-459f-86f4-13d0b8c5d6cf/crypt/zone" + }, + { + "zone": { + "id": "f65a6668-1aea-4deb-81ed-191fbe469328", + "underlay_address": "fd00:1122:3344:107::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::9]:32345", + "dataset": { + "pool_name": "oxp_9b61d4b2-66f6-459f-86f4-13d0b8c5d6cf" + } + } + }, + "root": "/pool/ext/d0584f4a-20ba-436d-a75b-7709e80deb79/crypt/zone" + }, + { + "zone": { + "id": "ee8bce67-8f8e-4221-97b0-85f1860d66d0", + "underlay_address": "fd00:1122:3344:107::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::8]:32345", + "dataset": { + "pool_name": "oxp_b252b176-3974-436a-915b-60382b21eb76" + } + } + }, + "root": "/pool/ext/b6bdfdaf-9c0d-4b74-926c-49ff3ed05562/crypt/zone" + }, + { + "zone": { + "id": "cf3b2d54-5e36-4c93-b44f-8bf36ac98071", + "underlay_address": "fd00:1122:3344:107::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::b]:32345", + "dataset": { + "pool_name": "oxp_d0584f4a-20ba-436d-a75b-7709e80deb79" + } + } + }, + "root": "/pool/ext/4c157f35-865d-4310-9d81-c6259cb69293/crypt/zone" + }, + { + "zone": { + "id": "5c8c244c-00dc-4b16-aa17-6d9eb4827fab", + "underlay_address": "fd00:1122:3344:107::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::a]:32345", + "dataset": { + "pool_name": "oxp_4c157f35-865d-4310-9d81-c6259cb69293" + } + } + }, + "root": "/pool/ext/845ff39a-3205-416f-8bda-e35829107c8a/crypt/zone" + }, + { + "zone": { + "id": "7d5e942b-926c-442d-937a-76cc4aa72bf3", + "underlay_address": "fd00:1122:3344:107::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::6]:32345", + "dataset": { + "pool_name": "oxp_fd82dcc7-00dd-4d01-826a-937a7d8238fb" + } + } + }, + "root": "/pool/ext/b252b176-3974-436a-915b-60382b21eb76/crypt/zone" + }, + { + "zone": { + "id": "a3628a56-6f85-43b5-be50-71d8f0e04877", + "underlay_address": "fd00:1122:3344:107::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:107::3]:32221", + "dataset": { + "pool_name": "oxp_0e485ad3-04e6-404b-b619-87d4fea9f5ae" + } + } + }, + "root": "/pool/ext/4c157f35-865d-4310-9d81-c6259cb69293/crypt/zone" + }, + { + "zone": { + "id": "7529be1c-ca8b-441a-89aa-37166cc450df", + "underlay_address": "fd00:1122:3344:107::f", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:107::f]:123", + "ntp_servers": [ + "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal", + "6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/fd82dcc7-00dd-4d01-826a-937a7d8238fb/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled11.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled11.json new file mode 100644 index 0000000000..79aae3e8c1 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled11.json @@ -0,0 +1,196 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "605be8b9-c652-4a5f-94ca-068ec7a39472", + "underlay_address": "fd00:1122:3344:106::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::a]:32345", + "dataset": { + "pool_name": "oxp_cf14d1b9-b4db-4594-b3ab-a9957e770ce9" + } + } + }, + "root": "/pool/ext/cf5f8849-0c5a-475b-8683-6d17da88d1d1/crypt/zone" + }, + { + "zone": { + "id": "af8a8712-457c-4ea7-a8b6-aecb04761c1b", + "underlay_address": "fd00:1122:3344:106::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::9]:32345", + "dataset": { + "pool_name": "oxp_cf5f8849-0c5a-475b-8683-6d17da88d1d1" + } + } + }, + "root": "/pool/ext/7f778610-7328-4554-98f6-b17f74f551c7/crypt/zone" + }, + { + "zone": { + "id": "0022703b-dcfc-44d4-897a-b42f6f53b433", + "underlay_address": "fd00:1122:3344:106::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::c]:32345", + "dataset": { + "pool_name": "oxp_025725fa-9e40-4b46-b018-c420408394ef" + } + } + }, + "root": "/pool/ext/025725fa-9e40-4b46-b018-c420408394ef/crypt/zone" + }, + { + "zone": { + "id": "fffddf56-10ca-4b62-9be3-5b3764a5f682", + "underlay_address": "fd00:1122:3344:106::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::d]:32345", + "dataset": { + "pool_name": "oxp_4d2f5aaf-eb14-4b1e-aa99-ae38ec844605" + } + } + }, + "root": "/pool/ext/834c9aad-c53b-4357-bc3f-f422efa63848/crypt/zone" + }, + { + "zone": { + "id": "9b8194ee-917d-4abc-a55c-94cea6cdaea1", + "underlay_address": "fd00:1122:3344:106::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::6]:32345", + "dataset": { + "pool_name": "oxp_d7665e0d-9354-4341-a76f-965d7c49f277" + } + } + }, + "root": "/pool/ext/cf5f8849-0c5a-475b-8683-6d17da88d1d1/crypt/zone" + }, + { + "zone": { + "id": "b369e133-485c-4d98-8fee-83542d1fd94d", + "underlay_address": "fd00:1122:3344:106::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::4]:32345", + "dataset": { + "pool_name": "oxp_4366f80d-3902-4b93-8f2d-380008e805fc" + } + } + }, + "root": "/pool/ext/025725fa-9e40-4b46-b018-c420408394ef/crypt/zone" + }, + { + "zone": { + "id": "edd99650-5df1-4241-815d-253e4ef2399c", + "underlay_address": "fd00:1122:3344:106::3", + "zone_type": { + "type": "external_dns", + "dataset": { + "pool_name": "oxp_4366f80d-3902-4b93-8f2d-380008e805fc" + }, + "http_address": "[fd00:1122:3344:106::3]:5353", + "dns_address": "172.20.26.1:53", + "nic": { + "id": "99b759fc-8e2e-44b7-aca8-93c3b201974d", + "kind": { + "type": "service", + "id": "edd99650-5df1-4241-815d-253e4ef2399c" + }, + "name": "external-dns-edd99650-5df1-4241-815d-253e4ef2399c", + "ip": "172.30.1.5", + "mac": "A8:40:25:FF:B0:9C", + "subnet": "172.30.1.0/24", + "vni": 100, + "primary": true, + "slot": 0 + } + } + }, + "root": "/pool/ext/7f778610-7328-4554-98f6-b17f74f551c7/crypt/zone" + }, + { + "zone": { + "id": "46d1afcc-cc3f-4b17-aafc-054dd4862d15", + "underlay_address": "fd00:1122:3344:106::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::5]:32345", + "dataset": { + "pool_name": "oxp_7f778610-7328-4554-98f6-b17f74f551c7" + } + } + }, + "root": "/pool/ext/cf5f8849-0c5a-475b-8683-6d17da88d1d1/crypt/zone" + }, + { + "zone": { + "id": "12afe1c3-bfe6-4278-8240-91d401347d36", + "underlay_address": "fd00:1122:3344:106::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::8]:32345", + "dataset": { + "pool_name": "oxp_534bcd4b-502f-4109-af6e-4b28a22c20f1" + } + } + }, + "root": "/pool/ext/4366f80d-3902-4b93-8f2d-380008e805fc/crypt/zone" + }, + { + "zone": { + "id": "c33b5912-9985-43ed-98f2-41297e2b796a", + "underlay_address": "fd00:1122:3344:106::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::b]:32345", + "dataset": { + "pool_name": "oxp_834c9aad-c53b-4357-bc3f-f422efa63848" + } + } + }, + "root": "/pool/ext/d7665e0d-9354-4341-a76f-965d7c49f277/crypt/zone" + }, + { + "zone": { + "id": "65b3db59-9361-4100-9cee-04e32a8c67d3", + "underlay_address": "fd00:1122:3344:106::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::7]:32345", + "dataset": { + "pool_name": "oxp_32b5303f-f667-4345-84d2-c7eec63b91b2" + } + } + }, + "root": "/pool/ext/d7665e0d-9354-4341-a76f-965d7c49f277/crypt/zone" + }, + { + "zone": { + "id": "82500cc9-f33d-4d59-9e6e-d70ea6133077", + "underlay_address": "fd00:1122:3344:106::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:106::e]:123", + "ntp_servers": [ + "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal", + "6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/cf14d1b9-b4db-4594-b3ab-a9957e770ce9/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled12.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled12.json new file mode 100644 index 0000000000..39ebad3183 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled12.json @@ -0,0 +1,232 @@ +{ + "omicron_generation": 2, + "ledger_generation": 5, + "zones": [ + { + "zone": { + "id": "a76b3357-b690-43b8-8352-3300568ffc2b", + "underlay_address": "fd00:1122:3344:104::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::a]:32345", + "dataset": { + "pool_name": "oxp_05715ad8-59a1-44ab-ad5f-0cdffb46baab" + } + } + }, + "root": "/pool/ext/2ec2a731-3340-4777-b1bb-4a906c598174/crypt/zone" + }, + { + "zone": { + "id": "8d202759-ca06-4383-b50f-7f3ec4062bf7", + "underlay_address": "fd00:1122:3344:104::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::4]:32345", + "dataset": { + "pool_name": "oxp_56e32a8f-0877-4437-9cab-94a4928b1495" + } + } + }, + "root": "/pool/ext/613b58fc-5a80-42dc-a61c-b143cf220fb5/crypt/zone" + }, + { + "zone": { + "id": "fcdda266-fc6a-4518-89db-aec007a4b682", + "underlay_address": "fd00:1122:3344:104::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::b]:32345", + "dataset": { + "pool_name": "oxp_7e1293ad-b903-4054-aeae-2182d5e4a785" + } + } + }, + "root": "/pool/ext/416fd29e-d3b5-4fdf-8101-d0d163fa0706/crypt/zone" + }, + { + "zone": { + "id": "167cf6a2-ec51-4de2-bc6c-7785bbc0e436", + "underlay_address": "fd00:1122:3344:104::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::c]:32345", + "dataset": { + "pool_name": "oxp_f96c8d49-fdf7-4bd6-84f6-c282202d1abc" + } + } + }, + "root": "/pool/ext/56e32a8f-0877-4437-9cab-94a4928b1495/crypt/zone" + }, + { + "zone": { + "id": "c6fde82d-8dae-4ef0-b557-6c3d094d9454", + "underlay_address": "fd00:1122:3344:104::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::9]:32345", + "dataset": { + "pool_name": "oxp_416fd29e-d3b5-4fdf-8101-d0d163fa0706" + } + } + }, + "root": "/pool/ext/3af01cc4-1f16-47d9-a489-abafcb91c2db/crypt/zone" + }, + { + "zone": { + "id": "650f5da7-86a0-4ade-af0f-bc96e021ded0", + "underlay_address": "fd00:1122:3344:104::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::5]:32345", + "dataset": { + "pool_name": "oxp_b4a71d3d-1ecd-418a-9a52-8d118f82082b" + } + } + }, + "root": "/pool/ext/613b58fc-5a80-42dc-a61c-b143cf220fb5/crypt/zone" + }, + { + "zone": { + "id": "7ce9a2c5-2d37-4188-b7b5-a9db819396c3", + "underlay_address": "fd00:1122:3344:104::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::d]:32345", + "dataset": { + "pool_name": "oxp_c87d16b8-e814-4159-8562-f8d7fdd19d13" + } + } + }, + "root": "/pool/ext/416fd29e-d3b5-4fdf-8101-d0d163fa0706/crypt/zone" + }, + { + "zone": { + "id": "23e1cf01-70ab-422f-997b-6216158965c3", + "underlay_address": "fd00:1122:3344:104::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::8]:32345", + "dataset": { + "pool_name": "oxp_3af01cc4-1f16-47d9-a489-abafcb91c2db" + } + } + }, + "root": "/pool/ext/3af01cc4-1f16-47d9-a489-abafcb91c2db/crypt/zone" + }, + { + "zone": { + "id": "50209816-89fb-48ed-9595-16899d114844", + "underlay_address": "fd00:1122:3344:104::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::6]:32345", + "dataset": { + "pool_name": "oxp_2ec2a731-3340-4777-b1bb-4a906c598174" + } + } + }, + "root": "/pool/ext/416fd29e-d3b5-4fdf-8101-d0d163fa0706/crypt/zone" + }, + { + "zone": { + "id": "20b100d0-84c3-4119-aa9b-0c632b0b6a3a", + "underlay_address": "fd00:1122:3344:104::3", + "zone_type": { + "type": "nexus", + "internal_address": "[fd00:1122:3344:104::3]:12221", + "external_ip": "172.20.26.4", + "nic": { + "id": "364b0ecd-bf08-4cac-a993-bbf4a70564c7", + "kind": { + "type": "service", + "id": "20b100d0-84c3-4119-aa9b-0c632b0b6a3a" + }, + "name": "nexus-20b100d0-84c3-4119-aa9b-0c632b0b6a3a", + "ip": "172.30.2.6", + "mac": "A8:40:25:FF:B4:C1", + "subnet": "172.30.2.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "external_tls": true, + "external_dns_servers": [ + "1.1.1.1", + "9.9.9.9" + ] + } + }, + "root": "/pool/ext/c87d16b8-e814-4159-8562-f8d7fdd19d13/crypt/zone" + }, + { + "zone": { + "id": "8bc0f29e-0c20-437e-b8ca-7b9844acda22", + "underlay_address": "fd00:1122:3344:104::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::7]:32345", + "dataset": { + "pool_name": "oxp_613b58fc-5a80-42dc-a61c-b143cf220fb5" + } + } + }, + "root": "/pool/ext/56e32a8f-0877-4437-9cab-94a4928b1495/crypt/zone" + }, + { + "zone": { + "id": "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55", + "underlay_address": "fd00:1122:3344:104::e", + "zone_type": { + "type": "boundary_ntp", + "address": "[fd00:1122:3344:104::e]:123", + "ntp_servers": [ + "ntp.eng.oxide.computer" + ], + "dns_servers": [ + "1.1.1.1", + "9.9.9.9" + ], + "domain": null, + "nic": { + "id": "a4b9bacf-6c04-431a-81ad-9bf0302af96e", + "kind": { + "type": "service", + "id": "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55" + }, + "name": "ntp-c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55", + "ip": "172.30.3.5", + "mac": "A8:40:25:FF:B2:52", + "subnet": "172.30.3.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "snat_cfg": { + "ip": "172.20.26.6", + "first_port": 0, + "last_port": 16383 + } + } + }, + "root": "/pool/ext/3af01cc4-1f16-47d9-a489-abafcb91c2db/crypt/zone" + }, + { + "zone": { + "id": "51c9ad09-7814-4643-8ad4-689ccbe53fbd", + "underlay_address": "fd00:1122:3344:1::1", + "zone_type": { + "type": "internal_dns", + "dataset": { + "pool_name": "oxp_56e32a8f-0877-4437-9cab-94a4928b1495" + }, + "http_address": "[fd00:1122:3344:1::1]:5353", + "dns_address": "[fd00:1122:3344:1::1]:53", + "gz_address": "fd00:1122:3344:1::2", + "gz_address_index": 0 + } + }, + "root": "/pool/ext/3af01cc4-1f16-47d9-a489-abafcb91c2db/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled14.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled14.json new file mode 100644 index 0000000000..25dfb72a78 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled14.json @@ -0,0 +1,192 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "ee8b2cfa-87fe-46a6-98ef-23640b80a968", + "underlay_address": "fd00:1122:3344:10b::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::d]:32345", + "dataset": { + "pool_name": "oxp_4a624324-003a-4255-98e8-546a90b5b7fa" + } + } + }, + "root": "/pool/ext/6b9ec5f1-859f-459c-9c06-6a51ba87786f/crypt/zone" + }, + { + "zone": { + "id": "9228f8ca-2a83-439f-9cb7-f2801b5fea27", + "underlay_address": "fd00:1122:3344:10b::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::6]:32345", + "dataset": { + "pool_name": "oxp_6b9ec5f1-859f-459c-9c06-6a51ba87786f" + } + } + }, + "root": "/pool/ext/6b9ec5f1-859f-459c-9c06-6a51ba87786f/crypt/zone" + }, + { + "zone": { + "id": "ee44cdde-7ac9-4469-9f1d-e8bcfeb5cc46", + "underlay_address": "fd00:1122:3344:10b::e", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::e]:32345", + "dataset": { + "pool_name": "oxp_11b02ce7-7e50-486f-86c2-de8af9575a45" + } + } + }, + "root": "/pool/ext/11b02ce7-7e50-486f-86c2-de8af9575a45/crypt/zone" + }, + { + "zone": { + "id": "96bac0b1-8b34-4c81-9e76-6404d2c37630", + "underlay_address": "fd00:1122:3344:10b::4", + "zone_type": { + "type": "crucible_pantry", + "address": "[fd00:1122:3344:10b::4]:17000" + } + }, + "root": "/pool/ext/350b2814-7b7f-40f1-9bf6-9818a1ef49bb/crypt/zone" + }, + { + "zone": { + "id": "d4e1e554-7b98-4413-809e-4a42561c3d0c", + "underlay_address": "fd00:1122:3344:10b::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::a]:32345", + "dataset": { + "pool_name": "oxp_e6d2fe1d-c74d-40cd-8fae-bc7d06bdaac8" + } + } + }, + "root": "/pool/ext/6b9ec5f1-859f-459c-9c06-6a51ba87786f/crypt/zone" + }, + { + "zone": { + "id": "1dd69b02-a032-46c3-8e2a-5012e8314455", + "underlay_address": "fd00:1122:3344:10b::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::b]:32345", + "dataset": { + "pool_name": "oxp_350b2814-7b7f-40f1-9bf6-9818a1ef49bb" + } + } + }, + "root": "/pool/ext/350b2814-7b7f-40f1-9bf6-9818a1ef49bb/crypt/zone" + }, + { + "zone": { + "id": "921f7752-d2f3-40df-a739-5cb1390abc2c", + "underlay_address": "fd00:1122:3344:10b::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::8]:32345", + "dataset": { + "pool_name": "oxp_2d1ebe24-6deb-4f81-8450-6842de28126c" + } + } + }, + "root": "/pool/ext/91ea7bb6-2be7-4498-9b0d-a0521509ec00/crypt/zone" + }, + { + "zone": { + "id": "609b25e8-9750-4308-ae6f-7202907a3675", + "underlay_address": "fd00:1122:3344:10b::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::9]:32345", + "dataset": { + "pool_name": "oxp_91ea7bb6-2be7-4498-9b0d-a0521509ec00" + } + } + }, + "root": "/pool/ext/2d1ebe24-6deb-4f81-8450-6842de28126c/crypt/zone" + }, + { + "zone": { + "id": "a232eba2-e94f-4592-a5a6-ec23f9be3296", + "underlay_address": "fd00:1122:3344:10b::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::5]:32345", + "dataset": { + "pool_name": "oxp_e12f29b8-1ab8-431e-bc96-1c1298947980" + } + } + }, + "root": "/pool/ext/021afd19-2f87-4def-9284-ab7add1dd6ae/crypt/zone" + }, + { + "zone": { + "id": "800d1758-9312-4b1a-8f02-dc6d644c2a9b", + "underlay_address": "fd00:1122:3344:10b::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::c]:32345", + "dataset": { + "pool_name": "oxp_b6932bb0-bab8-4876-914a-9c75a600e794" + } + } + }, + "root": "/pool/ext/b6932bb0-bab8-4876-914a-9c75a600e794/crypt/zone" + }, + { + "zone": { + "id": "668a4d4a-96dc-4b45-866b-bed3d64c26ec", + "underlay_address": "fd00:1122:3344:10b::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::7]:32345", + "dataset": { + "pool_name": "oxp_021afd19-2f87-4def-9284-ab7add1dd6ae" + } + } + }, + "root": "/pool/ext/91ea7bb6-2be7-4498-9b0d-a0521509ec00/crypt/zone" + }, + { + "zone": { + "id": "8bbea076-ff60-4330-8302-383e18140ef3", + "underlay_address": "fd00:1122:3344:10b::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:10b::3]:32221", + "dataset": { + "pool_name": "oxp_e12f29b8-1ab8-431e-bc96-1c1298947980" + } + } + }, + "root": "/pool/ext/4a624324-003a-4255-98e8-546a90b5b7fa/crypt/zone" + }, + { + "zone": { + "id": "3ccea933-89f2-4ce5-8367-efb0afeffe97", + "underlay_address": "fd00:1122:3344:10b::f", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:10b::f]:123", + "ntp_servers": [ + "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal", + "6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/4a624324-003a-4255-98e8-546a90b5b7fa/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled16.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled16.json new file mode 100644 index 0000000000..905742e678 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled16.json @@ -0,0 +1,192 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "b12aa520-a769-4eac-b56b-09960550a831", + "underlay_address": "fd00:1122:3344:108::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::7]:32345", + "dataset": { + "pool_name": "oxp_34dadf3f-f60c-4acc-b82b-4b0c82224222" + } + } + }, + "root": "/pool/ext/8be8c577-23ac-452e-a205-6d9c95088f61/crypt/zone" + }, + { + "zone": { + "id": "9bdc40ee-ccba-4d18-9efb-a30596e2d290", + "underlay_address": "fd00:1122:3344:108::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::d]:32345", + "dataset": { + "pool_name": "oxp_eb81728c-3b83-42fb-8133-ac32a0bdf70f" + } + } + }, + "root": "/pool/ext/8be8c577-23ac-452e-a205-6d9c95088f61/crypt/zone" + }, + { + "zone": { + "id": "c9a367c7-64d7-48e4-b484-9ecb4e8faea7", + "underlay_address": "fd00:1122:3344:108::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::9]:32345", + "dataset": { + "pool_name": "oxp_76ab5a67-e20f-4bf0-87b3-01fcc4144bd2" + } + } + }, + "root": "/pool/ext/34dadf3f-f60c-4acc-b82b-4b0c82224222/crypt/zone" + }, + { + "zone": { + "id": "bc5124d8-65e8-4879-bfac-64d59003d482", + "underlay_address": "fd00:1122:3344:108::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::a]:32345", + "dataset": { + "pool_name": "oxp_5fac7a1d-e855-46e1-b8c2-dd848ac4fee6" + } + } + }, + "root": "/pool/ext/0c4ef358-5533-43db-ad38-a8eff716e53a/crypt/zone" + }, + { + "zone": { + "id": "5cc7c840-8e6b-48c8-ac4b-f4297f8cf61a", + "underlay_address": "fd00:1122:3344:108::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::c]:32345", + "dataset": { + "pool_name": "oxp_0c4ef358-5533-43db-ad38-a8eff716e53a" + } + } + }, + "root": "/pool/ext/6d3e9cc6-f03b-4055-9785-05711d5e4fdc/crypt/zone" + }, + { + "zone": { + "id": "3b767edf-a72d-4d80-a0fc-65d6801ed0e0", + "underlay_address": "fd00:1122:3344:108::e", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::e]:32345", + "dataset": { + "pool_name": "oxp_f522118c-5dcd-4116-8044-07f0cceec52e" + } + } + }, + "root": "/pool/ext/5fac7a1d-e855-46e1-b8c2-dd848ac4fee6/crypt/zone" + }, + { + "zone": { + "id": "f3c02ed6-fbc5-45c3-a030-409f74b450fd", + "underlay_address": "fd00:1122:3344:108::4", + "zone_type": { + "type": "crucible_pantry", + "address": "[fd00:1122:3344:108::4]:17000" + } + }, + "root": "/pool/ext/eb81728c-3b83-42fb-8133-ac32a0bdf70f/crypt/zone" + }, + { + "zone": { + "id": "85bd9bdb-1ec5-4a8d-badb-8b5d502546a1", + "underlay_address": "fd00:1122:3344:108::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::5]:32345", + "dataset": { + "pool_name": "oxp_416232c1-bc8f-403f-bacb-28403dd8fced" + } + } + }, + "root": "/pool/ext/34dadf3f-f60c-4acc-b82b-4b0c82224222/crypt/zone" + }, + { + "zone": { + "id": "d2f1c3df-d4e0-4469-b50e-f1871da86ebf", + "underlay_address": "fd00:1122:3344:108::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::6]:32345", + "dataset": { + "pool_name": "oxp_6d3e9cc6-f03b-4055-9785-05711d5e4fdc" + } + } + }, + "root": "/pool/ext/34dadf3f-f60c-4acc-b82b-4b0c82224222/crypt/zone" + }, + { + "zone": { + "id": "88fe3c12-4c55-47df-b4ee-ed26b795439d", + "underlay_address": "fd00:1122:3344:108::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::8]:32345", + "dataset": { + "pool_name": "oxp_8be8c577-23ac-452e-a205-6d9c95088f61" + } + } + }, + "root": "/pool/ext/34dadf3f-f60c-4acc-b82b-4b0c82224222/crypt/zone" + }, + { + "zone": { + "id": "4d20175a-588b-44b8-8b9c-b16c6c3a97a0", + "underlay_address": "fd00:1122:3344:108::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::b]:32345", + "dataset": { + "pool_name": "oxp_a726cacd-fa35-4ed2-ade6-31ad928b24cb" + } + } + }, + "root": "/pool/ext/0c4ef358-5533-43db-ad38-a8eff716e53a/crypt/zone" + }, + { + "zone": { + "id": "e86845b5-eabd-49f5-9a10-6dfef9066209", + "underlay_address": "fd00:1122:3344:108::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:108::3]:32221", + "dataset": { + "pool_name": "oxp_416232c1-bc8f-403f-bacb-28403dd8fced" + } + } + }, + "root": "/pool/ext/416232c1-bc8f-403f-bacb-28403dd8fced/crypt/zone" + }, + { + "zone": { + "id": "209b6213-588b-43b6-a89b-19ee5c84ffba", + "underlay_address": "fd00:1122:3344:108::f", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:108::f]:123", + "ntp_servers": [ + "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal", + "6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/416232c1-bc8f-403f-bacb-28403dd8fced/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled17.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled17.json new file mode 100644 index 0000000000..1cccd0467b --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled17.json @@ -0,0 +1,181 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "90b53c3d-42fa-4ca9-bbfc-96fff245b508", + "underlay_address": "fd00:1122:3344:109::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::4]:32345", + "dataset": { + "pool_name": "oxp_ae56280b-17ce-4266-8573-e1da9db6c6bb" + } + } + }, + "root": "/pool/ext/b0e1a261-b932-47c4-81e9-1977275ae9d9/crypt/zone" + }, + { + "zone": { + "id": "4f9f2e1d-be04-4e8b-a50b-ffb18557a650", + "underlay_address": "fd00:1122:3344:109::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::5]:32345", + "dataset": { + "pool_name": "oxp_d5b07362-64db-4b18-a3e9-8d7cbabae2d5" + } + } + }, + "root": "/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone" + }, + { + "zone": { + "id": "2fa5671d-3109-4f11-ae70-1280f4fa3b89", + "underlay_address": "fd00:1122:3344:109::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::6]:32345", + "dataset": { + "pool_name": "oxp_9ba7bfbf-b9a2-4237-a142-94c1e68de984" + } + } + }, + "root": "/pool/ext/3cafbb47-c194-4a42-99ff-34dfeab999ed/crypt/zone" + }, + { + "zone": { + "id": "b63c6882-ca90-4156-b561-4781ab4a0962", + "underlay_address": "fd00:1122:3344:109::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::7]:32345", + "dataset": { + "pool_name": "oxp_b0e1a261-b932-47c4-81e9-1977275ae9d9" + } + } + }, + "root": "/pool/ext/d5b07362-64db-4b18-a3e9-8d7cbabae2d5/crypt/zone" + }, + { + "zone": { + "id": "f71344eb-f7e2-439d-82a0-9941e6868fb6", + "underlay_address": "fd00:1122:3344:109::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::9]:32345", + "dataset": { + "pool_name": "oxp_027a82e8-daa3-4fa6-8205-ed03445e1086" + } + } + }, + "root": "/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone" + }, + { + "zone": { + "id": "a60cf0d7-12d5-43cb-aa3f-7a9e84de08fb", + "underlay_address": "fd00:1122:3344:109::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::a]:32345", + "dataset": { + "pool_name": "oxp_8736aaf9-4d72-42b1-8e4f-07644d999c8b" + } + } + }, + "root": "/pool/ext/8736aaf9-4d72-42b1-8e4f-07644d999c8b/crypt/zone" + }, + { + "zone": { + "id": "5d0e03b2-8958-4c43-8851-bf819f102958", + "underlay_address": "fd00:1122:3344:109::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::8]:32345", + "dataset": { + "pool_name": "oxp_62426615-7832-49e7-9426-e39ffeb42c69" + } + } + }, + "root": "/pool/ext/07fc8ec9-1216-4d98-be34-c2970b585e61/crypt/zone" + }, + { + "zone": { + "id": "accc05a2-ec80-4856-a825-ec6b7f700eaa", + "underlay_address": "fd00:1122:3344:109::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::d]:32345", + "dataset": { + "pool_name": "oxp_dc083c53-7014-4482-8a79-f338ba2b0fb4" + } + } + }, + "root": "/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone" + }, + { + "zone": { + "id": "2e32fdcc-737a-4430-8290-cb7028ea4d50", + "underlay_address": "fd00:1122:3344:109::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::b]:32345", + "dataset": { + "pool_name": "oxp_3cafbb47-c194-4a42-99ff-34dfeab999ed" + } + } + }, + "root": "/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone" + }, + { + "zone": { + "id": "a97c6ae2-37f6-4d93-a66e-cb5cd3c6aaa2", + "underlay_address": "fd00:1122:3344:109::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::c]:32345", + "dataset": { + "pool_name": "oxp_07fc8ec9-1216-4d98-be34-c2970b585e61" + } + } + }, + "root": "/pool/ext/07fc8ec9-1216-4d98-be34-c2970b585e61/crypt/zone" + }, + { + "zone": { + "id": "3237a532-acaa-4ebe-bf11-dde794fea739", + "underlay_address": "fd00:1122:3344:109::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:109::3]:32221", + "dataset": { + "pool_name": "oxp_ae56280b-17ce-4266-8573-e1da9db6c6bb" + } + } + }, + "root": "/pool/ext/027a82e8-daa3-4fa6-8205-ed03445e1086/crypt/zone" + }, + { + "zone": { + "id": "83257100-5590-484a-b72a-a079389d8da6", + "underlay_address": "fd00:1122:3344:109::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:109::e]:123", + "ntp_servers": [ + "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal", + "6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/3cafbb47-c194-4a42-99ff-34dfeab999ed/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled21.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled21.json new file mode 100644 index 0000000000..35caa638e8 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled21.json @@ -0,0 +1,232 @@ +{ + "omicron_generation": 2, + "ledger_generation": 5, + "zones": [ + { + "zone": { + "id": "0437b69d-73a8-4231-86f9-6b5556e7e7ef", + "underlay_address": "fd00:1122:3344:102::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::5]:32345", + "dataset": { + "pool_name": "oxp_aa0ffe35-76db-42ab-adf2-ceb072bdf811" + } + } + }, + "root": "/pool/ext/0d2805da-6d24-4e57-a700-0c3865c05544/crypt/zone" + }, + { + "zone": { + "id": "47234ca5-305f-436a-9e9a-36bca9667680", + "underlay_address": "fd00:1122:3344:102::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::b]:32345", + "dataset": { + "pool_name": "oxp_0d2805da-6d24-4e57-a700-0c3865c05544" + } + } + }, + "root": "/pool/ext/160691d8-33a1-4d7d-a48a-c3fd27d76822/crypt/zone" + }, + { + "zone": { + "id": "2898657e-4141-4c05-851b-147bffc6bbbd", + "underlay_address": "fd00:1122:3344:102::3", + "zone_type": { + "type": "nexus", + "internal_address": "[fd00:1122:3344:102::3]:12221", + "external_ip": "172.20.26.5", + "nic": { + "id": "2e9a412e-c79a-48fe-8fa4-f5a6afed1040", + "kind": { + "type": "service", + "id": "2898657e-4141-4c05-851b-147bffc6bbbd" + }, + "name": "nexus-2898657e-4141-4c05-851b-147bffc6bbbd", + "ip": "172.30.2.7", + "mac": "A8:40:25:FF:C6:59", + "subnet": "172.30.2.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "external_tls": true, + "external_dns_servers": [ + "1.1.1.1", + "9.9.9.9" + ] + } + }, + "root": "/pool/ext/c0b4ecc1-a145-443f-90d1-2e8136b007bc/crypt/zone" + }, + { + "zone": { + "id": "cf98c4d6-4a7b-49c0-9b14-48a8adf52ce9", + "underlay_address": "fd00:1122:3344:102::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::c]:32345", + "dataset": { + "pool_name": "oxp_c0b4ecc1-a145-443f-90d1-2e8136b007bc" + } + } + }, + "root": "/pool/ext/f6acd70a-d6cb-464d-a460-dd5c60301562/crypt/zone" + }, + { + "zone": { + "id": "13c1e91e-bfcc-4eea-8185-412fc37fdea3", + "underlay_address": "fd00:1122:3344:102::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::9]:32345", + "dataset": { + "pool_name": "oxp_e9b0a2e4-8060-41bd-a3b5-d0642246d06d" + } + } + }, + "root": "/pool/ext/c0b4ecc1-a145-443f-90d1-2e8136b007bc/crypt/zone" + }, + { + "zone": { + "id": "c9cb60af-9e0e-4b3b-b971-53138a9b8d27", + "underlay_address": "fd00:1122:3344:102::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::4]:32345", + "dataset": { + "pool_name": "oxp_77749ec7-39a9-489d-904b-87f7223c4e3c" + } + } + }, + "root": "/pool/ext/77749ec7-39a9-489d-904b-87f7223c4e3c/crypt/zone" + }, + { + "zone": { + "id": "32995cfa-47ec-4b84-8514-7c1c8a86c19d", + "underlay_address": "fd00:1122:3344:102::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::8]:32345", + "dataset": { + "pool_name": "oxp_eac83f81-eb51-4f3e-874e-82f55dd952ba" + } + } + }, + "root": "/pool/ext/0d2805da-6d24-4e57-a700-0c3865c05544/crypt/zone" + }, + { + "zone": { + "id": "b93d2e2d-d54b-4503-85c3-9878e3cee9c7", + "underlay_address": "fd00:1122:3344:102::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::a]:32345", + "dataset": { + "pool_name": "oxp_160691d8-33a1-4d7d-a48a-c3fd27d76822" + } + } + }, + "root": "/pool/ext/138663ad-a382-4595-baf0-08f6b0276a67/crypt/zone" + }, + { + "zone": { + "id": "2ebbac4f-7b0f-43eb-99fd-dd6ff7f9e097", + "underlay_address": "fd00:1122:3344:102::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::6]:32345", + "dataset": { + "pool_name": "oxp_138663ad-a382-4595-baf0-08f6b0276a67" + } + } + }, + "root": "/pool/ext/e9b0a2e4-8060-41bd-a3b5-d0642246d06d/crypt/zone" + }, + { + "zone": { + "id": "d0eea3b2-e5ac-42bf-97b7-531b78fa06d1", + "underlay_address": "fd00:1122:3344:102::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::7]:32345", + "dataset": { + "pool_name": "oxp_69f0b863-f73f-42b2-9822-b2cb99f09003" + } + } + }, + "root": "/pool/ext/138663ad-a382-4595-baf0-08f6b0276a67/crypt/zone" + }, + { + "zone": { + "id": "2b34cd1d-ea7d-41a1-82b9-75550fdf6eb0", + "underlay_address": "fd00:1122:3344:102::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::d]:32345", + "dataset": { + "pool_name": "oxp_f6acd70a-d6cb-464d-a460-dd5c60301562" + } + } + }, + "root": "/pool/ext/c0b4ecc1-a145-443f-90d1-2e8136b007bc/crypt/zone" + }, + { + "zone": { + "id": "6ea2684c-115e-48a6-8453-ab52d1cecd73", + "underlay_address": "fd00:1122:3344:102::e", + "zone_type": { + "type": "boundary_ntp", + "address": "[fd00:1122:3344:102::e]:123", + "ntp_servers": [ + "ntp.eng.oxide.computer" + ], + "dns_servers": [ + "1.1.1.1", + "9.9.9.9" + ], + "domain": null, + "nic": { + "id": "4effd079-ed4e-4cf6-8545-bb9574f516d2", + "kind": { + "type": "service", + "id": "6ea2684c-115e-48a6-8453-ab52d1cecd73" + }, + "name": "ntp-6ea2684c-115e-48a6-8453-ab52d1cecd73", + "ip": "172.30.3.6", + "mac": "A8:40:25:FF:A0:F9", + "subnet": "172.30.3.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "snat_cfg": { + "ip": "172.20.26.7", + "first_port": 16384, + "last_port": 32767 + } + } + }, + "root": "/pool/ext/aa0ffe35-76db-42ab-adf2-ceb072bdf811/crypt/zone" + }, + { + "zone": { + "id": "3a1ea15f-06a4-4afd-959a-c3a00b2bdd80", + "underlay_address": "fd00:1122:3344:2::1", + "zone_type": { + "type": "internal_dns", + "dataset": { + "pool_name": "oxp_77749ec7-39a9-489d-904b-87f7223c4e3c" + }, + "http_address": "[fd00:1122:3344:2::1]:5353", + "dns_address": "[fd00:1122:3344:2::1]:53", + "gz_address": "fd00:1122:3344:2::2", + "gz_address_index": 1 + } + }, + "root": "/pool/ext/69f0b863-f73f-42b2-9822-b2cb99f09003/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled23.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled23.json new file mode 100644 index 0000000000..94fcb3a327 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled23.json @@ -0,0 +1,195 @@ +{ + "omicron_generation": 2, + "ledger_generation": 5, + "zones": [ + { + "zone": { + "id": "1876cdcf-b2e7-4b79-ad2e-67df716e1860", + "underlay_address": "fd00:1122:3344:10a::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::8]:32345", + "dataset": { + "pool_name": "oxp_d4c6bdc6-5e99-4f6c-b57a-9bfcb9a76be4" + } + } + }, + "root": "/pool/ext/86c58ea3-1413-4af3-9aff-9c0a3d758459/crypt/zone" + }, + { + "zone": { + "id": "0e708ee3-b7a6-4993-a88a-4489add33e29", + "underlay_address": "fd00:1122:3344:10a::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::d]:32345", + "dataset": { + "pool_name": "oxp_718ad834-b415-4abb-934d-9f987cde0a96" + } + } + }, + "root": "/pool/ext/30f7d236-c835-46cc-bc27-9099a6826f67/crypt/zone" + }, + { + "zone": { + "id": "4e1b9a65-848f-4649-b360-1df0d135b44d", + "underlay_address": "fd00:1122:3344:10a::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::c]:32345", + "dataset": { + "pool_name": "oxp_88ee08c6-1c0f-44c2-9110-b8d5a7589ebb" + } + } + }, + "root": "/pool/ext/30f7d236-c835-46cc-bc27-9099a6826f67/crypt/zone" + }, + { + "zone": { + "id": "da510a57-3af1-4d2b-b2ed-2e8849f27d8b", + "underlay_address": "fd00:1122:3344:10a::3", + "zone_type": { + "type": "oximeter", + "address": "[fd00:1122:3344:10a::3]:12223" + } + }, + "root": "/pool/ext/718ad834-b415-4abb-934d-9f987cde0a96/crypt/zone" + }, + { + "zone": { + "id": "d4d9acc8-3e0b-4fab-a0a2-d21920fabd7e", + "underlay_address": "fd00:1122:3344:10a::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::6]:32345", + "dataset": { + "pool_name": "oxp_9dfe424f-cba6-4bfb-a3dd-e8bd7fdea57d" + } + } + }, + "root": "/pool/ext/30f7d236-c835-46cc-bc27-9099a6826f67/crypt/zone" + }, + { + "zone": { + "id": "fcb75972-836b-4f55-ba21-9722832cf5c2", + "underlay_address": "fd00:1122:3344:10a::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::7]:32345", + "dataset": { + "pool_name": "oxp_9005671f-3d90-4ed1-be15-ad65b9a65bd5" + } + } + }, + "root": "/pool/ext/d4c6bdc6-5e99-4f6c-b57a-9bfcb9a76be4/crypt/zone" + }, + { + "zone": { + "id": "624beba0-7dcd-4d55-af05-4670c6fcb1fb", + "underlay_address": "fd00:1122:3344:10a::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::4]:32345", + "dataset": { + "pool_name": "oxp_93867156-a43d-4c03-a899-1535e566c8bd" + } + } + }, + "root": "/pool/ext/93867156-a43d-4c03-a899-1535e566c8bd/crypt/zone" + }, + { + "zone": { + "id": "26fb3830-898e-4086-afaf-8f9654716b8c", + "underlay_address": "fd00:1122:3344:10a::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::b]:32345", + "dataset": { + "pool_name": "oxp_86c58ea3-1413-4af3-9aff-9c0a3d758459" + } + } + }, + "root": "/pool/ext/93867156-a43d-4c03-a899-1535e566c8bd/crypt/zone" + }, + { + "zone": { + "id": "a3ef7eba-c08e-48ef-ae7a-89e2fcb49b66", + "underlay_address": "fd00:1122:3344:10a::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::a]:32345", + "dataset": { + "pool_name": "oxp_cd3fdbae-a9d9-4db7-866a-bca36f6dd634" + } + } + }, + "root": "/pool/ext/718ad834-b415-4abb-934d-9f987cde0a96/crypt/zone" + }, + { + "zone": { + "id": "5c1d4a02-f33b-433a-81f5-5c149e3433bd", + "underlay_address": "fd00:1122:3344:10a::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::5]:32345", + "dataset": { + "pool_name": "oxp_9adfc865-2eef-4880-a6e3-9d2f88c8efd0" + } + } + }, + "root": "/pool/ext/cd3fdbae-a9d9-4db7-866a-bca36f6dd634/crypt/zone" + }, + { + "zone": { + "id": "ee77efe9-81d0-4395-a237-15e30c2c2d04", + "underlay_address": "fd00:1122:3344:10a::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::9]:32345", + "dataset": { + "pool_name": "oxp_30f7d236-c835-46cc-bc27-9099a6826f67" + } + } + }, + "root": "/pool/ext/88ee08c6-1c0f-44c2-9110-b8d5a7589ebb/crypt/zone" + }, + { + "zone": { + "id": "71ab91b7-48d4-4d31-b47e-59f29f419116", + "underlay_address": "fd00:1122:3344:10a::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:10a::e]:123", + "ntp_servers": [ + "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal", + "6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/cd3fdbae-a9d9-4db7-866a-bca36f6dd634/crypt/zone" + }, + { + "zone": { + "id": "46ccd8fe-4e3c-4307-97ae-1f7ac505082a", + "underlay_address": "fd00:1122:3344:3::1", + "zone_type": { + "type": "internal_dns", + "dataset": { + "pool_name": "oxp_93867156-a43d-4c03-a899-1535e566c8bd" + }, + "http_address": "[fd00:1122:3344:3::1]:5353", + "dns_address": "[fd00:1122:3344:3::1]:53", + "gz_address": "fd00:1122:3344:3::2", + "gz_address_index": 2 + } + }, + "root": "/pool/ext/9dfe424f-cba6-4bfb-a3dd-e8bd7fdea57d/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled25.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled25.json new file mode 100644 index 0000000000..09a07149cf --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled25.json @@ -0,0 +1,196 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "180d466d-eb36-4546-8922-e52c4c076823", + "underlay_address": "fd00:1122:3344:101::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::5]:32345", + "dataset": { + "pool_name": "oxp_ac789935-fa42-4d00-8967-df0d96dbb74e" + } + } + }, + "root": "/pool/ext/d732addc-cfe8-4c2c-8028-72eb4481b04e/crypt/zone" + }, + { + "zone": { + "id": "b5af0303-bc03-40a3-b733-0396d705dfbf", + "underlay_address": "fd00:1122:3344:101::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::7]:32345", + "dataset": { + "pool_name": "oxp_d732addc-cfe8-4c2c-8028-72eb4481b04e" + } + } + }, + "root": "/pool/ext/677b0057-3a80-461b-aca8-c2cb501a7278/crypt/zone" + }, + { + "zone": { + "id": "9c7c805a-f5ed-4e48-86e3-7aa81a718881", + "underlay_address": "fd00:1122:3344:101::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::c]:32345", + "dataset": { + "pool_name": "oxp_923c930c-80f8-448d-8321-cebfc6c41760" + } + } + }, + "root": "/pool/ext/ac789935-fa42-4d00-8967-df0d96dbb74e/crypt/zone" + }, + { + "zone": { + "id": "4e49c83c-2d4a-491a-91ac-4ab022026dcf", + "underlay_address": "fd00:1122:3344:101::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::4]:32345", + "dataset": { + "pool_name": "oxp_c99e6032-1d4f-47d2-9efe-ae2b2479554e" + } + } + }, + "root": "/pool/ext/653065d2-ab70-47c9-b832-34238fdc95ef/crypt/zone" + }, + { + "zone": { + "id": "0e38475e-b8b2-4813-bf80-3c170081081a", + "underlay_address": "fd00:1122:3344:101::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::d]:32345", + "dataset": { + "pool_name": "oxp_653065d2-ab70-47c9-b832-34238fdc95ef" + } + } + }, + "root": "/pool/ext/4c7ad252-55c2-4a1a-9d93-9dfcdfdfacca/crypt/zone" + }, + { + "zone": { + "id": "75123e60-1116-4b8d-a466-7302220127da", + "underlay_address": "fd00:1122:3344:101::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::8]:32345", + "dataset": { + "pool_name": "oxp_c764a8ae-6862-4eec-9db0-cc6ea478e4a7" + } + } + }, + "root": "/pool/ext/c764a8ae-6862-4eec-9db0-cc6ea478e4a7/crypt/zone" + }, + { + "zone": { + "id": "fbd0379c-97fa-49ea-8980-17ae30ffff3c", + "underlay_address": "fd00:1122:3344:101::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::b]:32345", + "dataset": { + "pool_name": "oxp_fcb0e4c7-e046-4cf5-ad35-3ad90e1eb90c" + } + } + }, + "root": "/pool/ext/4c7ad252-55c2-4a1a-9d93-9dfcdfdfacca/crypt/zone" + }, + { + "zone": { + "id": "ec635326-cd1d-4f73-b8e6-c3a36a7020db", + "underlay_address": "fd00:1122:3344:101::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::a]:32345", + "dataset": { + "pool_name": "oxp_6bfb4120-488d-4f3d-90ef-e9bfa523b388" + } + } + }, + "root": "/pool/ext/c99e6032-1d4f-47d2-9efe-ae2b2479554e/crypt/zone" + }, + { + "zone": { + "id": "f500d564-c40a-4eca-ac8a-a26b435f2037", + "underlay_address": "fd00:1122:3344:101::3", + "zone_type": { + "type": "external_dns", + "dataset": { + "pool_name": "oxp_c99e6032-1d4f-47d2-9efe-ae2b2479554e" + }, + "http_address": "[fd00:1122:3344:101::3]:5353", + "dns_address": "172.20.26.2:53", + "nic": { + "id": "b0b42776-3914-4a69-889f-4831dc72327c", + "kind": { + "type": "service", + "id": "f500d564-c40a-4eca-ac8a-a26b435f2037" + }, + "name": "external-dns-f500d564-c40a-4eca-ac8a-a26b435f2037", + "ip": "172.30.1.6", + "mac": "A8:40:25:FF:D0:B4", + "subnet": "172.30.1.0/24", + "vni": 100, + "primary": true, + "slot": 0 + } + } + }, + "root": "/pool/ext/ac789935-fa42-4d00-8967-df0d96dbb74e/crypt/zone" + }, + { + "zone": { + "id": "56d4dbcc-3b4a-4ed0-8795-7734aadcc4c0", + "underlay_address": "fd00:1122:3344:101::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::9]:32345", + "dataset": { + "pool_name": "oxp_4c7ad252-55c2-4a1a-9d93-9dfcdfdfacca" + } + } + }, + "root": "/pool/ext/4c7ad252-55c2-4a1a-9d93-9dfcdfdfacca/crypt/zone" + }, + { + "zone": { + "id": "0d3a1bd5-f6fe-49cb-807a-190dabc90103", + "underlay_address": "fd00:1122:3344:101::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::6]:32345", + "dataset": { + "pool_name": "oxp_677b0057-3a80-461b-aca8-c2cb501a7278" + } + } + }, + "root": "/pool/ext/6bfb4120-488d-4f3d-90ef-e9bfa523b388/crypt/zone" + }, + { + "zone": { + "id": "d34c7184-5d4e-4cb5-8f91-df74a343ffbc", + "underlay_address": "fd00:1122:3344:101::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:101::e]:123", + "ntp_servers": [ + "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal", + "6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/ac789935-fa42-4d00-8967-df0d96dbb74e/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled8.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled8.json new file mode 100644 index 0000000000..669889b3c5 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled8.json @@ -0,0 +1,198 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "7153983f-8fd7-4fb9-92ac-0f07a07798b4", + "underlay_address": "fd00:1122:3344:103::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::a]:32345", + "dataset": { + "pool_name": "oxp_bf428719-1b16-4503-99f4-ad95846d916f" + } + } + }, + "root": "/pool/ext/26e698bb-006d-4208-94b9-d1bc279111fa/crypt/zone" + }, + { + "zone": { + "id": "7d44ba36-4a69-490a-bc40-f6f90a4208d4", + "underlay_address": "fd00:1122:3344:103::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::c]:32345", + "dataset": { + "pool_name": "oxp_414e235b-55c3-4dc1-a568-8adf4ea1a052" + } + } + }, + "root": "/pool/ext/cf940e15-dbc5-481b-866a-4de4b018898e/crypt/zone" + }, + { + "zone": { + "id": "65a11c18-7f59-41ac-b9e7-680627f996e7", + "underlay_address": "fd00:1122:3344:103::3", + "zone_type": { + "type": "nexus", + "internal_address": "[fd00:1122:3344:103::3]:12221", + "external_ip": "172.20.26.3", + "nic": { + "id": "a3e13dde-a2bc-4170-ad84-aad8085b6034", + "kind": { + "type": "service", + "id": "65a11c18-7f59-41ac-b9e7-680627f996e7" + }, + "name": "nexus-65a11c18-7f59-41ac-b9e7-680627f996e7", + "ip": "172.30.2.5", + "mac": "A8:40:25:FF:A6:83", + "subnet": "172.30.2.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "external_tls": true, + "external_dns_servers": [ + "1.1.1.1", + "9.9.9.9" + ] + } + }, + "root": "/pool/ext/e126ddcc-8bee-46ba-8199-2a74df0ba040/crypt/zone" + }, + { + "zone": { + "id": "072fdae8-2adf-4fd2-94ce-e9b0663b91e7", + "underlay_address": "fd00:1122:3344:103::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::b]:32345", + "dataset": { + "pool_name": "oxp_26e698bb-006d-4208-94b9-d1bc279111fa" + } + } + }, + "root": "/pool/ext/bf428719-1b16-4503-99f4-ad95846d916f/crypt/zone" + }, + { + "zone": { + "id": "01f93020-7e7d-4185-93fb-6ca234056c82", + "underlay_address": "fd00:1122:3344:103::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::5]:32345", + "dataset": { + "pool_name": "oxp_7b24095a-72df-45e3-984f-2b795e052ac7" + } + } + }, + "root": "/pool/ext/7b24095a-72df-45e3-984f-2b795e052ac7/crypt/zone" + }, + { + "zone": { + "id": "e238116d-e5cc-43d4-9c8a-6f138ae8a15d", + "underlay_address": "fd00:1122:3344:103::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::6]:32345", + "dataset": { + "pool_name": "oxp_e126ddcc-8bee-46ba-8199-2a74df0ba040" + } + } + }, + "root": "/pool/ext/7b24095a-72df-45e3-984f-2b795e052ac7/crypt/zone" + }, + { + "zone": { + "id": "585cd8c5-c41e-4be4-beb8-bfbef9b53856", + "underlay_address": "fd00:1122:3344:103::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::7]:32345", + "dataset": { + "pool_name": "oxp_6340805e-c5af-418d-8bd1-fc0085667f33" + } + } + }, + "root": "/pool/ext/414e235b-55c3-4dc1-a568-8adf4ea1a052/crypt/zone" + }, + { + "zone": { + "id": "0b41c560-3b20-42f4-82ad-92f5bb575d6b", + "underlay_address": "fd00:1122:3344:103::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::9]:32345", + "dataset": { + "pool_name": "oxp_b93f880e-c55b-4d6c-9a16-939d84b628fc" + } + } + }, + "root": "/pool/ext/6340805e-c5af-418d-8bd1-fc0085667f33/crypt/zone" + }, + { + "zone": { + "id": "0ccf27c0-e32d-4b52-a2c5-6db0c64a26f9", + "underlay_address": "fd00:1122:3344:103::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::d]:32345", + "dataset": { + "pool_name": "oxp_2115b084-be0f-4fba-941b-33a659798a9e" + } + } + }, + "root": "/pool/ext/414e235b-55c3-4dc1-a568-8adf4ea1a052/crypt/zone" + }, + { + "zone": { + "id": "a6ba8273-0320-4dab-b801-281f041b0c50", + "underlay_address": "fd00:1122:3344:103::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::4]:32345", + "dataset": { + "pool_name": "oxp_8a199f12-4f5c-483a-8aca-f97856658a35" + } + } + }, + "root": "/pool/ext/b93f880e-c55b-4d6c-9a16-939d84b628fc/crypt/zone" + }, + { + "zone": { + "id": "b9b7b4c2-284a-4ec1-80ea-75b7a43b71c4", + "underlay_address": "fd00:1122:3344:103::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::8]:32345", + "dataset": { + "pool_name": "oxp_cf940e15-dbc5-481b-866a-4de4b018898e" + } + } + }, + "root": "/pool/ext/cf940e15-dbc5-481b-866a-4de4b018898e/crypt/zone" + }, + { + "zone": { + "id": "7a85d50e-b524-41c1-a052-118027eb77db", + "underlay_address": "fd00:1122:3344:103::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:103::e]:123", + "ntp_servers": [ + "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal", + "6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/b93f880e-c55b-4d6c-9a16-939d84b628fc/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack2-sled9.json b/sled-agent/tests/output/new-zones-ledgers/rack2-sled9.json new file mode 100644 index 0000000000..d4a429f9b0 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack2-sled9.json @@ -0,0 +1,192 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "912346a2-d7e6-427e-b373-e8dcbe4fcea9", + "underlay_address": "fd00:1122:3344:105::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::5]:32345", + "dataset": { + "pool_name": "oxp_b358fb1e-f52a-4a63-9aab-170225509b37" + } + } + }, + "root": "/pool/ext/0ae29053-29a2-489e-a1e6-6aec0ecd05f8/crypt/zone" + }, + { + "zone": { + "id": "3d420dff-c616-4c7d-bab1-0f9c2b5396bf", + "underlay_address": "fd00:1122:3344:105::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::a]:32345", + "dataset": { + "pool_name": "oxp_4eb2e4eb-41d8-496c-9a5a-687d7e004aa4" + } + } + }, + "root": "/pool/ext/eb1234a5-fdf7-4977-94d5-2eef25ce56a1/crypt/zone" + }, + { + "zone": { + "id": "9c5d88c9-8ff1-4f23-9438-7b81322eaf68", + "underlay_address": "fd00:1122:3344:105::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::b]:32345", + "dataset": { + "pool_name": "oxp_aadf48eb-6ff0-40b5-a092-1fdd06c03e11" + } + } + }, + "root": "/pool/ext/4358f47f-f21e-4cc8-829e-0c7fc2400a59/crypt/zone" + }, + { + "zone": { + "id": "f9c1deca-1898-429e-8c93-254c7aa7bae6", + "underlay_address": "fd00:1122:3344:105::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::8]:32345", + "dataset": { + "pool_name": "oxp_d1cb6b7d-2b92-4b7d-8a4d-551987f0277e" + } + } + }, + "root": "/pool/ext/f8b11629-ced6-412a-9c3f-d169b99ee996/crypt/zone" + }, + { + "zone": { + "id": "ce8563f3-4a93-45ff-b727-cbfbee6aa413", + "underlay_address": "fd00:1122:3344:105::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::9]:32345", + "dataset": { + "pool_name": "oxp_4358f47f-f21e-4cc8-829e-0c7fc2400a59" + } + } + }, + "root": "/pool/ext/eb1234a5-fdf7-4977-94d5-2eef25ce56a1/crypt/zone" + }, + { + "zone": { + "id": "9470ea7d-1920-4b4b-8fca-e7659a1ef733", + "underlay_address": "fd00:1122:3344:105::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::c]:32345", + "dataset": { + "pool_name": "oxp_17eff217-f0b1-4353-b133-0f68bbd5ceaa" + } + } + }, + "root": "/pool/ext/eb1234a5-fdf7-4977-94d5-2eef25ce56a1/crypt/zone" + }, + { + "zone": { + "id": "375296e5-0a23-466c-b605-4204080f8103", + "underlay_address": "fd00:1122:3344:105::4", + "zone_type": { + "type": "crucible_pantry", + "address": "[fd00:1122:3344:105::4]:17000" + } + }, + "root": "/pool/ext/4eb2e4eb-41d8-496c-9a5a-687d7e004aa4/crypt/zone" + }, + { + "zone": { + "id": "f9940969-b0e8-4e8c-86c7-4bc49cd15a5f", + "underlay_address": "fd00:1122:3344:105::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::7]:32345", + "dataset": { + "pool_name": "oxp_f8b11629-ced6-412a-9c3f-d169b99ee996" + } + } + }, + "root": "/pool/ext/17eff217-f0b1-4353-b133-0f68bbd5ceaa/crypt/zone" + }, + { + "zone": { + "id": "23dca27d-c79b-4930-a817-392e8aeaa4c1", + "underlay_address": "fd00:1122:3344:105::e", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::e]:32345", + "dataset": { + "pool_name": "oxp_57650e05-36ff-4de8-865f-b9562bdb67f5" + } + } + }, + "root": "/pool/ext/0ae29053-29a2-489e-a1e6-6aec0ecd05f8/crypt/zone" + }, + { + "zone": { + "id": "92d3e4e9-0768-4772-83c1-23cce52190e9", + "underlay_address": "fd00:1122:3344:105::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::6]:32345", + "dataset": { + "pool_name": "oxp_eb1234a5-fdf7-4977-94d5-2eef25ce56a1" + } + } + }, + "root": "/pool/ext/b358fb1e-f52a-4a63-9aab-170225509b37/crypt/zone" + }, + { + "zone": { + "id": "b3e9fee2-24d2-44e7-8539-a6918e85cf2b", + "underlay_address": "fd00:1122:3344:105::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::d]:32345", + "dataset": { + "pool_name": "oxp_0ae29053-29a2-489e-a1e6-6aec0ecd05f8" + } + } + }, + "root": "/pool/ext/eb1234a5-fdf7-4977-94d5-2eef25ce56a1/crypt/zone" + }, + { + "zone": { + "id": "4c3ef132-ec83-4b1b-9574-7c7d3035f9e9", + "underlay_address": "fd00:1122:3344:105::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:105::3]:32221", + "dataset": { + "pool_name": "oxp_b358fb1e-f52a-4a63-9aab-170225509b37" + } + } + }, + "root": "/pool/ext/d1cb6b7d-2b92-4b7d-8a4d-551987f0277e/crypt/zone" + }, + { + "zone": { + "id": "76b79b96-eaa2-4341-9aba-e77cfc92e0a9", + "underlay_address": "fd00:1122:3344:105::f", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:105::f]:123", + "ntp_servers": [ + "c3ec3d1a-3172-4d36-bfd3-f54a04d5ba55.host.control-plane.oxide.internal", + "6ea2684c-115e-48a6-8453-ab52d1cecd73.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/0ae29053-29a2-489e-a1e6-6aec0ecd05f8/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled0.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled0.json new file mode 100644 index 0000000000..db6c55f556 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled0.json @@ -0,0 +1,181 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "0710ecea-dbc4-417f-a6f7-1b97c3045db1", + "underlay_address": "fd00:1122:3344:116::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::6]:32345", + "dataset": { + "pool_name": "oxp_d5313ef5-019c-4c47-bc5e-63794107a1bb" + } + } + }, + "root": "/pool/ext/904e93a9-d175-4a20-9006-8c1e847aecf7/crypt/zone" + }, + { + "zone": { + "id": "28b29d14-d55f-4b55-bbc1-f66e46ae3e70", + "underlay_address": "fd00:1122:3344:116::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::9]:32345", + "dataset": { + "pool_name": "oxp_60755ffe-e9ee-4619-a751-8b3ea6405e67" + } + } + }, + "root": "/pool/ext/d5313ef5-019c-4c47-bc5e-63794107a1bb/crypt/zone" + }, + { + "zone": { + "id": "6f8f9fd2-b139-4069-a7e2-8d40efd58f6c", + "underlay_address": "fd00:1122:3344:116::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::d]:32345", + "dataset": { + "pool_name": "oxp_ccd2cb0b-782f-4026-a160-6d1192f04ca3" + } + } + }, + "root": "/pool/ext/d5313ef5-019c-4c47-bc5e-63794107a1bb/crypt/zone" + }, + { + "zone": { + "id": "450308ad-bf4d-40ff-ba62-f3290f7fffaf", + "underlay_address": "fd00:1122:3344:116::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::4]:32345", + "dataset": { + "pool_name": "oxp_46b09442-65ba-4d59-9121-9803fe3b724b" + } + } + }, + "root": "/pool/ext/54d901cc-f75e-417d-8a9f-24363136d0ef/crypt/zone" + }, + { + "zone": { + "id": "9a22bbaa-eab4-4a32-8546-9882dc029483", + "underlay_address": "fd00:1122:3344:116::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::8]:32345", + "dataset": { + "pool_name": "oxp_93e3f350-75a0-4af0-bdac-baf9b423926f" + } + } + }, + "root": "/pool/ext/d5313ef5-019c-4c47-bc5e-63794107a1bb/crypt/zone" + }, + { + "zone": { + "id": "63a9dc49-0b5b-4483-95ed-553b545dc202", + "underlay_address": "fd00:1122:3344:116::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::a]:32345", + "dataset": { + "pool_name": "oxp_e3532845-76c0-42a9-903b-a07f7992e937" + } + } + }, + "root": "/pool/ext/60755ffe-e9ee-4619-a751-8b3ea6405e67/crypt/zone" + }, + { + "zone": { + "id": "1fef5b6c-78e4-4ad9-9973-9d8c78f1e232", + "underlay_address": "fd00:1122:3344:116::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::7]:32345", + "dataset": { + "pool_name": "oxp_54d901cc-f75e-417d-8a9f-24363136d0ef" + } + } + }, + "root": "/pool/ext/90d7b6f9-3e28-48b0-86ac-0486728075cf/crypt/zone" + }, + { + "zone": { + "id": "b2aab21a-cccd-4aa9-977f-a32090e6eaa7", + "underlay_address": "fd00:1122:3344:116::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::5]:32345", + "dataset": { + "pool_name": "oxp_90d7b6f9-3e28-48b0-86ac-0486728075cf" + } + } + }, + "root": "/pool/ext/46b09442-65ba-4d59-9121-9803fe3b724b/crypt/zone" + }, + { + "zone": { + "id": "fc1bbf28-24f3-4c1f-b367-2bc8231eb7d4", + "underlay_address": "fd00:1122:3344:116::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::b]:32345", + "dataset": { + "pool_name": "oxp_0a7bb0d3-408b-42b1-8846-76cf106a9580" + } + } + }, + "root": "/pool/ext/e3532845-76c0-42a9-903b-a07f7992e937/crypt/zone" + }, + { + "zone": { + "id": "bcb7617a-f76a-4912-8ccc-802d2a697e3c", + "underlay_address": "fd00:1122:3344:116::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:116::c]:32345", + "dataset": { + "pool_name": "oxp_904e93a9-d175-4a20-9006-8c1e847aecf7" + } + } + }, + "root": "/pool/ext/ccd2cb0b-782f-4026-a160-6d1192f04ca3/crypt/zone" + }, + { + "zone": { + "id": "371fba3a-658b-469b-b675-c90cc0d39254", + "underlay_address": "fd00:1122:3344:116::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:116::3]:32221", + "dataset": { + "pool_name": "oxp_46b09442-65ba-4d59-9121-9803fe3b724b" + } + } + }, + "root": "/pool/ext/46b09442-65ba-4d59-9121-9803fe3b724b/crypt/zone" + }, + { + "zone": { + "id": "5a4d89f5-49e0-4566-a99c-342d1bb26b1c", + "underlay_address": "fd00:1122:3344:116::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:116::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/60755ffe-e9ee-4619-a751-8b3ea6405e67/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled1.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled1.json new file mode 100644 index 0000000000..ae3e3d8f4a --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled1.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "f401d06c-46fc-42f8-aa51-7515a51355ce", + "underlay_address": "fd00:1122:3344:11c::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::8]:32345", + "dataset": { + "pool_name": "oxp_8a88768a-2dd5-43b7-bd40-0db77be4d3a8" + } + } + }, + "root": "/pool/ext/19d23d27-6a33-4203-b8c1-4b0df4ac791f/crypt/zone" + }, + { + "zone": { + "id": "721c96ea-08d4-4c89-828f-600e7e344916", + "underlay_address": "fd00:1122:3344:11c::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::6]:32345", + "dataset": { + "pool_name": "oxp_15259003-fb04-4547-b4a9-b4511893c0fd" + } + } + }, + "root": "/pool/ext/d2a8ed82-22ef-46d8-ad40-e1cb2cecebee/crypt/zone" + }, + { + "zone": { + "id": "ca17bdf9-51c5-4e1e-b822-856609070ec6", + "underlay_address": "fd00:1122:3344:11c::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::5]:32345", + "dataset": { + "pool_name": "oxp_d2a8ed82-22ef-46d8-ad40-e1cb2cecebee" + } + } + }, + "root": "/pool/ext/15259003-fb04-4547-b4a9-b4511893c0fd/crypt/zone" + }, + { + "zone": { + "id": "5825447e-1b5b-4960-b202-e75853d3d250", + "underlay_address": "fd00:1122:3344:11c::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::9]:32345", + "dataset": { + "pool_name": "oxp_04e94454-cbd4-4cee-ad69-42372bcbabd5" + } + } + }, + "root": "/pool/ext/542e0fb3-552c-4d3b-b853-da1f13b581a0/crypt/zone" + }, + { + "zone": { + "id": "b937d3f0-1352-47a2-b9d1-a9ccf9c82b16", + "underlay_address": "fd00:1122:3344:11c::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::c]:32345", + "dataset": { + "pool_name": "oxp_542e0fb3-552c-4d3b-b853-da1f13b581a0" + } + } + }, + "root": "/pool/ext/eedd1d58-4892-456f-aaf7-9d650c7921ca/crypt/zone" + }, + { + "zone": { + "id": "d63a677b-8dac-44ee-89a2-cc4cb151254d", + "underlay_address": "fd00:1122:3344:11c::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::3]:32345", + "dataset": { + "pool_name": "oxp_45b5f1ee-7b66-4d74-8364-54fa0c73775f" + } + } + }, + "root": "/pool/ext/8a88768a-2dd5-43b7-bd40-0db77be4d3a8/crypt/zone" + }, + { + "zone": { + "id": "abcb92ea-9f17-4cd8-897b-9d0d1ef7903a", + "underlay_address": "fd00:1122:3344:11c::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::4]:32345", + "dataset": { + "pool_name": "oxp_341d49db-c06a-416d-90e1-b0a3426ed02e" + } + } + }, + "root": "/pool/ext/eedd1d58-4892-456f-aaf7-9d650c7921ca/crypt/zone" + }, + { + "zone": { + "id": "000ac89d-db07-47ae-83cf-d9cafef013de", + "underlay_address": "fd00:1122:3344:11c::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::b]:32345", + "dataset": { + "pool_name": "oxp_eedd1d58-4892-456f-aaf7-9d650c7921ca" + } + } + }, + "root": "/pool/ext/04e94454-cbd4-4cee-ad69-42372bcbabd5/crypt/zone" + }, + { + "zone": { + "id": "29e1e2e4-695e-4c05-8f0c-c16a0a61d390", + "underlay_address": "fd00:1122:3344:11c::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::7]:32345", + "dataset": { + "pool_name": "oxp_19d23d27-6a33-4203-b8c1-4b0df4ac791f" + } + } + }, + "root": "/pool/ext/d2a8ed82-22ef-46d8-ad40-e1cb2cecebee/crypt/zone" + }, + { + "zone": { + "id": "9fa7d7be-a6de-4d36-b56b-d1cc5ca7c82c", + "underlay_address": "fd00:1122:3344:11c::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11c::a]:32345", + "dataset": { + "pool_name": "oxp_0fd7a0b1-ed4b-4dc6-8c44-a49c9628c7e1" + } + } + }, + "root": "/pool/ext/d2a8ed82-22ef-46d8-ad40-e1cb2cecebee/crypt/zone" + }, + { + "zone": { + "id": "249db5f1-45e2-4a5c-a91f-cc51dbd87040", + "underlay_address": "fd00:1122:3344:11c::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:11c::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/542e0fb3-552c-4d3b-b853-da1f13b581a0/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled11.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled11.json new file mode 100644 index 0000000000..c94417ffb8 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled11.json @@ -0,0 +1,201 @@ +{ + "omicron_generation": 2, + "ledger_generation": 5, + "zones": [ + { + "zone": { + "id": "7ddd0738-59df-4b67-a41e-7f0de9827187", + "underlay_address": "fd00:1122:3344:11e::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::4]:32345", + "dataset": { + "pool_name": "oxp_09af632a-6b1b-4a18-8c91-d392da38b02f" + } + } + }, + "root": "/pool/ext/09af632a-6b1b-4a18-8c91-d392da38b02f/crypt/zone" + }, + { + "zone": { + "id": "9706189f-713a-4394-b5dc-45dcf67dc46e", + "underlay_address": "fd00:1122:3344:11e::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::9]:32345", + "dataset": { + "pool_name": "oxp_4e1837c8-91ab-4d1d-abfd-f5144d88535e" + } + } + }, + "root": "/pool/ext/2f0d47cb-28d1-4350-8656-60c6121f773b/crypt/zone" + }, + { + "zone": { + "id": "7bdd841b-5e34-4c19-9066-b12578651446", + "underlay_address": "fd00:1122:3344:11e::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::a]:32345", + "dataset": { + "pool_name": "oxp_78d1e7f7-8d11-4fed-8b1e-be58908aea2f" + } + } + }, + "root": "/pool/ext/62c23f4b-8e7b-4cd8-9055-19c1d8bd5ac8/crypt/zone" + }, + { + "zone": { + "id": "74c0f60b-de5f-4456-a85f-f992a6e10424", + "underlay_address": "fd00:1122:3344:11e::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::b]:32345", + "dataset": { + "pool_name": "oxp_3b81d709-bf10-4dd7-a2c0-759d8acc2da0" + } + } + }, + "root": "/pool/ext/09af632a-6b1b-4a18-8c91-d392da38b02f/crypt/zone" + }, + { + "zone": { + "id": "da81ce6f-bd38-440e-b966-8a743092fa21", + "underlay_address": "fd00:1122:3344:11e::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::6]:32345", + "dataset": { + "pool_name": "oxp_62c23f4b-8e7b-4cd8-9055-19c1d8bd5ac8" + } + } + }, + "root": "/pool/ext/215dd02b-0de6-488a-9e65-5e588cd079fb/crypt/zone" + }, + { + "zone": { + "id": "febbca37-5279-400f-a2e9-6b5271b2d2fc", + "underlay_address": "fd00:1122:3344:11e::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::7]:32345", + "dataset": { + "pool_name": "oxp_fb33e773-fb93-41a0-8078-b653b9078dda" + } + } + }, + "root": "/pool/ext/2f0d47cb-28d1-4350-8656-60c6121f773b/crypt/zone" + }, + { + "zone": { + "id": "5100e222-5ea4-4e67-9040-679137e666c8", + "underlay_address": "fd00:1122:3344:11e::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::5]:32345", + "dataset": { + "pool_name": "oxp_23767587-2253-431b-8944-18b9bfefcb3d" + } + } + }, + "root": "/pool/ext/3b81d709-bf10-4dd7-a2c0-759d8acc2da0/crypt/zone" + }, + { + "zone": { + "id": "c7ec3bc8-08ca-4901-a45e-0d68db72c6a7", + "underlay_address": "fd00:1122:3344:11e::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::3]:32345", + "dataset": { + "pool_name": "oxp_2f0d47cb-28d1-4350-8656-60c6121f773b" + } + } + }, + "root": "/pool/ext/215dd02b-0de6-488a-9e65-5e588cd079fb/crypt/zone" + }, + { + "zone": { + "id": "1fc80dd3-0fd9-4403-96bd-5bbf9eb0f15a", + "underlay_address": "fd00:1122:3344:11e::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::c]:32345", + "dataset": { + "pool_name": "oxp_2c932d54-41fb-4ffe-a57f-0479b9e5841e" + } + } + }, + "root": "/pool/ext/3b81d709-bf10-4dd7-a2c0-759d8acc2da0/crypt/zone" + }, + { + "zone": { + "id": "4eacc68d-5699-440a-ab33-c75f259e4cc3", + "underlay_address": "fd00:1122:3344:11e::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11e::8]:32345", + "dataset": { + "pool_name": "oxp_215dd02b-0de6-488a-9e65-5e588cd079fb" + } + } + }, + "root": "/pool/ext/4e1837c8-91ab-4d1d-abfd-f5144d88535e/crypt/zone" + }, + { + "zone": { + "id": "cb901d3e-8811-4c4c-a274-a44130501ecf", + "underlay_address": "fd00:1122:3344:11e::d", + "zone_type": { + "type": "boundary_ntp", + "address": "[fd00:1122:3344:11e::d]:123", + "ntp_servers": [ + "time.cloudflare.com" + ], + "dns_servers": [ + "1.1.1.1", + "8.8.8.8" + ], + "domain": null, + "nic": { + "id": "bcf9d9eb-b4ba-4fd5-91e0-55a3414ae049", + "kind": { + "type": "service", + "id": "cb901d3e-8811-4c4c-a274-a44130501ecf" + }, + "name": "ntp-cb901d3e-8811-4c4c-a274-a44130501ecf", + "ip": "172.30.3.6", + "mac": "A8:40:25:FF:D5:2F", + "subnet": "172.30.3.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "snat_cfg": { + "ip": "45.154.216.39", + "first_port": 16384, + "last_port": 32767 + } + } + }, + "root": "/pool/ext/23767587-2253-431b-8944-18b9bfefcb3d/crypt/zone" + }, + { + "zone": { + "id": "be4aada9-d160-401d-a630-a0764c039702", + "underlay_address": "fd00:1122:3344:2::1", + "zone_type": { + "type": "internal_dns", + "dataset": { + "pool_name": "oxp_2f0d47cb-28d1-4350-8656-60c6121f773b" + }, + "http_address": "[fd00:1122:3344:2::1]:5353", + "dns_address": "[fd00:1122:3344:2::1]:53", + "gz_address": "fd00:1122:3344:2::2", + "gz_address_index": 1 + } + }, + "root": "/pool/ext/78d1e7f7-8d11-4fed-8b1e-be58908aea2f/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled12.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled12.json new file mode 100644 index 0000000000..bfc30cf160 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled12.json @@ -0,0 +1,181 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "d8f1b9d2-fa2e-4f03-bbea-2039448d7792", + "underlay_address": "fd00:1122:3344:112::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::5]:32345", + "dataset": { + "pool_name": "oxp_7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1" + } + } + }, + "root": "/pool/ext/78d9f0ae-8e7f-450e-abc2-76b983efa5cd/crypt/zone" + }, + { + "zone": { + "id": "2074a935-c0b3-4c4f-aae5-a29adae3e1ac", + "underlay_address": "fd00:1122:3344:112::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::8]:32345", + "dataset": { + "pool_name": "oxp_ac663368-45fb-447c-811e-561c68e37bdd" + } + } + }, + "root": "/pool/ext/ac663368-45fb-447c-811e-561c68e37bdd/crypt/zone" + }, + { + "zone": { + "id": "2885d3c7-ad7d-445c-8630-dc6c81f8caa0", + "underlay_address": "fd00:1122:3344:112::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::a]:32345", + "dataset": { + "pool_name": "oxp_8e82e8da-e1c5-4867-bc1c-b5441f9c1010" + } + } + }, + "root": "/pool/ext/8e82e8da-e1c5-4867-bc1c-b5441f9c1010/crypt/zone" + }, + { + "zone": { + "id": "1eca241b-6868-4c59-876b-58356654f3b5", + "underlay_address": "fd00:1122:3344:112::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::c]:32345", + "dataset": { + "pool_name": "oxp_fde16c69-aa47-4a15-bb3f-3a5861ae45bd" + } + } + }, + "root": "/pool/ext/7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1/crypt/zone" + }, + { + "zone": { + "id": "cc656f2e-8542-4986-8524-2f55984939c1", + "underlay_address": "fd00:1122:3344:112::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::d]:32345", + "dataset": { + "pool_name": "oxp_21e6d0f9-887e-4d6f-9a00-4cd61139eea6" + } + } + }, + "root": "/pool/ext/21e6d0f9-887e-4d6f-9a00-4cd61139eea6/crypt/zone" + }, + { + "zone": { + "id": "dfb1ebce-a4c7-4b50-9435-9a79b884c1af", + "underlay_address": "fd00:1122:3344:112::3", + "zone_type": { + "type": "clickhouse", + "address": "[fd00:1122:3344:112::3]:8123", + "dataset": { + "pool_name": "oxp_4f045315-de51-46ed-a011-16496615278f" + } + } + }, + "root": "/pool/ext/7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1/crypt/zone" + }, + { + "zone": { + "id": "a95d90ed-b2b1-4a5d-8d0d-4195b34bc764", + "underlay_address": "fd00:1122:3344:112::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::6]:32345", + "dataset": { + "pool_name": "oxp_d2c77c69-14d7-442e-8b47-a0d7af5a0e7e" + } + } + }, + "root": "/pool/ext/fad56ff1-ad9f-4215-b584-522eab18cf7b/crypt/zone" + }, + { + "zone": { + "id": "1d3ebc90-d5a5-4cb0-ae90-50bb2163ae13", + "underlay_address": "fd00:1122:3344:112::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::b]:32345", + "dataset": { + "pool_name": "oxp_fad56ff1-ad9f-4215-b584-522eab18cf7b" + } + } + }, + "root": "/pool/ext/7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1/crypt/zone" + }, + { + "zone": { + "id": "7af9f38b-0c7a-402e-8db3-7c7fb50b4665", + "underlay_address": "fd00:1122:3344:112::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::9]:32345", + "dataset": { + "pool_name": "oxp_d0693580-5c5a-449f-803f-ce7188ebc580" + } + } + }, + "root": "/pool/ext/d2c77c69-14d7-442e-8b47-a0d7af5a0e7e/crypt/zone" + }, + { + "zone": { + "id": "94d9bb0a-ecd2-4501-b960-60982f55ad12", + "underlay_address": "fd00:1122:3344:112::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::7]:32345", + "dataset": { + "pool_name": "oxp_78d9f0ae-8e7f-450e-abc2-76b983efa5cd" + } + } + }, + "root": "/pool/ext/ac663368-45fb-447c-811e-561c68e37bdd/crypt/zone" + }, + { + "zone": { + "id": "277c1105-576e-4ec1-8e2c-cbae2f5ac9f6", + "underlay_address": "fd00:1122:3344:112::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:112::4]:32345", + "dataset": { + "pool_name": "oxp_4f045315-de51-46ed-a011-16496615278f" + } + } + }, + "root": "/pool/ext/7d7ed1b7-7b77-4f0a-abb1-27de7cb584d1/crypt/zone" + }, + { + "zone": { + "id": "555c3407-a76c-4ea4-a17a-a670d85a59b0", + "underlay_address": "fd00:1122:3344:112::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:112::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/8e82e8da-e1c5-4867-bc1c-b5441f9c1010/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled13.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled13.json new file mode 100644 index 0000000000..66c04be148 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled13.json @@ -0,0 +1,201 @@ +{ + "omicron_generation": 2, + "ledger_generation": 5, + "zones": [ + { + "zone": { + "id": "fbcf51c9-a732-4a03-8c19-cfb5b819cb7a", + "underlay_address": "fd00:1122:3344:104::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::5]:32345", + "dataset": { + "pool_name": "oxp_382a2961-cd27-4a9c-901d-468a45ff5708" + } + } + }, + "root": "/pool/ext/e99994ae-61ca-4742-a02c-eb0a8a5b69ff/crypt/zone" + }, + { + "zone": { + "id": "7f8a5026-1f1d-4ab3-8c04-077bfda2f815", + "underlay_address": "fd00:1122:3344:104::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::4]:32345", + "dataset": { + "pool_name": "oxp_9c99b9b6-8018-455e-a58a-c048ddd3e11b" + } + } + }, + "root": "/pool/ext/22c79e54-37ef-4ad2-a6cb-a7ee3e4f7167/crypt/zone" + }, + { + "zone": { + "id": "6d45d856-0e49-4eb7-ad76-989a9ae636a2", + "underlay_address": "fd00:1122:3344:104::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::3]:32345", + "dataset": { + "pool_name": "oxp_b74a84fa-b4c8-4c5f-92f4-f4e62a0a311d" + } + } + }, + "root": "/pool/ext/9c99b9b6-8018-455e-a58a-c048ddd3e11b/crypt/zone" + }, + { + "zone": { + "id": "c8dc7fff-72c8-49eb-a552-d605f8655134", + "underlay_address": "fd00:1122:3344:104::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::6]:32345", + "dataset": { + "pool_name": "oxp_22c79e54-37ef-4ad2-a6cb-a7ee3e4f7167" + } + } + }, + "root": "/pool/ext/22c79e54-37ef-4ad2-a6cb-a7ee3e4f7167/crypt/zone" + }, + { + "zone": { + "id": "128a90f5-8889-4665-8343-2c7098f2922c", + "underlay_address": "fd00:1122:3344:104::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::7]:32345", + "dataset": { + "pool_name": "oxp_8b3d0b51-c6a5-4d2c-827a-0d0d1471136d" + } + } + }, + "root": "/pool/ext/29cd042b-e772-4d26-ac85-ef16009950bd/crypt/zone" + }, + { + "zone": { + "id": "a72f1878-3b03-4267-9024-5df5ebae69de", + "underlay_address": "fd00:1122:3344:104::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::a]:32345", + "dataset": { + "pool_name": "oxp_e99994ae-61ca-4742-a02c-eb0a8a5b69ff" + } + } + }, + "root": "/pool/ext/8b3d0b51-c6a5-4d2c-827a-0d0d1471136d/crypt/zone" + }, + { + "zone": { + "id": "6a9165a2-9b66-485a-aaf0-70d89d60bb6c", + "underlay_address": "fd00:1122:3344:104::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::b]:32345", + "dataset": { + "pool_name": "oxp_6a02f05f-e400-4c80-8df8-89aaecb6c12b" + } + } + }, + "root": "/pool/ext/9c99b9b6-8018-455e-a58a-c048ddd3e11b/crypt/zone" + }, + { + "zone": { + "id": "9677c4ed-96bc-4dcb-ae74-f7a3e9d2b5e2", + "underlay_address": "fd00:1122:3344:104::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::c]:32345", + "dataset": { + "pool_name": "oxp_7c30978f-ee87-4e53-8fdf-3455e5e851b7" + } + } + }, + "root": "/pool/ext/29cd042b-e772-4d26-ac85-ef16009950bd/crypt/zone" + }, + { + "zone": { + "id": "179039e7-3ffd-4b76-9379-bef41d42a5ff", + "underlay_address": "fd00:1122:3344:104::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::8]:32345", + "dataset": { + "pool_name": "oxp_4db7e002-e112-4bfc-a41e-8ae26991b01e" + } + } + }, + "root": "/pool/ext/8b3d0b51-c6a5-4d2c-827a-0d0d1471136d/crypt/zone" + }, + { + "zone": { + "id": "6067e31e-b6a3-4114-9e49-0296adc8e7af", + "underlay_address": "fd00:1122:3344:104::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:104::9]:32345", + "dataset": { + "pool_name": "oxp_29cd042b-e772-4d26-ac85-ef16009950bd" + } + } + }, + "root": "/pool/ext/9c99b9b6-8018-455e-a58a-c048ddd3e11b/crypt/zone" + }, + { + "zone": { + "id": "440dd615-e11f-4a5d-aeb4-dcf88bb314de", + "underlay_address": "fd00:1122:3344:104::d", + "zone_type": { + "type": "boundary_ntp", + "address": "[fd00:1122:3344:104::d]:123", + "ntp_servers": [ + "time.cloudflare.com" + ], + "dns_servers": [ + "1.1.1.1", + "8.8.8.8" + ], + "domain": null, + "nic": { + "id": "0b52fe1b-f4cc-43b1-9ac3-4ebb4ab60133", + "kind": { + "type": "service", + "id": "440dd615-e11f-4a5d-aeb4-dcf88bb314de" + }, + "name": "ntp-440dd615-e11f-4a5d-aeb4-dcf88bb314de", + "ip": "172.30.3.5", + "mac": "A8:40:25:FF:85:1E", + "subnet": "172.30.3.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "snat_cfg": { + "ip": "45.154.216.38", + "first_port": 0, + "last_port": 16383 + } + } + }, + "root": "/pool/ext/382a2961-cd27-4a9c-901d-468a45ff5708/crypt/zone" + }, + { + "zone": { + "id": "06e2de03-bd92-404c-a8ea-a13185539d24", + "underlay_address": "fd00:1122:3344:1::1", + "zone_type": { + "type": "internal_dns", + "dataset": { + "pool_name": "oxp_b74a84fa-b4c8-4c5f-92f4-f4e62a0a311d" + }, + "http_address": "[fd00:1122:3344:1::1]:5353", + "dns_address": "[fd00:1122:3344:1::1]:53", + "gz_address": "fd00:1122:3344:1::2", + "gz_address_index": 0 + } + }, + "root": "/pool/ext/e99994ae-61ca-4742-a02c-eb0a8a5b69ff/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled14.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled14.json new file mode 100644 index 0000000000..e8d061dbfd --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled14.json @@ -0,0 +1,198 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "ac35afab-a312-43c3-a42d-04b8e99fcbde", + "underlay_address": "fd00:1122:3344:111::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::4]:32345", + "dataset": { + "pool_name": "oxp_6601065c-c172-4118-81b4-16adde7e9401" + } + } + }, + "root": "/pool/ext/24d7e250-9fc6-459e-8155-30f8e8ccb28c/crypt/zone" + }, + { + "zone": { + "id": "6cd94da2-35b9-4683-a931-29ad4a5ed0ef", + "underlay_address": "fd00:1122:3344:111::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::c]:32345", + "dataset": { + "pool_name": "oxp_58276eba-a53c-4ef3-b374-4cdcde4d6e12" + } + } + }, + "root": "/pool/ext/24d7e250-9fc6-459e-8155-30f8e8ccb28c/crypt/zone" + }, + { + "zone": { + "id": "41f07d39-fcc0-4796-8b7c-7cfcd9135f78", + "underlay_address": "fd00:1122:3344:111::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::9]:32345", + "dataset": { + "pool_name": "oxp_4b90abdc-3348-4158-bedc-5bcd56e281d8" + } + } + }, + "root": "/pool/ext/8e955f54-fbef-4021-9eec-457825468813/crypt/zone" + }, + { + "zone": { + "id": "44c35566-dd64-4e4a-896e-c50aaa3df14f", + "underlay_address": "fd00:1122:3344:111::3", + "zone_type": { + "type": "nexus", + "internal_address": "[fd00:1122:3344:111::3]:12221", + "external_ip": "45.154.216.37", + "nic": { + "id": "6f824d20-6ce0-4e8b-9ce3-b12dd2b59913", + "kind": { + "type": "service", + "id": "44c35566-dd64-4e4a-896e-c50aaa3df14f" + }, + "name": "nexus-44c35566-dd64-4e4a-896e-c50aaa3df14f", + "ip": "172.30.2.7", + "mac": "A8:40:25:FF:E8:5F", + "subnet": "172.30.2.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "external_tls": true, + "external_dns_servers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "root": "/pool/ext/435d7a1b-2865-4d49-903f-a68f464ade4d/crypt/zone" + }, + { + "zone": { + "id": "e5020d24-8652-456b-bf92-cd7d255a34c5", + "underlay_address": "fd00:1122:3344:111::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::6]:32345", + "dataset": { + "pool_name": "oxp_f6925045-363d-4e18-9bde-ee2987b33d21" + } + } + }, + "root": "/pool/ext/6601065c-c172-4118-81b4-16adde7e9401/crypt/zone" + }, + { + "zone": { + "id": "8f25f258-afd7-4351-83e4-24220ec0c251", + "underlay_address": "fd00:1122:3344:111::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::8]:32345", + "dataset": { + "pool_name": "oxp_8e955f54-fbef-4021-9eec-457825468813" + } + } + }, + "root": "/pool/ext/6601065c-c172-4118-81b4-16adde7e9401/crypt/zone" + }, + { + "zone": { + "id": "26aa50ec-d70a-47ea-85fc-e55c62a2e0c6", + "underlay_address": "fd00:1122:3344:111::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::5]:32345", + "dataset": { + "pool_name": "oxp_24d7e250-9fc6-459e-8155-30f8e8ccb28c" + } + } + }, + "root": "/pool/ext/435d7a1b-2865-4d49-903f-a68f464ade4d/crypt/zone" + }, + { + "zone": { + "id": "68dc212f-a96a-420f-8334-b11ee5d7cb95", + "underlay_address": "fd00:1122:3344:111::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::7]:32345", + "dataset": { + "pool_name": "oxp_4353b00b-937e-4d07-aea6-014c57b6f12c" + } + } + }, + "root": "/pool/ext/24d7e250-9fc6-459e-8155-30f8e8ccb28c/crypt/zone" + }, + { + "zone": { + "id": "475140fa-a5dc-4ec1-876d-751c48adfc37", + "underlay_address": "fd00:1122:3344:111::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::a]:32345", + "dataset": { + "pool_name": "oxp_ee55b053-6874-4e20-86b5-2e105e64c068" + } + } + }, + "root": "/pool/ext/ee55b053-6874-4e20-86b5-2e105e64c068/crypt/zone" + }, + { + "zone": { + "id": "09d5a8c9-00db-4914-a2c6-7ae3d2da4558", + "underlay_address": "fd00:1122:3344:111::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::d]:32345", + "dataset": { + "pool_name": "oxp_9ab5aba5-47dc-4bc4-8f6d-7cbe0f98a9a2" + } + } + }, + "root": "/pool/ext/8e955f54-fbef-4021-9eec-457825468813/crypt/zone" + }, + { + "zone": { + "id": "014f6a39-ad64-4f0a-9fef-01ca0d184cbf", + "underlay_address": "fd00:1122:3344:111::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:111::b]:32345", + "dataset": { + "pool_name": "oxp_435d7a1b-2865-4d49-903f-a68f464ade4d" + } + } + }, + "root": "/pool/ext/f6925045-363d-4e18-9bde-ee2987b33d21/crypt/zone" + }, + { + "zone": { + "id": "aceaf348-ba07-4965-a543-63a800826fe8", + "underlay_address": "fd00:1122:3344:111::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:111::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/8e955f54-fbef-4021-9eec-457825468813/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled15.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled15.json new file mode 100644 index 0000000000..e3b3dba86a --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled15.json @@ -0,0 +1,196 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "09a9ecee-1e7c-4819-b27a-73bb61099ce7", + "underlay_address": "fd00:1122:3344:114::3", + "zone_type": { + "type": "external_dns", + "dataset": { + "pool_name": "oxp_b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e" + }, + "http_address": "[fd00:1122:3344:114::3]:5353", + "dns_address": "45.154.216.33:53", + "nic": { + "id": "400ca77b-7fee-47d5-8f17-1f4b9c729f27", + "kind": { + "type": "service", + "id": "09a9ecee-1e7c-4819-b27a-73bb61099ce7" + }, + "name": "external-dns-09a9ecee-1e7c-4819-b27a-73bb61099ce7", + "ip": "172.30.1.5", + "mac": "A8:40:25:FF:B7:C7", + "subnet": "172.30.1.0/24", + "vni": 100, + "primary": true, + "slot": 0 + } + } + }, + "root": "/pool/ext/9e878b1e-bf92-4155-8162-640851c2f5d5/crypt/zone" + }, + { + "zone": { + "id": "1792e003-55f7-49b8-906c-4160db91bc23", + "underlay_address": "fd00:1122:3344:114::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::5]:32345", + "dataset": { + "pool_name": "oxp_7f3a760f-a4c0-456f-8a22-2d06ecac1022" + } + } + }, + "root": "/pool/ext/76f09ad5-c96c-4748-bbe4-71afaea7bc5e/crypt/zone" + }, + { + "zone": { + "id": "73bc7c0e-1034-449f-8920-4a1f418653ff", + "underlay_address": "fd00:1122:3344:114::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::8]:32345", + "dataset": { + "pool_name": "oxp_e87037be-1cdf-4c6e-a8a3-c27b830eaef9" + } + } + }, + "root": "/pool/ext/b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e/crypt/zone" + }, + { + "zone": { + "id": "06dc6619-6251-4543-9a10-da1698af49d5", + "underlay_address": "fd00:1122:3344:114::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::9]:32345", + "dataset": { + "pool_name": "oxp_ee34c530-ce70-4f1a-8c97-d0ebb77ccfc8" + } + } + }, + "root": "/pool/ext/9e878b1e-bf92-4155-8162-640851c2f5d5/crypt/zone" + }, + { + "zone": { + "id": "0d796c52-37ca-490d-b42f-dcc22fe5fd6b", + "underlay_address": "fd00:1122:3344:114::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::c]:32345", + "dataset": { + "pool_name": "oxp_9ec2b893-d486-4b24-a077-1a297f9eb15f" + } + } + }, + "root": "/pool/ext/9e72c0e2-4895-4791-b606-2f18e432fb69/crypt/zone" + }, + { + "zone": { + "id": "91d0011f-de44-4823-bc26-a447affa39bc", + "underlay_address": "fd00:1122:3344:114::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::a]:32345", + "dataset": { + "pool_name": "oxp_85e81a14-031d-4a63-a91f-981c64e91f60" + } + } + }, + "root": "/pool/ext/b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e/crypt/zone" + }, + { + "zone": { + "id": "0c44a2f1-559a-459c-9931-e0e7964d41c6", + "underlay_address": "fd00:1122:3344:114::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::b]:32345", + "dataset": { + "pool_name": "oxp_76f09ad5-c96c-4748-bbe4-71afaea7bc5e" + } + } + }, + "root": "/pool/ext/e87037be-1cdf-4c6e-a8a3-c27b830eaef9/crypt/zone" + }, + { + "zone": { + "id": "ea363819-96f6-4fb6-a203-f18414f1c60e", + "underlay_address": "fd00:1122:3344:114::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::4]:32345", + "dataset": { + "pool_name": "oxp_b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e" + } + } + }, + "root": "/pool/ext/b7fbb6db-aa4a-4a6d-8206-b7bdc000d56e/crypt/zone" + }, + { + "zone": { + "id": "21592c39-da6b-4527-842e-edeeceffafa1", + "underlay_address": "fd00:1122:3344:114::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::6]:32345", + "dataset": { + "pool_name": "oxp_9e72c0e2-4895-4791-b606-2f18e432fb69" + } + } + }, + "root": "/pool/ext/7aff8429-b65d-4a53-a796-7221ac7581a9/crypt/zone" + }, + { + "zone": { + "id": "f33b1263-f1b2-43a6-a8aa-5f8570dd4e72", + "underlay_address": "fd00:1122:3344:114::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::7]:32345", + "dataset": { + "pool_name": "oxp_9e878b1e-bf92-4155-8162-640851c2f5d5" + } + } + }, + "root": "/pool/ext/7f3a760f-a4c0-456f-8a22-2d06ecac1022/crypt/zone" + }, + { + "zone": { + "id": "6f42b469-5a36-4048-a152-e884f7e8a206", + "underlay_address": "fd00:1122:3344:114::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:114::d]:32345", + "dataset": { + "pool_name": "oxp_7aff8429-b65d-4a53-a796-7221ac7581a9" + } + } + }, + "root": "/pool/ext/9e72c0e2-4895-4791-b606-2f18e432fb69/crypt/zone" + }, + { + "zone": { + "id": "ad77d594-8f78-4d33-a5e4-59887060178e", + "underlay_address": "fd00:1122:3344:114::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:114::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/85e81a14-031d-4a63-a91f-981c64e91f60/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled16.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled16.json new file mode 100644 index 0000000000..3cd727e1bc --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled16.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "dcb9a4ae-2c89-4a74-905b-b7936ff49c19", + "underlay_address": "fd00:1122:3344:11f::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11f::9]:32345", + "dataset": { + "pool_name": "oxp_af509039-d27f-4095-bc9d-cecbc5c606db" + } + } + }, + "root": "/pool/ext/44ee0fb4-6034-44e8-b3de-b3a44457ffca/crypt/zone" + }, + { + "zone": { + "id": "dbd46f71-ec39-4b72-a77d-9d281ccb37e0", + "underlay_address": "fd00:1122:3344:11f::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11f::b]:32345", + "dataset": { + "pool_name": "oxp_44ee0fb4-6034-44e8-b3de-b3a44457ffca" + } + } + }, + "root": "/pool/ext/5e32c0a3-1210-402b-91fb-256946eeac2b/crypt/zone" + }, + { + "zone": { + "id": "a1f30569-a5c6-4a6d-922e-241966aea142", + "underlay_address": "fd00:1122:3344:11f::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11f::6]:32345", + "dataset": { + "pool_name": "oxp_d2133e8b-51cc-455e-89d0-5454fd4fe109" + } + } + }, + "root": "/pool/ext/3f57835b-1469-499a-8757-7cc56acc5d49/crypt/zone" + }, + { + "zone": { + "id": "a33e25ae-4e41-40f4-843d-3d12f62d8cb6", + "underlay_address": "fd00:1122:3344:11f::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11f::8]:32345", + "dataset": { + "pool_name": "oxp_c8e4a7f4-1ae6-4683-8397-ea53475a53e8" + } + } + }, + "root": "/pool/ext/5e32c0a3-1210-402b-91fb-256946eeac2b/crypt/zone" + }, + { + "zone": { + "id": "65ed75c2-2d80-4de5-a6f6-adfa6516c7cf", + "underlay_address": "fd00:1122:3344:11f::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11f::c]:32345", + "dataset": { + "pool_name": "oxp_3f57835b-1469-499a-8757-7cc56acc5d49" + } + } + }, + "root": "/pool/ext/cd8cd75c-632b-4527-889a-7ca0c080fe2c/crypt/zone" + }, + { + "zone": { + "id": "bc6ccf18-6b9b-4687-8b70-c7917d972ae0", + "underlay_address": "fd00:1122:3344:11f::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11f::a]:32345", + "dataset": { + "pool_name": "oxp_cd8cd75c-632b-4527-889a-7ca0c080fe2c" + } + } + }, + "root": "/pool/ext/5e32c0a3-1210-402b-91fb-256946eeac2b/crypt/zone" + }, + { + "zone": { + "id": "06233bfe-a857-4819-aefe-212af9eeb90f", + "underlay_address": "fd00:1122:3344:11f::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11f::5]:32345", + "dataset": { + "pool_name": "oxp_c8a1aaf1-d27c-45fd-9f8d-80ac6bf6865d" + } + } + }, + "root": "/pool/ext/af509039-d27f-4095-bc9d-cecbc5c606db/crypt/zone" + }, + { + "zone": { + "id": "0bbfef71-9eae-43b6-b5e7-0060ce9269dd", + "underlay_address": "fd00:1122:3344:11f::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11f::4]:32345", + "dataset": { + "pool_name": "oxp_5e32c0a3-1210-402b-91fb-256946eeac2b" + } + } + }, + "root": "/pool/ext/af509039-d27f-4095-bc9d-cecbc5c606db/crypt/zone" + }, + { + "zone": { + "id": "550e10ee-24d1-444f-80be-2744dd321e0f", + "underlay_address": "fd00:1122:3344:11f::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11f::7]:32345", + "dataset": { + "pool_name": "oxp_f437ce0e-eb45-4be8-b1fe-33ed2656eb01" + } + } + }, + "root": "/pool/ext/44ee0fb4-6034-44e8-b3de-b3a44457ffca/crypt/zone" + }, + { + "zone": { + "id": "86d768f3-ece2-4956-983f-999bdb23a983", + "underlay_address": "fd00:1122:3344:11f::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:11f::3]:32221", + "dataset": { + "pool_name": "oxp_5e32c0a3-1210-402b-91fb-256946eeac2b" + } + } + }, + "root": "/pool/ext/c8a1aaf1-d27c-45fd-9f8d-80ac6bf6865d/crypt/zone" + }, + { + "zone": { + "id": "2f358812-f72c-4838-a5ea-7d78d0954be0", + "underlay_address": "fd00:1122:3344:11f::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:11f::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/f437ce0e-eb45-4be8-b1fe-33ed2656eb01/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled17.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled17.json new file mode 100644 index 0000000000..09981ecacc --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled17.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "525a19a2-d4ac-418d-bdcf-2ce26e7abe70", + "underlay_address": "fd00:1122:3344:107::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::a]:32345", + "dataset": { + "pool_name": "oxp_cb774d2f-ff86-4fd7-866b-17a6b10e61f0" + } + } + }, + "root": "/pool/ext/e17b68b5-f50c-4fc3-b55a-80d284c6c32d/crypt/zone" + }, + { + "zone": { + "id": "7af188e1-6175-4769-9e4f-2ca7a98b76f6", + "underlay_address": "fd00:1122:3344:107::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::4]:32345", + "dataset": { + "pool_name": "oxp_0cbbcf22-770d-4e75-9148-e6109b129093" + } + } + }, + "root": "/pool/ext/b998e8df-ea69-4bdd-84cb-b7f17075b060/crypt/zone" + }, + { + "zone": { + "id": "2544540f-6ffc-46c0-84bf-f42a110c02d7", + "underlay_address": "fd00:1122:3344:107::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::6]:32345", + "dataset": { + "pool_name": "oxp_e17b68b5-f50c-4fc3-b55a-80d284c6c32d" + } + } + }, + "root": "/pool/ext/521fa477-4d83-49a8-a5cf-c267b7f0c409/crypt/zone" + }, + { + "zone": { + "id": "cfc20f72-cac2-4681-a6d8-e5a0accafbb7", + "underlay_address": "fd00:1122:3344:107::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::7]:32345", + "dataset": { + "pool_name": "oxp_b998e8df-ea69-4bdd-84cb-b7f17075b060" + } + } + }, + "root": "/pool/ext/0cbbcf22-770d-4e75-9148-e6109b129093/crypt/zone" + }, + { + "zone": { + "id": "e24be791-5773-425e-a3df-e35ca81570c7", + "underlay_address": "fd00:1122:3344:107::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::9]:32345", + "dataset": { + "pool_name": "oxp_7849c221-dc7f-43ac-ac47-bc51864e083b" + } + } + }, + "root": "/pool/ext/7849c221-dc7f-43ac-ac47-bc51864e083b/crypt/zone" + }, + { + "zone": { + "id": "170856ee-21cf-4780-8903-175d558bc7cc", + "underlay_address": "fd00:1122:3344:107::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::3]:32345", + "dataset": { + "pool_name": "oxp_618e21e5-77d4-40ba-9f8e-7960e9ad92e2" + } + } + }, + "root": "/pool/ext/aa7a37fb-2f03-4d5c-916b-db3a4fc269ac/crypt/zone" + }, + { + "zone": { + "id": "604278ff-525a-4d41-82ff-07aef3174d38", + "underlay_address": "fd00:1122:3344:107::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::5]:32345", + "dataset": { + "pool_name": "oxp_521fa477-4d83-49a8-a5cf-c267b7f0c409" + } + } + }, + "root": "/pool/ext/0cbbcf22-770d-4e75-9148-e6109b129093/crypt/zone" + }, + { + "zone": { + "id": "d0d4fcc0-6ed0-410a-99c7-5daf34014421", + "underlay_address": "fd00:1122:3344:107::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::b]:32345", + "dataset": { + "pool_name": "oxp_aa7a37fb-2f03-4d5c-916b-db3a4fc269ac" + } + } + }, + "root": "/pool/ext/aa7a37fb-2f03-4d5c-916b-db3a4fc269ac/crypt/zone" + }, + { + "zone": { + "id": "c935df7b-2629-48ee-bc10-20508301905d", + "underlay_address": "fd00:1122:3344:107::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::c]:32345", + "dataset": { + "pool_name": "oxp_793fd018-5fdc-4e54-9c45-f8023fa3ea18" + } + } + }, + "root": "/pool/ext/7849c221-dc7f-43ac-ac47-bc51864e083b/crypt/zone" + }, + { + "zone": { + "id": "4ba5f3b6-8be5-4a85-bc57-a5e3b0b867d8", + "underlay_address": "fd00:1122:3344:107::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:107::8]:32345", + "dataset": { + "pool_name": "oxp_e80e7996-c572-481e-8c22-61c16c6e47f4" + } + } + }, + "root": "/pool/ext/e17b68b5-f50c-4fc3-b55a-80d284c6c32d/crypt/zone" + }, + { + "zone": { + "id": "395c9d6e-3bd0-445e-9269-46c3260edb83", + "underlay_address": "fd00:1122:3344:107::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:107::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/0cbbcf22-770d-4e75-9148-e6109b129093/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled18.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled18.json new file mode 100644 index 0000000000..708019883e --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled18.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "c7096dd4-e429-4a6f-9725-041a77ef2513", + "underlay_address": "fd00:1122:3344:11a::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::6]:32345", + "dataset": { + "pool_name": "oxp_dcf62af6-c0f9-4eb5-9b23-9424ef8f3d32" + } + } + }, + "root": "/pool/ext/b869e463-c8b9-4c12-a6b9-13175b3896dd/crypt/zone" + }, + { + "zone": { + "id": "09dd367f-b32f-43f3-aa53-11ccec1cd0c9", + "underlay_address": "fd00:1122:3344:11a::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::9]:32345", + "dataset": { + "pool_name": "oxp_d7d00317-42c7-4d1e-a04c-85491fb230cd" + } + } + }, + "root": "/pool/ext/d7d00317-42c7-4d1e-a04c-85491fb230cd/crypt/zone" + }, + { + "zone": { + "id": "fb2f85f1-05b3-432f-9bb5-63fb27a762b1", + "underlay_address": "fd00:1122:3344:11a::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::5]:32345", + "dataset": { + "pool_name": "oxp_db4a9949-68da-4c1c-9a1c-49083eba14fe" + } + } + }, + "root": "/pool/ext/db4a9949-68da-4c1c-9a1c-49083eba14fe/crypt/zone" + }, + { + "zone": { + "id": "5b89425e-69e4-4305-8f33-dc5768a1849e", + "underlay_address": "fd00:1122:3344:11a::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::a]:32345", + "dataset": { + "pool_name": "oxp_64a1bad7-d1b1-4e39-a3f3-9b8d73c4709e" + } + } + }, + "root": "/pool/ext/64a1bad7-d1b1-4e39-a3f3-9b8d73c4709e/crypt/zone" + }, + { + "zone": { + "id": "a5156db4-273a-4f8b-b8d8-df77062a6c63", + "underlay_address": "fd00:1122:3344:11a::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::4]:32345", + "dataset": { + "pool_name": "oxp_b869e463-c8b9-4c12-a6b9-13175b3896dd" + } + } + }, + "root": "/pool/ext/dcf62af6-c0f9-4eb5-9b23-9424ef8f3d32/crypt/zone" + }, + { + "zone": { + "id": "1f2d2f86-b69b-4130-bb9b-e62ba0cb6802", + "underlay_address": "fd00:1122:3344:11a::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::b]:32345", + "dataset": { + "pool_name": "oxp_153ffee4-5d7a-4786-ad33-d5567b434fe0" + } + } + }, + "root": "/pool/ext/174a067d-1c5a-49f7-a29f-1e62ab1c3796/crypt/zone" + }, + { + "zone": { + "id": "1e249cc9-52e7-4d66-b713-8ace1392e991", + "underlay_address": "fd00:1122:3344:11a::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::7]:32345", + "dataset": { + "pool_name": "oxp_04b6215e-9651-4a3c-ba1b-b8a1e67b3d89" + } + } + }, + "root": "/pool/ext/db4a9949-68da-4c1c-9a1c-49083eba14fe/crypt/zone" + }, + { + "zone": { + "id": "eb779538-2b1b-4d1d-8c7e-b15f04db6e53", + "underlay_address": "fd00:1122:3344:11a::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::3]:32345", + "dataset": { + "pool_name": "oxp_aacb8524-3562-4f97-a616-9023230d6efa" + } + } + }, + "root": "/pool/ext/174a067d-1c5a-49f7-a29f-1e62ab1c3796/crypt/zone" + }, + { + "zone": { + "id": "b575d52d-be7d-46af-814b-91e6d18f3464", + "underlay_address": "fd00:1122:3344:11a::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::8]:32345", + "dataset": { + "pool_name": "oxp_174a067d-1c5a-49f7-a29f-1e62ab1c3796" + } + } + }, + "root": "/pool/ext/64a1bad7-d1b1-4e39-a3f3-9b8d73c4709e/crypt/zone" + }, + { + "zone": { + "id": "274200bc-eac7-47d7-8a57-4b7be794caba", + "underlay_address": "fd00:1122:3344:11a::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11a::c]:32345", + "dataset": { + "pool_name": "oxp_2e7644e4-7d46-42bf-8e7a-9c3f39085b3f" + } + } + }, + "root": "/pool/ext/2e7644e4-7d46-42bf-8e7a-9c3f39085b3f/crypt/zone" + }, + { + "zone": { + "id": "bc20ba3a-df62-4a62-97c2-75b5653f84b4", + "underlay_address": "fd00:1122:3344:11a::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:11a::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/04b6215e-9651-4a3c-ba1b-b8a1e67b3d89/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled19.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled19.json new file mode 100644 index 0000000000..197df304e3 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled19.json @@ -0,0 +1,181 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "9c73abb9-edb8-4aa2-835b-c25ebe4466d9", + "underlay_address": "fd00:1122:3344:109::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::7]:32345", + "dataset": { + "pool_name": "oxp_b7a3032f-7b8c-4a6a-9fa2-e5773bfdbc94" + } + } + }, + "root": "/pool/ext/46d21f3d-23be-4361-b5c5-9d0f6ece5b8c/crypt/zone" + }, + { + "zone": { + "id": "ca576bda-cbdd-4bb9-9d75-ce06d569e926", + "underlay_address": "fd00:1122:3344:109::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::a]:32345", + "dataset": { + "pool_name": "oxp_863c4bc4-9c7e-453c-99d8-a3d509f49f3e" + } + } + }, + "root": "/pool/ext/7e67cb32-0c00-4090-9647-eb7bae75deeb/crypt/zone" + }, + { + "zone": { + "id": "f010978d-346e-49cd-b265-7607a25685f9", + "underlay_address": "fd00:1122:3344:109::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::c]:32345", + "dataset": { + "pool_name": "oxp_9bc1dab8-2d2a-4f92-bdfb-94ebca7881f1" + } + } + }, + "root": "/pool/ext/9bc1dab8-2d2a-4f92-bdfb-94ebca7881f1/crypt/zone" + }, + { + "zone": { + "id": "daff4162-cc81-4586-a457-91d767b8f1d9", + "underlay_address": "fd00:1122:3344:109::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::6]:32345", + "dataset": { + "pool_name": "oxp_b9b5b50c-e823-41ae-9585-01b818883521" + } + } + }, + "root": "/pool/ext/de682b18-afaf-4d53-b62e-934f6bd4a1f8/crypt/zone" + }, + { + "zone": { + "id": "9f300d3d-e698-4cc8-be4c-1f81ac8c927f", + "underlay_address": "fd00:1122:3344:109::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::d]:32345", + "dataset": { + "pool_name": "oxp_f1d82c22-ad7d-4cda-9ab0-8f5f496d90ce" + } + } + }, + "root": "/pool/ext/de682b18-afaf-4d53-b62e-934f6bd4a1f8/crypt/zone" + }, + { + "zone": { + "id": "8db7c7be-da40-4a1c-9681-4d02606a7eb7", + "underlay_address": "fd00:1122:3344:109::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::9]:32345", + "dataset": { + "pool_name": "oxp_46d21f3d-23be-4361-b5c5-9d0f6ece5b8c" + } + } + }, + "root": "/pool/ext/b7a3032f-7b8c-4a6a-9fa2-e5773bfdbc94/crypt/zone" + }, + { + "zone": { + "id": "b990911b-805a-4f9d-bd83-e977f5b19a35", + "underlay_address": "fd00:1122:3344:109::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::4]:32345", + "dataset": { + "pool_name": "oxp_7e67cb32-0c00-4090-9647-eb7bae75deeb" + } + } + }, + "root": "/pool/ext/de682b18-afaf-4d53-b62e-934f6bd4a1f8/crypt/zone" + }, + { + "zone": { + "id": "c99392f5-8f30-41ac-9eeb-12d7f4b707f1", + "underlay_address": "fd00:1122:3344:109::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::b]:32345", + "dataset": { + "pool_name": "oxp_de682b18-afaf-4d53-b62e-934f6bd4a1f8" + } + } + }, + "root": "/pool/ext/46d21f3d-23be-4361-b5c5-9d0f6ece5b8c/crypt/zone" + }, + { + "zone": { + "id": "7f6cb339-9eb1-4866-8a4f-383bad25b36f", + "underlay_address": "fd00:1122:3344:109::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::5]:32345", + "dataset": { + "pool_name": "oxp_458cbfa3-3752-415d-8a3b-fb64e88468e1" + } + } + }, + "root": "/pool/ext/b9b5b50c-e823-41ae-9585-01b818883521/crypt/zone" + }, + { + "zone": { + "id": "11946372-f253-4648-b00c-c7874a7b2888", + "underlay_address": "fd00:1122:3344:109::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:109::8]:32345", + "dataset": { + "pool_name": "oxp_d73332f5-b2a5-46c0-94cf-c5c5712abfe8" + } + } + }, + "root": "/pool/ext/b9b5b50c-e823-41ae-9585-01b818883521/crypt/zone" + }, + { + "zone": { + "id": "58ece9e1-387f-4d2f-a42f-69cd34f9f380", + "underlay_address": "fd00:1122:3344:109::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:109::3]:32221", + "dataset": { + "pool_name": "oxp_7e67cb32-0c00-4090-9647-eb7bae75deeb" + } + } + }, + "root": "/pool/ext/b9b5b50c-e823-41ae-9585-01b818883521/crypt/zone" + }, + { + "zone": { + "id": "f016a25a-deb5-4f20-bdb0-2425c00d41a6", + "underlay_address": "fd00:1122:3344:109::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:109::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/b9b5b50c-e823-41ae-9585-01b818883521/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled2.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled2.json new file mode 100644 index 0000000000..ba6ab6f915 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled2.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "dd799dd4-03f9-451d-85e2-844155753a03", + "underlay_address": "fd00:1122:3344:10a::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::7]:32345", + "dataset": { + "pool_name": "oxp_7dcf3acc-bde9-4306-bb46-4c6a6cbbb7ba" + } + } + }, + "root": "/pool/ext/7dcf3acc-bde9-4306-bb46-4c6a6cbbb7ba/crypt/zone" + }, + { + "zone": { + "id": "dbf9346d-b46d-4402-bb44-92ce20fb5290", + "underlay_address": "fd00:1122:3344:10a::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::9]:32345", + "dataset": { + "pool_name": "oxp_9275d50f-da2c-4f84-9775-598a364309ad" + } + } + }, + "root": "/pool/ext/d83e36ef-dd7a-4cc2-be19-379b1114c031/crypt/zone" + }, + { + "zone": { + "id": "9a55ebdd-eeef-4954-b0a1-e32b04837f14", + "underlay_address": "fd00:1122:3344:10a::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::4]:32345", + "dataset": { + "pool_name": "oxp_7f30f77e-5998-4676-a226-b433b5940e77" + } + } + }, + "root": "/pool/ext/9275d50f-da2c-4f84-9775-598a364309ad/crypt/zone" + }, + { + "zone": { + "id": "bc2935f8-e4fa-4015-968e-f90985533a6a", + "underlay_address": "fd00:1122:3344:10a::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::6]:32345", + "dataset": { + "pool_name": "oxp_022c9d58-e91f-480d-bda6-0cf32ce3b1f5" + } + } + }, + "root": "/pool/ext/c395dcc3-6ece-4b3f-b143-e111a54ef7da/crypt/zone" + }, + { + "zone": { + "id": "63f8c861-fa1d-4121-92d9-7efa5ef7f5a0", + "underlay_address": "fd00:1122:3344:10a::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::a]:32345", + "dataset": { + "pool_name": "oxp_3c805784-f403-4d01-9eb0-4f77d0821980" + } + } + }, + "root": "/pool/ext/9275d50f-da2c-4f84-9775-598a364309ad/crypt/zone" + }, + { + "zone": { + "id": "4996dcf9-78de-4f69-94fa-c09cc86a8d3c", + "underlay_address": "fd00:1122:3344:10a::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::b]:32345", + "dataset": { + "pool_name": "oxp_f9fe9ce6-be0d-4974-bc30-78a8f1330496" + } + } + }, + "root": "/pool/ext/9275d50f-da2c-4f84-9775-598a364309ad/crypt/zone" + }, + { + "zone": { + "id": "36b9a4bf-7b30-4fe7-903d-3b722c79fa86", + "underlay_address": "fd00:1122:3344:10a::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::c]:32345", + "dataset": { + "pool_name": "oxp_cb1052e0-4c70-4d37-b979-dd55e6a25f08" + } + } + }, + "root": "/pool/ext/3c805784-f403-4d01-9eb0-4f77d0821980/crypt/zone" + }, + { + "zone": { + "id": "a109a902-6a27-41b6-a881-c353e28e5389", + "underlay_address": "fd00:1122:3344:10a::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::8]:32345", + "dataset": { + "pool_name": "oxp_d83e36ef-dd7a-4cc2-be19-379b1114c031" + } + } + }, + "root": "/pool/ext/d83e36ef-dd7a-4cc2-be19-379b1114c031/crypt/zone" + }, + { + "zone": { + "id": "d2a9a0bc-ea12-44e3-ac4a-904c76120d11", + "underlay_address": "fd00:1122:3344:10a::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::3]:32345", + "dataset": { + "pool_name": "oxp_c395dcc3-6ece-4b3f-b143-e111a54ef7da" + } + } + }, + "root": "/pool/ext/9898a289-2f0d-43a6-b053-850f6e784e9a/crypt/zone" + }, + { + "zone": { + "id": "b3c3e53b-d9ec-4dd8-bd2c-bd811319aa44", + "underlay_address": "fd00:1122:3344:10a::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10a::5]:32345", + "dataset": { + "pool_name": "oxp_9898a289-2f0d-43a6-b053-850f6e784e9a" + } + } + }, + "root": "/pool/ext/9275d50f-da2c-4f84-9775-598a364309ad/crypt/zone" + }, + { + "zone": { + "id": "7b445d3b-fd25-4538-ac3f-f439c66d1223", + "underlay_address": "fd00:1122:3344:10a::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:10a::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/f9fe9ce6-be0d-4974-bc30-78a8f1330496/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled20.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled20.json new file mode 100644 index 0000000000..f02f1f05e5 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled20.json @@ -0,0 +1,198 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "4b49e669-264d-4bfb-8ab1-555b520b679c", + "underlay_address": "fd00:1122:3344:108::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::c]:32345", + "dataset": { + "pool_name": "oxp_799a1c86-9e1a-4626-91e2-a19f7ff5356e" + } + } + }, + "root": "/pool/ext/d2478613-b7c9-4bd3-856f-1fe8e9c903c2/crypt/zone" + }, + { + "zone": { + "id": "d802baae-9c3f-437a-85fe-cd72653b6db1", + "underlay_address": "fd00:1122:3344:108::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::5]:32345", + "dataset": { + "pool_name": "oxp_d2478613-b7c9-4bd3-856f-1fe8e9c903c2" + } + } + }, + "root": "/pool/ext/116f216c-e151-410f-82bf-8913904cf7b4/crypt/zone" + }, + { + "zone": { + "id": "e5f69e60-3421-49a4-8c1d-2db8cbb6a5e9", + "underlay_address": "fd00:1122:3344:108::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::b]:32345", + "dataset": { + "pool_name": "oxp_116f216c-e151-410f-82bf-8913904cf7b4" + } + } + }, + "root": "/pool/ext/eea15142-4635-4e40-b0b4-b0c4f13eca3c/crypt/zone" + }, + { + "zone": { + "id": "3e598962-ef8c-4cb6-bdfe-ec8563939d6a", + "underlay_address": "fd00:1122:3344:108::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::4]:32345", + "dataset": { + "pool_name": "oxp_ababce44-01d1-4c50-b389-f60464c5dde9" + } + } + }, + "root": "/pool/ext/ababce44-01d1-4c50-b389-f60464c5dde9/crypt/zone" + }, + { + "zone": { + "id": "25355c9f-cc2b-4b24-8eaa-65190f8936a8", + "underlay_address": "fd00:1122:3344:108::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::d]:32345", + "dataset": { + "pool_name": "oxp_fed46d41-136d-4462-8782-359014efba59" + } + } + }, + "root": "/pool/ext/eea15142-4635-4e40-b0b4-b0c4f13eca3c/crypt/zone" + }, + { + "zone": { + "id": "efb2f16c-ebad-4192-b575-dcb4d9b1d5cd", + "underlay_address": "fd00:1122:3344:108::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::a]:32345", + "dataset": { + "pool_name": "oxp_bf509067-0165-456d-98ae-72c86378e626" + } + } + }, + "root": "/pool/ext/95220093-e3b8-4f7f-9f5a-cb32cb75180a/crypt/zone" + }, + { + "zone": { + "id": "89191f0d-4e0b-47fa-9a9e-fbe2a6db1385", + "underlay_address": "fd00:1122:3344:108::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::8]:32345", + "dataset": { + "pool_name": "oxp_eea15142-4635-4e40-b0b4-b0c4f13eca3c" + } + } + }, + "root": "/pool/ext/eea15142-4635-4e40-b0b4-b0c4f13eca3c/crypt/zone" + }, + { + "zone": { + "id": "e4589324-c528-49c7-9141-35e0a7af6947", + "underlay_address": "fd00:1122:3344:108::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::6]:32345", + "dataset": { + "pool_name": "oxp_95220093-e3b8-4f7f-9f5a-cb32cb75180a" + } + } + }, + "root": "/pool/ext/ababce44-01d1-4c50-b389-f60464c5dde9/crypt/zone" + }, + { + "zone": { + "id": "95ebe94d-0e68-421d-9260-c30bd7fe4bd6", + "underlay_address": "fd00:1122:3344:108::3", + "zone_type": { + "type": "nexus", + "internal_address": "[fd00:1122:3344:108::3]:12221", + "external_ip": "45.154.216.35", + "nic": { + "id": "301aa595-f072-4da3-a533-99647b44a66a", + "kind": { + "type": "service", + "id": "95ebe94d-0e68-421d-9260-c30bd7fe4bd6" + }, + "name": "nexus-95ebe94d-0e68-421d-9260-c30bd7fe4bd6", + "ip": "172.30.2.5", + "mac": "A8:40:25:FF:F1:30", + "subnet": "172.30.2.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "external_tls": true, + "external_dns_servers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "root": "/pool/ext/eea15142-4635-4e40-b0b4-b0c4f13eca3c/crypt/zone" + }, + { + "zone": { + "id": "4b7a7052-f8e8-4196-8d6b-315943986ce6", + "underlay_address": "fd00:1122:3344:108::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::7]:32345", + "dataset": { + "pool_name": "oxp_a549421c-2f12-45cc-b691-202f0a9bfa8b" + } + } + }, + "root": "/pool/ext/bf509067-0165-456d-98ae-72c86378e626/crypt/zone" + }, + { + "zone": { + "id": "71b8ff53-c781-47bb-8ddc-2c7129680542", + "underlay_address": "fd00:1122:3344:108::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:108::9]:32345", + "dataset": { + "pool_name": "oxp_9d19f891-a3d9-4c6e-b1e1-6b0b085a9440" + } + } + }, + "root": "/pool/ext/fed46d41-136d-4462-8782-359014efba59/crypt/zone" + }, + { + "zone": { + "id": "eaf7bf77-f4c2-4016-9909-4b88a27e9d9a", + "underlay_address": "fd00:1122:3344:108::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:108::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/ababce44-01d1-4c50-b389-f60464c5dde9/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled21.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled21.json new file mode 100644 index 0000000000..d6c19b96ed --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled21.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "a91e4af3-5d18-4b08-8cb6-0583db8f8842", + "underlay_address": "fd00:1122:3344:117::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::a]:32345", + "dataset": { + "pool_name": "oxp_4b2896b8-5f0e-42fb-a474-658b28421e65" + } + } + }, + "root": "/pool/ext/23393ed9-acee-4686-861f-7fc825af1249/crypt/zone" + }, + { + "zone": { + "id": "1ce74512-ce3a-4125-95f1-12c86e0275d5", + "underlay_address": "fd00:1122:3344:117::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::8]:32345", + "dataset": { + "pool_name": "oxp_46ece76f-ef00-4dd0-9f73-326c63959470" + } + } + }, + "root": "/pool/ext/1bd5955e-14a9-463f-adeb-f12bcb45a6c1/crypt/zone" + }, + { + "zone": { + "id": "fef5d35f-9622-4dee-8635-d26e9f7f6869", + "underlay_address": "fd00:1122:3344:117::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::4]:32345", + "dataset": { + "pool_name": "oxp_e4d7c2e8-016b-4617-afb5-38a2d9c1b508" + } + } + }, + "root": "/pool/ext/e372bba3-ef60-466f-b819-a3d5b9acbe77/crypt/zone" + }, + { + "zone": { + "id": "4f024a31-cd38-4219-8381-9f1af70d1d54", + "underlay_address": "fd00:1122:3344:117::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::c]:32345", + "dataset": { + "pool_name": "oxp_7cb2a3c2-9d33-4c6a-af57-669f251cf4cf" + } + } + }, + "root": "/pool/ext/cfbd185d-e185-4aaa-a598-9216124ceec4/crypt/zone" + }, + { + "zone": { + "id": "d00e1d0b-e12f-420a-a4df-21e4cac176f6", + "underlay_address": "fd00:1122:3344:117::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::b]:32345", + "dataset": { + "pool_name": "oxp_e372bba3-ef60-466f-b819-a3d5b9acbe77" + } + } + }, + "root": "/pool/ext/cfbd185d-e185-4aaa-a598-9216124ceec4/crypt/zone" + }, + { + "zone": { + "id": "1598058a-6064-449e-b39c-1e3d345ed793", + "underlay_address": "fd00:1122:3344:117::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::5]:32345", + "dataset": { + "pool_name": "oxp_022a8d67-1e00-49f3-81ed-a0a1bc187cfa" + } + } + }, + "root": "/pool/ext/022a8d67-1e00-49f3-81ed-a0a1bc187cfa/crypt/zone" + }, + { + "zone": { + "id": "c723c4b8-3031-4b25-8c16-fe08bc0b5f00", + "underlay_address": "fd00:1122:3344:117::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::7]:32345", + "dataset": { + "pool_name": "oxp_23393ed9-acee-4686-861f-7fc825af1249" + } + } + }, + "root": "/pool/ext/1bd5955e-14a9-463f-adeb-f12bcb45a6c1/crypt/zone" + }, + { + "zone": { + "id": "7751b307-888f-46c8-8787-75d2f3fdaef3", + "underlay_address": "fd00:1122:3344:117::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::9]:32345", + "dataset": { + "pool_name": "oxp_e54e53d4-f68f-4b19-b8c1-9d5ab42e51c1" + } + } + }, + "root": "/pool/ext/e372bba3-ef60-466f-b819-a3d5b9acbe77/crypt/zone" + }, + { + "zone": { + "id": "89413ff1-d5de-4931-8389-e84e7ea321af", + "underlay_address": "fd00:1122:3344:117::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::6]:32345", + "dataset": { + "pool_name": "oxp_1bd5955e-14a9-463f-adeb-f12bcb45a6c1" + } + } + }, + "root": "/pool/ext/1bd5955e-14a9-463f-adeb-f12bcb45a6c1/crypt/zone" + }, + { + "zone": { + "id": "287b0b24-72aa-41b5-a597-8523d84225ef", + "underlay_address": "fd00:1122:3344:117::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:117::3]:32345", + "dataset": { + "pool_name": "oxp_cfbd185d-e185-4aaa-a598-9216124ceec4" + } + } + }, + "root": "/pool/ext/cfbd185d-e185-4aaa-a598-9216124ceec4/crypt/zone" + }, + { + "zone": { + "id": "4728253e-c534-4a5b-b707-c64ac9a8eb8c", + "underlay_address": "fd00:1122:3344:117::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:117::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/cfbd185d-e185-4aaa-a598-9216124ceec4/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled22.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled22.json new file mode 100644 index 0000000000..1cd6fed362 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled22.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "49f20cd1-a8a3-4fa8-9209-59da60cd8f9b", + "underlay_address": "fd00:1122:3344:103::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::5]:32345", + "dataset": { + "pool_name": "oxp_13a9ef4a-f33a-4781-8f83-712c07a79b1f" + } + } + }, + "root": "/pool/ext/711eff4e-736c-478e-83aa-ae86f5efbf1d/crypt/zone" + }, + { + "zone": { + "id": "896fd564-f94e-496b-9fcf-ddfbfcfac9f7", + "underlay_address": "fd00:1122:3344:103::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::c]:32345", + "dataset": { + "pool_name": "oxp_0944c0a2-0fb7-4f51-bced-52cc257cd2f6" + } + } + }, + "root": "/pool/ext/bc54d8c5-955d-429d-84e0-a20a4e5e27a3/crypt/zone" + }, + { + "zone": { + "id": "911fb8b3-05c2-4af7-8974-6c74a61d94ad", + "underlay_address": "fd00:1122:3344:103::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::9]:32345", + "dataset": { + "pool_name": "oxp_29f59fce-a867-4571-9d2e-b03fa5c13510" + } + } + }, + "root": "/pool/ext/711eff4e-736c-478e-83aa-ae86f5efbf1d/crypt/zone" + }, + { + "zone": { + "id": "682b34db-0b06-4770-a8fe-74437cf184d6", + "underlay_address": "fd00:1122:3344:103::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::6]:32345", + "dataset": { + "pool_name": "oxp_094d11d2-8049-4138-bcf4-562f5f8e77c0" + } + } + }, + "root": "/pool/ext/0944c0a2-0fb7-4f51-bced-52cc257cd2f6/crypt/zone" + }, + { + "zone": { + "id": "d8d20365-ecd3-4fd5-9495-c0670e3bd5d9", + "underlay_address": "fd00:1122:3344:103::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::a]:32345", + "dataset": { + "pool_name": "oxp_fb97ff7b-0225-400c-a137-3b38a786c0a0" + } + } + }, + "root": "/pool/ext/094d11d2-8049-4138-bcf4-562f5f8e77c0/crypt/zone" + }, + { + "zone": { + "id": "673620b6-44d9-4310-8e17-3024ac84e708", + "underlay_address": "fd00:1122:3344:103::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::7]:32345", + "dataset": { + "pool_name": "oxp_711eff4e-736c-478e-83aa-ae86f5efbf1d" + } + } + }, + "root": "/pool/ext/fb97ff7b-0225-400c-a137-3b38a786c0a0/crypt/zone" + }, + { + "zone": { + "id": "bf6dfc04-4d4c-41b6-a011-40ffc3bc5080", + "underlay_address": "fd00:1122:3344:103::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::8]:32345", + "dataset": { + "pool_name": "oxp_f815f1b6-48ef-436d-8768-eb08227e2386" + } + } + }, + "root": "/pool/ext/13a9ef4a-f33a-4781-8f83-712c07a79b1f/crypt/zone" + }, + { + "zone": { + "id": "ac8a82a8-fb6f-4635-a9a9-d98617eab390", + "underlay_address": "fd00:1122:3344:103::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::3]:32345", + "dataset": { + "pool_name": "oxp_97d6c860-4e2f-496e-974b-2e293fee6af9" + } + } + }, + "root": "/pool/ext/0944c0a2-0fb7-4f51-bced-52cc257cd2f6/crypt/zone" + }, + { + "zone": { + "id": "4ed66558-4815-4b85-9b94-9edf3ee69ead", + "underlay_address": "fd00:1122:3344:103::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::4]:32345", + "dataset": { + "pool_name": "oxp_bc54d8c5-955d-429d-84e0-a20a4e5e27a3" + } + } + }, + "root": "/pool/ext/13a9ef4a-f33a-4781-8f83-712c07a79b1f/crypt/zone" + }, + { + "zone": { + "id": "8a71c6ee-b08d-4c3d-b13c-c9cebc4c328a", + "underlay_address": "fd00:1122:3344:103::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:103::b]:32345", + "dataset": { + "pool_name": "oxp_2bdfa429-09bd-4fa1-aa20-eea99f0d2b85" + } + } + }, + "root": "/pool/ext/29f59fce-a867-4571-9d2e-b03fa5c13510/crypt/zone" + }, + { + "zone": { + "id": "7e6b8962-7a1e-4d7b-b7ea-49e64a51d98d", + "underlay_address": "fd00:1122:3344:103::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:103::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/2bdfa429-09bd-4fa1-aa20-eea99f0d2b85/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled23.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled23.json new file mode 100644 index 0000000000..ab171ad8cd --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled23.json @@ -0,0 +1,181 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "6b7e931d-4b91-4dc6-9a7b-4c19ac669e5d", + "underlay_address": "fd00:1122:3344:105::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::4]:32345", + "dataset": { + "pool_name": "oxp_24dab7f5-164a-47f3-a878-f32ab1e68cce" + } + } + }, + "root": "/pool/ext/ad493851-2d11-4c2d-8d75-989579d9616a/crypt/zone" + }, + { + "zone": { + "id": "6c58e7aa-71e1-4868-9d4b-e12c7ef40303", + "underlay_address": "fd00:1122:3344:105::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::a]:32345", + "dataset": { + "pool_name": "oxp_d664c9e8-bc81-4225-a618-a8ae2d057186" + } + } + }, + "root": "/pool/ext/ad493851-2d11-4c2d-8d75-989579d9616a/crypt/zone" + }, + { + "zone": { + "id": "51c6dc8d-b1a4-454a-9b19-01e45eb0b599", + "underlay_address": "fd00:1122:3344:105::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::d]:32345", + "dataset": { + "pool_name": "oxp_f5f85537-eb25-4d0e-8e94-b775c41abd73" + } + } + }, + "root": "/pool/ext/4f1eafe9-b28d-49d3-83e2-ceac8721d6b5/crypt/zone" + }, + { + "zone": { + "id": "8cbffa61-0bd0-4ad2-bd7d-30fe0dd57469", + "underlay_address": "fd00:1122:3344:105::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::9]:32345", + "dataset": { + "pool_name": "oxp_88abca38-3f61-4d4b-80a1-4ea3e4827f84" + } + } + }, + "root": "/pool/ext/88abca38-3f61-4d4b-80a1-4ea3e4827f84/crypt/zone" + }, + { + "zone": { + "id": "2177f37f-2ac9-4e66-bf74-a10bd91f4d33", + "underlay_address": "fd00:1122:3344:105::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::6]:32345", + "dataset": { + "pool_name": "oxp_59e20871-4670-40d6-8ff4-aa97899fc991" + } + } + }, + "root": "/pool/ext/4f1eafe9-b28d-49d3-83e2-ceac8721d6b5/crypt/zone" + }, + { + "zone": { + "id": "e4e43855-4879-4910-a2ba-40f625c1cc2d", + "underlay_address": "fd00:1122:3344:105::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::b]:32345", + "dataset": { + "pool_name": "oxp_967d2f05-b141-44f5-837d-9b2aa67ee128" + } + } + }, + "root": "/pool/ext/6b6f34cd-6d3d-4832-a4e6-3df112c97133/crypt/zone" + }, + { + "zone": { + "id": "8d2517e1-f9ad-40f2-abb9-2f5122839910", + "underlay_address": "fd00:1122:3344:105::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::7]:32345", + "dataset": { + "pool_name": "oxp_ad493851-2d11-4c2d-8d75-989579d9616a" + } + } + }, + "root": "/pool/ext/88abca38-3f61-4d4b-80a1-4ea3e4827f84/crypt/zone" + }, + { + "zone": { + "id": "44cb3698-a7b1-4388-9165-ac76082ec8bc", + "underlay_address": "fd00:1122:3344:105::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::5]:32345", + "dataset": { + "pool_name": "oxp_4292a83c-8c1f-4b2e-9120-72e0c510bf3c" + } + } + }, + "root": "/pool/ext/24dab7f5-164a-47f3-a878-f32ab1e68cce/crypt/zone" + }, + { + "zone": { + "id": "931b5c86-9d72-4518-bfd6-97863152ac65", + "underlay_address": "fd00:1122:3344:105::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::c]:32345", + "dataset": { + "pool_name": "oxp_6b6f34cd-6d3d-4832-a4e6-3df112c97133" + } + } + }, + "root": "/pool/ext/ad493851-2d11-4c2d-8d75-989579d9616a/crypt/zone" + }, + { + "zone": { + "id": "ac568073-1889-463e-8cc4-cfed16ce2a34", + "underlay_address": "fd00:1122:3344:105::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:105::8]:32345", + "dataset": { + "pool_name": "oxp_4f1eafe9-b28d-49d3-83e2-ceac8721d6b5" + } + } + }, + "root": "/pool/ext/4292a83c-8c1f-4b2e-9120-72e0c510bf3c/crypt/zone" + }, + { + "zone": { + "id": "e8f86fbb-864e-4d5a-961c-b50b54ae853e", + "underlay_address": "fd00:1122:3344:105::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:105::3]:32221", + "dataset": { + "pool_name": "oxp_24dab7f5-164a-47f3-a878-f32ab1e68cce" + } + } + }, + "root": "/pool/ext/4f1eafe9-b28d-49d3-83e2-ceac8721d6b5/crypt/zone" + }, + { + "zone": { + "id": "c79caea0-37b1-49d6-ae6e-8cf849d91374", + "underlay_address": "fd00:1122:3344:105::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:105::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/24dab7f5-164a-47f3-a878-f32ab1e68cce/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled24.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled24.json new file mode 100644 index 0000000000..9968abe6d9 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled24.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "d2b1e468-bc3c-4d08-b855-ae3327465375", + "underlay_address": "fd00:1122:3344:106::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::3]:32345", + "dataset": { + "pool_name": "oxp_9db196bf-828d-4e55-a2c1-dd9d579d3908" + } + } + }, + "root": "/pool/ext/74df4c92-edbb-4431-a770-1d015110e66b/crypt/zone" + }, + { + "zone": { + "id": "61f94a16-79fd-42e3-b225-a4dc67228437", + "underlay_address": "fd00:1122:3344:106::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::6]:32345", + "dataset": { + "pool_name": "oxp_d77d5b08-5f70-496a-997b-b38804dc3b8a" + } + } + }, + "root": "/pool/ext/daf9e3cd-5a40-4eba-a0f6-4f94dab37dae/crypt/zone" + }, + { + "zone": { + "id": "7d32ef34-dec5-4fd8-899e-20bbc473a3ee", + "underlay_address": "fd00:1122:3344:106::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::7]:32345", + "dataset": { + "pool_name": "oxp_50c1b653-6231-41fe-b3cf-b7ba709a0746" + } + } + }, + "root": "/pool/ext/9db196bf-828d-4e55-a2c1-dd9d579d3908/crypt/zone" + }, + { + "zone": { + "id": "c34b7ae5-26b9-4651-a3c4-20bba2bd0d2c", + "underlay_address": "fd00:1122:3344:106::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::5]:32345", + "dataset": { + "pool_name": "oxp_88aea92c-ab92-44c1-9471-eb8e30e075d3" + } + } + }, + "root": "/pool/ext/8da316d4-6b18-4980-a0a8-6e76e72cc40d/crypt/zone" + }, + { + "zone": { + "id": "36472be8-9a70-4c14-bd02-439b725cec1a", + "underlay_address": "fd00:1122:3344:106::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::8]:32345", + "dataset": { + "pool_name": "oxp_54544b3a-1513-4db2-911e-7c1eb4b12385" + } + } + }, + "root": "/pool/ext/54544b3a-1513-4db2-911e-7c1eb4b12385/crypt/zone" + }, + { + "zone": { + "id": "2548f8ab-5255-4334-a1fb-5d7d95213129", + "underlay_address": "fd00:1122:3344:106::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::9]:32345", + "dataset": { + "pool_name": "oxp_08050450-967f-431c-9a12-0d051aff020e" + } + } + }, + "root": "/pool/ext/08050450-967f-431c-9a12-0d051aff020e/crypt/zone" + }, + { + "zone": { + "id": "1455c069-853c-49cd-853a-3ea81b89acd4", + "underlay_address": "fd00:1122:3344:106::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::c]:32345", + "dataset": { + "pool_name": "oxp_8da316d4-6b18-4980-a0a8-6e76e72cc40d" + } + } + }, + "root": "/pool/ext/08050450-967f-431c-9a12-0d051aff020e/crypt/zone" + }, + { + "zone": { + "id": "27c0244b-f91a-46c3-bc96-e8eec009371e", + "underlay_address": "fd00:1122:3344:106::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::b]:32345", + "dataset": { + "pool_name": "oxp_daf9e3cd-5a40-4eba-a0f6-4f94dab37dae" + } + } + }, + "root": "/pool/ext/74df4c92-edbb-4431-a770-1d015110e66b/crypt/zone" + }, + { + "zone": { + "id": "9e46d837-1e0f-42b6-a352-84e6946b8734", + "underlay_address": "fd00:1122:3344:106::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::4]:32345", + "dataset": { + "pool_name": "oxp_74df4c92-edbb-4431-a770-1d015110e66b" + } + } + }, + "root": "/pool/ext/15f94c39-d48c-41f6-a913-cc1d04aef1a2/crypt/zone" + }, + { + "zone": { + "id": "b972fcd4-c1b3-4b3c-9e24-f59c7a7cb192", + "underlay_address": "fd00:1122:3344:106::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:106::a]:32345", + "dataset": { + "pool_name": "oxp_15f94c39-d48c-41f6-a913-cc1d04aef1a2" + } + } + }, + "root": "/pool/ext/74df4c92-edbb-4431-a770-1d015110e66b/crypt/zone" + }, + { + "zone": { + "id": "e1c8c655-1950-42d5-ae1f-a4ce84854bbc", + "underlay_address": "fd00:1122:3344:106::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:106::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/15f94c39-d48c-41f6-a913-cc1d04aef1a2/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled25.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled25.json new file mode 100644 index 0000000000..8deca6b56a --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled25.json @@ -0,0 +1,196 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "10b80058-9b2e-4d6c-8a1a-a61a8258c12f", + "underlay_address": "fd00:1122:3344:118::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::9]:32345", + "dataset": { + "pool_name": "oxp_953c19bb-9fff-4488-8a7b-29de9994a948" + } + } + }, + "root": "/pool/ext/a78caf97-6145-4908-83b5-a03a6d2e0ac4/crypt/zone" + }, + { + "zone": { + "id": "f58fef96-7b5e-40c2-9482-669088a19209", + "underlay_address": "fd00:1122:3344:118::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::d]:32345", + "dataset": { + "pool_name": "oxp_d7976706-d6ed-4465-8b04-450c96d8feec" + } + } + }, + "root": "/pool/ext/d7976706-d6ed-4465-8b04-450c96d8feec/crypt/zone" + }, + { + "zone": { + "id": "624f1168-47b6-4aa1-84da-e20a0d74d783", + "underlay_address": "fd00:1122:3344:118::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::b]:32345", + "dataset": { + "pool_name": "oxp_a78caf97-6145-4908-83b5-a03a6d2e0ac4" + } + } + }, + "root": "/pool/ext/a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d/crypt/zone" + }, + { + "zone": { + "id": "8ea85412-19b4-45c1-a53c-027ddd629296", + "underlay_address": "fd00:1122:3344:118::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::6]:32345", + "dataset": { + "pool_name": "oxp_d5f4c903-155a-4c91-aadd-6039a4f64821" + } + } + }, + "root": "/pool/ext/7d2a7685-c1c9-4d2d-a2bb-df65d96ea3e2/crypt/zone" + }, + { + "zone": { + "id": "fd226b82-71d7-4719-b32c-a6c7abe28a2a", + "underlay_address": "fd00:1122:3344:118::3", + "zone_type": { + "type": "external_dns", + "dataset": { + "pool_name": "oxp_84a80b58-70e9-439c-9558-5b343d9a4b53" + }, + "http_address": "[fd00:1122:3344:118::3]:5353", + "dns_address": "45.154.216.34:53", + "nic": { + "id": "7f72b6fd-1120-44dc-b3a7-f727502ba47c", + "kind": { + "type": "service", + "id": "fd226b82-71d7-4719-b32c-a6c7abe28a2a" + }, + "name": "external-dns-fd226b82-71d7-4719-b32c-a6c7abe28a2a", + "ip": "172.30.1.6", + "mac": "A8:40:25:FF:9E:D1", + "subnet": "172.30.1.0/24", + "vni": 100, + "primary": true, + "slot": 0 + } + } + }, + "root": "/pool/ext/a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d/crypt/zone" + }, + { + "zone": { + "id": "08d0c38d-f0d9-45b9-856d-b85059fe5f07", + "underlay_address": "fd00:1122:3344:118::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::4]:32345", + "dataset": { + "pool_name": "oxp_84a80b58-70e9-439c-9558-5b343d9a4b53" + } + } + }, + "root": "/pool/ext/a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d/crypt/zone" + }, + { + "zone": { + "id": "5de7d3fd-4a3f-4fdd-b6b2-d1186e16dce5", + "underlay_address": "fd00:1122:3344:118::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::7]:32345", + "dataset": { + "pool_name": "oxp_d76e058f-2d1e-4b15-b3a0-e5509a246876" + } + } + }, + "root": "/pool/ext/a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d/crypt/zone" + }, + { + "zone": { + "id": "5d0f5cad-10b3-497c-903b-eeeabce920e2", + "underlay_address": "fd00:1122:3344:118::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::8]:32345", + "dataset": { + "pool_name": "oxp_3a3ad639-8800-4951-bc2a-201d269e47a2" + } + } + }, + "root": "/pool/ext/3a3ad639-8800-4951-bc2a-201d269e47a2/crypt/zone" + }, + { + "zone": { + "id": "39f9cefa-801c-4843-9fb9-05446ffbdd1a", + "underlay_address": "fd00:1122:3344:118::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::a]:32345", + "dataset": { + "pool_name": "oxp_7d2a7685-c1c9-4d2d-a2bb-df65d96ea3e2" + } + } + }, + "root": "/pool/ext/a78caf97-6145-4908-83b5-a03a6d2e0ac4/crypt/zone" + }, + { + "zone": { + "id": "0711e710-7fdd-4e68-94c8-294b8677e804", + "underlay_address": "fd00:1122:3344:118::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::5]:32345", + "dataset": { + "pool_name": "oxp_a5b16ffe-a834-4a83-a4e9-487d4cbb7e3d" + } + } + }, + "root": "/pool/ext/3a3ad639-8800-4951-bc2a-201d269e47a2/crypt/zone" + }, + { + "zone": { + "id": "318a62cc-5c6c-4805-9fb6-c0f6a75ce31c", + "underlay_address": "fd00:1122:3344:118::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:118::c]:32345", + "dataset": { + "pool_name": "oxp_1d5f0ba3-6b31-4cea-a9a9-2065a538887d" + } + } + }, + "root": "/pool/ext/d7976706-d6ed-4465-8b04-450c96d8feec/crypt/zone" + }, + { + "zone": { + "id": "463d0498-85b9-40eb-af96-d99af58a587c", + "underlay_address": "fd00:1122:3344:118::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:118::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/d5f4c903-155a-4c91-aadd-6039a4f64821/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled26.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled26.json new file mode 100644 index 0000000000..a3c5d97b53 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled26.json @@ -0,0 +1,178 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "d8b3de97-cc79-48f6-83ad-02017c21223b", + "underlay_address": "fd00:1122:3344:119::3", + "zone_type": { + "type": "crucible_pantry", + "address": "[fd00:1122:3344:119::3]:17000" + } + }, + "root": "/pool/ext/e0faea44-8b5c-40b0-bb75-a1aec1a10377/crypt/zone" + }, + { + "zone": { + "id": "adba1a3b-5bac-44d5-aa5a-879dc6eadb5f", + "underlay_address": "fd00:1122:3344:119::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::c]:32345", + "dataset": { + "pool_name": "oxp_21c339c3-6461-4bdb-8b0e-c0f9f08ee10b" + } + } + }, + "root": "/pool/ext/f5c73c28-2168-4321-b737-4ca6663155c9/crypt/zone" + }, + { + "zone": { + "id": "42bb9833-5c39-4aba-b2c4-da2ca1287728", + "underlay_address": "fd00:1122:3344:119::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::a]:32345", + "dataset": { + "pool_name": "oxp_1f91451d-a466-4c9a-a6e6-0abd7985595f" + } + } + }, + "root": "/pool/ext/21c339c3-6461-4bdb-8b0e-c0f9f08ee10b/crypt/zone" + }, + { + "zone": { + "id": "197695e1-d949-4982-b679-6e5c9ab4bcc7", + "underlay_address": "fd00:1122:3344:119::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::b]:32345", + "dataset": { + "pool_name": "oxp_e0faea44-8b5c-40b0-bb75-a1aec1a10377" + } + } + }, + "root": "/pool/ext/b31e1815-cae0-4145-940c-874fff63bdd5/crypt/zone" + }, + { + "zone": { + "id": "bf99d4f8-edf1-4de5-98d4-8e6a24965005", + "underlay_address": "fd00:1122:3344:119::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::8]:32345", + "dataset": { + "pool_name": "oxp_ef2c3afb-6962-4f6b-b567-14766bbd9ec0" + } + } + }, + "root": "/pool/ext/21c339c3-6461-4bdb-8b0e-c0f9f08ee10b/crypt/zone" + }, + { + "zone": { + "id": "390d1853-8be9-4987-b8b6-f022999bf4e7", + "underlay_address": "fd00:1122:3344:119::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::7]:32345", + "dataset": { + "pool_name": "oxp_06eed00a-d8d3-4b9d-84c9-23fce535f63e" + } + } + }, + "root": "/pool/ext/ef2c3afb-6962-4f6b-b567-14766bbd9ec0/crypt/zone" + }, + { + "zone": { + "id": "76fe2161-90df-41b5-9c94-067de9c29db1", + "underlay_address": "fd00:1122:3344:119::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::4]:32345", + "dataset": { + "pool_name": "oxp_f5c73c28-2168-4321-b737-4ca6663155c9" + } + } + }, + "root": "/pool/ext/ef2c3afb-6962-4f6b-b567-14766bbd9ec0/crypt/zone" + }, + { + "zone": { + "id": "f49dc522-2b13-4055-964c-8315671096aa", + "underlay_address": "fd00:1122:3344:119::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::d]:32345", + "dataset": { + "pool_name": "oxp_662c278b-7f5f-4c7e-91ff-70207e8a307b" + } + } + }, + "root": "/pool/ext/1f91451d-a466-4c9a-a6e6-0abd7985595f/crypt/zone" + }, + { + "zone": { + "id": "08cc7bd6-368e-4d16-a619-28b17eff35af", + "underlay_address": "fd00:1122:3344:119::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::9]:32345", + "dataset": { + "pool_name": "oxp_5516b9ac-b139-40da-aa3b-f094568ba095" + } + } + }, + "root": "/pool/ext/06eed00a-d8d3-4b9d-84c9-23fce535f63e/crypt/zone" + }, + { + "zone": { + "id": "74b0613f-bce8-4922-93e0-b5bfccfc8443", + "underlay_address": "fd00:1122:3344:119::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::5]:32345", + "dataset": { + "pool_name": "oxp_b31e1815-cae0-4145-940c-874fff63bdd5" + } + } + }, + "root": "/pool/ext/21c339c3-6461-4bdb-8b0e-c0f9f08ee10b/crypt/zone" + }, + { + "zone": { + "id": "55fcfc62-8435-475f-a2aa-29373901b993", + "underlay_address": "fd00:1122:3344:119::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:119::6]:32345", + "dataset": { + "pool_name": "oxp_eadf6a03-1028-4d48-ac0d-0d27ef2c8c0f" + } + } + }, + "root": "/pool/ext/1f91451d-a466-4c9a-a6e6-0abd7985595f/crypt/zone" + }, + { + "zone": { + "id": "d52ccea3-6d7f-43a6-a19f-e0409f4e9cdc", + "underlay_address": "fd00:1122:3344:119::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:119::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/f5c73c28-2168-4321-b737-4ca6663155c9/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled27.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled27.json new file mode 100644 index 0000000000..193df7a567 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled27.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "095e612f-e218-4a16-aa6e-98c3d69a470a", + "underlay_address": "fd00:1122:3344:10d::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::a]:32345", + "dataset": { + "pool_name": "oxp_9f657858-623f-4d78-9841-6e620b5ede30" + } + } + }, + "root": "/pool/ext/2d086b51-2b77-4bc7-adc6-43586ea38ce9/crypt/zone" + }, + { + "zone": { + "id": "de818730-0e3b-4567-94e7-344bd9b6f564", + "underlay_address": "fd00:1122:3344:10d::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::3]:32345", + "dataset": { + "pool_name": "oxp_ba6ab301-07e1-4d35-80ac-59612f2c2bdb" + } + } + }, + "root": "/pool/ext/7cee2806-e898-47d8-b568-e276a6e271f8/crypt/zone" + }, + { + "zone": { + "id": "6a21dc3c-3a9d-4520-9a91-7d8f2737bcd4", + "underlay_address": "fd00:1122:3344:10d::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::4]:32345", + "dataset": { + "pool_name": "oxp_7cee2806-e898-47d8-b568-e276a6e271f8" + } + } + }, + "root": "/pool/ext/cef23d87-31ed-40d5-99b8-12d7be8e46e7/crypt/zone" + }, + { + "zone": { + "id": "e01b7f45-b8d7-4944-ba5b-41fb699889a9", + "underlay_address": "fd00:1122:3344:10d::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::b]:32345", + "dataset": { + "pool_name": "oxp_d9af8878-50bd-4425-95d9-e6556ce92cfa" + } + } + }, + "root": "/pool/ext/6fe9bcaa-88cb-451d-b086-24a3ad53fa22/crypt/zone" + }, + { + "zone": { + "id": "4271ef62-d319-4e80-b157-915321cec8c7", + "underlay_address": "fd00:1122:3344:10d::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::c]:32345", + "dataset": { + "pool_name": "oxp_ba8ee7dd-cdfb-48bd-92ce-4dc45e070930" + } + } + }, + "root": "/pool/ext/9f657858-623f-4d78-9841-6e620b5ede30/crypt/zone" + }, + { + "zone": { + "id": "6bdcc159-aeb9-4903-9486-dd8b43a3dc16", + "underlay_address": "fd00:1122:3344:10d::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::8]:32345", + "dataset": { + "pool_name": "oxp_5b03a5dc-bb5a-4bf4-bc21-0af849cd1dab" + } + } + }, + "root": "/pool/ext/d9af8878-50bd-4425-95d9-e6556ce92cfa/crypt/zone" + }, + { + "zone": { + "id": "85540e54-cdd7-4baa-920c-5cf54cbc1f83", + "underlay_address": "fd00:1122:3344:10d::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::7]:32345", + "dataset": { + "pool_name": "oxp_ee24f9a6-84ab-49a5-a28f-e394abfcaa95" + } + } + }, + "root": "/pool/ext/9f657858-623f-4d78-9841-6e620b5ede30/crypt/zone" + }, + { + "zone": { + "id": "750d1a0b-6a14-46c5-9a0b-a504caefb198", + "underlay_address": "fd00:1122:3344:10d::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::9]:32345", + "dataset": { + "pool_name": "oxp_cef23d87-31ed-40d5-99b8-12d7be8e46e7" + } + } + }, + "root": "/pool/ext/ba8ee7dd-cdfb-48bd-92ce-4dc45e070930/crypt/zone" + }, + { + "zone": { + "id": "b5996893-1a9a-434e-a257-d702694f058b", + "underlay_address": "fd00:1122:3344:10d::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::6]:32345", + "dataset": { + "pool_name": "oxp_2d086b51-2b77-4bc7-adc6-43586ea38ce9" + } + } + }, + "root": "/pool/ext/7cee2806-e898-47d8-b568-e276a6e271f8/crypt/zone" + }, + { + "zone": { + "id": "8b36686a-b98d-451a-9124-a3583000a83a", + "underlay_address": "fd00:1122:3344:10d::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10d::5]:32345", + "dataset": { + "pool_name": "oxp_6fe9bcaa-88cb-451d-b086-24a3ad53fa22" + } + } + }, + "root": "/pool/ext/9f657858-623f-4d78-9841-6e620b5ede30/crypt/zone" + }, + { + "zone": { + "id": "88d695a2-c8c1-41af-85b0-77424f4d650d", + "underlay_address": "fd00:1122:3344:10d::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:10d::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/ba6ab301-07e1-4d35-80ac-59612f2c2bdb/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled28.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled28.json new file mode 100644 index 0000000000..210b388a19 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled28.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "a126365d-f459-43bf-9f99-dbe1c4cdecf8", + "underlay_address": "fd00:1122:3344:113::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::4]:32345", + "dataset": { + "pool_name": "oxp_c99eabb2-6815-416a-9660-87e2609b357a" + } + } + }, + "root": "/pool/ext/6461a450-f043-4d1e-bc03-4a68ed5fe94a/crypt/zone" + }, + { + "zone": { + "id": "52f57ef8-546a-43bd-a0f3-8c42b99c37a6", + "underlay_address": "fd00:1122:3344:113::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::3]:32345", + "dataset": { + "pool_name": "oxp_f6530e9c-6d64-44fa-93d5-ae427916fbf1" + } + } + }, + "root": "/pool/ext/97662260-6b62-450f-9d7e-42f7dee5d568/crypt/zone" + }, + { + "zone": { + "id": "3ee87855-9423-43ff-800a-fa4fdbf1d956", + "underlay_address": "fd00:1122:3344:113::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::a]:32345", + "dataset": { + "pool_name": "oxp_6461a450-f043-4d1e-bc03-4a68ed5fe94a" + } + } + }, + "root": "/pool/ext/9515dc86-fe62-4d4f-b38d-b3461cc042fc/crypt/zone" + }, + { + "zone": { + "id": "55d0ddf9-9b24-4a7a-b97f-248e240f9ba6", + "underlay_address": "fd00:1122:3344:113::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::5]:32345", + "dataset": { + "pool_name": "oxp_97662260-6b62-450f-9d7e-42f7dee5d568" + } + } + }, + "root": "/pool/ext/9515dc86-fe62-4d4f-b38d-b3461cc042fc/crypt/zone" + }, + { + "zone": { + "id": "014cad37-56a7-4b2a-9c9e-505b15b4de85", + "underlay_address": "fd00:1122:3344:113::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::b]:32345", + "dataset": { + "pool_name": "oxp_8529ce8e-21d2-4b23-b9fd-6b90c7ae4f90" + } + } + }, + "root": "/pool/ext/6461a450-f043-4d1e-bc03-4a68ed5fe94a/crypt/zone" + }, + { + "zone": { + "id": "e14fb192-aaab-42ab-aa86-c85f13955940", + "underlay_address": "fd00:1122:3344:113::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::6]:32345", + "dataset": { + "pool_name": "oxp_5a9455ca-fb01-4549-9a70-7579c031779d" + } + } + }, + "root": "/pool/ext/f6530e9c-6d64-44fa-93d5-ae427916fbf1/crypt/zone" + }, + { + "zone": { + "id": "14540609-9371-442b-8486-88c244e97cd4", + "underlay_address": "fd00:1122:3344:113::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::8]:32345", + "dataset": { + "pool_name": "oxp_2916d6f3-8775-4887-a6d3-f9723982756f" + } + } + }, + "root": "/pool/ext/8529ce8e-21d2-4b23-b9fd-6b90c7ae4f90/crypt/zone" + }, + { + "zone": { + "id": "97a6b35f-0af9-41eb-93a1-f8bc5dbba357", + "underlay_address": "fd00:1122:3344:113::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::7]:32345", + "dataset": { + "pool_name": "oxp_9515dc86-fe62-4d4f-b38d-b3461cc042fc" + } + } + }, + "root": "/pool/ext/8529ce8e-21d2-4b23-b9fd-6b90c7ae4f90/crypt/zone" + }, + { + "zone": { + "id": "5734aa24-cb66-4b0a-9eb2-564646f8d729", + "underlay_address": "fd00:1122:3344:113::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::9]:32345", + "dataset": { + "pool_name": "oxp_9f889a6c-17b1-4edd-9659-458d91439dc1" + } + } + }, + "root": "/pool/ext/a5074e7f-8d3b-40e0-a79e-dbd9af9d5693/crypt/zone" + }, + { + "zone": { + "id": "ba86eca1-1427-4540-b4a6-1d9a0e1bc656", + "underlay_address": "fd00:1122:3344:113::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:113::c]:32345", + "dataset": { + "pool_name": "oxp_a5074e7f-8d3b-40e0-a79e-dbd9af9d5693" + } + } + }, + "root": "/pool/ext/2916d6f3-8775-4887-a6d3-f9723982756f/crypt/zone" + }, + { + "zone": { + "id": "6634dbc4-d22f-40a4-8cd3-4f271d781fa1", + "underlay_address": "fd00:1122:3344:113::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:113::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/a5074e7f-8d3b-40e0-a79e-dbd9af9d5693/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled29.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled29.json new file mode 100644 index 0000000000..ccd1bd65be --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled29.json @@ -0,0 +1,184 @@ +{ + "omicron_generation": 2, + "ledger_generation": 5, + "zones": [ + { + "zone": { + "id": "1cdd1ebf-9321-4f2d-914c-1e617f60b41a", + "underlay_address": "fd00:1122:3344:120::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::8]:32345", + "dataset": { + "pool_name": "oxp_74046573-78a2-46b4-86dc-40bb2ee29dd5" + } + } + }, + "root": "/pool/ext/c1f0a9e4-ea10-4fd9-8b6d-79a2bacfec5e/crypt/zone" + }, + { + "zone": { + "id": "720a0d08-d1c0-43ba-af86-f2dac1a53639", + "underlay_address": "fd00:1122:3344:120::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::c]:32345", + "dataset": { + "pool_name": "oxp_068d2790-1044-41ed-97a5-b493490b14d1" + } + } + }, + "root": "/pool/ext/86cd16cf-d00d-40bc-b14a-8220b1e11476/crypt/zone" + }, + { + "zone": { + "id": "d9f0b97b-2cef-4155-b45f-7db89263e4cf", + "underlay_address": "fd00:1122:3344:120::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::9]:32345", + "dataset": { + "pool_name": "oxp_8171bf0d-e61e-43f9-87d6-ec8833b80102" + } + } + }, + "root": "/pool/ext/86cd16cf-d00d-40bc-b14a-8220b1e11476/crypt/zone" + }, + { + "zone": { + "id": "018edff1-0d95-45a3-9a01-39c419bec55a", + "underlay_address": "fd00:1122:3344:120::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::b]:32345", + "dataset": { + "pool_name": "oxp_0b11e026-f265-49a0-935f-7b234c19c789" + } + } + }, + "root": "/pool/ext/35db8700-d6a7-498c-9d2c-08eb9ab41b7c/crypt/zone" + }, + { + "zone": { + "id": "f8cc1c1e-a556-436c-836d-42052101c38a", + "underlay_address": "fd00:1122:3344:120::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::3]:32345", + "dataset": { + "pool_name": "oxp_ed8e5a26-5591-405a-b792-408f5b16e444" + } + } + }, + "root": "/pool/ext/1069bdee-fe5a-4164-a856-ff8ae56c07fb/crypt/zone" + }, + { + "zone": { + "id": "f9600313-fac0-45a1-a1b5-02dd6af468b9", + "underlay_address": "fd00:1122:3344:120::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::4]:32345", + "dataset": { + "pool_name": "oxp_c1f0a9e4-ea10-4fd9-8b6d-79a2bacfec5e" + } + } + }, + "root": "/pool/ext/74046573-78a2-46b4-86dc-40bb2ee29dd5/crypt/zone" + }, + { + "zone": { + "id": "869e4f7c-5312-4b98-bacc-1508f236bf5a", + "underlay_address": "fd00:1122:3344:120::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::6]:32345", + "dataset": { + "pool_name": "oxp_04aea8dc-4316-432f-a13a-d7d9b2efa3f2" + } + } + }, + "root": "/pool/ext/0b11e026-f265-49a0-935f-7b234c19c789/crypt/zone" + }, + { + "zone": { + "id": "31ed5a0c-7caf-4825-b730-85ee94fe27f1", + "underlay_address": "fd00:1122:3344:120::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::a]:32345", + "dataset": { + "pool_name": "oxp_86cd16cf-d00d-40bc-b14a-8220b1e11476" + } + } + }, + "root": "/pool/ext/04aea8dc-4316-432f-a13a-d7d9b2efa3f2/crypt/zone" + }, + { + "zone": { + "id": "7e5a3c39-152a-4270-b01e-9e144cca4aaa", + "underlay_address": "fd00:1122:3344:120::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::5]:32345", + "dataset": { + "pool_name": "oxp_1069bdee-fe5a-4164-a856-ff8ae56c07fb" + } + } + }, + "root": "/pool/ext/04aea8dc-4316-432f-a13a-d7d9b2efa3f2/crypt/zone" + }, + { + "zone": { + "id": "9a03a386-7304-4a86-bee8-153ef643195e", + "underlay_address": "fd00:1122:3344:120::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:120::7]:32345", + "dataset": { + "pool_name": "oxp_35db8700-d6a7-498c-9d2c-08eb9ab41b7c" + } + } + }, + "root": "/pool/ext/068d2790-1044-41ed-97a5-b493490b14d1/crypt/zone" + }, + { + "zone": { + "id": "a800d0a7-1020-481c-8be8-ecfd28b7a2be", + "underlay_address": "fd00:1122:3344:120::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:120::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/c1f0a9e4-ea10-4fd9-8b6d-79a2bacfec5e/crypt/zone" + }, + { + "zone": { + "id": "be469efd-8e07-4b8e-bcee-6fd33373cdef", + "underlay_address": "fd00:1122:3344:3::1", + "zone_type": { + "type": "internal_dns", + "dataset": { + "pool_name": "oxp_ed8e5a26-5591-405a-b792-408f5b16e444" + }, + "http_address": "[fd00:1122:3344:3::1]:5353", + "dns_address": "[fd00:1122:3344:3::1]:53", + "gz_address": "fd00:1122:3344:3::2", + "gz_address_index": 2 + } + }, + "root": "/pool/ext/068d2790-1044-41ed-97a5-b493490b14d1/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled3.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled3.json new file mode 100644 index 0000000000..5da6d95389 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled3.json @@ -0,0 +1,178 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "19d091b8-e005-4ff4-97e1-026de95e3667", + "underlay_address": "fd00:1122:3344:10f::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::c]:32345", + "dataset": { + "pool_name": "oxp_11a63469-4f57-4976-8620-0055bf82dc97" + } + } + }, + "root": "/pool/ext/6a73a62c-c636-4557-af45-042cb287aee6/crypt/zone" + }, + { + "zone": { + "id": "57d77171-104e-4977-b2f9-9b529ee7f8a0", + "underlay_address": "fd00:1122:3344:10f::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::8]:32345", + "dataset": { + "pool_name": "oxp_7f3060af-058f-4f52-ab80-902bd13e7ef4" + } + } + }, + "root": "/pool/ext/7f3060af-058f-4f52-ab80-902bd13e7ef4/crypt/zone" + }, + { + "zone": { + "id": "b0371ccf-67da-4562-baf2-eaabe5243e9b", + "underlay_address": "fd00:1122:3344:10f::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::7]:32345", + "dataset": { + "pool_name": "oxp_58ae04cb-26ff-4e30-a20d-9f847bafba4d" + } + } + }, + "root": "/pool/ext/125ddcda-f94b-46bc-a10a-94e9acf40265/crypt/zone" + }, + { + "zone": { + "id": "ae3791ff-2657-4252-bd61-58ec5dc237cd", + "underlay_address": "fd00:1122:3344:10f::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::9]:32345", + "dataset": { + "pool_name": "oxp_125ddcda-f94b-46bc-a10a-94e9acf40265" + } + } + }, + "root": "/pool/ext/58ae04cb-26ff-4e30-a20d-9f847bafba4d/crypt/zone" + }, + { + "zone": { + "id": "73f865dc-5db7-48c6-9dc4-dff56dd8c045", + "underlay_address": "fd00:1122:3344:10f::3", + "zone_type": { + "type": "crucible_pantry", + "address": "[fd00:1122:3344:10f::3]:17000" + } + }, + "root": "/pool/ext/11a63469-4f57-4976-8620-0055bf82dc97/crypt/zone" + }, + { + "zone": { + "id": "e5d0170a-0d60-4c51-8f72-4c301979690e", + "underlay_address": "fd00:1122:3344:10f::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::6]:32345", + "dataset": { + "pool_name": "oxp_efe4cbab-2a39-4d7d-ae6c-83eb3ab8d4b5" + } + } + }, + "root": "/pool/ext/6a73a62c-c636-4557-af45-042cb287aee6/crypt/zone" + }, + { + "zone": { + "id": "ea6894de-c575-43bc-86e9-65b8a58499ff", + "underlay_address": "fd00:1122:3344:10f::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::a]:32345", + "dataset": { + "pool_name": "oxp_a87dc882-8b88-4a99-9628-5db79072cffa" + } + } + }, + "root": "/pool/ext/11a63469-4f57-4976-8620-0055bf82dc97/crypt/zone" + }, + { + "zone": { + "id": "3081dc99-4fa9-4238-adfa-b9ca381c1f7b", + "underlay_address": "fd00:1122:3344:10f::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::b]:32345", + "dataset": { + "pool_name": "oxp_6a73a62c-c636-4557-af45-042cb287aee6" + } + } + }, + "root": "/pool/ext/a87dc882-8b88-4a99-9628-5db79072cffa/crypt/zone" + }, + { + "zone": { + "id": "b4a3d7c8-487d-4d76-ae4e-a6a51595a5a6", + "underlay_address": "fd00:1122:3344:10f::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::d]:32345", + "dataset": { + "pool_name": "oxp_a12f87ee-9918-4269-9de4-4bad4fb41caa" + } + } + }, + "root": "/pool/ext/a12f87ee-9918-4269-9de4-4bad4fb41caa/crypt/zone" + }, + { + "zone": { + "id": "5ebcee26-f76c-4206-8d81-584ac138d3b9", + "underlay_address": "fd00:1122:3344:10f::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::4]:32345", + "dataset": { + "pool_name": "oxp_27f1917e-fb69-496a-9d40-8ef0d0c0ee55" + } + } + }, + "root": "/pool/ext/58ae04cb-26ff-4e30-a20d-9f847bafba4d/crypt/zone" + }, + { + "zone": { + "id": "90b2bc57-3a2a-4117-bb6d-7eda7542329a", + "underlay_address": "fd00:1122:3344:10f::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10f::5]:32345", + "dataset": { + "pool_name": "oxp_a222e405-40f6-4fdd-9146-94f7d94ed08a" + } + } + }, + "root": "/pool/ext/a12f87ee-9918-4269-9de4-4bad4fb41caa/crypt/zone" + }, + { + "zone": { + "id": "0fb540af-58d3-4abc-bfad-e49765c2b1ee", + "underlay_address": "fd00:1122:3344:10f::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:10f::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/58ae04cb-26ff-4e30-a20d-9f847bafba4d/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled30.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled30.json new file mode 100644 index 0000000000..c92a638b85 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled30.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "dda0f1c6-84a5-472c-b350-a799c8d3d0eb", + "underlay_address": "fd00:1122:3344:115::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::8]:32345", + "dataset": { + "pool_name": "oxp_028b6c9e-5a0e-43d2-a8ed-a5946cf62924" + } + } + }, + "root": "/pool/ext/b8d84b9c-a65e-4c86-8196-69da5317ae63/crypt/zone" + }, + { + "zone": { + "id": "157672f9-113f-48b7-9808-dff3c3e67dcd", + "underlay_address": "fd00:1122:3344:115::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::a]:32345", + "dataset": { + "pool_name": "oxp_4fdca201-b37e-4072-a1cc-3cb7705954eb" + } + } + }, + "root": "/pool/ext/b8d84b9c-a65e-4c86-8196-69da5317ae63/crypt/zone" + }, + { + "zone": { + "id": "5a7d4f67-a70f-4d8b-8d35-4dc600991fb5", + "underlay_address": "fd00:1122:3344:115::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::5]:32345", + "dataset": { + "pool_name": "oxp_11a991e5-19a9-48b0-8186-34249ef67957" + } + } + }, + "root": "/pool/ext/1e9c9764-aaa4-4681-b110-a937b4c52748/crypt/zone" + }, + { + "zone": { + "id": "c7036645-b680-4816-834f-8ae1af24c159", + "underlay_address": "fd00:1122:3344:115::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::b]:32345", + "dataset": { + "pool_name": "oxp_0780be56-c13d-4c6a-a1ac-37753a0da820" + } + } + }, + "root": "/pool/ext/80a8d756-ee22-4c88-8b5b-4a46f7eca249/crypt/zone" + }, + { + "zone": { + "id": "45e47e4b-708f-40b5-a8c8-fbfd73696d45", + "underlay_address": "fd00:1122:3344:115::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::7]:32345", + "dataset": { + "pool_name": "oxp_80a8d756-ee22-4c88-8b5b-4a46f7eca249" + } + } + }, + "root": "/pool/ext/4fdca201-b37e-4072-a1cc-3cb7705954eb/crypt/zone" + }, + { + "zone": { + "id": "e805b0c1-3f80-49da-8dc1-caaf843e5003", + "underlay_address": "fd00:1122:3344:115::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::c]:32345", + "dataset": { + "pool_name": "oxp_d54e1ed7-e589-4413-a487-6e9a257104e7" + } + } + }, + "root": "/pool/ext/d54e1ed7-e589-4413-a487-6e9a257104e7/crypt/zone" + }, + { + "zone": { + "id": "e47d3f81-3df6-4c35-bec6-41277bc74c07", + "underlay_address": "fd00:1122:3344:115::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::4]:32345", + "dataset": { + "pool_name": "oxp_b8d84b9c-a65e-4c86-8196-69da5317ae63" + } + } + }, + "root": "/pool/ext/772b3aaa-3501-4dc7-9b3d-048b8b1f7970/crypt/zone" + }, + { + "zone": { + "id": "2a796a69-b061-44c7-b2df-35bc611f10f5", + "underlay_address": "fd00:1122:3344:115::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::6]:32345", + "dataset": { + "pool_name": "oxp_73abe9e0-d38e-48fc-bdec-b094bfa5670d" + } + } + }, + "root": "/pool/ext/028b6c9e-5a0e-43d2-a8ed-a5946cf62924/crypt/zone" + }, + { + "zone": { + "id": "4e1d2af1-8ef4-4762-aa80-b08da08b45bb", + "underlay_address": "fd00:1122:3344:115::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::3]:32345", + "dataset": { + "pool_name": "oxp_772b3aaa-3501-4dc7-9b3d-048b8b1f7970" + } + } + }, + "root": "/pool/ext/d54e1ed7-e589-4413-a487-6e9a257104e7/crypt/zone" + }, + { + "zone": { + "id": "fb1b10d5-b7cb-416d-98fc-b5d3bc02d495", + "underlay_address": "fd00:1122:3344:115::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:115::9]:32345", + "dataset": { + "pool_name": "oxp_1e9c9764-aaa4-4681-b110-a937b4c52748" + } + } + }, + "root": "/pool/ext/b8d84b9c-a65e-4c86-8196-69da5317ae63/crypt/zone" + }, + { + "zone": { + "id": "5155463c-8a09-45a5-ad1b-817f2e93b284", + "underlay_address": "fd00:1122:3344:115::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:115::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/772b3aaa-3501-4dc7-9b3d-048b8b1f7970/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled31.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled31.json new file mode 100644 index 0000000000..5e38262740 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled31.json @@ -0,0 +1,181 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "a0eae689-8e6b-4297-bb3d-8b7ffc5c4a07", + "underlay_address": "fd00:1122:3344:102::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::c]:32345", + "dataset": { + "pool_name": "oxp_274cb567-fd74-4e00-b9c7-6ca367b3fda4" + } + } + }, + "root": "/pool/ext/1443b190-de16-42b0-b881-e87e875dd507/crypt/zone" + }, + { + "zone": { + "id": "9cea406d-451e-4328-9052-b58487f799a5", + "underlay_address": "fd00:1122:3344:102::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::b]:32345", + "dataset": { + "pool_name": "oxp_89c7f72e-632c-462b-a515-01cd80683711" + } + } + }, + "root": "/pool/ext/274cb567-fd74-4e00-b9c7-6ca367b3fda4/crypt/zone" + }, + { + "zone": { + "id": "9c7dad7e-7f60-4bf4-8efc-0883a17e7cf6", + "underlay_address": "fd00:1122:3344:102::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::6]:32345", + "dataset": { + "pool_name": "oxp_2c8e5637-b989-4b8f-82ac-ff2e9102b560" + } + } + }, + "root": "/pool/ext/1443b190-de16-42b0-b881-e87e875dd507/crypt/zone" + }, + { + "zone": { + "id": "73015cba-79c6-4a67-97d8-fa0819cbf750", + "underlay_address": "fd00:1122:3344:102::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::a]:32345", + "dataset": { + "pool_name": "oxp_fa62108e-f7bb-4f6d-86f3-8094a1ea8352" + } + } + }, + "root": "/pool/ext/2c8e5637-b989-4b8f-82ac-ff2e9102b560/crypt/zone" + }, + { + "zone": { + "id": "f9ca3097-072e-4e7f-9f50-eb7c7ae39b6f", + "underlay_address": "fd00:1122:3344:102::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::5]:32345", + "dataset": { + "pool_name": "oxp_42c6602c-2ccf-48ce-8344-693c832fd693" + } + } + }, + "root": "/pool/ext/2c8e5637-b989-4b8f-82ac-ff2e9102b560/crypt/zone" + }, + { + "zone": { + "id": "e7855e05-a125-4a80-ac2c-8a2db96e1bf8", + "underlay_address": "fd00:1122:3344:102::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::7]:32345", + "dataset": { + "pool_name": "oxp_1f72afd3-d2aa-46a8-b81a-54dbcc2f6317" + } + } + }, + "root": "/pool/ext/42c6602c-2ccf-48ce-8344-693c832fd693/crypt/zone" + }, + { + "zone": { + "id": "e5de9bc9-e996-4fea-8318-ad7a8a6be4a3", + "underlay_address": "fd00:1122:3344:102::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::4]:32345", + "dataset": { + "pool_name": "oxp_1443b190-de16-42b0-b881-e87e875dd507" + } + } + }, + "root": "/pool/ext/89c7f72e-632c-462b-a515-01cd80683711/crypt/zone" + }, + { + "zone": { + "id": "cd0d0aac-44ff-4566-9260-a64ae6cecef4", + "underlay_address": "fd00:1122:3344:102::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::8]:32345", + "dataset": { + "pool_name": "oxp_92c0d1f6-cb4d-4ddb-b5ba-979fb3491812" + } + } + }, + "root": "/pool/ext/89c7f72e-632c-462b-a515-01cd80683711/crypt/zone" + }, + { + "zone": { + "id": "a8230592-0e7a-46c8-a653-7587a27f05bf", + "underlay_address": "fd00:1122:3344:102::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::9]:32345", + "dataset": { + "pool_name": "oxp_1b7873de-99fd-454f-b576-bff695524133" + } + } + }, + "root": "/pool/ext/92c0d1f6-cb4d-4ddb-b5ba-979fb3491812/crypt/zone" + }, + { + "zone": { + "id": "c19ffbb1-4dc1-4825-a3cf-080e9b543b16", + "underlay_address": "fd00:1122:3344:102::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:102::d]:32345", + "dataset": { + "pool_name": "oxp_67823df7-511c-4984-b98c-7a8f5c40c22d" + } + } + }, + "root": "/pool/ext/1443b190-de16-42b0-b881-e87e875dd507/crypt/zone" + }, + { + "zone": { + "id": "ff30fe7c-51f3-43b9-a788-d8f94a7bb028", + "underlay_address": "fd00:1122:3344:102::3", + "zone_type": { + "type": "cockroach_db", + "address": "[fd00:1122:3344:102::3]:32221", + "dataset": { + "pool_name": "oxp_1443b190-de16-42b0-b881-e87e875dd507" + } + } + }, + "root": "/pool/ext/fa62108e-f7bb-4f6d-86f3-8094a1ea8352/crypt/zone" + }, + { + "zone": { + "id": "16b50c55-8117-4efd-aabf-0273677b89d5", + "underlay_address": "fd00:1122:3344:102::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:102::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/fa62108e-f7bb-4f6d-86f3-8094a1ea8352/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled4.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled4.json new file mode 100644 index 0000000000..7c1d269d61 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled4.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "22452953-ee80-4659-a555-8e027bf205b0", + "underlay_address": "fd00:1122:3344:10c::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::4]:32345", + "dataset": { + "pool_name": "oxp_92ba1667-a6f7-4913-9b00-14825384c7bf" + } + } + }, + "root": "/pool/ext/ab62b941-5f84-42c7-929d-295b20efffe7/crypt/zone" + }, + { + "zone": { + "id": "9a5a2fcf-44a0-4468-979a-a71686cef627", + "underlay_address": "fd00:1122:3344:10c::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::3]:32345", + "dataset": { + "pool_name": "oxp_dbfdc981-1b81-4d7d-9449-9530890b199a" + } + } + }, + "root": "/pool/ext/74ac4da9-cdae-4c08-8431-11211184aa09/crypt/zone" + }, + { + "zone": { + "id": "a014f12e-2636-4258-af76-e01d9b8d1c1f", + "underlay_address": "fd00:1122:3344:10c::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::b]:32345", + "dataset": { + "pool_name": "oxp_ab62b941-5f84-42c7-929d-295b20efffe7" + } + } + }, + "root": "/pool/ext/a624a843-1c4e-41c3-a1d2-4be7a6c57e9b/crypt/zone" + }, + { + "zone": { + "id": "431768b8-26ba-4ab4-b616-9e183bb79b8b", + "underlay_address": "fd00:1122:3344:10c::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::7]:32345", + "dataset": { + "pool_name": "oxp_7c121177-3210-4457-9b42-3657add6e166" + } + } + }, + "root": "/pool/ext/74ac4da9-cdae-4c08-8431-11211184aa09/crypt/zone" + }, + { + "zone": { + "id": "22992c56-bd5a-4d0f-86c5-d6f8e87b7bbb", + "underlay_address": "fd00:1122:3344:10c::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::9]:32345", + "dataset": { + "pool_name": "oxp_842bdd28-196e-4b18-83db-68bd81176a44" + } + } + }, + "root": "/pool/ext/74ac4da9-cdae-4c08-8431-11211184aa09/crypt/zone" + }, + { + "zone": { + "id": "de376149-aa45-4660-9ae6-15e8ba4a4233", + "underlay_address": "fd00:1122:3344:10c::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::5]:32345", + "dataset": { + "pool_name": "oxp_25856a84-6707-4b94-81d1-b43d5bc990d7" + } + } + }, + "root": "/pool/ext/7c121177-3210-4457-9b42-3657add6e166/crypt/zone" + }, + { + "zone": { + "id": "ceeba69d-8c0a-47df-a37b-7f1b90f23016", + "underlay_address": "fd00:1122:3344:10c::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::a]:32345", + "dataset": { + "pool_name": "oxp_a624a843-1c4e-41c3-a1d2-4be7a6c57e9b" + } + } + }, + "root": "/pool/ext/74ac4da9-cdae-4c08-8431-11211184aa09/crypt/zone" + }, + { + "zone": { + "id": "65293ce4-2e63-4336-9207-3c61f58667f9", + "underlay_address": "fd00:1122:3344:10c::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::c]:32345", + "dataset": { + "pool_name": "oxp_74ac4da9-cdae-4c08-8431-11211184aa09" + } + } + }, + "root": "/pool/ext/842bdd28-196e-4b18-83db-68bd81176a44/crypt/zone" + }, + { + "zone": { + "id": "e8f55a5d-65f9-436c-bc25-1d1a7070e876", + "underlay_address": "fd00:1122:3344:10c::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::6]:32345", + "dataset": { + "pool_name": "oxp_9bfe385c-16dd-4209-bc0b-f28ae75d58e3" + } + } + }, + "root": "/pool/ext/92ba1667-a6f7-4913-9b00-14825384c7bf/crypt/zone" + }, + { + "zone": { + "id": "2dfbd4c6-afbf-4c8c-bf40-764f02727852", + "underlay_address": "fd00:1122:3344:10c::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10c::8]:32345", + "dataset": { + "pool_name": "oxp_55eb093d-6b6f-418c-9767-09afe4c51fff" + } + } + }, + "root": "/pool/ext/dbfdc981-1b81-4d7d-9449-9530890b199a/crypt/zone" + }, + { + "zone": { + "id": "8c73baf7-1a58-4e2c-b4d1-966c89a18d03", + "underlay_address": "fd00:1122:3344:10c::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:10c::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/842bdd28-196e-4b18-83db-68bd81176a44/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled5.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled5.json new file mode 100644 index 0000000000..acbfa17eda --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled5.json @@ -0,0 +1,178 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "2f488e7b-fd93-48a6-8b2b-61f6e8336268", + "underlay_address": "fd00:1122:3344:101::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::b]:32345", + "dataset": { + "pool_name": "oxp_5840a3b7-f765-45d3-8a41-7f543f936bee" + } + } + }, + "root": "/pool/ext/dd084b76-1130-4ad3-9196-6b02be607fe9/crypt/zone" + }, + { + "zone": { + "id": "1ed5fd3f-933a-4921-a91f-5c286823f8d4", + "underlay_address": "fd00:1122:3344:101::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::a]:32345", + "dataset": { + "pool_name": "oxp_c1e807e7-b64a-4dbd-b845-ffed0b9a54f1" + } + } + }, + "root": "/pool/ext/be06ea9c-df86-4fec-b5dd-8809710893af/crypt/zone" + }, + { + "zone": { + "id": "0f8f1013-465d-4b49-b55d-f0b9bf6f789a", + "underlay_address": "fd00:1122:3344:101::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::6]:32345", + "dataset": { + "pool_name": "oxp_4dfa7003-0305-47f5-b23d-88a228c1e12e" + } + } + }, + "root": "/pool/ext/be06ea9c-df86-4fec-b5dd-8809710893af/crypt/zone" + }, + { + "zone": { + "id": "2e4ef017-6c62-40bc-bab5-f2e01addad22", + "underlay_address": "fd00:1122:3344:101::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::7]:32345", + "dataset": { + "pool_name": "oxp_d94e9c58-e6d1-444b-b7d8-19ac17dea042" + } + } + }, + "root": "/pool/ext/c1e807e7-b64a-4dbd-b845-ffed0b9a54f1/crypt/zone" + }, + { + "zone": { + "id": "6a0baf13-a80b-4778-a0ab-a69cd851de2d", + "underlay_address": "fd00:1122:3344:101::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::9]:32345", + "dataset": { + "pool_name": "oxp_be06ea9c-df86-4fec-b5dd-8809710893af" + } + } + }, + "root": "/pool/ext/a9d419d4-5915-4a40-baa3-3512785de034/crypt/zone" + }, + { + "zone": { + "id": "391ec257-fd47-4cc8-9bfa-49a0747a9a67", + "underlay_address": "fd00:1122:3344:101::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::8]:32345", + "dataset": { + "pool_name": "oxp_a9d419d4-5915-4a40-baa3-3512785de034" + } + } + }, + "root": "/pool/ext/709d5d04-5dff-4558-8b5d-fbc2a7d83036/crypt/zone" + }, + { + "zone": { + "id": "fd8e615a-f170-4da9-b8d0-2a5a123d8682", + "underlay_address": "fd00:1122:3344:101::3", + "zone_type": { + "type": "crucible_pantry", + "address": "[fd00:1122:3344:101::3]:17000" + } + }, + "root": "/pool/ext/dd084b76-1130-4ad3-9196-6b02be607fe9/crypt/zone" + }, + { + "zone": { + "id": "f8a793f4-cd08-49ec-8fee-6bcd37092fdc", + "underlay_address": "fd00:1122:3344:101::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::c]:32345", + "dataset": { + "pool_name": "oxp_709d5d04-5dff-4558-8b5d-fbc2a7d83036" + } + } + }, + "root": "/pool/ext/d94e9c58-e6d1-444b-b7d8-19ac17dea042/crypt/zone" + }, + { + "zone": { + "id": "c67d44be-d6b8-4a08-a7e0-3ab300749ad6", + "underlay_address": "fd00:1122:3344:101::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::4]:32345", + "dataset": { + "pool_name": "oxp_231cd696-2839-4a9a-ae42-6d875a98a797" + } + } + }, + "root": "/pool/ext/709d5d04-5dff-4558-8b5d-fbc2a7d83036/crypt/zone" + }, + { + "zone": { + "id": "e91b4957-8165-451d-9fa5-090c3a39f199", + "underlay_address": "fd00:1122:3344:101::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::d]:32345", + "dataset": { + "pool_name": "oxp_dd084b76-1130-4ad3-9196-6b02be607fe9" + } + } + }, + "root": "/pool/ext/5840a3b7-f765-45d3-8a41-7f543f936bee/crypt/zone" + }, + { + "zone": { + "id": "5e737b6e-d33d-4a2c-b8c0-3cad9d05a68f", + "underlay_address": "fd00:1122:3344:101::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:101::5]:32345", + "dataset": { + "pool_name": "oxp_8fa4f837-c6f3-4c65-88d4-21eb3cd7ffee" + } + } + }, + "root": "/pool/ext/dd084b76-1130-4ad3-9196-6b02be607fe9/crypt/zone" + }, + { + "zone": { + "id": "7e6b7816-b1a6-40f3-894a-a5d5c0571dbb", + "underlay_address": "fd00:1122:3344:101::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:101::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/be06ea9c-df86-4fec-b5dd-8809710893af/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled6.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled6.json new file mode 100644 index 0000000000..ce4b6f03cd --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled6.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "eafffae7-69fd-49e1-9541-7cf237ab12b3", + "underlay_address": "fd00:1122:3344:110::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::3]:32345", + "dataset": { + "pool_name": "oxp_929404cd-2522-4440-b21c-91d466a9a7e0" + } + } + }, + "root": "/pool/ext/aff390ed-8d70-49fa-9000-5420b54ab118/crypt/zone" + }, + { + "zone": { + "id": "f4bccf15-d69f-402d-9bd2-7959a4cb2823", + "underlay_address": "fd00:1122:3344:110::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::9]:32345", + "dataset": { + "pool_name": "oxp_f80f96be-a3d7-490a-96a7-faf7da80a579" + } + } + }, + "root": "/pool/ext/6bcd54c8-d4a8-429d-8f17-cf02615eb063/crypt/zone" + }, + { + "zone": { + "id": "82e51c9d-c187-4baa-8307-e46eeafc5ff2", + "underlay_address": "fd00:1122:3344:110::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::5]:32345", + "dataset": { + "pool_name": "oxp_37d86199-6834-49d9-888a-88ff6f281b29" + } + } + }, + "root": "/pool/ext/d2e27e2a-2deb-42ae-84a7-c2d06f3aeb4f/crypt/zone" + }, + { + "zone": { + "id": "cf667caf-304c-40c4-acce-f0eb05d011ef", + "underlay_address": "fd00:1122:3344:110::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::8]:32345", + "dataset": { + "pool_name": "oxp_625c0110-644e-4d63-8321-b85ab5642260" + } + } + }, + "root": "/pool/ext/d2e27e2a-2deb-42ae-84a7-c2d06f3aeb4f/crypt/zone" + }, + { + "zone": { + "id": "14e60912-108e-4dd3-984e-2332a183b346", + "underlay_address": "fd00:1122:3344:110::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::b]:32345", + "dataset": { + "pool_name": "oxp_fa6470f5-0a4c-4fef-b0b1-57c8749c6cca" + } + } + }, + "root": "/pool/ext/6c5ab641-3bd4-4d8c-96f4-4f56c1045142/crypt/zone" + }, + { + "zone": { + "id": "1aacf923-c96f-4bab-acb0-63f28e86eef6", + "underlay_address": "fd00:1122:3344:110::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::c]:32345", + "dataset": { + "pool_name": "oxp_21b0f3ed-d27f-4996-968b-bf2b494d9308" + } + } + }, + "root": "/pool/ext/625c0110-644e-4d63-8321-b85ab5642260/crypt/zone" + }, + { + "zone": { + "id": "b9db0845-04d3-4dc1-84ba-224749562a6c", + "underlay_address": "fd00:1122:3344:110::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::6]:32345", + "dataset": { + "pool_name": "oxp_d2e27e2a-2deb-42ae-84a7-c2d06f3aeb4f" + } + } + }, + "root": "/pool/ext/aff390ed-8d70-49fa-9000-5420b54ab118/crypt/zone" + }, + { + "zone": { + "id": "38b51865-ee80-4e1b-a40b-3452951f9022", + "underlay_address": "fd00:1122:3344:110::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::7]:32345", + "dataset": { + "pool_name": "oxp_6bcd54c8-d4a8-429d-8f17-cf02615eb063" + } + } + }, + "root": "/pool/ext/37d86199-6834-49d9-888a-88ff6f281b29/crypt/zone" + }, + { + "zone": { + "id": "4bc441f6-f7e5-4d68-8751-53ef1e251c47", + "underlay_address": "fd00:1122:3344:110::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::a]:32345", + "dataset": { + "pool_name": "oxp_6c5ab641-3bd4-4d8c-96f4-4f56c1045142" + } + } + }, + "root": "/pool/ext/21b0f3ed-d27f-4996-968b-bf2b494d9308/crypt/zone" + }, + { + "zone": { + "id": "d2c20cf8-ed4c-4815-add9-45996364f721", + "underlay_address": "fd00:1122:3344:110::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:110::4]:32345", + "dataset": { + "pool_name": "oxp_aff390ed-8d70-49fa-9000-5420b54ab118" + } + } + }, + "root": "/pool/ext/6c5ab641-3bd4-4d8c-96f4-4f56c1045142/crypt/zone" + }, + { + "zone": { + "id": "1bb548cb-889a-411e-8c67-d1b785225180", + "underlay_address": "fd00:1122:3344:110::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:110::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/6bcd54c8-d4a8-429d-8f17-cf02615eb063/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled7.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled7.json new file mode 100644 index 0000000000..62653d0767 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled7.json @@ -0,0 +1,167 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "2eb74fa3-71ec-484c-8ffa-3daeab0e4c78", + "underlay_address": "fd00:1122:3344:11d::3", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::3]:32345", + "dataset": { + "pool_name": "oxp_c6b63fea-e3e2-4806-b8dc-bdfe7b5c3d89" + } + } + }, + "root": "/pool/ext/9f20cbae-7a63-4c31-9386-2ac3cbe12030/crypt/zone" + }, + { + "zone": { + "id": "9f92bfcf-7435-44a6-8e77-0597f93cd0b4", + "underlay_address": "fd00:1122:3344:11d::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::7]:32345", + "dataset": { + "pool_name": "oxp_9fa336f1-2b69-4ebf-9553-e3bab7e3e6ef" + } + } + }, + "root": "/pool/ext/e05a6264-63f2-4961-bc14-57b4f65614c0/crypt/zone" + }, + { + "zone": { + "id": "1bf9aed4-9fd3-4d87-b8e7-7f066d25ec1d", + "underlay_address": "fd00:1122:3344:11d::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::b]:32345", + "dataset": { + "pool_name": "oxp_a5a52f47-9c9a-4519-83dc-abc56619495d" + } + } + }, + "root": "/pool/ext/cbcad26e-5e52-41b7-9875-1a84d30d8a15/crypt/zone" + }, + { + "zone": { + "id": "2a722aa7-cd8a-445d-83fe-57fc9b9a8249", + "underlay_address": "fd00:1122:3344:11d::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::8]:32345", + "dataset": { + "pool_name": "oxp_1f4b71eb-505f-4706-912c-b13dd3f2eafb" + } + } + }, + "root": "/pool/ext/a5a52f47-9c9a-4519-83dc-abc56619495d/crypt/zone" + }, + { + "zone": { + "id": "76af5b23-d833-435c-b848-2a09d9fad9a1", + "underlay_address": "fd00:1122:3344:11d::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::c]:32345", + "dataset": { + "pool_name": "oxp_cbcad26e-5e52-41b7-9875-1a84d30d8a15" + } + } + }, + "root": "/pool/ext/9f20cbae-7a63-4c31-9386-2ac3cbe12030/crypt/zone" + }, + { + "zone": { + "id": "3a412bf4-a385-4e66-9ada-a87f6536d6ca", + "underlay_address": "fd00:1122:3344:11d::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::4]:32345", + "dataset": { + "pool_name": "oxp_e05a6264-63f2-4961-bc14-57b4f65614c0" + } + } + }, + "root": "/pool/ext/e05a6264-63f2-4961-bc14-57b4f65614c0/crypt/zone" + }, + { + "zone": { + "id": "99a25fa7-8231-4a46-a6ec-ffc5281db1f8", + "underlay_address": "fd00:1122:3344:11d::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::5]:32345", + "dataset": { + "pool_name": "oxp_722494ab-9a2b-481b-ac11-292fded682a5" + } + } + }, + "root": "/pool/ext/e05a6264-63f2-4961-bc14-57b4f65614c0/crypt/zone" + }, + { + "zone": { + "id": "06c7ddc8-9b3e-48ef-9874-0c40874e9877", + "underlay_address": "fd00:1122:3344:11d::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::a]:32345", + "dataset": { + "pool_name": "oxp_8c3972d1-5b17-4479-88cc-1c33e4344160" + } + } + }, + "root": "/pool/ext/8c3972d1-5b17-4479-88cc-1c33e4344160/crypt/zone" + }, + { + "zone": { + "id": "1212b2dc-157d-4bd3-94af-fb5db1d91f24", + "underlay_address": "fd00:1122:3344:11d::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::9]:32345", + "dataset": { + "pool_name": "oxp_9f20cbae-7a63-4c31-9386-2ac3cbe12030" + } + } + }, + "root": "/pool/ext/977aa6c3-2026-4178-9948-e09f78008575/crypt/zone" + }, + { + "zone": { + "id": "b1fb5f2e-b20d-4f4c-9f6f-bbeb1a98dd50", + "underlay_address": "fd00:1122:3344:11d::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:11d::6]:32345", + "dataset": { + "pool_name": "oxp_977aa6c3-2026-4178-9948-e09f78008575" + } + } + }, + "root": "/pool/ext/722494ab-9a2b-481b-ac11-292fded682a5/crypt/zone" + }, + { + "zone": { + "id": "e68dde0f-0647-46db-ae1c-711835c13e25", + "underlay_address": "fd00:1122:3344:11d::d", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:11d::d]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/1f4b71eb-505f-4706-912c-b13dd3f2eafb/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled8.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled8.json new file mode 100644 index 0000000000..b848826231 --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled8.json @@ -0,0 +1,198 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "85c18b7c-a100-458c-b18d-ecfdacaefac4", + "underlay_address": "fd00:1122:3344:10e::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::5]:32345", + "dataset": { + "pool_name": "oxp_07b266bc-86c3-4a76-9522-8b34ba1ae78c" + } + } + }, + "root": "/pool/ext/5b88e44e-f886-4de8-8a6b-48ea5ed9d70b/crypt/zone" + }, + { + "zone": { + "id": "db303465-7879-4d86-8da8-a0c7162e5184", + "underlay_address": "fd00:1122:3344:10e::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::4]:32345", + "dataset": { + "pool_name": "oxp_e9488a32-880d-44a2-8948-db0b7e3a35b5" + } + } + }, + "root": "/pool/ext/8d798756-7200-4db4-9faf-f41b75106a63/crypt/zone" + }, + { + "zone": { + "id": "c44ce6be-512d-4104-9260-a5b8fe373937", + "underlay_address": "fd00:1122:3344:10e::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::9]:32345", + "dataset": { + "pool_name": "oxp_025dfc06-5aeb-407f-adc8-ba18dc9bba35" + } + } + }, + "root": "/pool/ext/1544ce68-3544-4cba-b3b6-1927d08b78a5/crypt/zone" + }, + { + "zone": { + "id": "1cfdb5b6-e568-436a-a85f-7fecf1b8eef2", + "underlay_address": "fd00:1122:3344:10e::3", + "zone_type": { + "type": "nexus", + "internal_address": "[fd00:1122:3344:10e::3]:12221", + "external_ip": "45.154.216.36", + "nic": { + "id": "569754a2-a5e0-4aa8-90a7-2fa65f43b667", + "kind": { + "type": "service", + "id": "1cfdb5b6-e568-436a-a85f-7fecf1b8eef2" + }, + "name": "nexus-1cfdb5b6-e568-436a-a85f-7fecf1b8eef2", + "ip": "172.30.2.6", + "mac": "A8:40:25:FF:EC:6B", + "subnet": "172.30.2.0/24", + "vni": 100, + "primary": true, + "slot": 0 + }, + "external_tls": true, + "external_dns_servers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + }, + "root": "/pool/ext/025dfc06-5aeb-407f-adc8-ba18dc9bba35/crypt/zone" + }, + { + "zone": { + "id": "44a68792-ca14-442e-b7a9-11970d50ba0e", + "underlay_address": "fd00:1122:3344:10e::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::a]:32345", + "dataset": { + "pool_name": "oxp_2a492098-7df3-4409-9466-561edb7aa99b" + } + } + }, + "root": "/pool/ext/1544ce68-3544-4cba-b3b6-1927d08b78a5/crypt/zone" + }, + { + "zone": { + "id": "514cf0ca-6d23-434e-9785-446b83b2f029", + "underlay_address": "fd00:1122:3344:10e::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::7]:32345", + "dataset": { + "pool_name": "oxp_5b88e44e-f886-4de8-8a6b-48ea5ed9d70b" + } + } + }, + "root": "/pool/ext/5b88e44e-f886-4de8-8a6b-48ea5ed9d70b/crypt/zone" + }, + { + "zone": { + "id": "bc6d8347-8f64-4031-912c-932349df07fe", + "underlay_address": "fd00:1122:3344:10e::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::6]:32345", + "dataset": { + "pool_name": "oxp_1544ce68-3544-4cba-b3b6-1927d08b78a5" + } + } + }, + "root": "/pool/ext/1544ce68-3544-4cba-b3b6-1927d08b78a5/crypt/zone" + }, + { + "zone": { + "id": "1ab0a4f5-99ad-4341-8c89-7fd03e5ccb08", + "underlay_address": "fd00:1122:3344:10e::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::b]:32345", + "dataset": { + "pool_name": "oxp_033eb462-968f-42ce-9c29-377bd40a3014" + } + } + }, + "root": "/pool/ext/9e1a0803-7453-4eac-91c9-d7891ecd634f/crypt/zone" + }, + { + "zone": { + "id": "d6f2520b-3d04-44d9-bd46-6ffccfcb46d2", + "underlay_address": "fd00:1122:3344:10e::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::8]:32345", + "dataset": { + "pool_name": "oxp_36e8d29c-1e88-4c2b-8f59-f312201067c3" + } + } + }, + "root": "/pool/ext/1544ce68-3544-4cba-b3b6-1927d08b78a5/crypt/zone" + }, + { + "zone": { + "id": "d6da9d13-bfcf-469d-a99e-faeb5e30be32", + "underlay_address": "fd00:1122:3344:10e::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::c]:32345", + "dataset": { + "pool_name": "oxp_9e1a0803-7453-4eac-91c9-d7891ecd634f" + } + } + }, + "root": "/pool/ext/8d798756-7200-4db4-9faf-f41b75106a63/crypt/zone" + }, + { + "zone": { + "id": "a1dc59c2-5883-4fb8-83be-ac2d95d255d1", + "underlay_address": "fd00:1122:3344:10e::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10e::d]:32345", + "dataset": { + "pool_name": "oxp_8d798756-7200-4db4-9faf-f41b75106a63" + } + } + }, + "root": "/pool/ext/36e8d29c-1e88-4c2b-8f59-f312201067c3/crypt/zone" + }, + { + "zone": { + "id": "48f25dba-7392-44ce-9bb0-28489ebc44bc", + "underlay_address": "fd00:1122:3344:10e::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:10e::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/5b88e44e-f886-4de8-8a6b-48ea5ed9d70b/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/sled-agent/tests/output/new-zones-ledgers/rack3-sled9.json b/sled-agent/tests/output/new-zones-ledgers/rack3-sled9.json new file mode 100644 index 0000000000..62d45a2f5a --- /dev/null +++ b/sled-agent/tests/output/new-zones-ledgers/rack3-sled9.json @@ -0,0 +1,178 @@ +{ + "omicron_generation": 2, + "ledger_generation": 4, + "zones": [ + { + "zone": { + "id": "b452e5e1-ab4c-4994-9679-ef21b3b4fee9", + "underlay_address": "fd00:1122:3344:10b::6", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::6]:32345", + "dataset": { + "pool_name": "oxp_d63a297d-ae6a-4072-9dca-dda404044989" + } + } + }, + "root": "/pool/ext/7c204111-31df-4c32-9a3e-780411f700fd/crypt/zone" + }, + { + "zone": { + "id": "e9826cdc-6d3a-4eff-b1b5-ec4364ebe6b9", + "underlay_address": "fd00:1122:3344:10b::3", + "zone_type": { + "type": "oximeter", + "address": "[fd00:1122:3344:10b::3]:12223" + } + }, + "root": "/pool/ext/7c204111-31df-4c32-9a3e-780411f700fd/crypt/zone" + }, + { + "zone": { + "id": "b0cde4a8-f27c-46e8-8355-756be9045afc", + "underlay_address": "fd00:1122:3344:10b::b", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::b]:32345", + "dataset": { + "pool_name": "oxp_07c1a8e7-51f5-4f12-a43d-734719fef92b" + } + } + }, + "root": "/pool/ext/1f6adf64-c9b9-4ed7-b3e2-37fb25624646/crypt/zone" + }, + { + "zone": { + "id": "e2f70cf6-e285-4212-9b01-77ebf2ca9219", + "underlay_address": "fd00:1122:3344:10b::d", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::d]:32345", + "dataset": { + "pool_name": "oxp_a809f28a-7f25-4362-bc56-0cbdd72af2cb" + } + } + }, + "root": "/pool/ext/92a1bd39-6e8a-4226-b9d0-e3e8a9b8504f/crypt/zone" + }, + { + "zone": { + "id": "b0949c9d-4aa1-4bc4-9cb3-5875b9166885", + "underlay_address": "fd00:1122:3344:10b::a", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::a]:32345", + "dataset": { + "pool_name": "oxp_af0cc12b-43c5-473a-89a7-28351fbbb430" + } + } + }, + "root": "/pool/ext/cf1594ed-7c0c-467c-b0af-a689dcb427a3/crypt/zone" + }, + { + "zone": { + "id": "7cea4d59-a8ca-4826-901d-8d5bd935dc09", + "underlay_address": "fd00:1122:3344:10b::9", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::9]:32345", + "dataset": { + "pool_name": "oxp_d75dae09-4992-4a61-ab7d-5ae1d2b068ba" + } + } + }, + "root": "/pool/ext/a809f28a-7f25-4362-bc56-0cbdd72af2cb/crypt/zone" + }, + { + "zone": { + "id": "08adaeee-c3b5-4cd8-8fbd-ac371b3101c9", + "underlay_address": "fd00:1122:3344:10b::4", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::4]:32345", + "dataset": { + "pool_name": "oxp_d9f23187-fbf9-4ea5-a103-bc112263a9a7" + } + } + }, + "root": "/pool/ext/7c204111-31df-4c32-9a3e-780411f700fd/crypt/zone" + }, + { + "zone": { + "id": "3da1ade5-3fcb-4e64-aa08-81ee8a9ef723", + "underlay_address": "fd00:1122:3344:10b::8", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::8]:32345", + "dataset": { + "pool_name": "oxp_1f6adf64-c9b9-4ed7-b3e2-37fb25624646" + } + } + }, + "root": "/pool/ext/07c1a8e7-51f5-4f12-a43d-734719fef92b/crypt/zone" + }, + { + "zone": { + "id": "816f26a7-4c28-4a39-b9ad-a036678520ab", + "underlay_address": "fd00:1122:3344:10b::7", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::7]:32345", + "dataset": { + "pool_name": "oxp_92a1bd39-6e8a-4226-b9d0-e3e8a9b8504f" + } + } + }, + "root": "/pool/ext/d9f23187-fbf9-4ea5-a103-bc112263a9a7/crypt/zone" + }, + { + "zone": { + "id": "839f9839-409f-45d3-b8a6-7085507b90f6", + "underlay_address": "fd00:1122:3344:10b::c", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::c]:32345", + "dataset": { + "pool_name": "oxp_7c204111-31df-4c32-9a3e-780411f700fd" + } + } + }, + "root": "/pool/ext/af0cc12b-43c5-473a-89a7-28351fbbb430/crypt/zone" + }, + { + "zone": { + "id": "c717c81f-a228-4412-a34e-90f8c491d847", + "underlay_address": "fd00:1122:3344:10b::5", + "zone_type": { + "type": "crucible", + "address": "[fd00:1122:3344:10b::5]:32345", + "dataset": { + "pool_name": "oxp_cf1594ed-7c0c-467c-b0af-a689dcb427a3" + } + } + }, + "root": "/pool/ext/d63a297d-ae6a-4072-9dca-dda404044989/crypt/zone" + }, + { + "zone": { + "id": "e1fa2023-6c86-40a4-ae59-a0de112cf7a9", + "underlay_address": "fd00:1122:3344:10b::e", + "zone_type": { + "type": "internal_ntp", + "address": "[fd00:1122:3344:10b::e]:123", + "ntp_servers": [ + "440dd615-e11f-4a5d-aeb4-dcf88bb314de.host.control-plane.oxide.internal", + "cb901d3e-8811-4c4c-a274-a44130501ecf.host.control-plane.oxide.internal" + ], + "dns_servers": [ + "fd00:1122:3344:1::1", + "fd00:1122:3344:2::1", + "fd00:1122:3344:3::1" + ], + "domain": null + } + }, + "root": "/pool/ext/d9f23187-fbf9-4ea5-a103-bc112263a9a7/crypt/zone" + } + ] +} \ No newline at end of file diff --git a/smf/nexus/multi-sled/config-partial.toml b/smf/nexus/multi-sled/config-partial.toml index 94c8f5572e..d330f32ab6 100644 --- a/smf/nexus/multi-sled/config-partial.toml +++ b/smf/nexus/multi-sled/config-partial.toml @@ -46,6 +46,7 @@ inventory.period_secs = 600 inventory.nkeep = 3 # Disable inventory collection altogether (for emergencies) inventory.disable = false +phantom_disks.period_secs = 30 [default_region_allocation_strategy] # by default, allocate across 3 distinct sleds diff --git a/smf/nexus/single-sled/config-partial.toml b/smf/nexus/single-sled/config-partial.toml index fcaa6176a8..cbd4851613 100644 --- a/smf/nexus/single-sled/config-partial.toml +++ b/smf/nexus/single-sled/config-partial.toml @@ -46,6 +46,7 @@ inventory.period_secs = 600 inventory.nkeep = 3 # Disable inventory collection altogether (for emergencies) inventory.disable = false +phantom_disks.period_secs = 30 [default_region_allocation_strategy] # by default, allocate without requirement for distinct sleds. diff --git a/smf/sled-agent/gimlet-standalone/config-rss.toml b/smf/sled-agent/gimlet-standalone/config-rss.toml index 29a7a79eba..f7a93260e3 100644 --- a/smf/sled-agent/gimlet-standalone/config-rss.toml +++ b/smf/sled-agent/gimlet-standalone/config-rss.toml @@ -110,6 +110,8 @@ port = "qsfp0" uplink_port_speed = "40G" # The forward error correction mode for this port. uplink_port_fec="none" +# Do not use autonegotiation +autoneg = false # Switch to use for the uplink. For single-rack deployments this can be # "switch0" (upper slot) or "switch1" (lower slot). For single-node softnpu # and dendrite stub environments, use "switch0" diff --git a/smf/sled-agent/non-gimlet/config-rss.toml b/smf/sled-agent/non-gimlet/config-rss.toml index fea3cfa5d8..fdc81c0f8f 100644 --- a/smf/sled-agent/non-gimlet/config-rss.toml +++ b/smf/sled-agent/non-gimlet/config-rss.toml @@ -109,7 +109,9 @@ port = "qsfp0" # The speed of this port. uplink_port_speed = "40G" # The forward error correction mode for this port. -uplink_port_fec="none" +uplink_port_fec = "none" +# Do not use autonegotiation +autoneg = false # Switch to use for the uplink. For single-rack deployments this can be # "switch0" (upper slot) or "switch1" (lower slot). For single-node softnpu # and dendrite stub environments, use "switch0" diff --git a/sp-sim/src/gimlet.rs b/sp-sim/src/gimlet.rs index 635e8fde6b..5cfad94c86 100644 --- a/sp-sim/src/gimlet.rs +++ b/sp-sim/src/gimlet.rs @@ -123,6 +123,15 @@ impl SimulatedSp for Gimlet { handler.update_state.last_rot_update_data() } + async fn last_host_phase1_update_data( + &self, + slot: u16, + ) -> Option> { + let handler = self.handler.as_ref()?; + let handler = handler.lock().await; + handler.update_state.last_host_phase1_update_data(slot) + } + async fn current_update_status(&self) -> gateway_messages::UpdateStatus { let Some(handler) = self.handler.as_ref() else { return gateway_messages::UpdateStatus::None; @@ -1188,7 +1197,7 @@ impl SpHandler for Handler { port: SpPort, component: SpComponent, ) -> Result { - warn!( + debug!( &self.log, "asked for component active slot"; "sender" => %sender, "port" => ?port, @@ -1211,7 +1220,7 @@ impl SpHandler for Handler { slot: u16, persist: bool, ) -> Result<(), SpError> { - warn!( + debug!( &self.log, "asked to set component active slot"; "sender" => %sender, "port" => ?port, @@ -1222,9 +1231,12 @@ impl SpHandler for Handler { if component == SpComponent::ROT { self.rot_active_slot = rot_slot_id_from_u16(slot)?; Ok(()) + } else if component == SpComponent::HOST_CPU_BOOT_FLASH { + self.update_state.set_active_host_slot(slot); + Ok(()) } else { // The real SP returns `RequestUnsupportedForComponent` for anything - // other than the RoT, including SP_ITSELF. + // other than the RoT and host boot flash, including SP_ITSELF. Err(SpError::RequestUnsupportedForComponent) } } diff --git a/sp-sim/src/lib.rs b/sp-sim/src/lib.rs index 0958e8a177..87643af9a8 100644 --- a/sp-sim/src/lib.rs +++ b/sp-sim/src/lib.rs @@ -68,6 +68,12 @@ pub trait SimulatedSp { /// Only returns data after a simulated reset of the RoT. async fn last_rot_update_data(&self) -> Option>; + /// Get the last completed update delivered to the host phase1 flash slot. + async fn last_host_phase1_update_data( + &self, + slot: u16, + ) -> Option>; + /// Get the current update status, just as would be returned by an MGS /// request to get the update status. async fn current_update_status(&self) -> gateway_messages::UpdateStatus; diff --git a/sp-sim/src/sidecar.rs b/sp-sim/src/sidecar.rs index 19e84ffc64..1bd6fe4964 100644 --- a/sp-sim/src/sidecar.rs +++ b/sp-sim/src/sidecar.rs @@ -134,6 +134,14 @@ impl SimulatedSp for Sidecar { handler.update_state.last_rot_update_data() } + async fn last_host_phase1_update_data( + &self, + _slot: u16, + ) -> Option> { + // sidecars do not have attached hosts + None + } + async fn current_update_status(&self) -> gateway_messages::UpdateStatus { let Some(handler) = self.handler.as_ref() else { return gateway_messages::UpdateStatus::None; diff --git a/sp-sim/src/update.rs b/sp-sim/src/update.rs index 9879a3ecde..0efa730a26 100644 --- a/sp-sim/src/update.rs +++ b/sp-sim/src/update.rs @@ -2,6 +2,7 @@ // 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; use std::io::Cursor; use std::mem; @@ -15,6 +16,8 @@ pub(crate) struct SimSpUpdate { state: UpdateState, last_sp_update_data: Option>, last_rot_update_data: Option>, + last_host_phase1_update_data: BTreeMap>, + active_host_slot: Option, } impl Default for SimSpUpdate { @@ -23,6 +26,13 @@ impl Default for SimSpUpdate { state: UpdateState::NotPrepared, last_sp_update_data: None, last_rot_update_data: None, + last_host_phase1_update_data: BTreeMap::new(), + + // In the real SP, there is always _some_ active host slot. We could + // emulate that by always defaulting to slot 0, but instead we'll + // ensure any tests that expect to read or write a particular slot + // set that slot as active first. + active_host_slot: None, } } } @@ -43,9 +53,20 @@ impl SimSpUpdate { UpdateState::NotPrepared | UpdateState::Aborted(_) | UpdateState::Completed { .. } => { + let slot = if component == SpComponent::HOST_CPU_BOOT_FLASH { + match self.active_host_slot { + Some(slot) => slot, + None => return Err(SpError::InvalidSlotForComponent), + } + } else { + // We don't manage SP or RoT slots, so just use 0 + 0 + }; + self.state = UpdateState::Prepared { component, id, + slot, data: Cursor::new(vec![0u8; total_size].into_boxed_slice()), }; Ok(()) @@ -63,7 +84,7 @@ impl SimSpUpdate { chunk_data: &[u8], ) -> Result<(), SpError> { match &mut self.state { - UpdateState::Prepared { component, id, data } => { + UpdateState::Prepared { component, id, slot, data } => { // Ensure that the update ID and target component are correct. if chunk.id != *id || chunk.component != *component { return Err(SpError::InvalidUpdateId { sp_update_id: *id }); @@ -84,10 +105,17 @@ impl SimSpUpdate { if data.position() == data.get_ref().len() as u64 { let mut stolen = Cursor::new(Box::default()); mem::swap(data, &mut stolen); + let data = stolen.into_inner(); + + if *component == SpComponent::HOST_CPU_BOOT_FLASH { + self.last_host_phase1_update_data + .insert(*slot, data.clone()); + } + self.state = UpdateState::Completed { component: *component, id: *id, - data: stolen.into_inner(), + data, }; } @@ -150,6 +178,17 @@ impl SimSpUpdate { pub(crate) fn last_rot_update_data(&self) -> Option> { self.last_rot_update_data.clone() } + + pub(crate) fn last_host_phase1_update_data( + &self, + slot: u16, + ) -> Option> { + self.last_host_phase1_update_data.get(&slot).cloned() + } + + pub(crate) fn set_active_host_slot(&mut self, slot: u16) { + self.active_host_slot = Some(slot); + } } enum UpdateState { @@ -157,6 +196,7 @@ enum UpdateState { Prepared { component: SpComponent, id: UpdateId, + slot: u16, // data would ordinarily be a Cursor>, but that can grow and // reallocate. We want to ensure that we don't receive any more data // than originally promised, so use a Cursor> to ensure that diff --git a/tools/dendrite_openapi_version b/tools/dendrite_openapi_version index ba4b5a5722..c2dda4dbd0 100644 --- a/tools/dendrite_openapi_version +++ b/tools/dendrite_openapi_version @@ -1,2 +1,2 @@ -COMMIT="8ff834e7d0a6adb263240edd40537f2c0768f1a4" +COMMIT="2af6adea85c62ac37e451148b84e5eb0ef005f36" SHA2="07d115bfa8498a8015ca2a8447efeeac32e24aeb25baf3d5e2313216e11293c0" diff --git a/tools/dendrite_stub_checksums b/tools/dendrite_stub_checksums index 619a6bf287..77ee198fc5 100644 --- a/tools/dendrite_stub_checksums +++ b/tools/dendrite_stub_checksums @@ -1,3 +1,3 @@ -CIDL_SHA256_ILLUMOS="c00e79f55e0bdf048069b2d18a4d009ddfef46e7e5d846887cf96e843a8884bd" -CIDL_SHA256_LINUX_DPD="b5d829b4628759ac374106f3c56c29074b29577fd0ff72f61c3b8289fea430fe" -CIDL_SHA256_LINUX_SWADM="afc68828f54dc57b32dc1556fc588baeab12341c30e96cc0fadb49f401b4b48f" +CIDL_SHA256_ILLUMOS="dc93b671cce54e83ed55faaa267f81ba9e65abcd6714aa559d68a8783d73b1c1" +CIDL_SHA256_LINUX_DPD="b13b391a085ba6bf16fdd99774f64c9d53cd7220ad518d5839c8558fb925c40c" +CIDL_SHA256_LINUX_SWADM="6bfa4e367eb2b0be89f1588ac458026a186314597a4feb9fee6cea60101c7ebe" diff --git a/tools/generate-wicketd-api.sh b/tools/generate-wicketd-api.sh new file mode 100755 index 0000000000..f1af33aecc --- /dev/null +++ b/tools/generate-wicketd-api.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +./target/debug/wicketd openapi > openapi/wicketd.json diff --git a/tools/install_builder_prerequisites.sh b/tools/install_builder_prerequisites.sh index d3ecd8eaa8..1ce133dff3 100755 --- a/tools/install_builder_prerequisites.sh +++ b/tools/install_builder_prerequisites.sh @@ -131,6 +131,8 @@ function install_packages { "library/libxmlsec1" # "bindgen leverages libclang to preprocess, parse, and type check C and C++ header files." "pkg:/ooce/developer/clang-$CLANGVER" + "system/library/gcc-runtime" + "system/library/g++-runtime" ) # Install/update the set of packages. diff --git a/tufaceous-lib/Cargo.toml b/tufaceous-lib/Cargo.toml index 0df3a33f98..aa9a26e3bb 100644 --- a/tufaceous-lib/Cargo.toml +++ b/tufaceous-lib/Cargo.toml @@ -33,7 +33,7 @@ tar.workspace = true tokio.workspace = true toml.workspace = true tough.workspace = true -url = "2.4.1" +url = "2.5.0" zip.workspace = true omicron-workspace-hack.workspace = true diff --git a/update-engine/src/display/group_display.rs b/update-engine/src/display/group_display.rs index cfd37aac16..0e04361ce4 100644 --- a/update-engine/src/display/group_display.rs +++ b/update-engine/src/display/group_display.rs @@ -153,7 +153,7 @@ impl GroupDisplay { self.stats.apply_result(result); if result.before != result.after { - slog::info!( + slog::debug!( self.log, "add_event_report caused state transition"; "prefix" => &state.prefix, diff --git a/wicket/src/cli/rack_setup/config_template.toml b/wicket/src/cli/rack_setup/config_template.toml index 617b61fadc..2886fa01d7 100644 --- a/wicket/src/cli/rack_setup/config_template.toml +++ b/wicket/src/cli/rack_setup/config_template.toml @@ -65,6 +65,9 @@ uplink_port_speed = "" # `none`, `firecode`, or `rs` uplink_port_fec = "" +# `true` or `false` +autoneg = "" + # A list of bgp peers # { addr = "1.7.0.1", asn = 47, port = "qsfp0" } bgp_peers = [] diff --git a/wicket/src/cli/rack_setup/config_toml.rs b/wicket/src/cli/rack_setup/config_toml.rs index 9b1a25a50e..5a8e8a560e 100644 --- a/wicket/src/cli/rack_setup/config_toml.rs +++ b/wicket/src/cli/rack_setup/config_toml.rs @@ -229,6 +229,12 @@ fn populate_network_table( ); _last_key = Some(property); } + uplink.insert( + "autoneg", + Item::Value(Value::Boolean(Formatted::new( + cfg.autoneg, + ))), + ); let mut routes = Array::new(); for r in &cfg.routes { @@ -449,6 +455,7 @@ mod tests { PortFec::None => InternalPortFec::None, PortFec::Rs => InternalPortFec::Rs, }, + autoneg: config.autoneg, switch: match config.switch { SwitchLocation::Switch0 => { InternalSwitchLocation::Switch0 @@ -529,6 +536,7 @@ mod tests { }], uplink_port_speed: PortSpeed::Speed400G, uplink_port_fec: PortFec::Firecode, + autoneg: true, port: "port0".into(), switch: SwitchLocation::Switch0, }], diff --git a/wicketd/src/preflight_check/uplink.rs b/wicketd/src/preflight_check/uplink.rs index d94baf1995..25411f17a5 100644 --- a/wicketd/src/preflight_check/uplink.rs +++ b/wicketd/src/preflight_check/uplink.rs @@ -775,10 +775,8 @@ fn build_port_settings( LinkSettings { addrs, params: LinkCreate { - // TODO we should take these parameters too - // https://github.com/oxidecomputer/omicron/issues/3061 - autoneg: false, - kr: false, + autoneg: uplink.autoneg, + kr: false, //NOTE: kr does not apply to user configurable links fec, speed, lane: Some(LinkId(0)), diff --git a/wicketd/src/rss_config.rs b/wicketd/src/rss_config.rs index 0aaea427f3..f654597d81 100644 --- a/wicketd/src/rss_config.rs +++ b/wicketd/src/rss_config.rs @@ -548,6 +548,7 @@ fn validate_rack_network_config( PortFec::None => BaPortFec::None, PortFec::Rs => BaPortFec::Rs, }, + autoneg: config.autoneg, }) .collect(), bgp: config diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 7757b4ad8b..fe7c3bdc81 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -105,7 +105,7 @@ unicode-normalization = { version = "0.1.22" } usdt = { version = "0.3.5" } uuid = { version = "1.6.1", features = ["serde", "v4"] } yasna = { version = "0.5.2", features = ["bit-vec", "num-bigint", "std", "time"] } -zeroize = { version = "1.6.0", features = ["std", "zeroize_derive"] } +zeroize = { version = "1.7.0", features = ["std", "zeroize_derive"] } zip = { version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] } [build-dependencies] @@ -201,7 +201,7 @@ unicode-normalization = { version = "0.1.22" } usdt = { version = "0.3.5" } uuid = { version = "1.6.1", features = ["serde", "v4"] } yasna = { version = "0.5.2", features = ["bit-vec", "num-bigint", "std", "time"] } -zeroize = { version = "1.6.0", features = ["std", "zeroize_derive"] } +zeroize = { version = "1.7.0", features = ["std", "zeroize_derive"] } zip = { version = "0.6.6", default-features = false, features = ["bzip2", "deflate"] } [target.x86_64-unknown-linux-gnu.dependencies]