diff --git a/.config/hakari.toml b/.config/hakari.toml new file mode 100644 index 0000000000..9562f92300 --- /dev/null +++ b/.config/hakari.toml @@ -0,0 +1,29 @@ +# This file contains settings for `cargo hakari`. +# See https://docs.rs/cargo-hakari/latest/cargo_hakari/config for a full list of options. + +hakari-package = "omicron-workspace-hack" + +# Format for `workspace-hack = ...` lines in other Cargo.tomls. Requires cargo-hakari 0.9.8 or above. +dep-format-version = "4" + +# Setting workspace.resolver = "2" in the root Cargo.toml is HIGHLY recommended. +# Hakari works much better with the new feature resolver. +# For more about the new feature resolver, see: +# https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html#cargos-new-feature-resolver +resolver = "2" + +# Add triples corresponding to platforms commonly used by developers here. +# https://doc.rust-lang.org/rustc/platform-support.html +platforms = [ + "x86_64-unknown-linux-gnu", + "x86_64-apple-darwin", + "aarch64-apple-darwin", + "x86_64-unknown-illumos", + # "x86_64-pc-windows-msvc", +] + +[traversal-excludes] +workspace-members = ["xtask"] + +# Write out exact versions rather than a semver range. (Defaults to false.) +# exact-versions = true diff --git a/.github/buildomat/jobs/build-and-test-helios.sh b/.github/buildomat/jobs/build-and-test-helios.sh index c1a9bee581..f9722a2b92 100755 --- a/.github/buildomat/jobs/build-and-test-helios.sh +++ b/.github/buildomat/jobs/build-and-test-helios.sh @@ -3,7 +3,7 @@ #: name = "build-and-test (helios)" #: variety = "basic" #: target = "helios-2.0" -#: rust_toolchain = "1.72.0" +#: rust_toolchain = "1.72.1" #: output_rules = [ #: "/var/tmp/omicron_tmp/*", #: "!/var/tmp/omicron_tmp/crdb-base*", diff --git a/.github/buildomat/jobs/build-and-test-linux.sh b/.github/buildomat/jobs/build-and-test-linux.sh index 50bdb22e56..f33d1a8cfa 100755 --- a/.github/buildomat/jobs/build-and-test-linux.sh +++ b/.github/buildomat/jobs/build-and-test-linux.sh @@ -3,7 +3,7 @@ #: name = "build-and-test (ubuntu-20.04)" #: variety = "basic" #: target = "ubuntu-20.04" -#: rust_toolchain = "1.72.0" +#: rust_toolchain = "1.72.1" #: output_rules = [ #: "/var/tmp/omicron_tmp/*", #: "!/var/tmp/omicron_tmp/crdb-base*", diff --git a/.github/buildomat/jobs/ci-tools.sh b/.github/buildomat/jobs/ci-tools.sh index b5d1d26f1a..702561a951 100755 --- a/.github/buildomat/jobs/ci-tools.sh +++ b/.github/buildomat/jobs/ci-tools.sh @@ -3,7 +3,7 @@ #: name = "helios / CI tools" #: variety = "basic" #: target = "helios-2.0" -#: rust_toolchain = "1.72.0" +#: rust_toolchain = "1.72.1" #: output_rules = [ #: "=/work/end-to-end-tests/*.gz", #: "=/work/caboose-util.gz", diff --git a/.github/buildomat/jobs/clippy.sh b/.github/buildomat/jobs/clippy.sh index 184b4ccbe6..dba1021919 100755 --- a/.github/buildomat/jobs/clippy.sh +++ b/.github/buildomat/jobs/clippy.sh @@ -3,7 +3,7 @@ #: name = "clippy (helios)" #: variety = "basic" #: target = "helios-2.0" -#: rust_toolchain = "1.72.0" +#: rust_toolchain = "1.72.1" #: output_rules = [] # Run clippy on illumos (not just other systems) because a bunch of our code diff --git a/.github/buildomat/jobs/host-image.sh b/.github/buildomat/jobs/host-image.sh index 0e2191ebc8..ba0b4e1ac3 100755 --- a/.github/buildomat/jobs/host-image.sh +++ b/.github/buildomat/jobs/host-image.sh @@ -3,7 +3,7 @@ #: name = "helios / build OS image" #: variety = "basic" #: target = "helios-2.0" -#: rust_toolchain = "1.72.0" +#: rust_toolchain = "1.72.1" #: output_rules = [ #: "=/work/helios/image/output/os.tar.gz", #: ] diff --git a/.github/buildomat/jobs/package.sh b/.github/buildomat/jobs/package.sh index eedb3b1723..fe5d6a9b7f 100755 --- a/.github/buildomat/jobs/package.sh +++ b/.github/buildomat/jobs/package.sh @@ -3,7 +3,7 @@ #: name = "helios / package" #: variety = "basic" #: target = "helios-2.0" -#: rust_toolchain = "1.72.0" +#: rust_toolchain = "1.72.1" #: output_rules = [ #: "=/work/version.txt", #: "=/work/package.tar.gz", diff --git a/.github/buildomat/jobs/trampoline-image.sh b/.github/buildomat/jobs/trampoline-image.sh index 794ebd96c1..6014d7dca0 100755 --- a/.github/buildomat/jobs/trampoline-image.sh +++ b/.github/buildomat/jobs/trampoline-image.sh @@ -3,7 +3,7 @@ #: name = "helios / build trampoline OS image" #: variety = "basic" #: target = "helios-2.0" -#: rust_toolchain = "1.72.0" +#: rust_toolchain = "1.72.1" #: output_rules = [ #: "=/work/helios/image/output/os.tar.gz", #: ] diff --git a/.github/workflows/hakari.yml b/.github/workflows/hakari.yml new file mode 100644 index 0000000000..d79196d318 --- /dev/null +++ b/.github/workflows/hakari.yml @@ -0,0 +1,37 @@ +# This workflow file serves as an example for cargo-hakari CI integration. + +on: + push: + branches: + - main + pull_request: + branches: + - main + +name: cargo hakari + +jobs: + workspace-hack-check: + name: Check workspace-hack + runs-on: ubuntu-latest + env: + RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: Install cargo-hakari + uses: taiki-e/install-action@v2 + with: + tool: cargo-hakari + - name: Check workspace-hack Cargo.toml is up-to-date + uses: actions-rs/cargo@v1 + with: + command: hakari + args: generate --diff + - name: Check all crates depend on workspace-hack + uses: actions-rs/cargo@v1 + with: + command: hakari + args: manage-deps --dry-run diff --git a/Cargo.lock b/Cargo.lock index d285b2bfa6..fa4131ed0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,6 +184,7 @@ dependencies = [ name = "api_identity" version = "0.1.0" dependencies = [ + "omicron-workspace-hack", "proc-macro2", "quote", "syn 2.0.32", @@ -369,6 +370,7 @@ name = "authz-macros" version = "0.1.0" dependencies = [ "heck 0.4.1", + "omicron-workspace-hack", "proc-macro2", "quote", "serde", @@ -657,6 +659,7 @@ dependencies = [ "omicron-common 0.1.0", "omicron-rpaths", "omicron-test-utils", + "omicron-workspace-hack", "pq-sys", "proptest", "rand 0.8.5", @@ -683,6 +686,7 @@ dependencies = [ "chrono", "ipnetwork", "omicron-common 0.1.0", + "omicron-workspace-hack", "progenitor", "regress", "reqwest", @@ -788,6 +792,7 @@ version = "0.1.0" dependencies = [ "anyhow", "hubtools", + "omicron-workspace-hack", ] [[package]] @@ -1244,6 +1249,7 @@ dependencies = [ "dropshot", "hex", "omicron-test-utils", + "omicron-workspace-hack", "ring", "slog", "tokio", @@ -1725,6 +1731,7 @@ name = "db-macros" version = "0.1.0" dependencies = [ "heck 0.4.1", + "omicron-workspace-hack", "proc-macro2", "quote", "rustfmt-wrapper", @@ -1740,6 +1747,7 @@ dependencies = [ "anyhow", "either", "omicron-common 0.1.0", + "omicron-workspace-hack", "omicron-zone-package", "progenitor", "progenitor-client", @@ -2008,6 +2016,7 @@ dependencies = [ "expectorate", "http", "omicron-test-utils", + "omicron-workspace-hack", "openapi-lint", "openapiv3", "pretty-hex 0.3.0", @@ -2038,6 +2047,7 @@ version = "0.1.0" dependencies = [ "chrono", "http", + "omicron-workspace-hack", "progenitor", "reqwest", "schemars", @@ -2094,6 +2104,7 @@ dependencies = [ "futures", "http", "ipnetwork", + "omicron-workspace-hack", "omicron-zone-package", "progenitor", "progenitor-client", @@ -2279,6 +2290,7 @@ dependencies = [ "http", "omicron-sled-agent", "omicron-test-utils", + "omicron-workspace-hack", "oxide-client", "rand 0.8.5", "reqwest", @@ -2719,6 +2731,7 @@ dependencies = [ "hex", "libc", "omicron-common 0.1.0", + "omicron-workspace-hack", "reqwest", "serde", "serde_json", @@ -2737,6 +2750,7 @@ version = "0.1.0" dependencies = [ "base64 0.21.4", "chrono", + "omicron-workspace-hack", "progenitor", "rand 0.8.5", "reqwest", @@ -2801,6 +2815,7 @@ dependencies = [ "gateway-messages", "omicron-gateway", "omicron-test-utils", + "omicron-workspace-hack", "slog", "sp-sim", "tokio", @@ -3378,6 +3393,7 @@ dependencies = [ "macaddr", "mockall", "omicron-common 0.1.0", + "omicron-workspace-hack", "opte-ioctl", "oxide-vpc", "regress", @@ -3487,6 +3503,7 @@ dependencies = [ "libc", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "once_cell", "partial-io", "progenitor-client", @@ -3516,6 +3533,7 @@ name = "installinator-artifact-client" version = "0.1.0" dependencies = [ "installinator-common", + "omicron-workspace-hack", "progenitor", "regress", "reqwest", @@ -3541,6 +3559,7 @@ dependencies = [ "installinator-common", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "openapi-lint", "openapiv3", "schemars", @@ -3560,6 +3579,7 @@ dependencies = [ "camino", "illumos-utils", "omicron-common 0.1.0", + "omicron-workspace-hack", "schemars", "serde", "serde_json", @@ -3592,6 +3612,7 @@ dependencies = [ "hyper", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "progenitor", "reqwest", "serde", @@ -3634,6 +3655,7 @@ dependencies = [ "dropshot", "internal-dns 0.1.0", "omicron-common 0.1.0", + "omicron-workspace-hack", "slog", "tokio", "trust-dns-resolver", @@ -3657,6 +3679,7 @@ dependencies = [ "ciborium", "libc", "omicron-common 0.1.0", + "omicron-workspace-hack", "proptest", "serde", "test-strategy", @@ -3761,6 +3784,7 @@ dependencies = [ "async-trait", "hkdf", "omicron-common 0.1.0", + "omicron-workspace-hack", "secrecy", "sha3", "slog", @@ -4251,6 +4275,7 @@ dependencies = [ "ipnetwork", "omicron-common 0.1.0", "omicron-passwords 0.1.0", + "omicron-workspace-hack", "progenitor", "regress", "reqwest", @@ -4300,6 +4325,7 @@ dependencies = [ "omicron-common 0.1.0", "omicron-passwords 0.1.0", "omicron-rpaths", + "omicron-workspace-hack", "parse-display", "pq-sys", "rand 0.8.5", @@ -4355,6 +4381,7 @@ dependencies = [ "omicron-rpaths", "omicron-sled-agent", "omicron-test-utils", + "omicron-workspace-hack", "once_cell", "openapiv3", "openssl", @@ -4400,6 +4427,7 @@ dependencies = [ "ipnetwork", "lazy_static", "omicron-common 0.1.0", + "omicron-workspace-hack", "rand 0.8.5", "serde_json", ] @@ -4413,6 +4441,7 @@ dependencies = [ "internal-dns 0.1.0", "nexus-types", "omicron-common 0.1.0", + "omicron-workspace-hack", "slog", "uuid", ] @@ -4441,6 +4470,7 @@ dependencies = [ "omicron-passwords 0.1.0", "omicron-sled-agent", "omicron-test-utils", + "omicron-workspace-hack", "oximeter 0.1.0", "oximeter-client", "oximeter-collector", @@ -4460,6 +4490,7 @@ dependencies = [ name = "nexus-test-utils-macros" version = "0.1.0" dependencies = [ + "omicron-workspace-hack", "proc-macro2", "quote", "syn 2.0.32", @@ -4478,6 +4509,7 @@ dependencies = [ "newtype_derive", "omicron-common 0.1.0", "omicron-passwords 0.1.0", + "omicron-workspace-hack", "openssl", "openssl-probe", "openssl-sys", @@ -4784,6 +4816,7 @@ dependencies = [ "foreign-types 0.3.2", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "openssl", "openssl-sys", "rcgen", @@ -4811,6 +4844,7 @@ dependencies = [ "lazy_static", "libc", "macaddr", + "omicron-workspace-hack", "parse-display", "progenitor", "proptest", @@ -4884,6 +4918,7 @@ dependencies = [ "clap 4.4.3", "crossbeam", "omicron-package", + "omicron-workspace-hack", "serde", "serde_derive", "thiserror", @@ -4909,6 +4944,7 @@ dependencies = [ "omicron-rpaths", "omicron-sled-agent", "omicron-test-utils", + "omicron-workspace-hack", "openssl", "oxide-client", "pq-sys", @@ -4942,6 +4978,7 @@ dependencies = [ "ipcc-key-value", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "once_cell", "openapi-lint", "openapiv3", @@ -5019,6 +5056,7 @@ dependencies = [ "omicron-rpaths", "omicron-sled-agent", "omicron-test-utils", + "omicron-workspace-hack", "once_cell", "openapi-lint", "openapiv3", @@ -5098,6 +5136,7 @@ dependencies = [ "omicron-nexus", "omicron-rpaths", "omicron-test-utils", + "omicron-workspace-hack", "pq-sys", "regex", "serde", @@ -5124,6 +5163,7 @@ dependencies = [ "illumos-utils", "indicatif", "omicron-common 0.1.0", + "omicron-workspace-hack", "omicron-zone-package", "petgraph", "rayon", @@ -5153,6 +5193,7 @@ version = "0.1.0" dependencies = [ "argon2", "criterion", + "omicron-workspace-hack", "rand 0.8.5", "rust-argon2", "schemars", @@ -5177,6 +5218,9 @@ dependencies = [ [[package]] name = "omicron-rpaths" version = "0.1.0" +dependencies = [ + "omicron-workspace-hack", +] [[package]] name = "omicron-sled-agent" @@ -5222,6 +5266,7 @@ dependencies = [ "nexus-client 0.1.0", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "once_cell", "openapi-lint", "openapiv3", @@ -5277,6 +5322,7 @@ dependencies = [ "http", "libc", "omicron-common 0.1.0", + "omicron-workspace-hack", "pem", "rcgen", "regex", @@ -5291,6 +5337,110 @@ dependencies = [ "usdt", ] +[[package]] +name = "omicron-workspace-hack" +version = "0.1.0" +dependencies = [ + "anyhow", + "bit-set", + "bit-vec", + "bitflags 1.3.2", + "bitflags 2.4.0", + "bitvec", + "bstr 0.2.17", + "bstr 1.6.0", + "bytes", + "cc", + "chrono", + "cipher", + "clap 4.4.3", + "clap_builder", + "console", + "const-oid", + "crossbeam-epoch", + "crossbeam-utils", + "crypto-common", + "diesel", + "digest", + "either", + "flate2", + "futures", + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", + "gateway-messages", + "generic-array", + "getrandom 0.2.10", + "hashbrown 0.13.2", + "hashbrown 0.14.0", + "hex", + "hyper", + "hyper-rustls", + "indexmap 2.0.0", + "inout", + "ipnetwork", + "itertools 0.10.5", + "lalrpop-util", + "lazy_static", + "libc", + "log", + "managed", + "memchr", + "mio", + "num-bigint", + "num-integer", + "num-iter", + "num-traits", + "once_cell", + "openapiv3", + "petgraph", + "postgres-types", + "ppv-lite86", + "predicates 3.0.3", + "rand 0.8.5", + "rand_chacha 0.3.1", + "regex", + "regex-automata 0.3.8", + "regex-syntax 0.7.5", + "reqwest", + "ring", + "rustix 0.38.9", + "schemars", + "semver 1.0.18", + "serde", + "sha2", + "signature 2.1.0", + "similar", + "slog", + "spin 0.9.8", + "string_cache", + "subtle", + "syn 1.0.109", + "syn 2.0.32", + "textwrap 0.16.0", + "time", + "time-macros", + "tokio", + "tokio-postgres", + "tokio-stream", + "toml 0.7.8", + "toml_datetime", + "toml_edit 0.19.15", + "tracing", + "trust-dns-proto", + "unicode-bidi", + "unicode-normalization", + "unicode-xid", + "usdt", + "uuid", + "yasna", + "zeroize", + "zip", +] + [[package]] name = "omicron-zone-package" version = "0.8.3" @@ -5497,6 +5647,7 @@ dependencies = [ "futures", "http", "hyper", + "omicron-workspace-hack", "progenitor", "rand 0.8.5", "regress", @@ -5531,6 +5682,7 @@ dependencies = [ "chrono", "num", "omicron-common 0.1.0", + "omicron-workspace-hack", "oximeter-macro-impl 0.1.0", "rstest", "schemars", @@ -5562,6 +5714,7 @@ version = "0.1.0" dependencies = [ "chrono", "omicron-common 0.1.0", + "omicron-workspace-hack", "progenitor", "reqwest", "serde", @@ -5581,6 +5734,7 @@ dependencies = [ "nexus-client 0.1.0", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "openapi-lint", "openapiv3", "oximeter 0.1.0", @@ -5609,6 +5763,7 @@ dependencies = [ "dropshot", "itertools 0.11.0", "omicron-test-utils", + "omicron-workspace-hack", "oximeter 0.1.0", "regex", "reqwest", @@ -5633,6 +5788,7 @@ dependencies = [ "dropshot", "futures", "http", + "omicron-workspace-hack", "oximeter 0.1.0", "tokio", "uuid", @@ -5642,6 +5798,7 @@ dependencies = [ name = "oximeter-macro-impl" version = "0.1.0" dependencies = [ + "omicron-workspace-hack", "proc-macro2", "quote", "syn 2.0.32", @@ -5665,6 +5822,7 @@ dependencies = [ "dropshot", "nexus-client 0.1.0", "omicron-common 0.1.0", + "omicron-workspace-hack", "oximeter 0.1.0", "reqwest", "schemars", @@ -7919,6 +8077,7 @@ dependencies = [ "chrono", "ipnetwork", "omicron-common 0.1.0", + "omicron-workspace-hack", "progenitor", "regress", "reqwest", @@ -7944,6 +8103,7 @@ dependencies = [ "nexus-client 0.1.0", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "rand 0.8.5", "schemars", "serde", @@ -8170,6 +8330,7 @@ dependencies = [ "hex", "omicron-common 0.1.0", "omicron-gateway", + "omicron-workspace-hack", "serde", "slog", "slog-dtrace", @@ -9167,6 +9328,7 @@ dependencies = [ "humantime", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "predicates 3.0.3", "slog", "slog-async", @@ -9195,6 +9357,7 @@ dependencies = [ "itertools 0.11.0", "omicron-common 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "rand 0.8.5", "ring", "serde", @@ -9421,6 +9584,7 @@ dependencies = [ "indicatif", "linear-map", "omicron-test-utils", + "omicron-workspace-hack", "owo-colors", "petgraph", "schemars", @@ -9798,6 +9962,7 @@ dependencies = [ "itertools 0.11.0", "omicron-common 0.1.0", "omicron-passwords 0.1.0", + "omicron-workspace-hack", "once_cell", "owo-colors", "proptest", @@ -9833,6 +9998,7 @@ dependencies = [ "anyhow", "gateway-client", "omicron-common 0.1.0", + "omicron-workspace-hack", "schemars", "serde", "serde_json", @@ -9850,6 +10016,7 @@ dependencies = [ "ciborium", "clap 4.4.3", "crossterm 0.27.0", + "omicron-workspace-hack", "ratatui", "reedline", "serde", @@ -9895,10 +10062,12 @@ dependencies = [ "installinator-artifact-client", "installinator-artifactd", "installinator-common", + "itertools 0.11.0", "omicron-certificates", "omicron-common 0.1.0", "omicron-passwords 0.1.0", "omicron-test-utils", + "omicron-workspace-hack", "openapi-lint", "openapiv3", "rand 0.8.5", @@ -9935,6 +10104,7 @@ dependencies = [ "chrono", "installinator-common", "ipnetwork", + "omicron-workspace-hack", "progenitor", "regress", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index fd2d720cfd..1d0b8443a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ members = [ "wicket", "wicketd-client", "wicketd", + "workspace-hack", ] default-members = [ diff --git a/README.adoc b/README.adoc index 988c1276fd..93d1fa4fb8 100644 --- a/README.adoc +++ b/README.adoc @@ -152,30 +152,17 @@ Many of these components themselves are made up of other packages (e.g., `nexus- Use Cargo's `-p PACKAGE` to check/build/test only the package you're working on. Since people are usually only working on one or two components at a time, you can usually iterate faster this way. -=== Why is Cargo rebuilding stuff all the time? +=== Workspace management -People are often surprised to find Cargo rebuilding stuff that it seems like it's just built, even when the relevant source files haven't changed. +Omicron uses `cargo-hakari` to ensure that all workspace dependencies enable the same set of features. This dramatically improves compilation time when switching between different subsets of packages (e.g. `-p wicket` or `-p nexus-db-model`), because the sets of enabled features remain consistent. -* Say you're iterating on code, running `cargo build -p nexus-db-model` to build just that package. Great, it works. Let's run tests: `cargo nextest run -p nexus-db-model`. Now it's rebuilding some _dependency_ of `nexus-db-model` again?! -* Say you've just run `cargo nextest run -p nexus-db-model`. Now you go run `cargo nextest run -p omicron-nexus`, which uses `nexus-db-model`. You see Cargo building `nexus-db-model` again?! +`cargo hakari` status is checked in CI; if the CI check fails, then update the configuration locally with -This usually has to do with the way Cargo selects package https://doc.rust-lang.org/cargo/reference/features.html[features]. These are essentially tags that are used at build time to include specific code or dependencies. For example, the https://docs.rs/serde/latest/serde/[serde] crate defines a feature called https://docs.rs/crate/serde/latest/features["derive"] that controls whether the `Serialize`/`Deserialize` derive macros will be included. Let's look at how this affects builds. - -TIP: You can use `cargo tree` to inspect a package's dependencies, including features. This is useful for debugging feature-related build issues. - -==== Feature selection differs when building tests - -When you run `cargo build -p nexus-db-model`, Cargo looks at all the packages in the depencency tree of `nexus-db-model` and figures out what features it needs for each one. Let's take the `uuid` package as an example. Cargo takes https://doc.rust-lang.org/cargo/reference/features.html#feature-unification[union of the features required by any of the packages that depend on `uuid` in the whole dependency tree of `nexus-db-model`]. Let's say that's just the "v4" feature. Simple enough. - -When you then run `cargo nextest run -p nexus-db-model`, it does the same thing. Only this time, it's looking at the `dev-dependencies` tree. `nexus-db-model` 's dev-dependencies might include some other package that depends on `uuid` and requires the "v5" feature. Now, Cargo has to rebuild `uuid` -- and anything else that depends on it. - -This is why when using Cargo's check/build/clippy commands, we suggest using `--all-targets`. When you use `cargo build --all-targets`, it builds the tests as well. It's usually not much more time and avoids extra rebuilds when switching back and forth between the default targets and the targets with tests included. - -==== Feature selection differs when building different packages - -People run into a similar problem when switching packages within Omicron. Once you've got `cargo nextest run -p nexus-db-model` working, you may run `cargo nextest run -p omicron-nexus`, which uses `nexus-db-model`. And you may be surprised to see Cargo rebuilding some common dependency like `uuid`. It's the same as above: we're building a different package now. It has a different (larger) dependency tree. That may result in some crate deep in the dependency tree needing some new feature, causing it and all of its dependents to be rebuilt. - -NOTE: https://github.com/rust-lang/cargo/issues/4463[There is interest in changing the way feature selection works in workspaces like Omicron for exactly this reason.] It's been suggested to have an option for Cargo to always look at the features required for all packages in the workspace, rather than just the one you've selected. This could eliminate this particular problem. In the meantime, we mitigate this with heavy use of https://doc.rust-lang.org/cargo/reference/workspaces.html#the-dependencies-table[workspace dependencies], which helps make sure that different packages _within_ Omicron depend on the same set of features for a given dependency. +``` +cargo install cargo-hakari --locked # only needed on the first run +cargo hakari generate +cargo hakari manage-deps +``` === Why am I getting compile errors after I thought I'd already built everything? diff --git a/api_identity/Cargo.toml b/api_identity/Cargo.toml index 761e5e3017..9faf2a1878 100644 --- a/api_identity/Cargo.toml +++ b/api_identity/Cargo.toml @@ -14,3 +14,4 @@ proc-macro = true proc-macro2.workspace = true quote.workspace = true syn.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/bootstore/Cargo.toml b/bootstore/Cargo.toml index d916bf80c7..eefe05c8d6 100644 --- a/bootstore/Cargo.toml +++ b/bootstore/Cargo.toml @@ -36,6 +36,7 @@ zeroize.workspace = true # utils`. Unfortunately, it doesn't appear possible to put the `pq-sys` dep # only in `[dev-dependencies]`. pq-sys = "*" +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] assert_matches.workspace = true diff --git a/bootstrap-agent-client/Cargo.toml b/bootstrap-agent-client/Cargo.toml index f7d3ad2db6..17989a5c5f 100644 --- a/bootstrap-agent-client/Cargo.toml +++ b/bootstrap-agent-client/Cargo.toml @@ -17,3 +17,4 @@ serde.workspace = true sled-hardware.workspace = true slog.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/caboose-util/Cargo.toml b/caboose-util/Cargo.toml index 195bbfd5d7..253d54643d 100644 --- a/caboose-util/Cargo.toml +++ b/caboose-util/Cargo.toml @@ -7,3 +7,4 @@ license = "MPL-2.0" [dependencies] anyhow.workspace = true hubtools.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/certificates/Cargo.toml b/certificates/Cargo.toml index 29c4a8bc2e..d20d257e4c 100644 --- a/certificates/Cargo.toml +++ b/certificates/Cargo.toml @@ -12,6 +12,7 @@ openssl-sys.workspace = true thiserror.workspace = true omicron-common.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] omicron-test-utils.workspace = true diff --git a/common/Cargo.toml b/common/Cargo.toml index 492b6ca860..bda88d0d43 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -40,6 +40,7 @@ toml.workspace = true uuid.workspace = true parse-display.workspace = true progenitor.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] camino-tempfile.workspace = true diff --git a/crdb-seed/Cargo.toml b/crdb-seed/Cargo.toml index 01af7cb1d7..fa71fe7e8a 100644 --- a/crdb-seed/Cargo.toml +++ b/crdb-seed/Cargo.toml @@ -13,3 +13,4 @@ omicron-test-utils.workspace = true ring.workspace = true slog.workspace = true tokio.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/ddm-admin-client/Cargo.toml b/ddm-admin-client/Cargo.toml index 6e9ee930a0..3814446b3e 100644 --- a/ddm-admin-client/Cargo.toml +++ b/ddm-admin-client/Cargo.toml @@ -15,6 +15,7 @@ tokio.workspace = true omicron-common.workspace = true sled-hardware.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [build-dependencies] anyhow.workspace = true diff --git a/deploy/Cargo.toml b/deploy/Cargo.toml index 1b8e6a92d8..17bacd6354 100644 --- a/deploy/Cargo.toml +++ b/deploy/Cargo.toml @@ -14,6 +14,7 @@ serde.workspace = true serde_derive.workspace = true thiserror.workspace = true toml.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [[bin]] name = "thing-flinger" diff --git a/dev-tools/omdb/Cargo.toml b/dev-tools/omdb/Cargo.toml index c9ebbe35ad..5b2adde1b2 100644 --- a/dev-tools/omdb/Cargo.toml +++ b/dev-tools/omdb/Cargo.toml @@ -32,6 +32,7 @@ tabled.workspace = true textwrap.workspace = true tokio = { workspace = true, features = [ "full" ] } uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } [dev-dependencies] expectorate.workspace = true diff --git a/dev-tools/omicron-dev/Cargo.toml b/dev-tools/omicron-dev/Cargo.toml index 2061489cbb..95da4d42ef 100644 --- a/dev-tools/omicron-dev/Cargo.toml +++ b/dev-tools/omicron-dev/Cargo.toml @@ -28,6 +28,7 @@ signal-hook-tokio.workspace = true tokio = { workspace = true, features = [ "full" ] } tokio-postgres.workspace = true toml.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } [dev-dependencies] camino-tempfile.workspace = true diff --git a/dev-tools/xtask/src/main.rs b/dev-tools/xtask/src/main.rs index a64c87a570..3e52d742f5 100644 --- a/dev-tools/xtask/src/main.rs +++ b/dev-tools/xtask/src/main.rs @@ -78,6 +78,8 @@ fn cmd_clippy() -> Result<()> { Ok(()) } +const WORKSPACE_HACK_PACKAGE_NAME: &str = "omicron-workspace-hack"; + fn cmd_check_workspace_deps() -> Result<()> { // Ignore issues with "pq-sys". See the omicron-rpaths package for details. const EXCLUDED: &[&'static str] = &["pq-sys"]; @@ -97,6 +99,12 @@ fn cmd_check_workspace_deps() -> Result<()> { // Iterate the workspace packages and fill out the maps above. for pkg_info in workspace.workspace_packages() { + if pkg_info.name == WORKSPACE_HACK_PACKAGE_NAME { + // Skip over workspace-hack because hakari doesn't yet support + // workspace deps: https://github.com/guppy-rs/guppy/issues/7 + continue; + } + let manifest_path = &pkg_info.manifest_path; let manifest = read_cargo_toml(manifest_path)?; for tree in [ @@ -125,6 +133,12 @@ fn cmd_check_workspace_deps() -> Result<()> { } } + if name == WORKSPACE_HACK_PACKAGE_NAME { + // Skip over workspace-hack because hakari doesn't yet support + // workspace deps: https://github.com/guppy-rs/guppy/issues/7 + continue; + } + non_workspace_dependencies .entry(name.to_owned()) .or_insert_with(Vec::new) diff --git a/dns-server/Cargo.toml b/dns-server/Cargo.toml index 243876a5a2..d7606dcff5 100644 --- a/dns-server/Cargo.toml +++ b/dns-server/Cargo.toml @@ -30,6 +30,7 @@ trust-dns-proto.workspace = true trust-dns-resolver.workspace = true trust-dns-server.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] expectorate.workspace = true diff --git a/dns-service-client/Cargo.toml b/dns-service-client/Cargo.toml index 7f5cf63d6a..e351d90da2 100644 --- a/dns-service-client/Cargo.toml +++ b/dns-service-client/Cargo.toml @@ -14,3 +14,4 @@ serde.workspace = true serde_json.workspace = true slog.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/dpd-client/Cargo.toml b/dpd-client/Cargo.toml index fdbdcd07af..26807f7d79 100644 --- a/dpd-client/Cargo.toml +++ b/dpd-client/Cargo.toml @@ -17,6 +17,7 @@ ipnetwork.workspace = true http.workspace = true schemars.workspace = true rand.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [build-dependencies] anyhow.workspace = true diff --git a/end-to-end-tests/Cargo.toml b/end-to-end-tests/Cargo.toml index a0e099b756..5ff0f9b377 100644 --- a/end-to-end-tests/Cargo.toml +++ b/end-to-end-tests/Cargo.toml @@ -24,3 +24,4 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } toml.workspace = true trust-dns-resolver.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/gateway-cli/Cargo.toml b/gateway-cli/Cargo.toml index d5083d1999..0d179750ea 100644 --- a/gateway-cli/Cargo.toml +++ b/gateway-cli/Cargo.toml @@ -24,3 +24,4 @@ uuid.workspace = true gateway-client.workspace = true gateway-messages.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/gateway-client/Cargo.toml b/gateway-client/Cargo.toml index 81d1630a1d..96a1eb221f 100644 --- a/gateway-client/Cargo.toml +++ b/gateway-client/Cargo.toml @@ -15,3 +15,4 @@ serde_json.workspace = true schemars.workspace = true slog.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/gateway-test-utils/Cargo.toml b/gateway-test-utils/Cargo.toml index 8f6e14d68a..9d80e63f05 100644 --- a/gateway-test-utils/Cargo.toml +++ b/gateway-test-utils/Cargo.toml @@ -14,3 +14,4 @@ slog.workspace = true sp-sim.workspace = true tokio.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 307baa3f27..f5abce88e9 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -34,6 +34,7 @@ tokio-tungstenite.workspace = true tokio-util.workspace = true toml.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] expectorate.workspace = true diff --git a/illumos-utils/Cargo.toml b/illumos-utils/Cargo.toml index 3c0c2e7fc9..e292097bc5 100644 --- a/illumos-utils/Cargo.toml +++ b/illumos-utils/Cargo.toml @@ -29,6 +29,7 @@ zone.workspace = true # only enabled via the `testing` feature mockall = { workspace = true, optional = true } +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [target.'cfg(target_os = "illumos")'.dependencies] opte-ioctl.workspace = true diff --git a/installinator-artifact-client/Cargo.toml b/installinator-artifact-client/Cargo.toml index ddbc106ee8..18447b8e83 100644 --- a/installinator-artifact-client/Cargo.toml +++ b/installinator-artifact-client/Cargo.toml @@ -15,3 +15,4 @@ serde_json.workspace = true slog.workspace = true update-engine.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/installinator-artifactd/Cargo.toml b/installinator-artifactd/Cargo.toml index 3ce6112165..9318b725db 100644 --- a/installinator-artifactd/Cargo.toml +++ b/installinator-artifactd/Cargo.toml @@ -20,6 +20,7 @@ uuid.workspace = true installinator-common.workspace = true omicron-common.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] expectorate.workspace = true diff --git a/installinator-common/Cargo.toml b/installinator-common/Cargo.toml index ff664f28a3..0f1bf86901 100644 --- a/installinator-common/Cargo.toml +++ b/installinator-common/Cargo.toml @@ -15,3 +15,4 @@ serde_json.workspace = true serde_with.workspace = true thiserror.workspace = true update-engine.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/installinator/Cargo.toml b/installinator/Cargo.toml index c0e7625e6e..3b2f04c38f 100644 --- a/installinator/Cargo.toml +++ b/installinator/Cargo.toml @@ -42,6 +42,7 @@ toml.workspace = true tufaceous-lib.workspace = true update-engine.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] omicron-test-utils.workspace = true diff --git a/internal-dns-cli/Cargo.toml b/internal-dns-cli/Cargo.toml index d922544722..fb5780d22a 100644 --- a/internal-dns-cli/Cargo.toml +++ b/internal-dns-cli/Cargo.toml @@ -13,3 +13,4 @@ omicron-common.workspace = true slog.workspace = true tokio.workspace = true trust-dns-resolver.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/internal-dns/Cargo.toml b/internal-dns/Cargo.toml index 5ead1cc8a4..d680ab3ce1 100644 --- a/internal-dns/Cargo.toml +++ b/internal-dns/Cargo.toml @@ -17,6 +17,7 @@ thiserror.workspace = true trust-dns-proto.workspace = true trust-dns-resolver.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] assert_matches.workspace = true diff --git a/ipcc-key-value/Cargo.toml b/ipcc-key-value/Cargo.toml index a3f17cea52..128fde9a01 100644 --- a/ipcc-key-value/Cargo.toml +++ b/ipcc-key-value/Cargo.toml @@ -11,6 +11,7 @@ omicron-common.workspace = true serde.workspace = true thiserror.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] omicron-common = { workspace = true, features = ["testing"] } diff --git a/key-manager/Cargo.toml b/key-manager/Cargo.toml index 7954a977a3..69ae3b25bd 100644 --- a/key-manager/Cargo.toml +++ b/key-manager/Cargo.toml @@ -14,4 +14,5 @@ slog.workspace = true thiserror.workspace = true tokio.workspace = true zeroize.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/nexus-client/Cargo.toml b/nexus-client/Cargo.toml index 589562c930..d59c013992 100644 --- a/nexus-client/Cargo.toml +++ b/nexus-client/Cargo.toml @@ -18,3 +18,4 @@ serde.workspace = true serde_json.workspace = true slog.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/nexus/Cargo.toml b/nexus/Cargo.toml index 1a09f07f6c..91872e2c32 100644 --- a/nexus/Cargo.toml +++ b/nexus/Cargo.toml @@ -90,6 +90,7 @@ oximeter.workspace = true oximeter-instruments = { workspace = true, features = ["http-instruments"] } oximeter-producer.workspace = true rustls = { workspace = true } +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] async-bb8-diesel.workspace = true diff --git a/nexus/authz-macros/Cargo.toml b/nexus/authz-macros/Cargo.toml index 40303b2e34..3d55afa477 100644 --- a/nexus/authz-macros/Cargo.toml +++ b/nexus/authz-macros/Cargo.toml @@ -14,3 +14,4 @@ quote.workspace = true serde.workspace = true serde_tokenstream.workspace = true syn.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } diff --git a/nexus/db-macros/Cargo.toml b/nexus/db-macros/Cargo.toml index 3fb228f26c..ce206bb56e 100644 --- a/nexus/db-macros/Cargo.toml +++ b/nexus/db-macros/Cargo.toml @@ -15,6 +15,7 @@ quote.workspace = true serde.workspace = true serde_tokenstream.workspace = true syn = { workspace = true, features = ["extra-traits"] } +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } [dev-dependencies] rustfmt-wrapper.workspace = true diff --git a/nexus/db-model/Cargo.toml b/nexus/db-model/Cargo.toml index dc83670725..aedbb9168b 100644 --- a/nexus/db-model/Cargo.toml +++ b/nexus/db-model/Cargo.toml @@ -36,6 +36,7 @@ nexus-defaults.workspace = true nexus-types.workspace = true omicron-passwords.workspace = true sled-agent-client.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } [dev-dependencies] expectorate.workspace = true diff --git a/nexus/db-queries/Cargo.toml b/nexus/db-queries/Cargo.toml index a8256cb60a..af01c1732b 100644 --- a/nexus/db-queries/Cargo.toml +++ b/nexus/db-queries/Cargo.toml @@ -63,6 +63,7 @@ nexus-types.workspace = true omicron-common.workspace = true omicron-passwords.workspace = true oximeter.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } [dev-dependencies] assert_matches.workspace = true diff --git a/nexus/defaults/Cargo.toml b/nexus/defaults/Cargo.toml index 910ae2afd6..09a95fa839 100644 --- a/nexus/defaults/Cargo.toml +++ b/nexus/defaults/Cargo.toml @@ -11,3 +11,4 @@ rand.workspace = true serde_json.workspace = true omicron-common.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } diff --git a/nexus/test-interface/Cargo.toml b/nexus/test-interface/Cargo.toml index 44c894411b..e0743e84bc 100644 --- a/nexus/test-interface/Cargo.toml +++ b/nexus/test-interface/Cargo.toml @@ -12,3 +12,4 @@ nexus-types.workspace = true omicron-common.workspace = true slog.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } diff --git a/nexus/test-utils-macros/Cargo.toml b/nexus/test-utils-macros/Cargo.toml index 4f9d3eca32..1bfa25017a 100644 --- a/nexus/test-utils-macros/Cargo.toml +++ b/nexus/test-utils-macros/Cargo.toml @@ -11,3 +11,4 @@ proc-macro = true proc-macro2.workspace = true quote.workspace = true syn = { workspace = true, features = [ "fold", "parsing" ] } +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } diff --git a/nexus/test-utils/Cargo.toml b/nexus/test-utils/Cargo.toml index bad225516a..a2e7600e93 100644 --- a/nexus/test-utils/Cargo.toml +++ b/nexus/test-utils/Cargo.toml @@ -38,3 +38,4 @@ tempfile.workspace = true trust-dns-proto.workspace = true trust-dns-resolver.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } diff --git a/nexus/types/Cargo.toml b/nexus/types/Cargo.toml index c0f175cf31..f7ffafec52 100644 --- a/nexus/types/Cargo.toml +++ b/nexus/types/Cargo.toml @@ -25,3 +25,4 @@ api_identity.workspace = true dns-service-client.workspace = true omicron-common.workspace = true omicron-passwords.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } diff --git a/openapi/wicketd.json b/openapi/wicketd.json index 40d798da00..d67fc79f7a 100644 --- a/openapi/wicketd.json +++ b/openapi/wicketd.json @@ -598,6 +598,33 @@ } } }, + "/update": { + "post": { + "summary": "An endpoint to start updating one or more sleds, switches and PSCs.", + "operationId": "post_start_update", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StartUpdateParams" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/update/{type}/{slot}": { "get": { "summary": "An endpoint to get the status of any update being performed or recently", @@ -641,51 +668,6 @@ "$ref": "#/components/responses/Error" } } - }, - "post": { - "summary": "An endpoint to start updating a sled.", - "operationId": "post_start_update", - "parameters": [ - { - "in": "path", - "name": "slot", - "required": true, - "schema": { - "type": "integer", - "format": "uint32", - "minimum": 0 - } - }, - { - "in": "path", - "name": "type", - "required": true, - "schema": { - "$ref": "#/components/schemas/SpType" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StartUpdateOptions" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } } } }, @@ -2761,6 +2743,31 @@ "skip_sp_version_check" ] }, + "StartUpdateParams": { + "type": "object", + "properties": { + "options": { + "description": "Options for the update.", + "allOf": [ + { + "$ref": "#/components/schemas/StartUpdateOptions" + } + ] + }, + "targets": { + "description": "The SP identifiers to start the update with. Must be non-empty.", + "type": "array", + "items": { + "$ref": "#/components/schemas/SpIdentifier" + }, + "uniqueItems": true + } + }, + "required": [ + "options", + "targets" + ] + }, "StepComponentSummaryForGenericSpec": { "type": "object", "properties": { diff --git a/oxide-client/Cargo.toml b/oxide-client/Cargo.toml index 0602066e6d..df34ab9721 100644 --- a/oxide-client/Cargo.toml +++ b/oxide-client/Cargo.toml @@ -21,3 +21,4 @@ thiserror.workspace = true tokio = { workspace = true, features = [ "net" ] } trust-dns-resolver.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/oximeter-client/Cargo.toml b/oximeter-client/Cargo.toml index e4e68464d7..297dfb6c92 100644 --- a/oximeter-client/Cargo.toml +++ b/oximeter-client/Cargo.toml @@ -12,3 +12,4 @@ reqwest = { workspace = true, features = ["json", "rustls-tls", "stream"] } serde.workspace = true slog.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/oximeter/collector/Cargo.toml b/oximeter/collector/Cargo.toml index 1137651aa0..c8c4030dba 100644 --- a/oximeter/collector/Cargo.toml +++ b/oximeter/collector/Cargo.toml @@ -22,6 +22,7 @@ thiserror.workspace = true tokio.workspace = true toml.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } [dev-dependencies] expectorate.workspace = true diff --git a/oximeter/db/Cargo.toml b/oximeter/db/Cargo.toml index 9ff4ac5c06..77bce09db9 100644 --- a/oximeter/db/Cargo.toml +++ b/oximeter/db/Cargo.toml @@ -25,6 +25,7 @@ thiserror.workspace = true tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } usdt.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } [dev-dependencies] itertools.workspace = true diff --git a/oximeter/instruments/Cargo.toml b/oximeter/instruments/Cargo.toml index 98f8f3b5b2..4adff0463a 100644 --- a/oximeter/instruments/Cargo.toml +++ b/oximeter/instruments/Cargo.toml @@ -12,6 +12,7 @@ oximeter.workspace = true tokio.workspace = true http = { workspace = true, optional = true } uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } [features] default = ["http-instruments"] diff --git a/oximeter/oximeter-macro-impl/Cargo.toml b/oximeter/oximeter-macro-impl/Cargo.toml index c38d85ed2d..ff116e1c9d 100644 --- a/oximeter/oximeter-macro-impl/Cargo.toml +++ b/oximeter/oximeter-macro-impl/Cargo.toml @@ -12,3 +12,4 @@ proc-macro = true proc-macro2.workspace = true quote.workspace = true syn = { workspace = true, features = [ "full", "extra-traits" ] } +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } diff --git a/oximeter/oximeter/Cargo.toml b/oximeter/oximeter/Cargo.toml index f0549548a6..b2aa15f85e 100644 --- a/oximeter/oximeter/Cargo.toml +++ b/oximeter/oximeter/Cargo.toml @@ -15,6 +15,7 @@ schemars = { workspace = true, features = [ "uuid1", "bytes", "chrono" ] } serde.workspace = true thiserror.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } [dev-dependencies] approx.workspace = true diff --git a/oximeter/producer/Cargo.toml b/oximeter/producer/Cargo.toml index e511294e52..f171f57e8a 100644 --- a/oximeter/producer/Cargo.toml +++ b/oximeter/producer/Cargo.toml @@ -19,3 +19,4 @@ slog-dtrace.workspace = true tokio.workspace = true thiserror.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../../workspace-hack" } diff --git a/package/Cargo.toml b/package/Cargo.toml index 7c786b77ef..9fc4610020 100644 --- a/package/Cargo.toml +++ b/package/Cargo.toml @@ -34,6 +34,7 @@ tokio = { workspace = true, features = [ "full" ] } toml.workspace = true topological-sort.workspace = true walkdir.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] expectorate.workspace = true diff --git a/passwords/Cargo.toml b/passwords/Cargo.toml index 1731716101..cbd569ef4c 100644 --- a/passwords/Cargo.toml +++ b/passwords/Cargo.toml @@ -11,6 +11,7 @@ thiserror.workspace = true schemars.workspace = true serde.workspace = true serde_with.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] argon2alt = { package = "rust-argon2", version = "1.0" } diff --git a/rpaths/Cargo.toml b/rpaths/Cargo.toml index 829b4ffe28..7671be4968 100644 --- a/rpaths/Cargo.toml +++ b/rpaths/Cargo.toml @@ -5,3 +5,4 @@ edition = "2021" license = "MPL-2.0" [dependencies] +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 77379db470..eacfef491b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -4,5 +4,5 @@ # # We choose a specific toolchain (rather than "stable") for repeatability. The # intent is to keep this up-to-date with recently-released stable Rust. -channel = "1.72.0" +channel = "1.72.1" profile = "default" diff --git a/sled-agent-client/Cargo.toml b/sled-agent-client/Cargo.toml index f6c58fbf2b..01c1032a51 100644 --- a/sled-agent-client/Cargo.toml +++ b/sled-agent-client/Cargo.toml @@ -15,3 +15,4 @@ reqwest = { workspace = true, features = [ "json", "rustls-tls", "stream" ] } serde.workspace = true slog.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/sled-agent/Cargo.toml b/sled-agent/Cargo.toml index f172136726..b131698395 100644 --- a/sled-agent/Cargo.toml +++ b/sled-agent/Cargo.toml @@ -76,6 +76,7 @@ uuid.workspace = true zeroize.workspace = true zone.workspace = true static_assertions.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [target.'cfg(target_os = "illumos")'.dependencies] opte-ioctl.workspace = true diff --git a/sled-hardware/Cargo.toml b/sled-hardware/Cargo.toml index c6bc09f41e..880f93441c 100644 --- a/sled-hardware/Cargo.toml +++ b/sled-hardware/Cargo.toml @@ -24,6 +24,7 @@ thiserror.workspace = true tofino.workspace = true tokio.workspace = true uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [target.'cfg(target_os = "illumos")'.dependencies] illumos-devinfo = { git = "https://github.com/oxidecomputer/illumos-devinfo", branch = "main" } diff --git a/sp-sim/Cargo.toml b/sp-sim/Cargo.toml index 5a73f46d9e..2a1ae19468 100644 --- a/sp-sim/Cargo.toml +++ b/sp-sim/Cargo.toml @@ -21,6 +21,7 @@ sprockets-rot.workspace = true thiserror.workspace = true tokio = { workspace = true, features = [ "full" ] } toml.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [[bin]] name = "sp-sim" diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 09ff12a806..a0227a4de2 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -25,6 +25,7 @@ usdt.workspace = true rcgen.workspace = true regex.workspace = true reqwest.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] expectorate.workspace = true diff --git a/tufaceous-lib/Cargo.toml b/tufaceous-lib/Cargo.toml index e4799a69a4..8b5c4fa7ca 100644 --- a/tufaceous-lib/Cargo.toml +++ b/tufaceous-lib/Cargo.toml @@ -32,6 +32,7 @@ toml.workspace = true tough.workspace = true url = "2.4.1" zip.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] omicron-test-utils.workspace = true diff --git a/tufaceous/Cargo.toml b/tufaceous/Cargo.toml index 09772daef4..f3e3b815d2 100644 --- a/tufaceous/Cargo.toml +++ b/tufaceous/Cargo.toml @@ -18,6 +18,7 @@ slog-async.workspace = true slog-envlogger.workspace = true slog-term.workspace = true tufaceous-lib.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] assert_cmd.workspace = true diff --git a/update-engine/Cargo.toml b/update-engine/Cargo.toml index 4c2841cf0f..25ade83f34 100644 --- a/update-engine/Cargo.toml +++ b/update-engine/Cargo.toml @@ -21,6 +21,7 @@ schemars = { workspace = true, features = ["uuid1"] } slog.workspace = true tokio = { workspace = true, features = ["macros", "sync", "time", "rt-multi-thread"] } uuid.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] buf-list.workspace = true diff --git a/wicket-common/Cargo.toml b/wicket-common/Cargo.toml index 735f4a758e..229561cd38 100644 --- a/wicket-common/Cargo.toml +++ b/wicket-common/Cargo.toml @@ -13,3 +13,4 @@ serde.workspace = true serde_json.workspace = true thiserror.workspace = true update-engine.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/wicket-dbg/Cargo.toml b/wicket-dbg/Cargo.toml index 1aa8e10171..bc22424c69 100644 --- a/wicket-dbg/Cargo.toml +++ b/wicket-dbg/Cargo.toml @@ -22,6 +22,7 @@ wicket.workspace = true # used only by wicket-dbg binary reedline = "0.23.0" +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [[bin]] name = "wicket-dbg" diff --git a/wicket/Cargo.toml b/wicket/Cargo.toml index d2004e0a68..58605c8037 100644 --- a/wicket/Cargo.toml +++ b/wicket/Cargo.toml @@ -46,6 +46,7 @@ omicron-passwords.workspace = true update-engine.workspace = true wicket-common.workspace = true wicketd-client.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [dev-dependencies] assert_cmd.workspace = true diff --git a/wicket/src/wicketd.rs b/wicket/src/wicketd.rs index 160bcb1c6a..2411542429 100644 --- a/wicket/src/wicketd.rs +++ b/wicket/src/wicketd.rs @@ -12,7 +12,7 @@ use tokio::time::{interval, Duration, MissedTickBehavior}; use wicketd_client::types::{ AbortUpdateOptions, ClearUpdateStateOptions, GetInventoryParams, GetInventoryResponse, GetLocationResponse, IgnitionCommand, SpIdentifier, - SpType, StartUpdateOptions, + SpType, StartUpdateOptions, StartUpdateParams, }; use crate::events::EventReportMap; @@ -164,10 +164,11 @@ impl WicketdManager { tokio::spawn(async move { let update_client = create_wicketd_client(&log, addr, WICKETD_TIMEOUT); - let sp: SpIdentifier = component_id.into(); - let response = match update_client - .post_start_update(sp.type_, sp.slot, &options) - .await + let params = StartUpdateParams { + targets: vec![component_id.into()], + options, + }; + let response = match update_client.post_start_update(¶ms).await { Ok(_) => Ok(()), Err(error) => Err(error.to_string()), diff --git a/wicketd-client/Cargo.toml b/wicketd-client/Cargo.toml index 69a7f8fae4..2d959f1f8d 100644 --- a/wicketd-client/Cargo.toml +++ b/wicketd-client/Cargo.toml @@ -18,3 +18,4 @@ slog.workspace = true update-engine.workspace = true uuid.workspace = true wicket-common.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } diff --git a/wicketd/Cargo.toml b/wicketd/Cargo.toml index 8f4faf6c40..6df5e0e4e5 100644 --- a/wicketd/Cargo.toml +++ b/wicketd/Cargo.toml @@ -24,6 +24,7 @@ hubtools.workspace = true http.workspace = true hyper.workspace = true illumos-utils.workspace = true +itertools.workspace = true reqwest.workspace = true schemars.workspace = true serde.workspace = true @@ -53,6 +54,7 @@ sled-hardware.workspace = true tufaceous-lib.workspace = true update-engine.workspace = true wicket-common.workspace = true +omicron-workspace-hack = { version = "0.1", path = "../workspace-hack" } [[bin]] name = "wicketd" diff --git a/wicketd/src/helpers.rs b/wicketd/src/helpers.rs new file mode 100644 index 0000000000..a8b47d4f12 --- /dev/null +++ b/wicketd/src/helpers.rs @@ -0,0 +1,41 @@ +// 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/. + +//! Helpers and utility functions for wicketd. + +use std::fmt; + +use gateway_client::types::{SpIdentifier, SpType}; +use itertools::Itertools; + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub(crate) struct SpIdentifierDisplay(pub(crate) SpIdentifier); + +impl From for SpIdentifierDisplay { + fn from(id: SpIdentifier) -> Self { + SpIdentifierDisplay(id) + } +} + +impl<'a> From<&'a SpIdentifier> for SpIdentifierDisplay { + fn from(id: &'a SpIdentifier) -> Self { + SpIdentifierDisplay(*id) + } +} + +impl fmt::Display for SpIdentifierDisplay { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0.type_ { + SpType::Sled => write!(f, "sled {}", self.0.slot), + SpType::Switch => write!(f, "switch {}", self.0.slot), + SpType::Power => write!(f, "PSC {}", self.0.slot), + } + } +} + +pub(crate) fn sps_to_string>( + sps: impl IntoIterator, +) -> String { + sps.into_iter().map_into().join(", ") +} diff --git a/wicketd/src/http_entrypoints.rs b/wicketd/src/http_entrypoints.rs index 98cac8dc5d..72c3341334 100644 --- a/wicketd/src/http_entrypoints.rs +++ b/wicketd/src/http_entrypoints.rs @@ -4,6 +4,8 @@ //! HTTP entrypoint functions for wicketd +use crate::helpers::sps_to_string; +use crate::helpers::SpIdentifierDisplay; use crate::mgs::GetInventoryError; use crate::mgs::GetInventoryResponse; use crate::mgs::MgsHandle; @@ -44,7 +46,6 @@ use std::net::IpAddr; use std::net::Ipv6Addr; use std::time::Duration; use tokio::io::AsyncWriteExt; -use uuid::Uuid; use wicket_common::rack_setup::PutRssUserConfigInsensitive; use wicket_common::update_events::EventReport; @@ -652,6 +653,15 @@ async fn get_artifacts_and_event_reports( Ok(HttpResponseOk(response)) } +#[derive(Clone, Debug, JsonSchema, Deserialize)] +pub(crate) struct StartUpdateParams { + /// The SP identifiers to start the update with. Must be non-empty. + pub(crate) targets: BTreeSet, + + /// Options for the update. + pub(crate) options: StartUpdateOptions, +} + #[derive(Clone, Debug, JsonSchema, Deserialize)] pub(crate) struct StartUpdateOptions { /// If passed in, fails the update with a simulated error. @@ -730,19 +740,24 @@ impl UpdateTestError { log: &slog::Logger, reason: &str, ) -> HttpError { + let message = self.into_error_string(log, reason).await; + HttpError::for_bad_request(None, message) + } + + pub(crate) async fn into_error_string( + self, + log: &slog::Logger, + reason: &str, + ) -> String { match self { - UpdateTestError::Fail => HttpError::for_bad_request( - None, - format!("Simulated failure while {reason}"), - ), + UpdateTestError::Fail => { + format!("Simulated failure while {reason}") + } UpdateTestError::Timeout { secs } => { slog::info!(log, "Simulating timeout while {reason}"); // 15 seconds should be enough to cause a timeout. tokio::time::sleep(Duration::from_secs(secs)).await; - HttpError::for_bad_request( - None, - "XXX request should time out before this is hit".into(), - ) + "XXX request should time out before this is hit".into() } } } @@ -834,21 +849,27 @@ async fn get_location( })) } -/// An endpoint to start updating a sled. +/// An endpoint to start updating one or more sleds, switches and PSCs. #[endpoint { method = POST, - path = "/update/{type}/{slot}", + path = "/update", }] async fn post_start_update( rqctx: RequestContext, - target: Path, - opts: TypedBody, + params: TypedBody, ) -> Result { let log = &rqctx.log; let rqctx = rqctx.context(); - let target = target.into_inner(); + let params = params.into_inner(); + + if params.targets.is_empty() { + return Err(HttpError::for_bad_request( + None, + "No update targets specified".into(), + )); + } - // Can we update the target SP? We refuse to update if: + // Can we update the target SPs? We refuse to update if, for any target SP: // // 1. We haven't pulled its state in our inventory (most likely cause: the // cubby is empty; less likely cause: the SP is misbehaving, which will @@ -870,70 +891,136 @@ async fn post_start_update( } }; - // Next, do we have the state of the target SP? - let sp_state = match inventory { + // Error cases. + let mut inventory_absent = BTreeSet::new(); + let mut self_update = None; + let mut maybe_self_update = BTreeSet::new(); + + // Next, do we have the states of the target SP? + let sp_states = match inventory { GetInventoryResponse::Response { inventory, .. } => inventory .sps .into_iter() - .filter_map(|sp| if sp.id == target { sp.state } else { None }) - .next(), - GetInventoryResponse::Unavailable => None, - }; - let Some(sp_state) = sp_state else { - return Err(HttpError::for_bad_request( - None, - "cannot update target sled (no inventory state present)".into(), - )); + .filter_map(|sp| { + if params.targets.contains(&sp.id) { + if let Some(sp_state) = sp.state { + Some((sp.id, sp_state)) + } else { + None + } + } else { + None + } + }) + .collect(), + GetInventoryResponse::Unavailable => BTreeMap::new(), }; - // If we have the state of the SP, are we allowed to update it? We - // refuse to try to update our own sled. - match &rqctx.baseboard { - Some(baseboard) => { - if baseboard.identifier() == sp_state.serial_number - && baseboard.model() == sp_state.model - && baseboard.revision() == i64::from(sp_state.revision) - { - return Err(HttpError::for_bad_request( - None, - "cannot update sled where wicketd is running".into(), - )); + for target in ¶ms.targets { + let sp_state = match sp_states.get(target) { + Some(sp_state) => sp_state, + None => { + // The state isn't present, so add to inventory_absent. + inventory_absent.insert(*target); + continue; } - } - None => { - // We don't know our own baseboard, which is a very - // questionable state to be in! For now, we will hard-code - // the possibly locations where we could be running: - // scrimlets can only be in cubbies 14 or 16, so we refuse - // to update either of those. - let target_is_scrimlet = - matches!((target.type_, target.slot), (SpType::Sled, 14 | 16)); - if target_is_scrimlet { - return Err(HttpError::for_bad_request( - None, - "wicketd does not know its own baseboard details: \ - refusing to update either scrimlet" - .into(), - )); + }; + + // If we have the state of the SP, are we allowed to update it? We + // refuse to try to update our own sled. + match &rqctx.baseboard { + Some(baseboard) => { + if baseboard.identifier() == sp_state.serial_number + && baseboard.model() == sp_state.model + && baseboard.revision() == i64::from(sp_state.revision) + { + self_update = Some(*target); + continue; + } + } + None => { + // We don't know our own baseboard, which is a very questionable + // state to be in! For now, we will hard-code the possibly + // locations where we could be running: scrimlets can only be in + // cubbies 14 or 16, so we refuse to update either of those. + let target_is_scrimlet = matches!( + (target.type_, target.slot), + (SpType::Sled, 14 | 16) + ); + if target_is_scrimlet { + maybe_self_update.insert(*target); + continue; + } } } } - let opts = opts.into_inner(); - if let Some(test_error) = opts.test_error { - return Err(test_error.into_http_error(log, "starting update").await); + // Do we have any errors? + let mut errors = Vec::new(); + if !inventory_absent.is_empty() { + errors.push(format!( + "cannot update sleds (no inventory state present for {})", + sps_to_string(&inventory_absent) + )); + } + if let Some(self_update) = self_update { + errors.push(format!( + "cannot update sled where wicketd is running ({})", + SpIdentifierDisplay(self_update) + )); + } + if !maybe_self_update.is_empty() { + errors.push(format!( + "wicketd does not know its own baseboard details: \ + refusing to update either scrimlet ({})", + sps_to_string(&inventory_absent) + )); } - // All pre-flight update checks look OK: start the update. - // - // Generate an ID for this update; the update tracker will send it to the - // sled as part of the InstallinatorImageId, and installinator will send it - // back to our artifact server with its progress reports. - let update_id = Uuid::new_v4(); + if let Some(test_error) = ¶ms.options.test_error { + errors.push(test_error.into_error_string(log, "starting update").await); + } - match rqctx.update_tracker.start(target, update_id, opts).await { - Ok(()) => Ok(HttpResponseUpdatedNoContent {}), - Err(err) => Err(err.to_http_error()), + let start_update_errors = if errors.is_empty() { + // No errors: we can try and proceed with this update. + match rqctx.update_tracker.start(params.targets, params.options).await { + Ok(()) => return Ok(HttpResponseUpdatedNoContent {}), + Err(errors) => errors, + } + } else { + // We've already found errors, so all we want to do is to check whether + // the update tracker thinks there are any errors as well. + match rqctx.update_tracker.update_pre_checks(params.targets).await { + Ok(()) => Vec::new(), + Err(errors) => errors, + } + }; + + errors.extend(start_update_errors.iter().map(|error| error.to_string())); + + // If we get here, we have errors to report. + + match errors.len() { + 0 => { + unreachable!( + "we already returned Ok(_) above if there were no errors" + ) + } + 1 => { + return Err(HttpError::for_bad_request( + None, + errors.pop().unwrap(), + )); + } + _ => { + return Err(HttpError::for_bad_request( + None, + format!( + "multiple errors encountered:\n - {}", + itertools::join(errors, "\n - ") + ), + )); + } } } diff --git a/wicketd/src/lib.rs b/wicketd/src/lib.rs index 78209ea04a..e17c15642c 100644 --- a/wicketd/src/lib.rs +++ b/wicketd/src/lib.rs @@ -6,6 +6,7 @@ mod artifacts; mod bootstrap_addrs; mod config; mod context; +mod helpers; mod http_entrypoints; mod installinator_progress; mod inventory; diff --git a/wicketd/src/update_tracker.rs b/wicketd/src/update_tracker.rs index a95a98bd72..1bbda00158 100644 --- a/wicketd/src/update_tracker.rs +++ b/wicketd/src/update_tracker.rs @@ -7,6 +7,7 @@ use crate::artifacts::ArtifactIdData; use crate::artifacts::UpdatePlan; use crate::artifacts::WicketdArtifactStore; +use crate::helpers::sps_to_string; use crate::http_entrypoints::GetArtifactsAndEventReportsResponse; use crate::http_entrypoints::StartUpdateOptions; use crate::http_entrypoints::UpdateSimulatedResult; @@ -19,7 +20,6 @@ use anyhow::ensure; use anyhow::Context; use display_error_chain::DisplayErrorChain; use dropshot::HttpError; -use futures::Future; use gateway_client::types::HostPhase2Progress; use gateway_client::types::HostPhase2RecoveryImageId; use gateway_client::types::HostStartupOptions; @@ -156,146 +156,23 @@ impl UpdateTracker { pub(crate) async fn start( &self, - sp: SpIdentifier, - update_id: Uuid, + sps: BTreeSet, opts: StartUpdateOptions, - ) -> Result<(), StartUpdateError> { - self.start_impl(sp, |plan| async { - // Do we need to upload this plan's trampoline phase 2 to MGS? - let upload_trampoline_phase_2_to_mgs = { - let mut upload_trampoline_phase_2_to_mgs = - self.upload_trampoline_phase_2_to_mgs.lock().await; - - match upload_trampoline_phase_2_to_mgs.as_mut() { - Some(prev) => { - // We've previously started an upload - does it match - // this artifact? If not, cancel the old task (which - // might still be trying to upload) and start a new one - // with our current image. - if prev.status.borrow().hash - != plan.trampoline_phase_2.data.hash() - { - // It does _not_ match - we have a new plan with a - // different trampoline image. If the old task is - // still running, cancel it, and start a new one. - prev.task.abort(); - *prev = self - .spawn_upload_trampoline_phase_2_to_mgs(&plan); - } - } - None => { - *upload_trampoline_phase_2_to_mgs = Some( - self.spawn_upload_trampoline_phase_2_to_mgs(&plan), - ); - } - } - - // Both branches above leave `upload_trampoline_phase_2_to_mgs` - // with data, so we can unwrap here to clone the `watch` - // channel. - upload_trampoline_phase_2_to_mgs - .as_ref() - .unwrap() - .status - .clone() - }; - - let event_buffer = Arc::new(StdMutex::new(EventBuffer::new(16))); - let ipr_start_receiver = - self.ipr_update_tracker.register(update_id); - - let update_cx = UpdateContext { - update_id, - sp, - mgs_client: self.mgs_client.clone(), - upload_trampoline_phase_2_to_mgs, - log: self.log.new(o!( - "sp" => format!("{sp:?}"), - "update_id" => update_id.to_string(), - )), - }; - // TODO do we need `UpdateDriver` as a distinct type? - let update_driver = UpdateDriver {}; - - // Using a oneshot channel to communicate the abort handle isn't - // ideal, but it works and is the easiest way to send it without - // restructuring this code. - let (abort_handle_sender, abort_handle_receiver) = - oneshot::channel(); - let task = tokio::spawn(update_driver.run( - plan, - update_cx, - event_buffer.clone(), - ipr_start_receiver, - opts, - abort_handle_sender, - )); - - let abort_handle = abort_handle_receiver - .await - .expect("abort handle is sent immediately"); - - SpUpdateData { task, abort_handle, event_buffer } - }) - .await + ) -> Result<(), Vec> { + let imp = RealSpawnUpdateDriver { update_tracker: self, opts }; + self.start_impl(sps, Some(imp)).await } /// Starts a fake update that doesn't perform any steps, but simply waits - /// for a oneshot receiver to resolve. + /// for a watch receiver to resolve. #[doc(hidden)] pub async fn start_fake_update( &self, - sp: SpIdentifier, - oneshot_receiver: oneshot::Receiver<()>, - ) -> Result<(), StartUpdateError> { - self.start_impl(sp, |_plan| async move { - let (sender, mut receiver) = mpsc::channel(128); - let event_buffer = Arc::new(StdMutex::new(EventBuffer::new(16))); - let event_buffer_2 = event_buffer.clone(); - let log = self.log.clone(); - - let engine = UpdateEngine::new(&log, sender); - let abort_handle = engine.abort_handle(); - - let task = tokio::spawn(async move { - // The step component and ID have been chosen arbitrarily here -- - // they aren't important. - engine - .new_step( - UpdateComponent::Host, - UpdateStepId::RunningInstallinator, - "Fake step that waits for receiver to resolve", - move |_cx| async move { - _ = oneshot_receiver.await; - StepSuccess::new(()).into() - }, - ) - .register(); - - // Spawn a task to accept all events from the executing engine. - let event_receiving_task = tokio::spawn(async move { - while let Some(event) = receiver.recv().await { - event_buffer_2.lock().unwrap().add_event(event); - } - }); - - match engine.execute().await { - Ok(_cx) => (), - Err(err) => { - error!(log, "update failed"; "err" => %err); - } - } - - // Wait for all events to be received and written to the event - // buffer. - event_receiving_task - .await - .expect("event receiving task panicked"); - }); - - SpUpdateData { task, abort_handle, event_buffer } - }) - .await + sps: BTreeSet, + watch_receiver: watch::Receiver<()>, + ) -> Result<(), Vec> { + let imp = FakeUpdateDriver { watch_receiver, log: self.log.clone() }; + self.start_impl(sps, Some(imp)).await } pub(crate) async fn clear_update_state( @@ -315,40 +192,107 @@ impl UpdateTracker { update_data.abort_update(sp, message).await } - async fn start_impl( + /// Checks whether an update can be started for the given SPs, without + /// actually starting it. + /// + /// This should only be used in situations where starting the update is not + /// desired (for example, if we've already encountered errors earlier in the + /// process and we're just adding to the list of errors). In cases where the + /// start method *is* desired, prefer the [`Self::start`] method, which also + /// performs the same checks. + pub(crate) async fn update_pre_checks( &self, - sp: SpIdentifier, - spawn_update_driver: F, - ) -> Result<(), StartUpdateError> + sps: BTreeSet, + ) -> Result<(), Vec> { + self.start_impl::(sps, None).await + } + + async fn start_impl( + &self, + sps: BTreeSet, + spawn_update_driver: Option, + ) -> Result<(), Vec> where - F: FnOnce(UpdatePlan) -> Fut, - Fut: Future + Send, + Spawn: SpawnUpdateDriver, { let mut update_data = self.sp_update_data.lock().await; - let plan = update_data - .artifact_store - .current_plan() - .ok_or(StartUpdateError::TufRepositoryUnavailable)?; + let mut errors = Vec::new(); - match update_data.sp_update_data.entry(sp) { - // Vacant: this is the first time we've started an update to this - // sp. - Entry::Vacant(slot) => { - slot.insert(spawn_update_driver(plan).await); - Ok(()) - } - // Occupied: we've previously started an update to this sp; only - // allow this one if that update is no longer running. - Entry::Occupied(mut slot) => { - if slot.get().task.is_finished() { - slot.insert(spawn_update_driver(plan).await); - Ok(()) - } else { - Err(StartUpdateError::UpdateInProgress(sp)) + // Check that we're not already updating any of these SPs. + let update_in_progress: Vec<_> = sps + .iter() + .filter(|sp| { + // If we don't have any update data for this SP, it's not in + // progress. + // + // If we do, it's in progress if the task is not finished. + update_data + .sp_update_data + .get(sp) + .map_or(false, |data| !data.task.is_finished()) + }) + .copied() + .collect(); + + if !update_in_progress.is_empty() { + errors.push(StartUpdateError::UpdateInProgress(update_in_progress)); + } + + let plan = update_data.artifact_store.current_plan(); + if plan.is_none() { + // (1), referred to below. + errors.push(StartUpdateError::TufRepositoryUnavailable); + } + + // If there are any errors, return now. + if !errors.is_empty() { + return Err(errors); + } + + let plan = + plan.expect("we'd have returned an error at (1) if plan was None"); + + // Call the setup method now. + if let Some(mut spawn_update_driver) = spawn_update_driver { + let setup_data = spawn_update_driver.setup(&plan).await; + + for sp in sps { + match update_data.sp_update_data.entry(sp) { + // Vacant: this is the first time we've started an update to this + // sp. + Entry::Vacant(slot) => { + slot.insert( + spawn_update_driver + .spawn_update_driver( + sp, + plan.clone(), + &setup_data, + ) + .await, + ); + } + // Occupied: we've previously started an update to this sp. + Entry::Occupied(mut slot) => { + assert!( + slot.get().task.is_finished(), + "we just checked that the task was finished" + ); + slot.insert( + spawn_update_driver + .spawn_update_driver( + sp, + plan.clone(), + &setup_data, + ) + .await, + ); + } } } } + + Ok(()) } fn spawn_upload_trampoline_phase_2_to_mgs( @@ -425,6 +369,226 @@ impl UpdateTracker { } } +/// A trait that represents a backend implementation for spawning the update +/// driver. +#[async_trait::async_trait] +trait SpawnUpdateDriver { + /// The type returned by the [`Self::setup`] method. This is passed in by + /// reference to [`Self::spawn_update_driver`]. + type Setup; + + /// Perform setup required to spawn the update driver. + /// + /// This is called *once*, before any calls to + /// [`Self::spawn_update_driver`]. + async fn setup(&mut self, plan: &UpdatePlan) -> Self::Setup; + + /// Spawn the update driver for the given SP. + /// + /// This is called once per SP. + async fn spawn_update_driver( + &mut self, + sp: SpIdentifier, + plan: UpdatePlan, + setup_data: &Self::Setup, + ) -> SpUpdateData; +} + +/// The production implementation of [`SpawnUpdateDriver`]. +/// +/// This implementation spawns real update drivers. +#[derive(Debug)] +struct RealSpawnUpdateDriver<'tr> { + update_tracker: &'tr UpdateTracker, + opts: StartUpdateOptions, +} + +#[async_trait::async_trait] +impl<'tr> SpawnUpdateDriver for RealSpawnUpdateDriver<'tr> { + type Setup = watch::Receiver; + + async fn setup(&mut self, plan: &UpdatePlan) -> Self::Setup { + // Do we need to upload this plan's trampoline phase 2 to MGS? + + let mut upload_trampoline_phase_2_to_mgs = + self.update_tracker.upload_trampoline_phase_2_to_mgs.lock().await; + + match upload_trampoline_phase_2_to_mgs.as_mut() { + Some(prev) => { + // We've previously started an upload - does it match + // this artifact? If not, cancel the old task (which + // might still be trying to upload) and start a new one + // with our current image. + if prev.status.borrow().hash + != plan.trampoline_phase_2.data.hash() + { + // It does _not_ match - we have a new plan with a + // different trampoline image. If the old task is + // still running, cancel it, and start a new one. + prev.task.abort(); + *prev = self + .update_tracker + .spawn_upload_trampoline_phase_2_to_mgs(&plan); + } + } + None => { + *upload_trampoline_phase_2_to_mgs = Some( + self.update_tracker + .spawn_upload_trampoline_phase_2_to_mgs(&plan), + ); + } + } + + // Both branches above leave `upload_trampoline_phase_2_to_mgs` + // with data, so we can unwrap here to clone the `watch` + // channel. + upload_trampoline_phase_2_to_mgs.as_ref().unwrap().status.clone() + } + + async fn spawn_update_driver( + &mut self, + sp: SpIdentifier, + plan: UpdatePlan, + setup_data: &Self::Setup, + ) -> SpUpdateData { + // Generate an ID for this update; the update tracker will send it to the + // sled as part of the InstallinatorImageId, and installinator will send it + // back to our artifact server with its progress reports. + let update_id = Uuid::new_v4(); + + let event_buffer = Arc::new(StdMutex::new(EventBuffer::new(16))); + let ipr_start_receiver = + self.update_tracker.ipr_update_tracker.register(update_id); + + let update_cx = UpdateContext { + update_id, + sp, + mgs_client: self.update_tracker.mgs_client.clone(), + upload_trampoline_phase_2_to_mgs: setup_data.clone(), + log: self.update_tracker.log.new(o!( + "sp" => format!("{sp:?}"), + "update_id" => update_id.to_string(), + )), + }; + // TODO do we need `UpdateDriver` as a distinct type? + let update_driver = UpdateDriver {}; + + // Using a oneshot channel to communicate the abort handle isn't + // ideal, but it works and is the easiest way to send it without + // restructuring this code. + let (abort_handle_sender, abort_handle_receiver) = oneshot::channel(); + let task = tokio::spawn(update_driver.run( + plan, + update_cx, + event_buffer.clone(), + ipr_start_receiver, + self.opts.clone(), + abort_handle_sender, + )); + + let abort_handle = abort_handle_receiver + .await + .expect("abort handle is sent immediately"); + + SpUpdateData { task, abort_handle, event_buffer } + } +} + +/// A fake implementation of [`SpawnUpdateDriver`]. +/// +/// This implementation is only used by tests. It contains a single step that +/// waits for a [`watch::Receiver`] to resolve. +#[derive(Debug)] +struct FakeUpdateDriver { + watch_receiver: watch::Receiver<()>, + log: Logger, +} + +#[async_trait::async_trait] +impl SpawnUpdateDriver for FakeUpdateDriver { + type Setup = (); + + async fn setup(&mut self, _plan: &UpdatePlan) -> Self::Setup {} + + async fn spawn_update_driver( + &mut self, + _sp: SpIdentifier, + _plan: UpdatePlan, + _setup_data: &Self::Setup, + ) -> SpUpdateData { + let (sender, mut receiver) = mpsc::channel(128); + let event_buffer = Arc::new(StdMutex::new(EventBuffer::new(16))); + let event_buffer_2 = event_buffer.clone(); + let log = self.log.clone(); + + let engine = UpdateEngine::new(&log, sender); + let abort_handle = engine.abort_handle(); + + let mut watch_receiver = self.watch_receiver.clone(); + + let task = tokio::spawn(async move { + // The step component and ID have been chosen arbitrarily here -- + // they aren't important. + engine + .new_step( + UpdateComponent::Host, + UpdateStepId::RunningInstallinator, + "Fake step that waits for receiver to resolve", + move |_cx| async move { + // This will resolve as soon as the watch sender + // (typically a test) sends a value over the watch + // channel. + _ = watch_receiver.changed().await; + StepSuccess::new(()).into() + }, + ) + .register(); + + // Spawn a task to accept all events from the executing engine. + let event_receiving_task = tokio::spawn(async move { + while let Some(event) = receiver.recv().await { + event_buffer_2.lock().unwrap().add_event(event); + } + }); + + match engine.execute().await { + Ok(_cx) => (), + Err(err) => { + error!(log, "update failed"; "err" => %err); + } + } + + // Wait for all events to be received and written to the event + // buffer. + event_receiving_task.await.expect("event receiving task panicked"); + }); + + SpUpdateData { task, abort_handle, event_buffer } + } +} + +/// An implementation of [`SpawnUpdateDriver`] that cannot be constructed. +/// +/// This is an uninhabited type (an empty enum), and is only used to provide a +/// type parameter for the [`UpdateTracker::update_pre_checks`] method. +enum NeverUpdateDriver {} + +#[async_trait::async_trait] +impl SpawnUpdateDriver for NeverUpdateDriver { + type Setup = (); + + async fn setup(&mut self, _plan: &UpdatePlan) -> Self::Setup {} + + async fn spawn_update_driver( + &mut self, + _sp: SpIdentifier, + _plan: UpdatePlan, + _setup_data: &Self::Setup, + ) -> SpUpdateData { + unreachable!("this update driver cannot be constructed") + } +} + #[derive(Debug)] struct UpdateTrackerData { artifact_store: WicketdArtifactStore, @@ -518,21 +682,8 @@ impl UpdateTrackerData { pub enum StartUpdateError { #[error("no TUF repository available")] TufRepositoryUnavailable, - #[error("target is already being updated: {0:?}")] - UpdateInProgress(SpIdentifier), -} - -impl StartUpdateError { - pub(crate) fn to_http_error(&self) -> HttpError { - let message = DisplayErrorChain::new(self).to_string(); - - match self { - StartUpdateError::TufRepositoryUnavailable - | StartUpdateError::UpdateInProgress(_) => { - HttpError::for_bad_request(None, message) - } - } - } + #[error("targets are already being updated: {}", sps_to_string(.0))] + UpdateInProgress(Vec), } #[derive(Debug, Clone, Error, Eq, PartialEq)] diff --git a/wicketd/tests/integration_tests/updates.rs b/wicketd/tests/integration_tests/updates.rs index a4b330930a..a198068ef3 100644 --- a/wicketd/tests/integration_tests/updates.rs +++ b/wicketd/tests/integration_tests/updates.rs @@ -16,13 +16,13 @@ use omicron_common::{ api::internal::nexus::KnownArtifactKind, update::{ArtifactHashId, ArtifactKind}, }; -use tokio::sync::oneshot; +use tokio::sync::watch; use uuid::Uuid; use wicket_common::update_events::{StepEventKind, UpdateComponent}; use wicketd::{RunningUpdateState, StartUpdateError}; use wicketd_client::types::{ GetInventoryParams, GetInventoryResponse, SpIdentifier, SpType, - StartUpdateOptions, + StartUpdateOptions, StartUpdateParams, }; #[tokio::test] @@ -138,13 +138,11 @@ async fn test_updates() { } // Now, try starting the update on SP 0. + let options = StartUpdateOptions::default(); + let params = StartUpdateParams { targets: vec![target_sp], options }; wicketd_testctx .wicketd_client - .post_start_update( - target_sp.type_, - target_sp.slot, - &StartUpdateOptions::default(), - ) + .post_start_update(¶ms) .await .expect("update started successfully"); @@ -352,12 +350,13 @@ async fn test_update_races() { slot: 0, type_: gateway_client::types::SpType::Sled, }; + let sps: BTreeSet<_> = vec![sp].into_iter().collect(); - let (sender, receiver) = oneshot::channel(); + let (sender, receiver) = watch::channel(()); wicketd_testctx .server .update_tracker - .start_fake_update(sp, receiver) + .start_fake_update(sps.clone(), receiver) .await .expect("start_fake_update successful"); @@ -372,14 +371,18 @@ async fn test_update_races() { // Also try starting another fake update, which should fail -- we don't let // updates be started in the middle of other updates. { - let (_, receiver) = oneshot::channel(); + let (_, receiver) = watch::channel(()); let err = wicketd_testctx .server .update_tracker - .start_fake_update(sp, receiver) + .start_fake_update(sps, receiver) .await .expect_err("start_fake_update failed while update is running"); - assert_eq!(err, StartUpdateError::UpdateInProgress(sp)); + assert_eq!(err.len(), 1, "one error returned: {err:?}"); + assert_eq!( + err.first().unwrap(), + &StartUpdateError::UpdateInProgress(vec![sp]) + ); } // Unblock the update, letting it run to completion. diff --git a/workspace-hack/.gitattributes b/workspace-hack/.gitattributes new file mode 100644 index 0000000000..3e9dba4b64 --- /dev/null +++ b/workspace-hack/.gitattributes @@ -0,0 +1,4 @@ +# Avoid putting conflict markers in the generated Cargo.toml file, since their presence breaks +# Cargo. +# Also do not check out the file as CRLF on Windows, as that's what hakari needs. +Cargo.toml merge=binary -crlf diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml new file mode 100644 index 0000000000..d3e00b1831 --- /dev/null +++ b/workspace-hack/Cargo.toml @@ -0,0 +1,261 @@ +# This file is generated by `cargo hakari`. +# To regenerate, run: +# cargo hakari generate + +[package] +name = "omicron-workspace-hack" +version = "0.1.0" +description = "workspace-hack package, managed by hakari" +# You can choose to publish this crate: see https://docs.rs/cargo-hakari/latest/cargo_hakari/publishing. +publish = false + +# The parts of the file between the BEGIN HAKARI SECTION and END HAKARI SECTION comments +# are managed by hakari. + +### BEGIN HAKARI SECTION +[dependencies] +anyhow = { version = "1", features = ["backtrace"] } +bit-set = { version = "0.5" } +bit-vec = { version = "0.6" } +bitflags-dff4ba8e3ae991db = { package = "bitflags", version = "1" } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["serde"] } +bitvec = { version = "1" } +bstr-6f8ce4dd05d13bba = { package = "bstr", version = "0.2" } +bstr-dff4ba8e3ae991db = { package = "bstr", version = "1" } +bytes = { version = "1", features = ["serde"] } +chrono = { version = "0.4", features = ["alloc", "serde"] } +cipher = { version = "0.4", default-features = false, features = ["block-padding", "zeroize"] } +clap = { version = "4", features = ["derive", "env", "wrap_help"] } +clap_builder = { version = "4", default-features = false, features = ["color", "env", "std", "suggestions", "usage", "wrap_help"] } +console = { version = "0.15" } +const-oid = { version = "0.9", default-features = false, features = ["db", "std"] } +crossbeam-epoch = { version = "0.9" } +crossbeam-utils = { version = "0.8" } +crypto-common = { version = "0.1", default-features = false, features = ["getrandom", "std"] } +diesel = { version = "2", features = ["chrono", "i-implement-a-third-party-backend-and-opt-into-breaking-changes", "network-address", "postgres", "r2d2", "serde_json", "uuid"] } +digest = { version = "0.10", features = ["mac", "oid", "std"] } +either = { version = "1" } +flate2 = { version = "1" } +futures = { version = "0.3" } +futures-channel = { version = "0.3", features = ["sink"] } +futures-core = { version = "0.3" } +futures-io = { version = "0.3", default-features = false, features = ["std"] } +futures-sink = { version = "0.3" } +futures-task = { version = "0.3", default-features = false, features = ["std"] } +futures-util = { version = "0.3", features = ["channel", "io", "sink"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "1e180ae55e56bd17af35cb868ffbd18ce487351d", features = ["std"] } +generic-array = { version = "0.14", default-features = false, features = ["more_lengths", "zeroize"] } +getrandom = { version = "0.2", default-features = false, features = ["js", "rdrand", "std"] } +hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14", features = ["raw"] } +hashbrown-594e8ee84c453af0 = { package = "hashbrown", version = "0.13" } +hex = { version = "0.4", features = ["serde"] } +hyper = { version = "0.14", features = ["full"] } +indexmap = { version = "2", features = ["serde"] } +inout = { version = "0.1", default-features = false, features = ["std"] } +ipnetwork = { version = "0.20", features = ["schemars"] } +itertools = { version = "0.10" } +lalrpop-util = { version = "0.19" } +lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] } +libc = { version = "0.2", features = ["extra_traits"] } +log = { version = "0.4", default-features = false, features = ["std"] } +managed = { version = "0.8", default-features = false, features = ["alloc", "map"] } +memchr = { version = "2" } +num-bigint = { version = "0.4", features = ["rand"] } +num-integer = { version = "0.1", features = ["i128"] } +num-iter = { version = "0.1", default-features = false, features = ["i128"] } +num-traits = { version = "0.2", features = ["i128", "libm"] } +openapiv3 = { version = "1", default-features = false, features = ["skip_serializing_defaults"] } +petgraph = { version = "0.6", features = ["serde-1"] } +postgres-types = { version = "0.2", default-features = false, features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } +ppv-lite86 = { version = "0.2", default-features = false, features = ["simd", "std"] } +predicates = { version = "3" } +rand = { version = "0.8", features = ["min_const_gen"] } +rand_chacha = { version = "0.3" } +regex = { version = "1" } +regex-automata = { version = "0.3", default-features = false, features = ["dfa-onepass", "dfa-search", "hybrid", "meta", "nfa-backtrack", "perf-inline", "perf-literal", "unicode"] } +regex-syntax = { version = "0.7" } +reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls", "stream"] } +ring = { version = "0.16", features = ["std"] } +schemars = { version = "0.8", features = ["bytes", "chrono", "uuid1"] } +semver = { version = "1", features = ["serde"] } +serde = { version = "1", features = ["alloc", "derive", "rc"] } +sha2 = { version = "0.10", features = ["oid"] } +signature = { version = "2", default-features = false, features = ["digest", "rand_core", "std"] } +similar = { version = "2", features = ["inline", "unicode"] } +slog = { version = "2", features = ["dynamic-keys", "max_level_trace", "release_max_level_debug", "release_max_level_trace"] } +spin = { version = "0.9" } +string_cache = { version = "0.8" } +subtle = { version = "2" } +syn-dff4ba8e3ae991db = { package = "syn", version = "1", features = ["extra-traits", "fold", "full", "visit"] } +syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } +textwrap = { version = "0.16" } +time = { version = "0.3", features = ["formatting", "local-offset", "macros", "parsing"] } +tokio = { version = "1", features = ["full", "test-util"] } +tokio-postgres = { version = "0.7", features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } +tokio-stream = { version = "0.1", features = ["net"] } +toml = { version = "0.7" } +toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } +toml_edit = { version = "0.19", features = ["serde"] } +tracing = { version = "0.1", features = ["log"] } +trust-dns-proto = { version = "0.22" } +unicode-bidi = { version = "0.3" } +unicode-normalization = { version = "0.1" } +usdt = { version = "0.3" } +uuid = { version = "1", features = ["serde", "v4"] } +yasna = { version = "0.5", features = ["bit-vec", "num-bigint", "std", "time"] } +zeroize = { version = "1", features = ["std", "zeroize_derive"] } +zip = { version = "0.6", default-features = false, features = ["bzip2", "deflate"] } + +[build-dependencies] +anyhow = { version = "1", features = ["backtrace"] } +bit-set = { version = "0.5" } +bit-vec = { version = "0.6" } +bitflags-dff4ba8e3ae991db = { package = "bitflags", version = "1" } +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["serde"] } +bitvec = { version = "1" } +bstr-6f8ce4dd05d13bba = { package = "bstr", version = "0.2" } +bstr-dff4ba8e3ae991db = { package = "bstr", version = "1" } +bytes = { version = "1", features = ["serde"] } +cc = { version = "1", default-features = false, features = ["parallel"] } +chrono = { version = "0.4", features = ["alloc", "serde"] } +cipher = { version = "0.4", default-features = false, features = ["block-padding", "zeroize"] } +clap = { version = "4", features = ["derive", "env", "wrap_help"] } +clap_builder = { version = "4", default-features = false, features = ["color", "env", "std", "suggestions", "usage", "wrap_help"] } +console = { version = "0.15" } +const-oid = { version = "0.9", default-features = false, features = ["db", "std"] } +crossbeam-epoch = { version = "0.9" } +crossbeam-utils = { version = "0.8" } +crypto-common = { version = "0.1", default-features = false, features = ["getrandom", "std"] } +diesel = { version = "2", features = ["chrono", "i-implement-a-third-party-backend-and-opt-into-breaking-changes", "network-address", "postgres", "r2d2", "serde_json", "uuid"] } +digest = { version = "0.10", features = ["mac", "oid", "std"] } +either = { version = "1" } +flate2 = { version = "1" } +futures = { version = "0.3" } +futures-channel = { version = "0.3", features = ["sink"] } +futures-core = { version = "0.3" } +futures-io = { version = "0.3", default-features = false, features = ["std"] } +futures-sink = { version = "0.3" } +futures-task = { version = "0.3", default-features = false, features = ["std"] } +futures-util = { version = "0.3", features = ["channel", "io", "sink"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "1e180ae55e56bd17af35cb868ffbd18ce487351d", features = ["std"] } +generic-array = { version = "0.14", default-features = false, features = ["more_lengths", "zeroize"] } +getrandom = { version = "0.2", default-features = false, features = ["js", "rdrand", "std"] } +hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14", features = ["raw"] } +hashbrown-594e8ee84c453af0 = { package = "hashbrown", version = "0.13" } +hex = { version = "0.4", features = ["serde"] } +hyper = { version = "0.14", features = ["full"] } +indexmap = { version = "2", features = ["serde"] } +inout = { version = "0.1", default-features = false, features = ["std"] } +ipnetwork = { version = "0.20", features = ["schemars"] } +itertools = { version = "0.10" } +lalrpop-util = { version = "0.19" } +lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] } +libc = { version = "0.2", features = ["extra_traits"] } +log = { version = "0.4", default-features = false, features = ["std"] } +managed = { version = "0.8", default-features = false, features = ["alloc", "map"] } +memchr = { version = "2" } +num-bigint = { version = "0.4", features = ["rand"] } +num-integer = { version = "0.1", features = ["i128"] } +num-iter = { version = "0.1", default-features = false, features = ["i128"] } +num-traits = { version = "0.2", features = ["i128", "libm"] } +openapiv3 = { version = "1", default-features = false, features = ["skip_serializing_defaults"] } +petgraph = { version = "0.6", features = ["serde-1"] } +postgres-types = { version = "0.2", default-features = false, features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } +ppv-lite86 = { version = "0.2", default-features = false, features = ["simd", "std"] } +predicates = { version = "3" } +rand = { version = "0.8", features = ["min_const_gen"] } +rand_chacha = { version = "0.3" } +regex = { version = "1" } +regex-automata = { version = "0.3", default-features = false, features = ["dfa-onepass", "dfa-search", "hybrid", "meta", "nfa-backtrack", "perf-inline", "perf-literal", "unicode"] } +regex-syntax = { version = "0.7" } +reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls", "stream"] } +ring = { version = "0.16", features = ["std"] } +schemars = { version = "0.8", features = ["bytes", "chrono", "uuid1"] } +semver = { version = "1", features = ["serde"] } +serde = { version = "1", features = ["alloc", "derive", "rc"] } +sha2 = { version = "0.10", features = ["oid"] } +signature = { version = "2", default-features = false, features = ["digest", "rand_core", "std"] } +similar = { version = "2", features = ["inline", "unicode"] } +slog = { version = "2", features = ["dynamic-keys", "max_level_trace", "release_max_level_debug", "release_max_level_trace"] } +spin = { version = "0.9" } +string_cache = { version = "0.8" } +subtle = { version = "2" } +syn-dff4ba8e3ae991db = { package = "syn", version = "1", features = ["extra-traits", "fold", "full", "visit"] } +syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] } +textwrap = { version = "0.16" } +time = { version = "0.3", features = ["formatting", "local-offset", "macros", "parsing"] } +time-macros = { version = "0.2", default-features = false, features = ["formatting", "parsing"] } +tokio = { version = "1", features = ["full", "test-util"] } +tokio-postgres = { version = "0.7", features = ["with-chrono-0_4", "with-serde_json-1", "with-uuid-1"] } +tokio-stream = { version = "0.1", features = ["net"] } +toml = { version = "0.7" } +toml_datetime = { version = "0.6", default-features = false, features = ["serde"] } +toml_edit = { version = "0.19", features = ["serde"] } +tracing = { version = "0.1", features = ["log"] } +trust-dns-proto = { version = "0.22" } +unicode-bidi = { version = "0.3" } +unicode-normalization = { version = "0.1" } +unicode-xid = { version = "0.2" } +usdt = { version = "0.3" } +uuid = { version = "1", features = ["serde", "v4"] } +yasna = { version = "0.5", features = ["bit-vec", "num-bigint", "std", "time"] } +zeroize = { version = "1", features = ["std", "zeroize_derive"] } +zip = { version = "0.6", default-features = false, features = ["bzip2", "deflate"] } + +[target.x86_64-unknown-linux-gnu.dependencies] +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["std"] } +hyper-rustls = { version = "0.24" } +mio = { version = "0.8", features = ["net", "os-ext"] } +once_cell = { version = "1", features = ["unstable"] } +rustix = { version = "0.38", features = ["fs", "termios"] } + +[target.x86_64-unknown-linux-gnu.build-dependencies] +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["std"] } +hyper-rustls = { version = "0.24" } +mio = { version = "0.8", features = ["net", "os-ext"] } +once_cell = { version = "1", features = ["unstable"] } +rustix = { version = "0.38", features = ["fs", "termios"] } + +[target.x86_64-apple-darwin.dependencies] +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["std"] } +hyper-rustls = { version = "0.24" } +mio = { version = "0.8", features = ["net", "os-ext"] } +once_cell = { version = "1", features = ["unstable"] } +rustix = { version = "0.38", features = ["fs", "termios"] } + +[target.x86_64-apple-darwin.build-dependencies] +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["std"] } +hyper-rustls = { version = "0.24" } +mio = { version = "0.8", features = ["net", "os-ext"] } +once_cell = { version = "1", features = ["unstable"] } +rustix = { version = "0.38", features = ["fs", "termios"] } + +[target.aarch64-apple-darwin.dependencies] +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["std"] } +hyper-rustls = { version = "0.24" } +mio = { version = "0.8", features = ["net", "os-ext"] } +once_cell = { version = "1", features = ["unstable"] } +rustix = { version = "0.38", features = ["fs", "termios"] } + +[target.aarch64-apple-darwin.build-dependencies] +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["std"] } +hyper-rustls = { version = "0.24" } +mio = { version = "0.8", features = ["net", "os-ext"] } +once_cell = { version = "1", features = ["unstable"] } +rustix = { version = "0.38", features = ["fs", "termios"] } + +[target.x86_64-unknown-illumos.dependencies] +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["std"] } +hyper-rustls = { version = "0.24" } +mio = { version = "0.8", features = ["net", "os-ext"] } +once_cell = { version = "1", features = ["unstable"] } +rustix = { version = "0.38", features = ["fs", "termios"] } + +[target.x86_64-unknown-illumos.build-dependencies] +bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2", default-features = false, features = ["std"] } +hyper-rustls = { version = "0.24" } +mio = { version = "0.8", features = ["net", "os-ext"] } +once_cell = { version = "1", features = ["unstable"] } +rustix = { version = "0.38", features = ["fs", "termios"] } + +### END HAKARI SECTION diff --git a/workspace-hack/build.rs b/workspace-hack/build.rs new file mode 100644 index 0000000000..92518ef04c --- /dev/null +++ b/workspace-hack/build.rs @@ -0,0 +1,2 @@ +// A build script is required for cargo to consider build dependencies. +fn main() {} diff --git a/workspace-hack/src/lib.rs b/workspace-hack/src/lib.rs new file mode 100644 index 0000000000..22489f632b --- /dev/null +++ b/workspace-hack/src/lib.rs @@ -0,0 +1 @@ +// This is a stub lib.rs.