diff --git a/.github/buildomat/jobs/deploy.sh b/.github/buildomat/jobs/deploy.sh index 5e43ff7f7c..a2957d35b4 100755 --- a/.github/buildomat/jobs/deploy.sh +++ b/.github/buildomat/jobs/deploy.sh @@ -102,19 +102,6 @@ z_swadm () { pfexec zlogin oxz_switch /opt/oxide/dendrite/bin/swadm $@ } -# XXX remove. This is just to test against a development branch of OPTE in CI. -set +x -OPTE_COMMIT="73d4669ea213d0b7aca35c4babb6fd09ed51d29e" -curl -sSfOL https://buildomat.eng.oxide.computer/public/file/oxidecomputer/opte/module/$OPTE_COMMIT/xde -pfexec rem_drv xde || true -pfexec mv xde /kernel/drv/amd64/xde -pfexec add_drv xde || true -curl -sSfOL https://buildomat.eng.oxide.computer/wg/0/artefact/01HM09S4M15WNXB2B2MX8R1GBT/yLalJU5vT4S4IEpwSeY4hPuspxw3JcINokZmlfNU14npHkzG/01HM09SJ2RQSFGW7MVKC9JKZ8D/01HM0A58D888AJ7YP6N1Q6T6ZD/opteadm -chmod +x opteadm -cp opteadm /tmp/opteadm -pfexec mv opteadm /opt/oxide/opte/bin/opteadm -set -x - # # XXX work around 14537 (UFS should not allow directories to be unlinked) which # is probably not yet fixed in xde branch? Once the xde branch merges from @@ -161,7 +148,7 @@ cd /opt/oxide/work ptime -m tar xvzf /input/package/work/package.tar.gz cp /input/package/work/zones/* out/ -mv out/omicron-nexus-single-sled.tar.gz out/omicron-nexus.tar.gz +mv out/nexus-single-sled.tar.gz out/nexus.tar.gz mkdir tests for p in /input/ci-tools/work/end-to-end-tests/*.gz; do ptime -m gunzip < "$p" > "tests/$(basename "${p%.gz}")" diff --git a/.github/buildomat/jobs/package.sh b/.github/buildomat/jobs/package.sh index 13f374779c..dc89bc787b 100755 --- a/.github/buildomat/jobs/package.sh +++ b/.github/buildomat/jobs/package.sh @@ -84,7 +84,7 @@ stamp_packages() { # Keep the single-sled Nexus zone around for the deploy job. (The global zone # build below overwrites the file.) -mv out/omicron-nexus.tar.gz out/omicron-nexus-single-sled.tar.gz +mv out/nexus.tar.gz out/nexus-single-sled.tar.gz # Build necessary for the global zone ptime -m cargo run --locked --release --bin omicron-package -- \ @@ -115,8 +115,8 @@ zones=( out/crucible-zone.tar.gz out/external-dns.tar.gz out/internal-dns.tar.gz - out/omicron-nexus.tar.gz - out/omicron-nexus-single-sled.tar.gz + out/nexus.tar.gz + out/nexus-single-sled.tar.gz out/oximeter.tar.gz out/propolis-server.tar.gz out/switch-*.tar.gz diff --git a/.github/buildomat/jobs/tuf-repo.sh b/.github/buildomat/jobs/tuf-repo.sh index 14c2293f5b..aca43422d9 100644 --- a/.github/buildomat/jobs/tuf-repo.sh +++ b/.github/buildomat/jobs/tuf-repo.sh @@ -93,7 +93,7 @@ target/release/omicron-package -t default target create -i standard -m gimlet -s ln -s /input/package/work/zones/* out/ rm out/switch-softnpu.tar.gz # not used when target switch=asic rm out/omicron-gateway-softnpu.tar.gz # not used when target switch=asic -rm out/omicron-nexus-single-sled.tar.gz # only used for deploy tests +rm out/nexus-single-sled.tar.gz # only used for deploy tests for zone in out/*.tar.gz; do target/release/omicron-package stamp "$(basename "${zone%.tar.gz}")" "$VERSION" done diff --git a/.github/workflows/hakari.yml b/.github/workflows/hakari.yml index cac987b277..557cfedbd3 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@dabb9c1ee51c21c545764a0d517f069ff52e6477 # v2 + uses: taiki-e/install-action@4978b3c8549425a60eb2d68446d0b58cef63ad0a # v2 with: tool: cargo-hakari - name: Check workspace-hack Cargo.toml is up-to-date diff --git a/Cargo.lock b/Cargo.lock index 637062629b..e8523191a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1708,6 +1708,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derror-macro" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/opte?rev=b85995f92ae94cdc78b97b0a610c69e103e00423#b85995f92ae94cdc78b97b0a610c69e103e00423" +dependencies = [ + "darling 0.20.3", + "proc-macro2", + "quote", + "syn 2.0.51", +] + [[package]] name = "diesel" version = "2.1.4" @@ -1856,7 +1867,7 @@ dependencies = [ "dns-service-client", "dropshot", "expectorate", - "http 0.2.11", + "http 0.2.12", "omicron-test-utils", "omicron-workspace-hack", "openapi-lint", @@ -1889,7 +1900,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "http 0.2.11", + "http 0.2.12", "omicron-workspace-hack", "progenitor", "reqwest", @@ -1941,7 +1952,7 @@ dependencies = [ "anyhow", "chrono", "futures", - "http 0.2.11", + "http 0.2.12", "ipnetwork", "omicron-workspace-hack", "omicron-zone-package", @@ -1976,7 +1987,7 @@ dependencies = [ "form_urlencoded", "futures", "hostname", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.27", "indexmap 2.2.5", "multer", @@ -1985,7 +1996,7 @@ dependencies = [ "percent-encoding", "proc-macro2", "rustls 0.22.2", - "rustls-pemfile 2.1.0", + "rustls-pemfile 2.1.1", "schemars", "serde", "serde_json", @@ -2158,7 +2169,7 @@ dependencies = [ "async-trait", "base64", "chrono", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.27", "omicron-sled-agent", "omicron-test-utils", @@ -2812,7 +2823,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.11", + "http 0.2.12", "indexmap 2.2.5", "slab", "tokio", @@ -2882,7 +2893,7 @@ dependencies = [ "base64", "bytes", "headers-core", - "http 0.2.11", + "http 0.2.12", "httpdate", "mime", "sha1", @@ -2894,7 +2905,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http 0.2.11", + "http 0.2.12", ] [[package]] @@ -3011,9 +3022,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -3038,7 +3049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http 0.2.11", + "http 0.2.12", "pin-project-lite", ] @@ -3081,7 +3092,7 @@ dependencies = [ "crossbeam-channel", "form_urlencoded", "futures", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.27", "log", "once_cell", @@ -3168,7 +3179,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body 0.4.5", "httparse", "httpdate", @@ -3206,7 +3217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.27", "rustls 0.21.9", "tokio", @@ -3239,7 +3250,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "318ca89e4827e7fe4ddd2824f52337239796ae8ecc761a663324407dc3d8d7e7" dependencies = [ "futures-util", - "http 0.2.11", + "http 0.2.12", "http-range", "httpdate", "hyper 0.14.27", @@ -3347,7 +3358,7 @@ dependencies = [ [[package]] name = "illumos-sys-hdrs" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=1d29ef60a18179babfb44f0f7a3c2fe71034a2c1#1d29ef60a18179babfb44f0f7a3c2fe71034a2c1" +source = "git+https://github.com/oxidecomputer/opte?rev=b85995f92ae94cdc78b97b0a610c69e103e00423#b85995f92ae94cdc78b97b0a610c69e103e00423" [[package]] name = "illumos-utils" @@ -3469,7 +3480,7 @@ dependencies = [ "futures", "hex", "hex-literal", - "http 0.2.11", + "http 0.2.12", "illumos-utils", "installinator-artifact-client", "installinator-common", @@ -3736,7 +3747,7 @@ dependencies = [ [[package]] name = "kstat-macro" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=1d29ef60a18179babfb44f0f7a3c2fe71034a2c1#1d29ef60a18179babfb44f0f7a3c2fe71034a2c1" +source = "git+https://github.com/oxidecomputer/opte?rev=b85995f92ae94cdc78b97b0a610c69e103e00423#b85995f92ae94cdc78b97b0a610c69e103e00423" dependencies = [ "quote", "syn 2.0.51", @@ -4364,7 +4375,7 @@ dependencies = [ "futures", "gateway-client", "headers", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.27", "hyper-rustls 0.26.0", "illumos-utils", @@ -4555,7 +4566,7 @@ dependencies = [ "gateway-messages", "gateway-test-utils", "headers", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.27", "internal-dns", "nexus-config", @@ -4907,7 +4918,7 @@ dependencies = [ "expectorate", "futures", "hex", - "http 0.2.11", + "http 0.2.12", "ipnetwork", "libc", "macaddr", @@ -4984,7 +4995,7 @@ dependencies = [ "gateway-sp-comms", "gateway-test-utils", "hex", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.27", "illumos-utils", "ipcc", @@ -5044,7 +5055,7 @@ dependencies = [ "gateway-test-utils", "headers", "hex", - "http 0.2.11", + "http 0.2.12", "httptest", "hubtools", "hyper 0.14.27", @@ -5099,7 +5110,7 @@ dependencies = [ "reqwest", "ring 0.17.8", "rustls 0.22.2", - "rustls-pemfile 2.1.0", + "rustls-pemfile 2.1.1", "samael", "schemars", "semver 1.0.22", @@ -5274,7 +5285,7 @@ dependencies = [ "glob", "guppy", "hex", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.27", "hyper-staticfile", "illumos-utils", @@ -5348,7 +5359,7 @@ dependencies = [ "filetime", "headers", "hex", - "http 0.2.11", + "http 0.2.12", "libc", "nexus-config", "omicron-common", @@ -5615,9 +5626,10 @@ dependencies = [ [[package]] name = "opte" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=1d29ef60a18179babfb44f0f7a3c2fe71034a2c1#1d29ef60a18179babfb44f0f7a3c2fe71034a2c1" +source = "git+https://github.com/oxidecomputer/opte?rev=b85995f92ae94cdc78b97b0a610c69e103e00423#b85995f92ae94cdc78b97b0a610c69e103e00423" dependencies = [ "cfg-if", + "derror-macro", "dyn-clone", "illumos-sys-hdrs", "kstat-macro", @@ -5625,13 +5637,14 @@ dependencies = [ "postcard", "serde", "smoltcp 0.11.0", + "tabwriter", "version_check", ] [[package]] name = "opte-api" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=1d29ef60a18179babfb44f0f7a3c2fe71034a2c1#1d29ef60a18179babfb44f0f7a3c2fe71034a2c1" +source = "git+https://github.com/oxidecomputer/opte?rev=b85995f92ae94cdc78b97b0a610c69e103e00423#b85995f92ae94cdc78b97b0a610c69e103e00423" dependencies = [ "illumos-sys-hdrs", "ipnetwork", @@ -5643,7 +5656,7 @@ dependencies = [ [[package]] name = "opte-ioctl" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=1d29ef60a18179babfb44f0f7a3c2fe71034a2c1#1d29ef60a18179babfb44f0f7a3c2fe71034a2c1" +source = "git+https://github.com/oxidecomputer/opte?rev=b85995f92ae94cdc78b97b0a610c69e103e00423#b85995f92ae94cdc78b97b0a610c69e103e00423" dependencies = [ "libc", "libnet", @@ -5699,7 +5712,7 @@ dependencies = [ "base64", "chrono", "futures", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.27", "omicron-workspace-hack", "progenitor", @@ -5717,7 +5730,7 @@ dependencies = [ [[package]] name = "oxide-vpc" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=1d29ef60a18179babfb44f0f7a3c2fe71034a2c1#1d29ef60a18179babfb44f0f7a3c2fe71034a2c1" +source = "git+https://github.com/oxidecomputer/opte?rev=b85995f92ae94cdc78b97b0a610c69e103e00423#b85995f92ae94cdc78b97b0a610c69e103e00423" dependencies = [ "cfg-if", "illumos-sys-hdrs", @@ -5725,6 +5738,7 @@ dependencies = [ "poptrie", "serde", "smoltcp 0.11.0", + "tabwriter", "zerocopy 0.7.32", ] @@ -5856,7 +5870,7 @@ dependencies = [ "chrono", "dropshot", "futures", - "http 0.2.11", + "http 0.2.12", "kstat-rs", "omicron-workspace-hack", "oximeter", @@ -6600,7 +6614,7 @@ source = "git+https://github.com/oxidecomputer/progenitor?branch=main#08bbafc251 dependencies = [ "getopts", "heck 0.4.1", - "http 0.2.11", + "http 0.2.12", "indexmap 2.2.5", "openapiv3", "proc-macro2", @@ -7095,7 +7109,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body 0.4.5", "hyper 0.14.27", "hyper-rustls 0.24.2", @@ -7450,7 +7464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.0", + "rustls-pemfile 2.1.1", "rustls-pki-types", "schannel", "security-framework", @@ -7467,9 +7481,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" +checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" dependencies = [ "base64", "rustls-pki-types", @@ -8863,6 +8877,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tabwriter" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a327282c4f64f6dc37e3bba4c2b6842cc3a992f204fa58d917696a89f691e5f6" +dependencies = [ + "unicode-width", +] + [[package]] name = "take_mut" version = "0.2.2" @@ -9676,7 +9699,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 0.2.11", + "http 0.2.12", "httparse", "log", "rand 0.8.5", @@ -10402,7 +10425,7 @@ dependencies = [ "gateway-messages", "gateway-test-utils", "hex", - "http 0.2.11", + "http 0.2.12", "hubtools", "hyper 0.14.27", "illumos-utils", diff --git a/Cargo.toml b/Cargo.toml index 474739a932..9cd22cbbcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -225,7 +225,7 @@ hex = "0.4.3" hex-literal = "0.4.1" highway = "1.1.0" hkdf = "0.12.4" -http = "0.2.11" +http = "0.2.12" httptest = "0.15.5" hubtools = { git = "https://github.com/oxidecomputer/hubtools.git", branch = "main" } humantime = "2.1.0" @@ -283,7 +283,7 @@ omicron-sled-agent = { path = "sled-agent" } omicron-test-utils = { path = "test-utils" } omicron-zone-package = "0.11.0" oxide-client = { path = "clients/oxide-client" } -oxide-vpc = { git = "https://github.com/oxidecomputer/opte", rev = "1d29ef60a18179babfb44f0f7a3c2fe71034a2c1", features = [ "api", "std" ] } +oxide-vpc = { git = "https://github.com/oxidecomputer/opte", rev = "b85995f92ae94cdc78b97b0a610c69e103e00423", features = [ "api", "std" ] } once_cell = "1.19.0" openapi-lint = { git = "https://github.com/oxidecomputer/openapi-lint", branch = "main" } openapiv3 = "2.0.0" @@ -291,7 +291,7 @@ openapiv3 = "2.0.0" openssl = "0.10" openssl-sys = "0.9" openssl-probe = "0.1.5" -opte-ioctl = { git = "https://github.com/oxidecomputer/opte", rev = "1d29ef60a18179babfb44f0f7a3c2fe71034a2c1" } +opte-ioctl = { git = "https://github.com/oxidecomputer/opte", rev = "b85995f92ae94cdc78b97b0a610c69e103e00423" } oso = "0.27" owo-colors = "4.0.0" oximeter = { path = "oximeter/oximeter" } @@ -336,7 +336,7 @@ rpassword = "7.3.1" rstest = "0.18.2" rustfmt-wrapper = "0.2" rustls = "0.22.2" -rustls-pemfile = "2.1.0" +rustls-pemfile = "2.1.1" rustyline = "13.0.0" samael = { version = "0.0.14", features = ["xmlsec"] } schemars = "0.8.16" diff --git a/dev-tools/omdb/src/bin/omdb/main.rs b/dev-tools/omdb/src/bin/omdb/main.rs index 32141d2809..0fb9ba0121 100644 --- a/dev-tools/omdb/src/bin/omdb/main.rs +++ b/dev-tools/omdb/src/bin/omdb/main.rs @@ -84,11 +84,24 @@ struct Omdb { #[arg(env = "OMDB_DNS_SERVER", long)] dns_server: Option, + /// allow potentially-destructive subcommands + #[arg(short = 'w', long = "destructive")] + allow_destructive: bool, + #[command(subcommand)] command: OmdbCommands, } impl Omdb { + fn check_allow_destructive(&self) -> anyhow::Result<()> { + anyhow::ensure!( + self.allow_destructive, + "This command is potentially destructive. \ + Pass the `-w` / `--destructive` flag to allow it." + ); + Ok(()) + } + async fn dns_lookup_all( &self, log: slog::Logger, diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index aed7d86ba0..03d6fd6e80 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -16,6 +16,7 @@ use nexus_client::types::ActivationReason; use nexus_client::types::BackgroundTask; use nexus_client::types::CurrentStatus; use nexus_client::types::LastResult; +use nexus_client::types::UninitializedSledId; use serde::Deserialize; use slog_error_chain::InlineErrorChain; use std::collections::BTreeMap; @@ -38,8 +39,10 @@ pub struct NexusArgs { enum NexusCommands { /// print information about background tasks BackgroundTasks(BackgroundTasksArgs), - /// print information about blueprints + /// interact with blueprints Blueprints(BlueprintsArgs), + /// interact with sleds + Sleds(SledsArgs), } #[derive(Debug, Args)] @@ -116,6 +119,28 @@ enum BlueprintTargetCommands { Set(BlueprintIdArgs), } +#[derive(Debug, Args)] +struct SledsArgs { + #[command(subcommand)] + command: SledsCommands, +} + +#[derive(Debug, Subcommand)] +enum SledsCommands { + /// List all uninitialized sleds + ListUninitialized, + /// Add an uninitialized sled + Add(SledAddArgs), +} + +#[derive(Debug, Args)] +struct SledAddArgs { + /// sled's serial number + serial: String, + /// sled's part number + part: String, +} + impl NexusArgs { /// Run a `omdb nexus` subcommand. pub(crate) async fn run_cmd( @@ -167,7 +192,10 @@ impl NexusArgs { }) => cmd_nexus_blueprints_diff(&client, args).await, NexusCommands::Blueprints(BlueprintsArgs { command: BlueprintsCommands::Delete(args), - }) => cmd_nexus_blueprints_delete(&client, args).await, + }) => { + omdb.check_allow_destructive()?; + cmd_nexus_blueprints_delete(&client, args).await + } NexusCommands::Blueprints(BlueprintsArgs { command: BlueprintsCommands::Target(BlueprintsTargetArgs { @@ -179,16 +207,33 @@ impl NexusArgs { BlueprintsCommands::Target(BlueprintsTargetArgs { command: BlueprintTargetCommands::Set(args), }), - }) => cmd_nexus_blueprints_target_set(&client, args).await, + }) => { + omdb.check_allow_destructive()?; + cmd_nexus_blueprints_target_set(&client, args).await + } NexusCommands::Blueprints(BlueprintsArgs { command: BlueprintsCommands::Regenerate, - }) => cmd_nexus_blueprints_regenerate(&client).await, + }) => { + omdb.check_allow_destructive()?; + cmd_nexus_blueprints_regenerate(&client).await + } NexusCommands::Blueprints(BlueprintsArgs { command: BlueprintsCommands::GenerateFromCollection(args), }) => { + omdb.check_allow_destructive()?; cmd_nexus_blueprints_generate_from_collection(&client, args) .await } + + NexusCommands::Sleds(SledsArgs { + command: SledsCommands::ListUninitialized, + }) => cmd_nexus_sleds_list_uninitialized(&client).await, + NexusCommands::Sleds(SledsArgs { + command: SledsCommands::Add(args), + }) => { + omdb.check_allow_destructive()?; + cmd_nexus_sled_add(&client, args).await + } } } } @@ -946,3 +991,61 @@ async fn cmd_nexus_blueprints_regenerate( eprintln!("generated new blueprint {}", blueprint.id); Ok(()) } + +/// Runs `omdb nexus sleds list-uninitialized` +async fn cmd_nexus_sleds_list_uninitialized( + client: &nexus_client::Client, +) -> Result<(), anyhow::Error> { + let response = client + .sled_list_uninitialized() + .await + .context("listing uninitialized sleds")?; + let sleds = response.into_inner(); + if sleds.next_page.is_some() { + eprintln!( + "warning: response includes next_page token; \ + pagination not implemented" + ); + } + let mut sleds = sleds.items; + sleds.sort_by_key(|sled| sled.cubby); + + #[derive(Tabled)] + #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] + struct UninitializedSledRow { + rack_id: Uuid, + cubby: u16, + serial: String, + part: String, + revision: i64, + } + let rows = sleds.into_iter().map(|sled| UninitializedSledRow { + rack_id: sled.rack_id, + cubby: sled.cubby, + serial: sled.baseboard.serial, + part: sled.baseboard.part, + revision: sled.baseboard.revision, + }); + let table = tabled::Table::new(rows) + .with(tabled::settings::Style::empty()) + .with(tabled::settings::Padding::new(0, 1, 0, 0)) + .to_string(); + println!("{}", table); + Ok(()) +} + +/// Runs `omdb nexus sleds add` +async fn cmd_nexus_sled_add( + client: &nexus_client::Client, + args: &SledAddArgs, +) -> Result<(), anyhow::Error> { + client + .sled_add(&UninitializedSledId { + part: args.part.clone(), + serial: args.serial.clone(), + }) + .await + .context("adding sled")?; + eprintln!("added sled {} ({})", args.serial, args.part); + Ok(()) +} diff --git a/dev-tools/omdb/tests/usage_errors.out b/dev-tools/omdb/tests/usage_errors.out index c10f95e23d..2f9001671d 100644 --- a/dev-tools/omdb/tests/usage_errors.out +++ b/dev-tools/omdb/tests/usage_errors.out @@ -19,6 +19,7 @@ Commands: Options: --log-level log level filter [env: LOG_LEVEL=] [default: warn] --dns-server [env: OMDB_DNS_SERVER=] + -w, --destructive allow potentially-destructive subcommands -h, --help Print help (see more with '--help') ============================================= EXECUTING COMMAND: omdb ["--help"] @@ -50,6 +51,9 @@ Options: --dns-server [env: OMDB_DNS_SERVER=] + -w, --destructive + allow potentially-destructive subcommands + -h, --help Print help (see a summary with '-h') --------------------------------------------- @@ -294,7 +298,8 @@ Usage: omdb nexus [OPTIONS] Commands: background-tasks print information about background tasks - blueprints print information about blueprints + blueprints interact with blueprints + sleds interact with sleds help Print this message or the help of the given subcommand(s) Options: diff --git a/illumos-utils/src/running_zone.rs b/illumos-utils/src/running_zone.rs index d86a27e3f7..02302347cd 100644 --- a/illumos-utils/src/running_zone.rs +++ b/illumos-utils/src/running_zone.rs @@ -404,7 +404,7 @@ impl RunningZone { /// Returns the filesystem path to the zone's root in the GZ. pub fn root(&self) -> Utf8PathBuf { - self.inner.zonepath.join(Self::ROOT_FS_PATH) + self.inner.root() } pub fn control_interface(&self) -> AddrObject { @@ -1094,6 +1094,9 @@ pub struct InstalledZone { } impl InstalledZone { + /// The path to the zone's root filesystem (i.e., `/`), within zonepath. + pub const ROOT_FS_PATH: &'static str = "root"; + /// Returns the name of a zone, based on the base zone name plus any unique /// identifying info. /// @@ -1135,6 +1138,11 @@ impl InstalledZone { pub fn opte_ports(&self) -> impl Iterator { self.opte_ports.iter().map(|(port, _)| port) } + + /// Returns the filesystem path to the zone's root in the GZ. + pub fn root(&self) -> Utf8PathBuf { + self.zonepath.join(Self::ROOT_FS_PATH) + } } #[derive(Clone)] diff --git a/nexus/src/app/sled.rs b/nexus/src/app/sled.rs index 17ab3c21e5..88f70c7d0d 100644 --- a/nexus/src/app/sled.rs +++ b/nexus/src/app/sled.rs @@ -57,8 +57,8 @@ impl super::Nexus { id, info.sa_address, db::model::SledBaseboard { - serial_number: info.baseboard.serial_number, - part_number: info.baseboard.part_number, + serial_number: info.baseboard.serial, + part_number: info.baseboard.part, revision: info.baseboard.revision, }, db::model::SledSystemHardware { diff --git a/nexus/src/app/switch.rs b/nexus/src/app/switch.rs index 362d56553a..cea16ba7af 100644 --- a/nexus/src/app/switch.rs +++ b/nexus/src/app/switch.rs @@ -32,8 +32,8 @@ impl super::Nexus { ) -> Result { let switch = db::model::Switch::new( id, - request.baseboard.serial_number, - request.baseboard.part_number, + request.baseboard.serial, + request.baseboard.part, request.baseboard.revision, request.rack_id, ); diff --git a/nexus/src/internal_api/http_entrypoints.rs b/nexus/src/internal_api/http_entrypoints.rs index 0c6a7ceec9..e298935fee 100644 --- a/nexus/src/internal_api/http_entrypoints.rs +++ b/nexus/src/internal_api/http_entrypoints.rs @@ -29,6 +29,8 @@ use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintMetadata; use nexus_types::deployment::BlueprintTarget; use nexus_types::deployment::BlueprintTargetSet; +use nexus_types::external_api::params::UninitializedSledId; +use nexus_types::external_api::shared::UninitializedSled; use nexus_types::internal_api::params::SwitchPutRequest; use nexus_types::internal_api::params::SwitchPutResponse; use nexus_types::internal_api::views::to_list; @@ -89,6 +91,9 @@ pub(crate) fn internal_api() -> NexusApiDescription { api.register(blueprint_generate_from_collection)?; api.register(blueprint_regenerate)?; + api.register(sled_list_uninitialized)?; + api.register(sled_add)?; + Ok(()) } @@ -817,3 +822,45 @@ async fn blueprint_regenerate( }; apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await } + +/// List uninitialized sleds +#[endpoint { + method = GET, + path = "/sleds/uninitialized", +}] +async fn sled_list_uninitialized( + rqctx: RequestContext>, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + let sleds = nexus.sled_list_uninitialized(&opctx).await?; + Ok(HttpResponseOk(ResultsPage { items: sleds, next_page: None })) + }; + apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Add sled to initialized rack +// +// TODO: In the future this should really be a PUT request, once we resolve +// https://github.com/oxidecomputer/omicron/issues/4494. It should also +// explicitly be tied to a rack via a `rack_id` path param. For now we assume +// we are only operating on single rack systems. +#[endpoint { + method = POST, + path = "/sleds/add", +}] +async fn sled_add( + rqctx: RequestContext>, + sled: TypedBody, +) -> Result { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let handler = async { + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + nexus.sled_add(&opctx, sled.into_inner()).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await +} diff --git a/nexus/test-utils/src/resource_helpers.rs b/nexus/test-utils/src/resource_helpers.rs index 25e58d093d..7331843000 100644 --- a/nexus/test-utils/src/resource_helpers.rs +++ b/nexus/test-utils/src/resource_helpers.rs @@ -18,6 +18,7 @@ use nexus_types::external_api::params; use nexus_types::external_api::params::PhysicalDiskKind; use nexus_types::external_api::params::UserId; use nexus_types::external_api::shared; +use nexus_types::external_api::shared::Baseboard; use nexus_types::external_api::shared::IdentityType; use nexus_types::external_api::shared::IpRange; use nexus_types::external_api::views; @@ -29,7 +30,6 @@ use nexus_types::external_api::views::User; use nexus_types::external_api::views::{Project, Silo, Vpc, VpcRouter}; use nexus_types::identity::Resource; use nexus_types::internal_api::params as internal_params; -use nexus_types::internal_api::params::Baseboard; use omicron_common::api::external::ByteCount; use omicron_common::api::external::Disk; use omicron_common::api::external::IdentityMetadataCreateParams; @@ -325,8 +325,8 @@ pub async fn create_switch( "/switches", &internal_params::SwitchPutRequest { baseboard: Baseboard { - serial_number: serial.to_string(), - part_number: part.to_string(), + serial: serial.to_string(), + part: part.to_string(), revision, }, rack_id, diff --git a/nexus/tests/integration_tests/rack.rs b/nexus/tests/integration_tests/rack.rs index 84b7643563..1148655195 100644 --- a/nexus/tests/integration_tests/rack.rs +++ b/nexus/tests/integration_tests/rack.rs @@ -13,7 +13,6 @@ use nexus_test_utils_macros::nexus_test; use nexus_types::external_api::params; use nexus_types::external_api::shared::UninitializedSled; use nexus_types::external_api::views::Rack; -use nexus_types::internal_api::params::Baseboard; use nexus_types::internal_api::params::SledAgentInfo; use nexus_types::internal_api::params::SledRole; use omicron_common::api::external::ByteCount; @@ -113,11 +112,7 @@ async fn test_sled_list_uninitialized(cptestctx: &ControlPlaneTestContext) { let sa = SledAgentInfo { sa_address: "[fd00:1122:3344:0100::1]:8080".parse().unwrap(), role: SledRole::Gimlet, - baseboard: Baseboard { - serial_number: baseboard.serial, - part_number: baseboard.part, - revision: baseboard.revision, - }, + baseboard, usable_hardware_threads: 32, usable_physical_ram: ByteCount::from_gibibytes_u32(100), reservoir_size: ByteCount::from_mebibytes_u32(100), diff --git a/nexus/types/src/internal_api/params.rs b/nexus/types/src/internal_api/params.rs index b1b4a56c0a..e8c4703008 100644 --- a/nexus/types/src/internal_api/params.rs +++ b/nexus/types/src/internal_api/params.rs @@ -6,6 +6,7 @@ use crate::external_api::params::PhysicalDiskKind; use crate::external_api::params::UserId; +use crate::external_api::shared::Baseboard; use crate::external_api::shared::IpRange; use omicron_common::api::external::ByteCount; use omicron_common::api::external::Generation; @@ -36,15 +37,6 @@ pub enum SledRole { Scrimlet, } -// TODO: We need a unified representation of these hardware identifiers -/// Describes properties that should uniquely identify Oxide manufactured hardware -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct Baseboard { - pub serial_number: String, - pub part_number: String, - pub revision: i64, -} - /// Sent by a sled agent to Nexus to inform about resources #[derive(Serialize, Deserialize, Debug, JsonSchema)] pub struct SledAgentInfo { diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 577fc61618..45a508ee9a 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -951,6 +951,57 @@ } } }, + "/sleds/add": { + "post": { + "summary": "Add sled to initialized rack", + "operationId": "sled_add", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UninitializedSledId" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/sleds/uninitialized": { + "get": { + "summary": "List uninitialized sleds", + "operationId": "sled_list_uninitialized", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UninitializedSledResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/switch/{switch_id}": { "put": { "operationId": "switch_put", @@ -1082,24 +1133,24 @@ ] }, "Baseboard": { - "description": "Describes properties that should uniquely identify Oxide manufactured hardware", + "description": "Properties that uniquely identify an Oxide hardware component", "type": "object", "properties": { - "part_number": { + "part": { "type": "string" }, "revision": { "type": "integer", "format": "int64" }, - "serial_number": { + "serial": { "type": "string" } }, "required": [ - "part_number", + "part", "revision", - "serial_number" + "serial" ] }, "BgpConfig": { @@ -6593,6 +6644,66 @@ "SwitchPutResponse": { "type": "object" }, + "UninitializedSled": { + "description": "A sled that has not been added to an initialized rack yet", + "type": "object", + "properties": { + "baseboard": { + "$ref": "#/components/schemas/Baseboard" + }, + "cubby": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "rack_id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "baseboard", + "cubby", + "rack_id" + ] + }, + "UninitializedSledId": { + "description": "The unique hardware ID for a sled", + "type": "object", + "properties": { + "part": { + "type": "string" + }, + "serial": { + "type": "string" + } + }, + "required": [ + "part", + "serial" + ] + }, + "UninitializedSledResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/UninitializedSled" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, "UserId": { "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.", diff --git a/package-manifest.toml b/package-manifest.toml index 5575efd285..c6f39d2ecd 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -97,9 +97,21 @@ source.paths = [ output.type = "zone" output.intermediate_only = true -[package.omicron-nexus] +[package.nexus] service_name = "nexus" only_for_targets.image = "standard" +source.type = "composite" +source.packages = [ + "omicron-nexus.tar.gz", + "zone-network-setup.tar.gz", + "zone-network-install.tar.gz", + "opte-interface-setup.tar.gz" +] +output.type = "zone" + +[package.omicron-nexus] +service_name = "omicron-nexus" +only_for_targets.image = "standard" source.type = "local" source.rust.binary_names = ["nexus", "schema-updater"] source.rust.release = true @@ -115,6 +127,7 @@ setup_hint = """ - Run `./tools/ci_download_console` to download the web console assets - Run `pkg install library/postgresql-13` to download Postgres libraries """ +output.intermediate_only = true [package.oximeter] service_name = "oximeter" @@ -228,7 +241,12 @@ output.intermediate_only = true service_name = "internal_dns" only_for_targets.image = "standard" source.type = "composite" -source.packages = [ "dns-server.tar.gz", "internal-dns-customizations.tar.gz" ] +source.packages = [ + "dns-server.tar.gz", + "internal-dns-customizations.tar.gz", + "zone-network-setup.tar.gz", + "zone-network-install.tar.gz" +] output.type = "zone" [package.external-dns] diff --git a/sled-agent/src/nexus.rs b/sled-agent/src/nexus.rs index f3811e3fe8..7e7d60f6a4 100644 --- a/sled-agent/src/nexus.rs +++ b/sled-agent/src/nexus.rs @@ -143,8 +143,8 @@ impl ConvertInto impl ConvertInto for sled_hardware::Baseboard { fn convert(self) -> nexus_client::types::Baseboard { nexus_client::types::Baseboard { - serial_number: self.identifier().to_string(), - part_number: self.model().to_string(), + serial: self.identifier().to_string(), + part: self.model().to_string(), revision: self.revision(), } } diff --git a/sled-agent/src/services.rs b/sled-agent/src/services.rs index 41acf6c079..9273e0ef3b 100644 --- a/sled-agent/src/services.rs +++ b/sled-agent/src/services.rs @@ -1400,12 +1400,12 @@ impl ServiceManager { } fn zone_network_setup_install( - info: &SledAgentInfo, + gw_addr: &Ipv6Addr, zone: &InstalledZone, static_addr: &String, ) -> Result { let datalink = zone.get_control_vnic_name(); - let gateway = &info.underlay_address.to_string(); + let gateway = &gw_addr.to_string(); let mut config_builder = PropertyGroupBuilder::new("config"); config_builder = config_builder @@ -1575,7 +1575,7 @@ impl ServiceManager { let listen_port = &CLICKHOUSE_PORT.to_string(); let nw_setup_service = Self::zone_network_setup_install( - info, + &info.underlay_address, &installed_zone, listen_addr, )?; @@ -1624,7 +1624,7 @@ impl ServiceManager { let listen_port = &CLICKHOUSE_KEEPER_PORT.to_string(); let nw_setup_service = Self::zone_network_setup_install( - info, + &info.underlay_address, &installed_zone, listen_addr, )?; @@ -1680,7 +1680,7 @@ impl ServiceManager { let listen_port = &address.port().to_string(); let nw_setup_service = Self::zone_network_setup_install( - info, + &info.underlay_address, &installed_zone, listen_addr, )?; @@ -1729,7 +1729,7 @@ impl ServiceManager { let listen_port = &CRUCIBLE_PORT.to_string(); let nw_setup_service = Self::zone_network_setup_install( - info, + &info.underlay_address, &installed_zone, listen_addr, )?; @@ -1784,7 +1784,7 @@ impl ServiceManager { let listen_port = &CRUCIBLE_PANTRY_PORT.to_string(); let nw_setup_service = Self::zone_network_setup_install( - info, + &info.underlay_address, &installed_zone, listen_addr, )?; @@ -1834,7 +1834,7 @@ impl ServiceManager { let listen_addr = &address.ip().to_string(); let nw_setup_service = Self::zone_network_setup_install( - info, + &info.underlay_address, &installed_zone, listen_addr, )?; @@ -1877,7 +1877,7 @@ impl ServiceManager { let static_addr = underlay_address.to_string(); let nw_setup_service = Self::zone_network_setup_install( - info, + &info.underlay_address, &installed_zone, &static_addr.clone(), )?; @@ -1966,7 +1966,7 @@ impl ServiceManager { let static_addr = underlay_address.to_string(); let nw_setup_service = Self::zone_network_setup_install( - info, + &info.underlay_address, &installed_zone, &static_addr.clone(), )?; @@ -2038,6 +2038,233 @@ impl ServiceManager { return Ok(RunningZone::boot(installed_zone).await?); } + ZoneArgs::Omicron(OmicronZoneConfigLocal { + zone: + OmicronZoneConfig { + zone_type: + OmicronZoneType::InternalDns { + http_address, + dns_address, + gz_address, + gz_address_index, + .. + }, + underlay_address, + .. + }, + .. + }) => { + let nw_setup_service = Self::zone_network_setup_install( + gz_address, + &installed_zone, + &underlay_address.to_string(), + )?; + + // 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; + + let http_addr = + format!("[{}]:{}", http_address.ip(), http_address.port()); + let dns_addr = + format!("[{}]:{}", dns_address.ip(), dns_address.port()); + + let internal_dns_config = PropertyGroupBuilder::new("config") + .add_property("http_address", "astring", &http_addr) + .add_property("dns_address", "astring", &dns_addr); + let internal_dns_service = + ServiceBuilder::new("oxide/internal_dns").add_instance( + ServiceInstanceBuilder::new("default") + .add_property_group(internal_dns_config), + ); + + let profile = ProfileBuilder::new("omicron") + .add_service(nw_setup_service) + .add_service(disabled_ssh_service) + .add_service(internal_dns_service) + .add_service(disabled_dns_client_service); + profile + .add_to_zone(&self.inner.log, &installed_zone) + .await + .map_err(|err| { + Error::io("Failed to setup Internal DNS profile", err) + })?; + return Ok(RunningZone::boot(installed_zone).await?); + } + ZoneArgs::Omicron(OmicronZoneConfigLocal { + zone: + OmicronZoneConfig { + zone_type: + OmicronZoneType::Nexus { + internal_address, + external_tls, + external_dns_servers, + .. + }, + underlay_address, + id, + }, + .. + }) => { + let Some(info) = self.inner.sled_info.get() else { + return Err(Error::SledAgentNotReady); + }; + + let static_addr = underlay_address.to_string(); + + let nw_setup_service = Self::zone_network_setup_install( + &info.underlay_address, + &installed_zone, + &static_addr.clone(), + )?; + + // 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 opte_interface_setup = + Self::opte_interface_set_up_install(&installed_zone)?; + + let port_idx = 0; + let port = installed_zone + .opte_ports() + .nth(port_idx) + .ok_or_else(|| { + Error::ZoneEnsureAddress( + EnsureAddressError::MissingOptePort { + zone: String::from(installed_zone.name()), + port_idx, + }, + ) + })?; + let opte_ip = port.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 = DeploymentConfig { + id: *id, + rack_id: info.rack_id, + techport_external_server_port: NEXUS_TECHPORT_EXTERNAL_PORT, + + dropshot_external: ConfigDropshotWithTls { + tls: *external_tls, + dropshot: dropshot::ConfigDropshot { + bind_address: SocketAddr::new(*opte_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, + }, + internal_dns: nexus_config::InternalDns::FromSubnet { + subnet: Ipv6Subnet::::new( + 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", + installed_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))?; + + let nexus_config = PropertyGroupBuilder::new("config"); + let nexus_service = ServiceBuilder::new("oxide/nexus") + .add_instance( + ServiceInstanceBuilder::new("default") + .add_property_group(nexus_config), + ); + + let profile = ProfileBuilder::new("omicron") + .add_service(nw_setup_service) + .add_service(opte_interface_setup) + .add_service(disabled_ssh_service) + .add_service(nexus_service) + .add_service(disabled_dns_client_service); + profile + .add_to_zone(&self.inner.log, &installed_zone) + .await + .map_err(|err| { + Error::io("Failed to setup Nexus profile", err) + })?; + return Ok(RunningZone::boot(installed_zone).await?); + } _ => {} } @@ -2155,203 +2382,7 @@ impl ServiceManager { 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 = DeploymentConfig { - 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, - }, - 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::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, - }, - )?; - - 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()?; - } OmicronZoneType::BoundaryNtp { .. } | OmicronZoneType::Clickhouse { .. } | OmicronZoneType::ClickhouseKeeper { .. } @@ -2359,7 +2390,9 @@ impl ServiceManager { | OmicronZoneType::Crucible { .. } | OmicronZoneType::CruciblePantry { .. } | OmicronZoneType::ExternalDns { .. } + | OmicronZoneType::InternalDns { .. } | OmicronZoneType::InternalNtp { .. } + | OmicronZoneType::Nexus { .. } | OmicronZoneType::Oximeter { .. } => { panic!( "{} is a service which exists as part of a \ @@ -2368,9 +2401,6 @@ impl ServiceManager { ) } }; - - debug!(self.inner.log, "enabling service"); - smfh.enable()?; } ZoneArgs::Switch(request) => { for service in &request.zone.services { diff --git a/sled-agent/src/sim/server.rs b/sled-agent/src/sim/server.rs index 9dbd777ee1..dd815775ff 100644 --- a/sled-agent/src/sim/server.rs +++ b/sled-agent/src/sim/server.rs @@ -106,11 +106,11 @@ impl Server { sa_address: sa_address.to_string(), role: NexusTypes::SledRole::Scrimlet, baseboard: NexusTypes::Baseboard { - serial_number: format!( + serial: format!( "sim-{}", &config.id.to_string()[0..8] ), - part_number: String::from("Unknown"), + part: String::from("Unknown"), revision: 0, }, usable_hardware_threads: config diff --git a/smf/internal-dns/manifest.xml b/smf/internal-dns/manifest.xml index 213f861b43..07cedc4ad5 100644 --- a/smf/internal-dns/manifest.xml +++ b/smf/internal-dns/manifest.xml @@ -4,13 +4,18 @@ - + + + + + diff --git a/smf/nexus/manifest.xml b/smf/nexus/manifest.xml index 5a72df8a22..0f43079cb9 100644 --- a/smf/nexus/manifest.xml +++ b/smf/nexus/manifest.xml @@ -4,28 +4,40 @@ - + + + + + + + + + + + diff --git a/tools/console_version b/tools/console_version index 24d16b17eb..b3ccaf80b8 100644 --- a/tools/console_version +++ b/tools/console_version @@ -1,2 +1,2 @@ -COMMIT="269c2c82018d09d442a932474d84366a54837068" -SHA2="7a6a04f43c6189384065675ebb7c429de2b8369ffbb7c1989a13aafe779be2d6" +COMMIT="784e8aac273dc282b6218994d09042aa5928a198" +SHA2="b5afd1243e40e86d3a779f089fe61ed9cb3fbbd6d59e547e522a59c1b0e4b7d8" diff --git a/tools/opte_version b/tools/opte_version index 0a04873e11..23d9da8fa3 100644 --- a/tools/opte_version +++ b/tools/opte_version @@ -1 +1 @@ -0.28.215 +0.28.227 diff --git a/tufaceous/README.adoc b/tufaceous/README.adoc index 973a37aa90..86e4cfc4e5 100644 --- a/tufaceous/README.adoc +++ b/tufaceous/README.adoc @@ -31,6 +31,6 @@ tuftool [-r PATH/TO/REPO] add-zone [--name NAME] ZONE_TAR_GZ VERSION Example: ---- -$ tuftool add-zone out/omicron-nexus.tar.gz 0.0.0 -added zone omicron-nexus, version 0.0.0 +$ tuftool add-zone out/nexus.tar.gz 0.0.0 +added zone nexus, version 0.0.0 ---- diff --git a/tufaceous/tests/integration-tests/command_tests.rs b/tufaceous/tests/integration-tests/command_tests.rs index 72c3a1a13a..20c6a06d66 100644 --- a/tufaceous/tests/integration-tests/command_tests.rs +++ b/tufaceous/tests/integration-tests/command_tests.rs @@ -25,7 +25,7 @@ async fn test_init_and_add() -> Result<()> { cmd.assert().success(); // Create a couple of stub files on disk. - let nexus_path = tempdir.path().join("omicron-nexus.tar.gz"); + let nexus_path = tempdir.path().join("nexus.tar.gz"); fs_err::write(&nexus_path, "test")?; let unknown_path = tempdir.path().join("my-unknown-kind.tar.gz"); fs_err::write(&unknown_path, "unknown test")?; @@ -65,7 +65,7 @@ async fn test_init_and_add() -> Result<()> { let mut artifacts_iter = artifacts.artifacts.into_iter(); let artifact = artifacts_iter.next().unwrap(); - assert_eq!(artifact.name, "omicron-nexus", "artifact name"); + assert_eq!(artifact.name, "nexus", "artifact name"); assert_eq!(artifact.version, "42.0.0".parse().unwrap(), "artifact version"); assert_eq!( artifact.kind, @@ -73,7 +73,7 @@ async fn test_init_and_add() -> Result<()> { "artifact kind" ); assert_eq!( - artifact.target, "gimlet_sp-omicron-nexus-42.0.0.tar.gz", + artifact.target, "gimlet_sp-nexus-42.0.0.tar.gz", "artifact target" );