From e4ba67bfece71a417201d844f89e3ff23d5176c8 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:02:32 -0800 Subject: [PATCH 01/22] chore(deps): update rust crate num-integer to 0.1.46 (#5076) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- workspace-hack/Cargo.toml | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f13d1e990..63d72bc117 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4699,11 +4699,10 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] diff --git a/Cargo.toml b/Cargo.toml index 3dae9211e3..e5fa364305 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -264,7 +264,7 @@ nexus-test-interface = { path = "nexus/test-interface" } nexus-test-utils-macros = { path = "nexus/test-utils-macros" } nexus-test-utils = { path = "nexus/test-utils" } nexus-types = { path = "nexus/types" } -num-integer = "0.1.45" +num-integer = "0.1.46" num = { version = "0.4.1", default-features = false, features = [ "libm" ] } omicron-common = { path = "common" } omicron-gateway = { path = "gateway" } diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index a52f63fec1..d72b63356b 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -71,7 +71,7 @@ managed = { version = "0.8.0", default-features = false, features = ["alloc", "m memchr = { version = "2.6.3" } nom = { version = "7.1.3" } num-bigint = { version = "0.4.4", features = ["rand"] } -num-integer = { version = "0.1.45", features = ["i128"] } +num-integer = { version = "0.1.46", features = ["i128"] } num-iter = { version = "0.1.43", default-features = false, features = ["i128"] } num-traits = { version = "0.2.16", features = ["i128", "libm"] } openapiv3 = { version = "2.0.0", default-features = false, features = ["skip_serializing_defaults"] } @@ -178,7 +178,7 @@ managed = { version = "0.8.0", default-features = false, features = ["alloc", "m memchr = { version = "2.6.3" } nom = { version = "7.1.3" } num-bigint = { version = "0.4.4", features = ["rand"] } -num-integer = { version = "0.1.45", features = ["i128"] } +num-integer = { version = "0.1.46", features = ["i128"] } num-iter = { version = "0.1.43", default-features = false, features = ["i128"] } num-traits = { version = "0.2.16", features = ["i128", "libm"] } openapiv3 = { version = "2.0.0", default-features = false, features = ["skip_serializing_defaults"] } From eda05500978621d28df6e15cb50549abed9a60ca Mon Sep 17 00:00:00 2001 From: bnaecker Date: Thu, 15 Feb 2024 14:43:47 -0800 Subject: [PATCH 02/22] Update progenitor to bc0bb4b (#5071) --- Cargo.lock | 61 +++++++++++---------------- clients/dns-service-client/src/lib.rs | 3 +- common/src/api/external/error.rs | 3 +- sled-agent/src/instance.rs | 3 +- workspace-hack/Cargo.toml | 6 +-- 5 files changed, 33 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63d72bc117..e82fd6a60f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -655,7 +655,7 @@ dependencies = [ "omicron-common", "omicron-workspace-hack", "progenitor 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", - "regress 0.8.0", + "regress", "reqwest", "schemars", "serde", @@ -1946,7 +1946,7 @@ dependencies = [ "progenitor-client 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", "quote", "rand 0.8.5", - "regress 0.8.0", + "regress", "reqwest", "rustfmt-wrapper", "schemars", @@ -3368,7 +3368,7 @@ dependencies = [ "opte-ioctl", "oxide-vpc", "oxlog", - "regress 0.8.0", + "regress", "schemars", "serde", "serde_json", @@ -3504,7 +3504,7 @@ dependencies = [ "installinator-common", "omicron-workspace-hack", "progenitor 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", - "regress 0.8.0", + "regress", "reqwest", "schemars", "serde", @@ -4302,7 +4302,7 @@ dependencies = [ "omicron-passwords", "omicron-workspace-hack", "progenitor 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", - "regress 0.8.0", + "regress", "reqwest", "schemars", "serde", @@ -4871,7 +4871,7 @@ dependencies = [ "progenitor 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", "proptest", "rand 0.8.5", - "regress 0.8.0", + "regress", "reqwest", "schemars", "semver 1.0.21", @@ -5365,7 +5365,6 @@ dependencies = [ "generic-array", "getrandom 0.2.10", "group", - "hashbrown 0.13.2", "hashbrown 0.14.3", "hex", "hmac", @@ -5647,7 +5646,7 @@ dependencies = [ "omicron-workspace-hack", "progenitor 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", "rand 0.8.5", - "regress 0.8.0", + "regress", "reqwest", "serde", "serde_json", @@ -6500,7 +6499,7 @@ dependencies = [ [[package]] name = "progenitor" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/progenitor?branch=main#86b60220b88a2ca3629fb87acf8f83ff35f63aaa" +source = "git+https://github.com/oxidecomputer/progenitor?branch=main#bc0bb4b0fb40084f189eb1a8807b17fbd0ce0b64" dependencies = [ "progenitor-client 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", "progenitor-impl 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", @@ -6511,7 +6510,7 @@ dependencies = [ [[package]] name = "progenitor" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/progenitor#86b60220b88a2ca3629fb87acf8f83ff35f63aaa" +source = "git+https://github.com/oxidecomputer/progenitor#bc0bb4b0fb40084f189eb1a8807b17fbd0ce0b64" dependencies = [ "progenitor-client 0.5.0 (git+https://github.com/oxidecomputer/progenitor)", "progenitor-impl 0.5.0 (git+https://github.com/oxidecomputer/progenitor)", @@ -6522,7 +6521,7 @@ dependencies = [ [[package]] name = "progenitor-client" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/progenitor?branch=main#86b60220b88a2ca3629fb87acf8f83ff35f63aaa" +source = "git+https://github.com/oxidecomputer/progenitor?branch=main#bc0bb4b0fb40084f189eb1a8807b17fbd0ce0b64" dependencies = [ "bytes", "futures-core", @@ -6536,7 +6535,7 @@ dependencies = [ [[package]] name = "progenitor-client" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/progenitor#86b60220b88a2ca3629fb87acf8f83ff35f63aaa" +source = "git+https://github.com/oxidecomputer/progenitor#bc0bb4b0fb40084f189eb1a8807b17fbd0ce0b64" dependencies = [ "bytes", "futures-core", @@ -6550,7 +6549,7 @@ dependencies = [ [[package]] name = "progenitor-impl" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/progenitor?branch=main#86b60220b88a2ca3629fb87acf8f83ff35f63aaa" +source = "git+https://github.com/oxidecomputer/progenitor?branch=main#bc0bb4b0fb40084f189eb1a8807b17fbd0ce0b64" dependencies = [ "getopts", "heck 0.4.1", @@ -6572,7 +6571,7 @@ dependencies = [ [[package]] name = "progenitor-impl" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/progenitor#86b60220b88a2ca3629fb87acf8f83ff35f63aaa" +source = "git+https://github.com/oxidecomputer/progenitor#bc0bb4b0fb40084f189eb1a8807b17fbd0ce0b64" dependencies = [ "getopts", "heck 0.4.1", @@ -6594,7 +6593,7 @@ dependencies = [ [[package]] name = "progenitor-macro" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/progenitor?branch=main#86b60220b88a2ca3629fb87acf8f83ff35f63aaa" +source = "git+https://github.com/oxidecomputer/progenitor?branch=main#bc0bb4b0fb40084f189eb1a8807b17fbd0ce0b64" dependencies = [ "openapiv3", "proc-macro2", @@ -6611,7 +6610,7 @@ dependencies = [ [[package]] name = "progenitor-macro" version = "0.5.0" -source = "git+https://github.com/oxidecomputer/progenitor#86b60220b88a2ca3629fb87acf8f83ff35f63aaa" +source = "git+https://github.com/oxidecomputer/progenitor#bc0bb4b0fb40084f189eb1a8807b17fbd0ce0b64" dependencies = [ "openapiv3", "proc-macro2", @@ -7051,16 +7050,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" -[[package]] -name = "regress" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed9969cad8051328011596bf549629f1b800cf1731e7964b1eef8dfc480d2c2" -dependencies = [ - "hashbrown 0.13.2", - "memchr", -] - [[package]] name = "regress" version = "0.8.0" @@ -8146,7 +8135,7 @@ dependencies = [ "omicron-common", "omicron-workspace-hack", "progenitor 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", - "regress 0.8.0", + "regress", "reqwest", "schemars", "serde", @@ -9009,18 +8998,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", @@ -9716,7 +9705,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "typify" version = "0.0.15" -source = "git+https://github.com/oxidecomputer/typify#1f97f167923f001818d461b1286f8a5242abf8b1" +source = "git+https://github.com/oxidecomputer/typify#ce009d6f83b620cbd0e3acdd9b9ea071018471d8" dependencies = [ "typify-impl", "typify-macro", @@ -9725,13 +9714,13 @@ dependencies = [ [[package]] name = "typify-impl" version = "0.0.15" -source = "git+https://github.com/oxidecomputer/typify#1f97f167923f001818d461b1286f8a5242abf8b1" +source = "git+https://github.com/oxidecomputer/typify#ce009d6f83b620cbd0e3acdd9b9ea071018471d8" dependencies = [ "heck 0.4.1", "log", "proc-macro2", "quote", - "regress 0.7.1", + "regress", "schemars", "serde_json", "syn 2.0.48", @@ -9742,7 +9731,7 @@ dependencies = [ [[package]] name = "typify-macro" version = "0.0.15" -source = "git+https://github.com/oxidecomputer/typify#1f97f167923f001818d461b1286f8a5242abf8b1" +source = "git+https://github.com/oxidecomputer/typify#ce009d6f83b620cbd0e3acdd9b9ea071018471d8" dependencies = [ "proc-macro2", "quote", @@ -10466,7 +10455,7 @@ dependencies = [ "ipnetwork", "omicron-workspace-hack", "progenitor 0.5.0 (git+https://github.com/oxidecomputer/progenitor?branch=main)", - "regress 0.8.0", + "regress", "reqwest", "schemars", "serde", diff --git a/clients/dns-service-client/src/lib.rs b/clients/dns-service-client/src/lib.rs index e437f1a7f6..52c2b8bcd2 100644 --- a/clients/dns-service-client/src/lib.rs +++ b/clients/dns-service-client/src/lib.rs @@ -32,7 +32,8 @@ pub fn is_retryable(error: &DnsConfigError) -> bool { | DnsConfigError::InvalidResponsePayload(_, _) | DnsConfigError::UnexpectedResponse(_) | DnsConfigError::InvalidUpgrade(_) - | DnsConfigError::ResponseBodyError(_) => return false, + | DnsConfigError::ResponseBodyError(_) + | DnsConfigError::PreHookError(_) => return false, DnsConfigError::ErrorResponse(response_value) => response_value, }; diff --git a/common/src/api/external/error.rs b/common/src/api/external/error.rs index d2e062f2e1..f7eb257e8f 100644 --- a/common/src/api/external/error.rs +++ b/common/src/api/external/error.rs @@ -496,7 +496,8 @@ impl From> for Error { ) | progenitor::progenitor_client::Error::UnexpectedResponse(_) | progenitor::progenitor_client::Error::InvalidUpgrade(_) - | progenitor::progenitor_client::Error::ResponseBodyError(_) => { + | progenitor::progenitor_client::Error::ResponseBodyError(_) + | progenitor::progenitor_client::Error::PreHookError(_) => { Error::internal_error(&e.to_string()) } // This error represents an expected error from the remote service. diff --git a/sled-agent/src/instance.rs b/sled-agent/src/instance.rs index 5b9bf54dd9..7a6033b4bb 100644 --- a/sled-agent/src/instance.rs +++ b/sled-agent/src/instance.rs @@ -504,7 +504,8 @@ impl InstanceRunner { | nexus_client::Error::InvalidResponsePayload(..) | nexus_client::Error::UnexpectedResponse(_) | nexus_client::Error::InvalidUpgrade(_) - | nexus_client::Error::ResponseBodyError(_) => { + | nexus_client::Error::ResponseBodyError(_) + | nexus_client::Error::PreHookError(_) => { BackoffError::permanent(Error::Notification( err, )) diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index d72b63356b..098be26460 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -54,8 +54,7 @@ gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway- generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom = { version = "0.2.10", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] } -hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14.3", features = ["raw"] } -hashbrown-594e8ee84c453af0 = { package = "hashbrown", version = "0.13.2" } +hashbrown = { version = "0.14.3", features = ["raw"] } hex = { version = "0.4.3", features = ["serde"] } hmac = { version = "0.12.1", default-features = false, features = ["reset"] } hyper = { version = "0.14.27", features = ["full"] } @@ -161,8 +160,7 @@ gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway- generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom = { version = "0.2.10", default-features = false, features = ["js", "rdrand", "std"] } group = { version = "0.13.0", default-features = false, features = ["alloc"] } -hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14.3", features = ["raw"] } -hashbrown-594e8ee84c453af0 = { package = "hashbrown", version = "0.13.2" } +hashbrown = { version = "0.14.3", features = ["raw"] } hex = { version = "0.4.3", features = ["serde"] } hmac = { version = "0.12.1", default-features = false, features = ["reset"] } hyper = { version = "0.14.27", features = ["full"] } From fedd8c87ccbf17af6251433a122652cf0962464e Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 23:06:32 +0000 Subject: [PATCH 03/22] chore(deps): update rust crate serde_with to 3.6.1 (#5077) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 9 +++++---- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e82fd6a60f..c5f0192dbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7931,9 +7931,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b0ed1662c5a68664f45b76d18deb0e234aff37207086803165c961eb695e981" +checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" dependencies = [ "base64", "chrono", @@ -7941,6 +7941,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.2.3", "serde", + "serde_derive", "serde_json", "serde_with_macros", "time", @@ -7948,9 +7949,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.6.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568577ff0ef47b879f736cd66740e022f3672788cdf002a05a4e609ea5a6fb15" +checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" dependencies = [ "darling 0.20.3", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index e5fa364305..bf9ebd9354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -341,7 +341,7 @@ serde_json = "1.0.113" serde_path_to_error = "0.1.15" serde_tokenstream = "0.2" serde_urlencoded = "0.7.1" -serde_with = "3.6.0" +serde_with = "3.6.1" sha2 = "0.10.8" sha3 = "0.10.8" shell-words = "1.1.0" From 1f48138840644e2573800b8f52dd00af35acc179 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 01:32:15 +0000 Subject: [PATCH 04/22] chore(deps): update actions/setup-node action to v4.0.2 (#5075) --- .github/workflows/validate-openapi-spec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-openapi-spec.yml b/.github/workflows/validate-openapi-spec.yml index a76567af2a..50aba2dc53 100644 --- a/.github/workflows/validate-openapi-spec.yml +++ b/.github/workflows/validate-openapi-spec.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: ${{ github.event.pull_request.head.sha }} # see omicron#4461 - - uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '18' - name: Install our tools From 235420ebd04831829ff3b2ce89fcd26f38c768f5 Mon Sep 17 00:00:00 2001 From: Rain Date: Thu, 15 Feb 2024 20:58:31 -0800 Subject: [PATCH 05/22] [nexus] basic scaffolding for typed UUIDs + a couple of simple examples (#4934) This PR contains a basic implementation of typed UUIDs using the [newtype-uuid](https://crates.io/crates/newtype-uuid) crate, plus a simple conversion of two UUID types to this model: `LoopbackAddress` and `TufRepo`. I picked these two types based on the fact that there were just a few places that used them. --- .config/hakari.toml | 11 +- Cargo.lock | 36 +++ Cargo.toml | 28 +++ common/Cargo.toml | 1 + common/src/api/external/error.rs | 6 + nexus/authz-macros/Cargo.toml | 1 + nexus/authz-macros/outputs/rack.txt | 75 ++++++ nexus/authz-macros/src/lib.rs | 114 ++++++++- nexus/db-macros/Cargo.toml | 1 + .../outputs/asset_with_uuid_kind.txt | 49 ++++ nexus/db-macros/outputs/project.txt | 6 +- .../outputs/resource_with_uuid_kind.txt | 66 +++++ nexus/db-macros/outputs/silo_user.txt | 6 +- nexus/db-macros/outputs/sled.txt | 182 ++++++++++++++ nexus/db-macros/src/lib.rs | 229 +++++++++++++++--- nexus/db-macros/src/lookup.rs | 111 +++++++-- nexus/db-macros/src/test_helpers.rs | 10 + nexus/db-model/Cargo.toml | 2 + nexus/db-model/src/lib.rs | 2 + nexus/db-model/src/switch_interface.rs | 7 +- nexus/db-model/src/tuf_repo.rs | 11 +- nexus/db-model/src/typed_uuid.rs | 116 +++++++++ nexus/db-queries/Cargo.toml | 3 +- nexus/db-queries/src/authz/api_resources.rs | 4 +- .../src/authz/policy_test/resources.rs | 5 +- .../src/db/datastore/switch_interface.rs | 9 +- nexus/db-queries/src/db/datastore/update.rs | 15 +- nexus/db-queries/src/db/lookup.rs | 6 +- nexus/macros-common/Cargo.toml | 11 + nexus/macros-common/src/lib.rs | 109 +++++++++ nexus/types/Cargo.toml | 1 + nexus/types/src/identity.rs | 13 +- uuid-kinds/Cargo.toml | 20 ++ uuid-kinds/README.adoc | 73 ++++++ uuid-kinds/src/lib.rs | 50 ++++ 35 files changed, 1297 insertions(+), 92 deletions(-) create mode 100644 nexus/authz-macros/outputs/rack.txt create mode 100644 nexus/db-macros/outputs/asset_with_uuid_kind.txt create mode 100644 nexus/db-macros/outputs/resource_with_uuid_kind.txt create mode 100644 nexus/db-macros/outputs/sled.txt create mode 100644 nexus/db-macros/src/test_helpers.rs create mode 100644 nexus/db-model/src/typed_uuid.rs create mode 100644 nexus/macros-common/Cargo.toml create mode 100644 nexus/macros-common/src/lib.rs create mode 100644 uuid-kinds/Cargo.toml create mode 100644 uuid-kinds/README.adoc create mode 100644 uuid-kinds/src/lib.rs diff --git a/.config/hakari.toml b/.config/hakari.toml index 0d883dc6f6..3c08a89e12 100644 --- a/.config/hakari.toml +++ b/.config/hakari.toml @@ -30,4 +30,13 @@ platforms = [ exact-versions = true [traversal-excludes] -workspace-members = ["xtask"] +workspace-members = [ + # Exclude xtask because it needs to be built quickly. + "xtask", + + # Exclude omicron-uuid-kinds because it is a no-std crate. Depending on the + # workspace-hack isn't too problematic because other projects pulling in + # omicron as a git dependency will only see an empty workspace-hack. But + # let's make this explicit. + "omicron-uuid-kinds", +] diff --git a/Cargo.lock b/Cargo.lock index c5f0192dbd..fca461f24b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,6 +354,7 @@ version = "0.1.0" dependencies = [ "expectorate", "heck 0.4.1", + "nexus-macros-common", "omicron-workspace-hack", "prettyplease", "proc-macro2", @@ -1548,6 +1549,7 @@ version = "0.1.0" dependencies = [ "expectorate", "heck 0.4.1", + "nexus-macros-common", "omicron-workspace-hack", "prettyplease", "proc-macro2", @@ -4256,6 +4258,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "newtype-uuid" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5ff2b31594942586c1520da8f1e5c705729ec67b3c2ad0fe459f0b576e4d9a" +dependencies = [ + "schemars", + "serde", + "uuid", +] + [[package]] name = "newtype_derive" version = "0.1.6" @@ -4318,6 +4331,7 @@ dependencies = [ "anyhow", "chrono", "db-macros", + "derive-where", "diesel", "expectorate", "hex", @@ -4330,6 +4344,7 @@ dependencies = [ "omicron-common", "omicron-passwords", "omicron-rpaths", + "omicron-uuid-kinds", "omicron-workspace-hack", "parse-display", "pq-sys", @@ -4388,6 +4403,7 @@ dependencies = [ "omicron-rpaths", "omicron-sled-agent", "omicron-test-utils", + "omicron-uuid-kinds", "omicron-workspace-hack", "once_cell", "openapiv3", @@ -4482,6 +4498,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "nexus-macros-common" +version = "0.1.0" +dependencies = [ + "omicron-workspace-hack", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "nexus-test-interface" version = "0.1.0" @@ -4557,6 +4583,7 @@ dependencies = [ "gateway-client", "omicron-common", "omicron-passwords", + "omicron-uuid-kinds", "omicron-workspace-hack", "openssl", "parse-display", @@ -4865,6 +4892,7 @@ dependencies = [ "ipnetwork", "libc", "macaddr", + "omicron-uuid-kinds", "omicron-workspace-hack", "once_cell", "parse-display", @@ -5319,6 +5347,14 @@ dependencies = [ "walkdir", ] +[[package]] +name = "omicron-uuid-kinds" +version = "0.1.0" +dependencies = [ + "newtype-uuid", + "schemars", +] + [[package]] name = "omicron-workspace-hack" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index bf9ebd9354..f71bd77730 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ members = [ "nexus/defaults", "nexus/deployment", "nexus/inventory", + "nexus/macros-common", "nexus/test-interface", "nexus/test-utils-macros", "nexus/test-utils", @@ -66,6 +67,7 @@ members = [ "tufaceous", "update-common", "update-engine", + "uuid-kinds", "wicket-common", "wicket-dbg", "wicket", @@ -113,6 +115,7 @@ default-members = [ "nexus", "nexus/authz-macros", "nexus/blueprint-execution", + "nexus/macros-common", "nexus/db-macros", "nexus/db-model", "nexus/db-queries", @@ -138,6 +141,7 @@ default-members = [ "tufaceous", "update-common", "update-engine", + "uuid-kinds", "wicket-common", "wicket-dbg", "wicket", @@ -256,6 +260,7 @@ nexus-db-queries = { path = "nexus/db-queries" } nexus-defaults = { path = "nexus/defaults" } nexus-deployment = { path = "nexus/deployment" } nexus-inventory = { path = "nexus/inventory" } +nexus-macros-common = { path = "nexus/macros-common" } omicron-certificates = { path = "certificates" } omicron-passwords = { path = "passwords" } omicron-workspace-hack = "0.1.0" @@ -415,6 +420,14 @@ zeroize = { version = "1.7.0", features = ["zeroize_derive", "std"] } zip = { version = "0.6.6", default-features = false, features = ["deflate","bzip2"] } zone = { version = "0.3", default-features = false, features = ["async", "sync"] } +# newtype-uuid is set to default-features = false because we don't want to +# depend on std in omicron-uuid-kinds (in case a no-std library wants to access +# the kinds). However, uses of omicron-uuid-kinds _within omicron_ will have +# std and the other features enabled because they'll refer to it via +# omicron-uuid-kinds.workspace = true. +newtype-uuid = { version = "1.0.1", default-features = false } +omicron-uuid-kinds = { path = "uuid-kinds", features = ["serde", "schemars08", "uuid-v4"] } + # NOTE: The test profile inherits from the dev profile, so settings under # profile.dev get inherited. AVOID setting anything under profile.test: that # will cause dev and test builds to diverge, which will cause more Cargo build @@ -570,6 +583,7 @@ opt-level = 3 [profile.dev.package.keccak] opt-level = 3 + # # It's common during development to use a local copy of various complex # dependencies. If you want to use those, uncomment one of these blocks. @@ -617,3 +631,17 @@ path = "workspace-hack" [patch.crates-io.samael] git = "https://github.com/oxidecomputer/samael" branch = "oxide/omicron" + +# Several crates such as crucible and propolis have have a Git dependency on +# this repo. Omicron itself depends on these crates, which can lead to two +# copies of these crates in the dependency graph. (As a Git dependency, and as +# a path dependency.) The goal of omicron-uuid-kinds is to provide a unified +# registry of UUID kinds. Two copies of the same kinds floating around is +# unnecessary and painful. +# +# This directive ensures that whenever we see omicron-uuid-kinds as a Git +# dependency, we'll use the path dependency version of the crate instead. +# +# See also: uuid-kinds/README.adoc. +[patch."https://github.com/oxidecomputer/omicron"] +omicron-uuid-kinds = { path = "uuid-kinds" } diff --git a/common/Cargo.toml b/common/Cargo.toml index ebb8c8c9b4..5628a93397 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -17,6 +17,7 @@ hex.workspace = true http.workspace = true ipnetwork.workspace = true macaddr.workspace = true +omicron-uuid-kinds.workspace = true proptest = { workspace = true, optional = true } rand.workspace = true reqwest = { workspace = true, features = ["rustls-tls", "stream"] } diff --git a/common/src/api/external/error.rs b/common/src/api/external/error.rs index f7eb257e8f..2cb3dc0d6e 100644 --- a/common/src/api/external/error.rs +++ b/common/src/api/external/error.rs @@ -9,6 +9,7 @@ use crate::api::external::Name; use crate::api::external::ResourceType; use dropshot::HttpError; +use omicron_uuid_kinds::GenericUuid; use serde::Deserialize; use serde::Serialize; use std::fmt::Display; @@ -152,6 +153,11 @@ pub enum LookupType { } impl LookupType { + /// Constructs a `ById` lookup type from a typed or untyped UUID. + pub fn by_id(id: T) -> Self { + LookupType::ById(id.into_untyped_uuid()) + } + /// Returns an ObjectNotFound error appropriate for the case where this /// lookup failed pub fn into_not_found(self, type_name: ResourceType) -> Error { diff --git a/nexus/authz-macros/Cargo.toml b/nexus/authz-macros/Cargo.toml index e9bdaf4708..4d2640abee 100644 --- a/nexus/authz-macros/Cargo.toml +++ b/nexus/authz-macros/Cargo.toml @@ -9,6 +9,7 @@ proc-macro = true [dependencies] heck.workspace = true +nexus-macros-common.workspace = true proc-macro2.workspace = true quote.workspace = true serde.workspace = true diff --git a/nexus/authz-macros/outputs/rack.txt b/nexus/authz-macros/outputs/rack.txt new file mode 100644 index 0000000000..40826951ee --- /dev/null +++ b/nexus/authz-macros/outputs/rack.txt @@ -0,0 +1,75 @@ +///`authz` type for a resource of type RackUsed to uniquely identify a resource of type Rack across renames, moves, etc., and to do authorization checks (see [`crate::context::OpContext::authorize()`]). See [`crate::authz`] module-level documentation for more information. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Rack { + parent: Fleet, + key: ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::RackKind>, + lookup_type: LookupType, +} +impl Rack { + /// Makes a new `authz` struct for this resource with the given + /// `parent`, unique key `key`, looked up as described by + /// `lookup_type` + pub fn new( + parent: Fleet, + key: ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::RackKind>, + lookup_type: LookupType, + ) -> Rack { + Rack { + parent, + key: key.into(), + lookup_type, + } + } + /// A version of `new` that takes the primary key type directly. + /// This is only different from [`Self::new`] if this resource + /// uses a different input key type. + pub fn with_primary_key( + parent: Fleet, + key: ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::RackKind>, + lookup_type: LookupType, + ) -> Rack { + Rack { parent, key, lookup_type } + } + pub fn id(&self) -> ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::RackKind> { + self.key.clone().into() + } + /// Describes how to register this type with Oso + pub(super) fn init() -> Init { + use oso::PolarClass; + Init { + polar_snippet: "\n resource Rack {\n permissions = [\n \"list_children\",\n \"modify\",\n \"read\",\n \"create_child\",\n ];\n \n relations = { parent_fleet: Fleet };\n \"list_children\" if \"viewer\" on \"parent_fleet\";\n \"read\" if \"viewer\" on \"parent_fleet\";\n \"modify\" if \"admin\" on \"parent_fleet\";\n \"create_child\" if \"admin\" on \"parent_fleet\";\n }\n has_relation(fleet: Fleet, \"parent_fleet\", child: Rack)\n if child.fleet = fleet;\n ", + polar_class: Rack::get_polar_class(), + } + } +} +impl Eq for Rack {} +impl PartialEq for Rack { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} +impl oso::PolarClass for Rack { + fn get_polar_class_builder() -> oso::ClassBuilder { + oso::Class::builder() + .with_equality_check() + .add_method( + "has_role", + |r: &Rack, actor: AuthenticatedActor, role: String| { false }, + ) + .add_attribute_getter("fleet", |r: &Rack| r.parent.clone()) + } +} +impl ApiResource for Rack { + fn parent(&self) -> Option<&dyn AuthorizedResource> { + Some(&self.parent) + } + fn resource_type(&self) -> ResourceType { + ResourceType::Rack + } + fn lookup_type(&self) -> &LookupType { + &self.lookup_type + } + fn as_resource_with_roles(&self) -> Option<&dyn ApiResourceWithRoles> { + None + } +} diff --git a/nexus/authz-macros/src/lib.rs b/nexus/authz-macros/src/lib.rs index 648ae6d952..0548113339 100644 --- a/nexus/authz-macros/src/lib.rs +++ b/nexus/authz-macros/src/lib.rs @@ -6,9 +6,11 @@ extern crate proc_macro; +use nexus_macros_common::PrimaryKeyType; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use serde_tokenstream::ParseWrapper; +use syn::parse_quote; /// Defines a structure and helpers for describing an API resource for authz /// @@ -81,6 +83,24 @@ use serde_tokenstream::ParseWrapper; /// } /// ``` /// +/// ## Resources with typed UUID primary keys +/// +/// Some resources use a [newtype_uuid](https://crates.io/crates/newtype_uuid) +/// `TypedUuid` as their primary key (and resources should generally move over +/// to that model). +/// +/// This can be specified with `primary_key = { uuid_kind = MyKind }`: +/// +/// ```ignore +/// authz_resource! { +/// name = "LoopbackAddress", +/// parent = "Fleet", +/// primary_key = { uuid_kind = LoopbackAddressKind }, +/// roles_allowed = false, +/// polar_snippet = FleetChild, +/// } +/// ``` +/// /// ## Resources with non-id primary keys /// /// Most API resources use "id" (a Uuid) as an immutable, unique identifier. @@ -137,7 +157,7 @@ pub fn authz_resource( } /// Arguments for [`authz_resource!`] -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Debug)] struct Input { /// Name of the resource /// @@ -146,8 +166,10 @@ struct Input { name: String, /// Name of the parent `authz` resource parent: String, - /// Rust type for the primary key for this resource - primary_key: ParseWrapper, + /// Rust type for the primary key for this resource. + primary_key: InputPrimaryKeyType, + /// The `TypedUuidKind` for this resource. Must be exclusive + /// /// Rust type for the input key for this resource (the key users specify /// for this resource, convertible to `primary_key`). /// @@ -160,8 +182,77 @@ struct Input { polar_snippet: PolarSnippet, } +#[derive(Debug)] +struct InputPrimaryKeyType(PrimaryKeyType); + +impl<'de> serde::Deserialize<'de> for InputPrimaryKeyType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // Attempt to parse as either a string or a map. + struct PrimaryKeyVisitor; + + impl<'de2> serde::de::Visitor<'de2> for PrimaryKeyVisitor { + type Value = PrimaryKeyType; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str( + "a Rust type, or a map with a single key `uuid_kind`", + ) + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + syn::parse_str(value) + .map(PrimaryKeyType::Standard) + .map_err(|e| E::custom(e.to_string())) + } + + // seq represents a tuple type + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de2>, + { + let mut elements = vec![]; + while let Some(element) = + seq.next_element::>()? + { + elements.push(element.into_inner()); + } + Ok(PrimaryKeyType::Standard(parse_quote!((#(#elements,)*)))) + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de2>, + { + let key: String = map.next_key()?.ok_or_else(|| { + serde::de::Error::custom("expected a single key") + })?; + if key == "uuid_kind" { + // uuid kinds must be plain identifiers + let value: ParseWrapper = map.next_value()?; + Ok(PrimaryKeyType::new_typed_uuid(&value)) + } else { + Err(serde::de::Error::custom( + "expected a single key `uuid_kind`", + )) + } + } + } + + deserializer.deserialize_any(PrimaryKeyVisitor).map(InputPrimaryKeyType) + } +} + /// How to generate the Polar snippet for this resource -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Debug)] enum PolarSnippet { /// Don't generate it at all -- it's generated elsewhere Custom, @@ -185,9 +276,8 @@ fn do_authz_resource( let resource_name = format_ident!("{}", input.name); let parent_resource_name = format_ident!("{}", input.parent); let parent_as_snake = heck::AsSnakeCase(&input.parent).to_string(); - let primary_key_type = &*input.primary_key; - let input_key_type = - &**input.input_key.as_ref().unwrap_or(&input.primary_key); + let primary_key_type = input.primary_key.0.external(); + let input_key_type = input.input_key.as_deref().unwrap_or(primary_key_type); let (has_role_body, as_roles_body, api_resource_roles_trait) = if input.roles_allowed { @@ -493,6 +583,16 @@ mod tests { }) .unwrap(); assert_contents("outputs/instance.txt", &pretty_format(output)); + + let output = do_authz_resource(quote! { + name = "Rack", + parent = "Fleet", + primary_key = { uuid_kind = RackKind }, + roles_allowed = false, + polar_snippet = FleetChild, + }) + .unwrap(); + assert_contents("outputs/rack.txt", &pretty_format(output)); } fn pretty_format(input: TokenStream) -> String { diff --git a/nexus/db-macros/Cargo.toml b/nexus/db-macros/Cargo.toml index 46e5d9a5d6..8032ba814d 100644 --- a/nexus/db-macros/Cargo.toml +++ b/nexus/db-macros/Cargo.toml @@ -10,6 +10,7 @@ proc-macro = true [dependencies] heck.workspace = true +nexus-macros-common.workspace = true proc-macro2.workspace = true quote.workspace = true serde.workspace = true diff --git a/nexus/db-macros/outputs/asset_with_uuid_kind.txt b/nexus/db-macros/outputs/asset_with_uuid_kind.txt new file mode 100644 index 0000000000..a4a756248d --- /dev/null +++ b/nexus/db-macros/outputs/asset_with_uuid_kind.txt @@ -0,0 +1,49 @@ +///Auto-generated identity for [`AssetWithUuidKind`] from deriving [`macro@Asset`]. +#[derive( + Clone, + Debug, + PartialEq, + Selectable, + Queryable, + Insertable, + serde::Serialize, + serde::Deserialize +)] +#[diesel(table_name = my_target)] +pub struct AssetWithUuidKindIdentity { + pub id: crate::typed_uuid::DbTypedUuid<::omicron_uuid_kinds::CustomKind>, + pub time_created: ::chrono::DateTime<::chrono::Utc>, + pub time_modified: ::chrono::DateTime<::chrono::Utc>, +} +impl AssetWithUuidKindIdentity { + pub fn new( + id: ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::CustomKind>, + ) -> Self { + let now = ::chrono::Utc::now(); + Self { + id: crate::to_db_typed_uuid(id), + time_created: now, + time_modified: now, + } + } +} +trait __AssetWithUuidKindIdentityMarker {} +impl __AssetWithUuidKindIdentityMarker for AssetWithUuidKindIdentity {} +const _: () = { + fn assert_identity() {} + fn assert_all() { + assert_identity::(); + } +}; +impl ::nexus_types::identity::Asset for AssetWithUuidKind { + type IdType = ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::CustomKind>; + fn id(&self) -> ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::CustomKind> { + ::omicron_uuid_kinds::TypedUuid::from(self.identity.id) + } + fn time_created(&self) -> ::chrono::DateTime<::chrono::Utc> { + self.identity.time_created + } + fn time_modified(&self) -> ::chrono::DateTime<::chrono::Utc> { + self.identity.time_modified + } +} diff --git a/nexus/db-macros/outputs/project.txt b/nexus/db-macros/outputs/project.txt index 9f4b5cfaa2..333cdb7acf 100644 --- a/nexus/db-macros/outputs/project.txt +++ b/nexus/db-macros/outputs/project.txt @@ -373,7 +373,9 @@ impl<'a> Project<'a> { e, ErrorHandler::NotFoundByLookup( ResourceType::Project, - LookupType::ById(v0.clone()), + LookupType::ById( + ::omicron_uuid_kinds::GenericUuid::into_untyped_uuid(*v0), + ), ), ) })?; @@ -386,7 +388,7 @@ impl<'a> Project<'a> { let authz_project = Self::make_authz( &authz_silo, &db_row, - LookupType::ById(v0.clone()), + LookupType::ById(::omicron_uuid_kinds::GenericUuid::into_untyped_uuid(*v0)), ); Ok((authz_silo, authz_project, db_row)) } diff --git a/nexus/db-macros/outputs/resource_with_uuid_kind.txt b/nexus/db-macros/outputs/resource_with_uuid_kind.txt new file mode 100644 index 0000000000..8d1628e7c9 --- /dev/null +++ b/nexus/db-macros/outputs/resource_with_uuid_kind.txt @@ -0,0 +1,66 @@ +///Auto-generated identity for [`ResourceWithUuidKind`] from deriving [`macro@Resource`]. +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Selectable, + Queryable, + Insertable, + serde::Serialize, + serde::Deserialize +)] +#[diesel(table_name = my_target)] +pub struct ResourceWithUuidKindIdentity { + pub id: crate::typed_uuid::DbTypedUuid<::omicron_uuid_kinds::CustomKind>, + pub name: crate::db::model::Name, + pub description: ::std::string::String, + pub time_created: ::chrono::DateTime<::chrono::Utc>, + pub time_modified: ::chrono::DateTime<::chrono::Utc>, + pub time_deleted: ::std::option::Option>, +} +impl ResourceWithUuidKindIdentity { + pub fn new( + id: ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::CustomKind>, + params: ::omicron_common::api::external::IdentityMetadataCreateParams, + ) -> Self { + let now = ::chrono::Utc::now(); + Self { + id: crate::to_db_typed_uuid(id), + name: params.name.into(), + description: params.description, + time_created: now, + time_modified: now, + time_deleted: None, + } + } +} +trait __ResourceWithUuidKindIdentityMarker {} +impl __ResourceWithUuidKindIdentityMarker for ResourceWithUuidKindIdentity {} +const _: () = { + fn assert_identity() {} + fn assert_all() { + assert_identity::(); + } +}; +impl ::nexus_types::identity::Resource for ResourceWithUuidKind { + type IdType = ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::CustomKind>; + fn id(&self) -> ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::CustomKind> { + ::omicron_uuid_kinds::TypedUuid::from(self.identity.id) + } + fn name(&self) -> &::omicron_common::api::external::Name { + &self.identity.name.0 + } + fn description(&self) -> &str { + &self.identity.description + } + fn time_created(&self) -> ::chrono::DateTime<::chrono::Utc> { + self.identity.time_created + } + fn time_modified(&self) -> ::chrono::DateTime<::chrono::Utc> { + self.identity.time_modified + } + fn time_deleted(&self) -> ::std::option::Option<::chrono::DateTime<::chrono::Utc>> { + self.identity.time_deleted + } +} diff --git a/nexus/db-macros/outputs/silo_user.txt b/nexus/db-macros/outputs/silo_user.txt index 2c9568ff54..1f42db54e8 100644 --- a/nexus/db-macros/outputs/silo_user.txt +++ b/nexus/db-macros/outputs/silo_user.txt @@ -169,14 +169,16 @@ impl<'a> SiloUser<'a> { e, ErrorHandler::NotFoundByLookup( ResourceType::SiloUser, - LookupType::ById(v0.clone()), + LookupType::ById( + ::omicron_uuid_kinds::GenericUuid::into_untyped_uuid(*v0), + ), ), ) })?; let authz_silo_user = Self::make_authz( &&authz::FLEET, &db_row, - LookupType::ById(v0.clone()), + LookupType::ById(::omicron_uuid_kinds::GenericUuid::into_untyped_uuid(*v0)), ); Ok((authz_silo_user, db_row)) } diff --git a/nexus/db-macros/outputs/sled.txt b/nexus/db-macros/outputs/sled.txt new file mode 100644 index 0000000000..30654b0e90 --- /dev/null +++ b/nexus/db-macros/outputs/sled.txt @@ -0,0 +1,182 @@ +///Selects a resource of type Sled (or any of its children, using the functions on this struct) for lookup or fetch +pub enum Sled<'a> { + /// An error occurred while selecting the resource + /// + /// This error will be returned by any lookup/fetch attempts. + Error(Root<'a>, Error), + /// We're looking for a resource with the given primary key + /// + /// This has no parent container -- a by-id lookup is always global + PrimaryKey( + Root<'a>, + ::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::SledKind>, + ), +} +impl<'a> Sled<'a> { + /// Fetch the record corresponding to the selected resource + /// + /// This is equivalent to `fetch_for(authz::Action::Read)`. + pub async fn fetch(&self) -> LookupResult<(authz::Sled, nexus_db_model::Sled)> { + self.fetch_for(authz::Action::Read).await + } + /// Turn the Result of [`fetch`] into a Result, E>. + pub async fn optional_fetch( + &self, + ) -> LookupResult> { + self.optional_fetch_for(authz::Action::Read).await + } + /// Fetch the record corresponding to the selected resource and + /// check whether the caller is allowed to do the specified `action` + /// + /// The return value is a tuple that also includes the `authz` + /// objects for all resources along the path to this one (i.e., all + /// parent resources) and the authz object for this resource itself. + /// These objects are useful for identifying those resources by + /// id, for doing other authz checks, or for looking up related + /// objects. + pub async fn fetch_for( + &self, + action: authz::Action, + ) -> LookupResult<(authz::Sled, nexus_db_model::Sled)> { + let lookup = self.lookup_root(); + let opctx = &lookup.opctx; + let datastore = &lookup.datastore; + match &self { + Sled::Error(_, error) => Err(error.clone()), + Sled::PrimaryKey(_, v0) => { + Self::fetch_by_id_for(opctx, datastore, v0, action).await + } + } + } + /// Turn the Result of [`fetch_for`] into a Result, E>. + pub async fn optional_fetch_for( + &self, + action: authz::Action, + ) -> LookupResult> { + let result = self.fetch_for(action).await; + match result { + Err(Error::ObjectNotFound { type_name: _, lookup_type: _ }) => Ok(None), + _ => Ok(Some(result?)), + } + } + /// Fetch an `authz` object for the selected resource and check + /// whether the caller is allowed to do the specified `action` + /// + /// The return value is a tuple that also includes the `authz` + /// objects for all resources along the path to this one (i.e., all + /// parent resources) and the authz object for this resource itself. + /// These objects are useful for identifying those resources by + /// id, for doing other authz checks, or for looking up related + /// objects. + pub async fn lookup_for( + &self, + action: authz::Action, + ) -> LookupResult<(authz::Sled,)> { + let lookup = self.lookup_root(); + let opctx = &lookup.opctx; + let (authz_sled,) = self.lookup().await?; + opctx.authorize(action, &authz_sled).await?; + Ok((authz_sled,)) + } + /// Turn the Result of [`lookup_for`] into a Result, E>. + pub async fn optional_lookup_for( + &self, + action: authz::Action, + ) -> LookupResult> { + let result = self.lookup_for(action).await; + match result { + Err(Error::ObjectNotFound { type_name: _, lookup_type: _ }) => Ok(None), + _ => Ok(Some(result?)), + } + } + /// Fetch the "authz" objects for the selected resource and all its + /// parents + /// + /// This function does not check whether the caller has permission + /// to read this information. That's why it's not `pub`. Outside + /// this module, you want `lookup_for(authz::Action)`. + async fn lookup(&self) -> LookupResult<(authz::Sled,)> { + let lookup = self.lookup_root(); + let opctx = &lookup.opctx; + let datastore = &lookup.datastore; + match &self { + Sled::Error(_, error) => Err(error.clone()), + Sled::PrimaryKey(_, v0) => { + let (authz_sled, _) = Self::lookup_by_id_no_authz(opctx, datastore, v0) + .await?; + Ok((authz_sled,)) + } + } + } + /// Build the `authz` object for this resource + fn make_authz( + authz_parent: &authz::Fleet, + db_row: &nexus_db_model::Sled, + lookup_type: LookupType, + ) -> authz::Sled { + authz::Sled::with_primary_key(authz_parent.clone(), db_row.id(), lookup_type) + } + /// Getting the [`LookupPath`] for this lookup + /// + /// This is used when we actually query the database. At that + /// point, we need the `OpContext` and `DataStore` that are being + /// used for this lookup. + fn lookup_root(&self) -> &LookupPath<'a> { + match &self { + Sled::Error(root, ..) => root.lookup_root(), + Sled::PrimaryKey(root, ..) => root.lookup_root(), + } + } + /// Fetch the database row for a resource by doing a lookup by id + /// + /// This function checks whether the caller has permissions to read + /// the requested data. However, it's not intended to be used + /// outside this module. See `fetch_for(authz::Action)`. + async fn fetch_by_id_for( + opctx: &OpContext, + datastore: &DataStore, + v0: &::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::SledKind>, + action: authz::Action, + ) -> LookupResult<(authz::Sled, nexus_db_model::Sled)> { + let (authz_sled, db_row) = Self::lookup_by_id_no_authz(opctx, datastore, v0) + .await?; + opctx.authorize(action, &authz_sled).await?; + Ok((authz_sled, db_row)) + } + /// Lowest-level function for looking up a resource in the database + /// by id + /// + /// This function does not check whether the caller has permission + /// to read this information. That's why it's not `pub`. Outside + /// this module, you want `fetch()` or `lookup_for(authz::Action)`. + async fn lookup_by_id_no_authz( + opctx: &OpContext, + datastore: &DataStore, + v0: &::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::SledKind>, + ) -> LookupResult<(authz::Sled, nexus_db_model::Sled)> { + use db::schema::sled::dsl; + let db_row = dsl::sled + .filter(dsl::time_deleted.is_null()) + .filter(dsl::id.eq(::nexus_db_model::to_db_typed_uuid(v0.clone()))) + .select(nexus_db_model::Sled::as_select()) + .get_result_async(&*datastore.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| { + public_error_from_diesel( + e, + ErrorHandler::NotFoundByLookup( + ResourceType::Sled, + LookupType::ById( + ::omicron_uuid_kinds::GenericUuid::into_untyped_uuid(*v0), + ), + ), + ) + })?; + let authz_sled = Self::make_authz( + &&authz::FLEET, + &db_row, + LookupType::ById(::omicron_uuid_kinds::GenericUuid::into_untyped_uuid(*v0)), + ); + Ok((authz_sled, db_row)) + } +} diff --git a/nexus/db-macros/src/lib.rs b/nexus/db-macros/src/lib.rs index fd15b59128..fd9aae4b0a 100644 --- a/nexus/db-macros/src/lib.rs +++ b/nexus/db-macros/src/lib.rs @@ -13,13 +13,17 @@ extern crate proc_macro; +use nexus_macros_common::PrimaryKeyType; use proc_macro2::TokenStream; use quote::{format_ident, quote}; +use serde_tokenstream::ParseWrapper; use syn::spanned::Spanned; -use syn::{Data, DataStruct, DeriveInput, Error, Fields, Ident}; +use syn::{parse_quote, Data, DataStruct, DeriveInput, Error, Fields, Ident}; mod lookup; mod subquery; +#[cfg(test)] +mod test_helpers; /// Defines a structure and helper functions for looking up resources /// @@ -65,8 +69,24 @@ mod subquery; /// } /// ``` /// -/// These define `Project<'a>` and `Instance<'a>`. For more on these structs -/// and how they're used, see nexus/src/db/lookup.rs. +/// These define `Project<'a>` and `Instance<'a>`. +/// +/// It is also possible to use a `TypedUuid` as a key column, by specifying +/// `uuid_kind` rather than `rust_type`. For example: +/// +/// ```ignore +/// lookup_resource! { +/// name = "Sled", +/// ancestors = [ "Organization", "Project" ], +/// children = [], +/// lookup_by_name = true, +/// soft_deletes = true, +/// primary_key_columns = [ { column_name = "id", uuid_kind = SledType } ] +/// } +/// ``` +/// +/// For more on these structs and how they're used, see +/// nexus/db-queries/src/lookup.rs. // Allow private intra-doc links. This is useful because the `Input` struct // cannot be exported (since we're a proc macro crate, and we can't expose // a struct), but its documentation is very useful. @@ -81,11 +101,14 @@ pub fn lookup_resource( } } -/// Looks for a Meta-style attribute with a particular identifier. +/// Looks for a Diesel Meta-style attribute with a particular identifier. /// /// As an example, for an attribute like `#[diesel(foo = bar)]`, we can find this /// attribute by calling `get_nv_attr(&item.attrs, "foo")`. -fn get_nv_attr(attrs: &[syn::Attribute], name: &str) -> Option { +fn get_diesel_nv_attr( + attrs: &[syn::Attribute], + name: &str, +) -> Option { attrs .iter() .filter(|attr| attr.path().is_ident("diesel")) @@ -141,11 +164,21 @@ pub fn subquery_target( } // Describes which derive macro is being used; allows sharing common code. +#[derive(Clone, Copy, Debug)] enum IdentityVariant { Asset, Resource, } +impl IdentityVariant { + fn attr_name(&self) -> &'static str { + match self { + IdentityVariant::Asset => "asset", + IdentityVariant::Resource => "resource", + } + } +} + /// Implements the "Resource" trait, and generates a bespoke Identity struct. /// /// Many tables within our database make use of common fields, @@ -160,7 +193,7 @@ enum IdentityVariant { /// Although these fields can be refactored into a common structure (to be used /// within the context of Diesel) they must be uniquely identified for a single /// table. -#[proc_macro_derive(Resource)] +#[proc_macro_derive(Resource, attributes(resource))] pub fn resource_target( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { @@ -175,7 +208,7 @@ pub fn resource_target( /// - ID /// - Time Created /// - Time Modified -#[proc_macro_derive(Asset)] +#[proc_macro_derive(Asset, attributes(asset))] pub fn asset_target(input: proc_macro::TokenStream) -> proc_macro::TokenStream { derive_impl(input.into(), IdentityVariant::Asset) .unwrap_or_else(|e| e.to_compile_error()) @@ -208,18 +241,22 @@ fn derive_impl( let name = &item.ident; // Ensure that the "table_name" attribute exists, and get it. - let table_nv = get_nv_attr(&item.attrs, "table_name").ok_or_else(|| { - Error::new( - item.span(), - format!( - "Resource needs 'table_name' attribute.\n\ + let table_nv = + get_diesel_nv_attr(&item.attrs, "table_name").ok_or_else(|| { + Error::new( + item.span(), + format!( + "Resource needs 'table_name' attribute.\n\ Try adding #[diesel(table_name = your_table_name)] to {}.", - name - ), - ) - })?; + name + ), + ) + })?; let table_name = table_nv.value; + let input = MacroAttributes::parse_from_attrs(&item.attrs, flavor)?; + let uuid_ty = input.uuid_ty(); + // Ensure that a field named "identity" exists within this struct. if let Data::Struct(ref data) = item.data { // We extract type of "identity" and enforce it is the expected type @@ -237,28 +274,64 @@ fn derive_impl( ) })?; - return Ok(build(name, &table_name, &field.ty, flavor)); + return Ok(build(name, &table_name, &field.ty, &uuid_ty, flavor)); } Err(Error::new(item.span(), "Resource can only be derived for structs")) } +/// Attributes specific to the `Resource` and `Asset` derive macros. +#[derive(serde::Deserialize)] +#[serde(deny_unknown_fields)] +struct MacroAttributes { + /// The `TypedUuid` type parameter. + #[serde(default)] + uuid_kind: Option>, +} + +impl MacroAttributes { + fn parse_from_attrs( + attrs: &[syn::Attribute], + flavor: IdentityVariant, + ) -> syn::Result { + let inner_attrs = attrs + .iter() + .filter_map(|attr| { + attr.path().is_ident(flavor.attr_name()).then(|| { + let meta_list = attr.meta.require_list()?; + Ok::<_, syn::Error>(&meta_list.tokens) + }) + }) + .collect::, _>>()?; + let tokens = quote! { #(#inner_attrs,)* }; + serde_tokenstream::from_tokenstream(&tokens) + } + + fn uuid_ty(&self) -> PrimaryKeyType { + self.uuid_kind.as_ref().map_or_else( + || PrimaryKeyType::Standard(parse_quote!(::uuid::Uuid)), + |v| PrimaryKeyType::new_typed_uuid(v), + ) + } +} + // Emits generated structures, depending on the requested flavor of identity. fn build( struct_name: &Ident, table_name: &syn::Path, observed_identity_ty: &syn::Type, + uuid_ty: &PrimaryKeyType, flavor: IdentityVariant, ) -> TokenStream { let (identity_struct, resource_impl) = { match flavor { IdentityVariant::Resource => ( - build_resource_identity(struct_name, table_name), - build_resource_impl(struct_name, observed_identity_ty), + build_resource_identity(struct_name, table_name, uuid_ty), + build_resource_impl(struct_name, observed_identity_ty, uuid_ty), ), IdentityVariant::Asset => ( - build_asset_identity(struct_name, table_name), - build_asset_impl(struct_name, observed_identity_ty), + build_asset_identity(struct_name, table_name, uuid_ty), + build_asset_impl(struct_name, observed_identity_ty, uuid_ty), ), } }; @@ -272,18 +345,25 @@ fn build( fn build_resource_identity( struct_name: &Ident, table_name: &syn::Path, + uuid_ty: &PrimaryKeyType, ) -> TokenStream { let identity_doc = format!( "Auto-generated identity for [`{}`] from deriving [`macro@Resource`].", struct_name, ); let identity_name = format_ident!("{}Identity", struct_name); + + let external_uuid_ty = uuid_ty.external(); + let db_uuid_ty = uuid_ty.db(); + let convert_external_to_db = + uuid_ty.external_to_db_nexus_db_model(quote! { id }); + quote! { #[doc = #identity_doc] #[derive(Clone, Debug, PartialEq, Eq, Selectable, Queryable, Insertable, serde::Serialize, serde::Deserialize)] #[diesel(table_name = #table_name) ] pub struct #identity_name { - pub id: ::uuid::Uuid, + pub id: #db_uuid_ty, pub name: crate::db::model::Name, pub description: ::std::string::String, pub time_created: ::chrono::DateTime<::chrono::Utc>, @@ -293,12 +373,12 @@ fn build_resource_identity( impl #identity_name { pub fn new( - id: ::uuid::Uuid, + id: #external_uuid_ty, params: ::omicron_common::api::external::IdentityMetadataCreateParams ) -> Self { let now = ::chrono::Utc::now(); Self { - id, + id: #convert_external_to_db, name: params.name.into(), description: params.description, time_created: now, @@ -314,29 +394,36 @@ fn build_resource_identity( fn build_asset_identity( struct_name: &Ident, table_name: &syn::Path, + uuid_ty: &PrimaryKeyType, ) -> TokenStream { let identity_doc = format!( "Auto-generated identity for [`{}`] from deriving [`macro@Asset`].", struct_name, ); let identity_name = format_ident!("{}Identity", struct_name); + + let external_uuid_ty = uuid_ty.external(); + let db_uuid_ty = uuid_ty.db(); + let convert_external_to_db = + uuid_ty.external_to_db_nexus_db_model(quote! { id }); + quote! { #[doc = #identity_doc] #[derive(Clone, Debug, PartialEq, Selectable, Queryable, Insertable, serde::Serialize, serde::Deserialize)] #[diesel(table_name = #table_name) ] pub struct #identity_name { - pub id: ::uuid::Uuid, + pub id: #db_uuid_ty, pub time_created: ::chrono::DateTime<::chrono::Utc>, pub time_modified: ::chrono::DateTime<::chrono::Utc>, } impl #identity_name { pub fn new( - id: ::uuid::Uuid, + id: #external_uuid_ty, ) -> Self { let now = ::chrono::Utc::now(); Self { - id, + id: #convert_external_to_db, time_created: now, time_modified: now, } @@ -349,9 +436,15 @@ fn build_asset_identity( fn build_resource_impl( struct_name: &Ident, observed_identity_type: &syn::Type, + uuid_ty: &PrimaryKeyType, ) -> TokenStream { let identity_trait = format_ident!("__{}IdentityMarker", struct_name); let identity_name = format_ident!("{}Identity", struct_name); + + let external_uuid_ty = uuid_ty.external(); + let convert_db_to_external = + uuid_ty.db_to_external(quote! { self.identity.id }); + quote! { // Verify that the field named "identity" is actually the generated // type within the struct deriving Resource. @@ -365,8 +458,10 @@ fn build_resource_impl( }; impl ::nexus_types::identity::Resource for #struct_name { - fn id(&self) -> ::uuid::Uuid { - self.identity.id + type IdType = #external_uuid_ty; + + fn id(&self) -> #external_uuid_ty { + #convert_db_to_external } fn name(&self) -> &::omicron_common::api::external::Name { @@ -396,9 +491,15 @@ fn build_resource_impl( fn build_asset_impl( struct_name: &Ident, observed_identity_type: &syn::Type, + uuid_ty: &PrimaryKeyType, ) -> TokenStream { let identity_trait = format_ident!("__{}IdentityMarker", struct_name); let identity_name = format_ident!("{}Identity", struct_name); + + let external_uuid_ty = uuid_ty.external(); + let convert_db_to_external = + uuid_ty.db_to_external(quote! { self.identity.id }); + quote! { // Verify that the field named "identity" is actually the generated // type within the struct deriving Asset. @@ -412,8 +513,10 @@ fn build_asset_impl( }; impl ::nexus_types::identity::Asset for #struct_name { - fn id(&self) -> ::uuid::Uuid { - self.identity.id + type IdType = #external_uuid_ty; + + fn id(&self) -> #external_uuid_ty { + #convert_db_to_external } fn time_created(&self) -> ::chrono::DateTime<::chrono::Utc> { @@ -431,6 +534,10 @@ fn build_asset_impl( mod tests { use super::*; + use crate::test_helpers::pretty_format; + + use expectorate::assert_contents; + #[test] fn test_derive_metadata_identity_fails_without_table_name() { let out = derive_impl( @@ -532,4 +639,64 @@ mod tests { ); assert!(out.is_ok()); } + + #[test] + fn test_derive_with_unknown_field() { + let out = derive_impl( + quote! { + #[derive(Resource)] + #[diesel(table_name = my_target)] + #[resource(foo = bar)] + struct MyTarget { + identity: MyTargetIdentity, + name: String, + is_cool: bool, + } + }, + IdentityVariant::Resource, + ); + let error = out.expect_err("input has unknown parameter for resource"); + assert!(error.to_string().contains("unknown field `foo`")); + } + + #[test] + fn test_derive_snapshots() { + let out = derive_impl( + quote! { + #[derive(Asset)] + #[diesel(table_name = my_target)] + #[asset(uuid_kind = CustomKind)] + struct AssetWithUuidKind { + identity: AssetWithUuidKindIdentity, + name: String, + is_cool: bool, + } + }, + IdentityVariant::Asset, + ) + .unwrap(); + assert_contents( + "outputs/asset_with_uuid_kind.txt", + &pretty_format(out), + ); + + let out = derive_impl( + quote! { + #[derive(Resource)] + #[diesel(table_name = my_target)] + #[resource(uuid_kind = CustomKind)] + struct ResourceWithUuidKind { + identity: ResourceWithUuidKindIdentity, + name: String, + is_cool: bool, + } + }, + IdentityVariant::Resource, + ) + .unwrap(); + assert_contents( + "outputs/resource_with_uuid_kind.txt", + &pretty_format(out), + ); + } } diff --git a/nexus/db-macros/src/lookup.rs b/nexus/db-macros/src/lookup.rs index c04c373ccb..3d3e93a863 100644 --- a/nexus/db-macros/src/lookup.rs +++ b/nexus/db-macros/src/lookup.rs @@ -6,10 +6,11 @@ //! //! See nexus/src/db/lookup.rs. +use nexus_macros_common::PrimaryKeyType; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use serde_tokenstream::ParseWrapper; -use std::ops::Deref; +use syn::spanned::Spanned; // // INPUT (arguments to the macro) @@ -39,7 +40,7 @@ pub struct Input { /// whether lookup by name is supported (usually within the parent collection) lookup_by_name: bool, /// Description of the primary key columns - primary_key_columns: Vec, + primary_key_columns: Vec, /// This resources supports soft-deletes soft_deletes: bool, /// This resource appears under the `Silo` hierarchy, but nevertheless @@ -52,10 +53,47 @@ pub struct Input { visible_outside_silo: bool, } -#[derive(serde::Deserialize)] struct PrimaryKeyColumn { column_name: String, - rust_type: ParseWrapper, + ty: PrimaryKeyType, +} + +#[derive(serde::Deserialize)] +struct InputPrimaryKeyColumn { + column_name: String, + // Exactly one of rust_type and uuid_kind must be specified. + #[serde(default)] + rust_type: Option>, + #[serde(default)] + uuid_kind: Option>, +} + +impl InputPrimaryKeyColumn { + fn validate(self) -> syn::Result { + let ty = match (self.rust_type, self.uuid_kind) { + (Some(rust_type), Some(_)) => { + return Err(syn::Error::new( + rust_type.span(), + "only one of rust_type and uuid_kind may be specified", + )); + } + (Some(rust_type), None) => { + PrimaryKeyType::Standard(rust_type.into_inner()) + } + (None, Some(uuid_kind)) => { + PrimaryKeyType::new_typed_uuid(&uuid_kind) + } + (None, None) => { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + "for primary_key_columns, \ + one of rust_type and uuid_kind must be specified", + )); + } + }; + + Ok(PrimaryKeyColumn { column_name: self.column_name, ty }) + } } // @@ -106,7 +144,7 @@ pub struct Config { } impl Config { - fn for_input(input: Input) -> Config { + fn for_input(input: Input) -> syn::Result { let resource = Resource::for_name(&input.name); let mut path_types: Vec<_> = @@ -127,7 +165,13 @@ impl Config { let silo_restricted = !input.visible_outside_silo && input.ancestors.iter().any(|s| s == "Silo"); - Config { + let primary_key_columns: Vec<_> = input + .primary_key_columns + .into_iter() + .map(|c| c.validate()) + .collect::>()?; + + Ok(Config { resource, silo_restricted, path_types, @@ -135,9 +179,9 @@ impl Config { parent, child_resources, lookup_by_name: input.lookup_by_name, - primary_key_columns: input.primary_key_columns, + primary_key_columns, soft_deletes: input.soft_deletes, - } + }) } } @@ -172,7 +216,7 @@ pub fn lookup_resource( raw_input: TokenStream, ) -> Result { let input = serde_tokenstream::from_tokenstream::(&raw_input)?; - let config = Config::for_input(input); + let config = Config::for_input(input)?; let resource_name = &config.resource.name; let the_basics = generate_struct(&config); @@ -204,8 +248,7 @@ fn generate_struct(config: &Config) -> TokenStream { functions on this struct) for lookup or fetch", resource_name ); - let pkey_types = - config.primary_key_columns.iter().map(|c| c.rust_type.deref()); + let pkey_types = config.primary_key_columns.iter().map(|c| c.ty.external()); /* configure the lookup enum */ let name_variant = if config.lookup_by_name { @@ -706,11 +749,8 @@ fn generate_database_functions(config: &Config) -> TokenStream { let resource_as_snake = format_ident!("{}", &config.resource.name_as_snake); let path_types = &config.path_types; let path_authz_names = &config.path_authz_names; - let pkey_types: Vec<_> = config - .primary_key_columns - .iter() - .map(|c| c.rust_type.deref()) - .collect(); + let pkey_types: Vec<_> = + config.primary_key_columns.iter().map(|c| c.ty.external()).collect(); let pkey_column_names = config .primary_key_columns .iter() @@ -721,6 +761,18 @@ fn generate_database_functions(config: &Config) -> TokenStream { .enumerate() .map(|(i, _)| format_ident!("v{}", i)) .collect(); + + // Generate tokens that also perform conversion from external to db types, + // if necessary. + let pkey_names_convert: Vec<_> = config + .primary_key_columns + .iter() + .zip(pkey_names.iter()) + .map(|(col, name)| { + col.ty.external_to_db_other(quote! { #name.clone() }) + }) + .collect(); + let ( parent_lookup_arg_formal, parent_lookup_arg_actual, @@ -837,7 +889,11 @@ fn generate_database_functions(config: &Config) -> TokenStream { let lookup_type = if config.primary_key_columns.len() == 1 && config.primary_key_columns[0].column_name == "id" { - quote! { LookupType::ById(#(#pkey_names.clone())*) } + let pkey_name = &pkey_names[0]; + let by_id = quote! { + ::omicron_uuid_kinds::GenericUuid::into_untyped_uuid(*#pkey_name) + }; + quote! { LookupType::ById(#by_id) } } else { let fmtstr = config .primary_key_columns @@ -889,7 +945,7 @@ fn generate_database_functions(config: &Config) -> TokenStream { let db_row = dsl::#resource_as_snake #soft_delete_filter - #(.filter(dsl::#pkey_column_names.eq(#pkey_names.clone())))* + #(.filter(dsl::#pkey_column_names.eq(#pkey_names_convert)))* .select(nexus_db_model::#resource_name::as_select()) .get_result_async(&*datastore.pool_connection_authorized(opctx).await?) .await @@ -915,9 +971,10 @@ fn generate_database_functions(config: &Config) -> TokenStream { #[cfg(test)] mod test { + use crate::test_helpers::pretty_format; + use super::lookup_resource; use expectorate::assert_contents; - use proc_macro2::TokenStream; use quote::quote; /// Ensure that generated code is as expected. @@ -953,6 +1010,17 @@ mod test { .unwrap(); assert_contents("outputs/silo_user.txt", &pretty_format(output)); + let output = lookup_resource(quote! { + name = "Sled", + ancestors = [], + children = [], + lookup_by_name = false, + soft_deletes = true, + primary_key_columns = [ { column_name = "id", uuid_kind = SledKind } ] + }) + .unwrap(); + assert_contents("outputs/sled.txt", &pretty_format(output)); + let output = lookup_resource(quote! { name = "UpdateArtifact", ancestors = [], @@ -968,9 +1036,4 @@ mod test { .unwrap(); assert_contents("outputs/update_artifact.txt", &pretty_format(output)); } - - fn pretty_format(input: TokenStream) -> String { - let parsed = syn::parse2(input).unwrap(); - prettyplease::unparse(&parsed) - } } diff --git a/nexus/db-macros/src/test_helpers.rs b/nexus/db-macros/src/test_helpers.rs new file mode 100644 index 0000000000..c069cb5c02 --- /dev/null +++ b/nexus/db-macros/src/test_helpers.rs @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use proc_macro2::TokenStream; + +pub(crate) fn pretty_format(input: TokenStream) -> String { + let parsed = syn::parse2(input).unwrap(); + prettyplease::unparse(&parsed) +} diff --git a/nexus/db-model/Cargo.toml b/nexus/db-model/Cargo.toml index 477ce7d11f..43b6f6a4b0 100644 --- a/nexus/db-model/Cargo.toml +++ b/nexus/db-model/Cargo.toml @@ -10,11 +10,13 @@ omicron-rpaths.workspace = true [dependencies] anyhow.workspace = true chrono.workspace = true +derive-where.workspace = true diesel = { workspace = true, features = ["postgres", "r2d2", "chrono", "serde_json", "network-address", "uuid"] } hex.workspace = true ipnetwork.workspace = true macaddr.workspace = true newtype_derive.workspace = true +omicron-uuid-kinds.workspace = true parse-display.workspace = true # See omicron-rpaths for more about the "pq-sys" dependency. pq-sys = "*" diff --git a/nexus/db-model/src/lib.rs b/nexus/db-model/src/lib.rs index b77d56059e..ecbb8365fe 100644 --- a/nexus/db-model/src/lib.rs +++ b/nexus/db-model/src/lib.rs @@ -81,6 +81,7 @@ mod snapshot; mod ssh_key; mod switch; mod tuf_repo; +mod typed_uuid; mod unsigned; mod user_builtin; mod utilization; @@ -170,6 +171,7 @@ pub use switch::*; pub use switch_interface::*; pub use switch_port::*; pub use tuf_repo::*; +pub use typed_uuid::to_db_typed_uuid; pub use user_builtin::*; pub use utilization::*; pub use virtual_provisioning_collection::*; diff --git a/nexus/db-model/src/switch_interface.rs b/nexus/db-model/src/switch_interface.rs index 71673354ea..78f502ed4c 100644 --- a/nexus/db-model/src/switch_interface.rs +++ b/nexus/db-model/src/switch_interface.rs @@ -9,6 +9,8 @@ use ipnetwork::IpNetwork; use nexus_types::external_api::params; use nexus_types::identity::Asset; use omicron_common::api::external; +use omicron_uuid_kinds::LoopbackAddressKind; +use omicron_uuid_kinds::TypedUuid; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -105,6 +107,7 @@ impl Into for SwitchVlanInterfaceConfig { Deserialize, )] #[diesel(table_name = loopback_address)] +#[asset(uuid_kind = LoopbackAddressKind)] pub struct LoopbackAddress { #[diesel(embed)] pub identity: LoopbackAddressIdentity, @@ -118,7 +121,7 @@ pub struct LoopbackAddress { impl LoopbackAddress { pub fn new( - id: Option, + id: Option>, address_lot_block_id: Uuid, rsvd_address_lot_block_id: Uuid, rack_id: Uuid, @@ -128,7 +131,7 @@ impl LoopbackAddress { ) -> Self { Self { identity: LoopbackAddressIdentity::new( - id.unwrap_or(Uuid::new_v4()), + id.unwrap_or(TypedUuid::new_v4()), ), address_lot_block_id, rsvd_address_lot_block_id, diff --git a/nexus/db-model/src/tuf_repo.rs b/nexus/db-model/src/tuf_repo.rs index 5fa2a0aac7..4a64566a62 100644 --- a/nexus/db-model/src/tuf_repo.rs +++ b/nexus/db-model/src/tuf_repo.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use crate::{ schema::{tuf_artifact, tuf_repo, tuf_repo_artifact}, + typed_uuid::DbTypedUuid, SemverVersion, }; use chrono::{DateTime, Utc}; @@ -17,6 +18,8 @@ use omicron_common::{ ArtifactKind, }, }; +use omicron_uuid_kinds::TufRepoKind; +use omicron_uuid_kinds::TypedUuid; use serde::{Deserialize, Serialize}; use std::fmt; use uuid::Uuid; @@ -73,7 +76,7 @@ impl TufRepoDescription { )] #[diesel(table_name = tuf_repo)] pub struct TufRepo { - pub id: Uuid, + pub id: DbTypedUuid, pub time_created: DateTime, // XXX: We're overloading ArtifactHash here to also mean the hash of the // repository zip itself. @@ -94,7 +97,7 @@ impl TufRepo { file_name: String, ) -> Self { Self { - id: Uuid::new_v4(), + id: TypedUuid::new_v4().into(), time_created: Utc::now(), sha256, targets_role_version: targets_role_version as i64, @@ -132,8 +135,8 @@ impl TufRepo { } /// Returns the repository's ID. - pub fn id(&self) -> Uuid { - self.id + pub fn id(&self) -> TypedUuid { + self.id.into() } /// Returns the targets role version. diff --git a/nexus/db-model/src/typed_uuid.rs b/nexus/db-model/src/typed_uuid.rs new file mode 100644 index 0000000000..7785b8c7dc --- /dev/null +++ b/nexus/db-model/src/typed_uuid.rs @@ -0,0 +1,116 @@ +// 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/. + +//! Typed UUID instances. + +use derive_where::derive_where; +use diesel::backend::Backend; +use diesel::deserialize::{self, FromSql}; +use diesel::serialize::{self, ToSql}; +use diesel::sql_types; +use omicron_uuid_kinds::{GenericUuid, TypedUuid, TypedUuidKind}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; +use uuid::Uuid; + +/// Returns the corresponding `DbTypedUuid` for this `TypedUuid`. +/// +/// Code external to the `db-model` crate sometimes needs a way to convert a +/// `TypedUuid` to a `DbTypedUuid`. We don't want `DbTypedUuid` to be used +/// anywhere, so we don't make it public. Instead, we expose this function. +#[inline] +pub fn to_db_typed_uuid(id: TypedUuid) -> DbTypedUuid { + DbTypedUuid(id) +} + +/// A UUID with information about the kind of type it is. +/// +/// Despite the fact that this is marked `pub`, this is *private* to the +/// `db-model` crate (this type is not exported at the top level). External +/// users must use omicron-common's `TypedUuid`. +#[derive_where(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)] +#[derive(AsExpression, FromSqlRow, Serialize, Deserialize)] +#[diesel(sql_type = sql_types::Uuid)] +#[serde(transparent, bound = "")] +pub struct DbTypedUuid(pub(crate) TypedUuid); + +impl ToSql for DbTypedUuid +where + DB: Backend, + Uuid: ToSql, +{ + fn to_sql<'a>( + &'a self, + out: &mut serialize::Output<'a, '_, DB>, + ) -> serialize::Result { + self.0.as_untyped_uuid().to_sql(out) + } +} + +impl FromSql for DbTypedUuid +where + DB: Backend, + Uuid: FromSql, +{ + #[inline] + fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result { + let id = Uuid::from_sql(bytes)?; + Ok(TypedUuid::from_untyped_uuid(id).into()) + } +} + +impl fmt::Debug for DbTypedUuid { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for DbTypedUuid { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for DbTypedUuid { + type Err = omicron_uuid_kinds::ParseError; + + #[inline] + fn from_str(s: &str) -> Result { + Ok(TypedUuid::from_str(s)?.into()) + } +} + +impl From> for DbTypedUuid { + #[inline] + fn from(id: TypedUuid) -> Self { + Self(id) + } +} + +impl From> for TypedUuid { + #[inline] + fn from(id: DbTypedUuid) -> Self { + id.0 + } +} + +impl GenericUuid for DbTypedUuid { + #[inline] + fn from_untyped_uuid(uuid: Uuid) -> Self { + TypedUuid::from_untyped_uuid(uuid).into() + } + + #[inline] + fn into_untyped_uuid(self) -> Uuid { + self.0.into_untyped_uuid() + } + + #[inline] + fn as_untyped_uuid(&self) -> &Uuid { + self.0.as_untyped_uuid() + } +} diff --git a/nexus/db-queries/Cargo.toml b/nexus/db-queries/Cargo.toml index 9cdcc88e6a..9c9a30799e 100644 --- a/nexus/db-queries/Cargo.toml +++ b/nexus/db-queries/Cargo.toml @@ -45,7 +45,7 @@ static_assertions.workspace = true steno.workspace = true swrite.workspace = true thiserror.workspace = true -tokio = { workspace = true, features = [ "full" ] } +tokio = { workspace = true, features = ["full"] } uuid.workspace = true usdt.workspace = true @@ -55,6 +55,7 @@ nexus-db-model.workspace = true nexus-types.workspace = true omicron-common.workspace = true omicron-passwords.workspace = true +omicron-uuid-kinds.workspace = true oximeter.workspace = true omicron-workspace-hack.workspace = true diff --git a/nexus/db-queries/src/authz/api_resources.rs b/nexus/db-queries/src/authz/api_resources.rs index b4fd4e1890..70bc9ab2eb 100644 --- a/nexus/db-queries/src/authz/api_resources.rs +++ b/nexus/db-queries/src/authz/api_resources.rs @@ -869,7 +869,7 @@ authz_resource! { authz_resource! { name = "LoopbackAddress", parent = "Fleet", - primary_key = Uuid, + primary_key = { uuid_kind = LoopbackAddressKind }, roles_allowed = false, polar_snippet = FleetChild, } @@ -1068,7 +1068,7 @@ authz_resource! { authz_resource! { name = "TufRepo", parent = "Fleet", - primary_key = Uuid, + primary_key = { uuid_kind = TufRepoKind }, roles_allowed = false, polar_snippet = FleetChild, } diff --git a/nexus/db-queries/src/authz/policy_test/resources.rs b/nexus/db-queries/src/authz/policy_test/resources.rs index 3e87f6db51..96cefb3db4 100644 --- a/nexus/db-queries/src/authz/policy_test/resources.rs +++ b/nexus/db-queries/src/authz/policy_test/resources.rs @@ -10,6 +10,7 @@ use crate::authz; use crate::db::model::ArtifactId; use nexus_db_model::SemverVersion; use omicron_common::api::external::LookupType; +use omicron_uuid_kinds::GenericUuid; use oso::PolarClass; use std::collections::BTreeSet; use uuid::Uuid; @@ -132,7 +133,7 @@ pub async fn make_resources( builder.new_resource(authz::TufRepo::new( authz::FLEET, tuf_repo_id, - LookupType::ById(tuf_repo_id), + LookupType::ById(tuf_repo_id.into_untyped_uuid()), )); let artifact_id = ArtifactId { @@ -160,7 +161,7 @@ pub async fn make_resources( builder.new_resource(authz::LoopbackAddress::new( authz::FLEET, loopback_address_id, - LookupType::ById(loopback_address_id), + LookupType::ById(loopback_address_id.into_untyped_uuid()), )); builder.build() diff --git a/nexus/db-queries/src/db/datastore/switch_interface.rs b/nexus/db-queries/src/db/datastore/switch_interface.rs index 67f16fa08f..aa4f6747fd 100644 --- a/nexus/db-queries/src/db/datastore/switch_interface.rs +++ b/nexus/db-queries/src/db/datastore/switch_interface.rs @@ -17,11 +17,14 @@ use crate::transaction_retry::OptionalError; use async_bb8_diesel::AsyncRunQueryDsl; use diesel::{ExpressionMethods, QueryDsl, SelectableHelper}; use ipnetwork::IpNetwork; +use nexus_db_model::to_db_typed_uuid; use nexus_types::external_api::params::LoopbackAddressCreate; use omicron_common::api::external::{ CreateResult, DataPageParams, DeleteResult, Error, ListResultVec, LookupResult, ResourceType, }; +use omicron_uuid_kinds::LoopbackAddressKind; +use omicron_uuid_kinds::TypedUuid; use uuid::Uuid; impl DataStore { @@ -29,7 +32,7 @@ impl DataStore { &self, opctx: &OpContext, params: &LoopbackAddressCreate, - id: Option, + id: Option>, authz_address_lot: &authz::AddressLot, ) -> CreateResult { use db::schema::loopback_address::dsl; @@ -130,7 +133,7 @@ impl DataStore { self.transaction_retry_wrapper("loopback_address_delete") .transaction(&conn, |conn| async move { let la = diesel::delete(dsl::loopback_address) - .filter(dsl::id.eq(id)) + .filter(dsl::id.eq(to_db_typed_uuid(id))) .returning(LoopbackAddress::as_returning()) .get_result_async(&conn) .await?; @@ -159,7 +162,7 @@ impl DataStore { let conn = self.pool_connection_authorized(opctx).await?; loopback_dsl::loopback_address - .filter(loopback_address::id.eq(id)) + .filter(loopback_address::id.eq(to_db_typed_uuid(id))) .select(LoopbackAddress::as_select()) .limit(1) .first_async::(&*conn) diff --git a/nexus/db-queries/src/db/datastore/update.rs b/nexus/db-queries/src/db/datastore/update.rs index 3725797f83..d73a3d903f 100644 --- a/nexus/db-queries/src/db/datastore/update.rs +++ b/nexus/db-queries/src/db/datastore/update.rs @@ -21,8 +21,9 @@ use omicron_common::api::external::{ self, CreateResult, LookupResult, LookupType, ResourceType, TufRepoInsertStatus, }; +use omicron_uuid_kinds::TufRepoKind; +use omicron_uuid_kinds::TypedUuid; use swrite::{swrite, SWrite}; -use uuid::Uuid; /// The return value of [`DataStore::update_tuf_repo_description_insert`]. /// @@ -43,7 +44,7 @@ impl TufRepoInsertResponse { } async fn artifacts_for_repo( - repo_id: Uuid, + repo_id: TypedUuid, conn: &async_bb8_diesel::Connection, ) -> Result, DieselError> { use db::schema::tuf_artifact::dsl as tuf_artifact_dsl; @@ -61,7 +62,10 @@ async fn artifacts_for_repo( // Don't bother paginating because each repo should only have a few (under // 20) artifacts. tuf_repo_artifact_dsl::tuf_repo_artifact - .filter(tuf_repo_artifact_dsl::tuf_repo_id.eq(repo_id)) + .filter( + tuf_repo_artifact_dsl::tuf_repo_id + .eq(nexus_db_model::to_db_typed_uuid(repo_id)), + ) .inner_join(tuf_artifact_dsl::tuf_artifact.on(join_on_dsl)) .select(TufArtifact::as_select()) .load_async(conn) @@ -138,7 +142,7 @@ impl DataStore { ) })?; - let artifacts = artifacts_for_repo(repo.id, &conn) + let artifacts = artifacts_for_repo(repo.id.into(), &conn) .await .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; Ok(TufRepoDescription { repo, artifacts }) @@ -177,7 +181,8 @@ async fn insert_impl( } // Just return the existing repo along with all of its artifacts. - let artifacts = artifacts_for_repo(existing_repo.id, &conn).await?; + let artifacts = + artifacts_for_repo(existing_repo.id.into(), &conn).await?; let recorded = TufRepoDescription { repo: existing_repo, artifacts }; diff --git a/nexus/db-queries/src/db/lookup.rs b/nexus/db-queries/src/db/lookup.rs index 1cf14c5a8f..18ea369685 100644 --- a/nexus/db-queries/src/db/lookup.rs +++ b/nexus/db-queries/src/db/lookup.rs @@ -21,6 +21,8 @@ use nexus_db_model::Name; use omicron_common::api::external::Error; use omicron_common::api::external::InternalContext; use omicron_common::api::external::{LookupResult, LookupType, ResourceType}; +use omicron_uuid_kinds::TufRepoKind; +use omicron_uuid_kinds::TypedUuid; use uuid::Uuid; /// Look up an API resource in the database @@ -431,7 +433,7 @@ impl<'a> LookupPath<'a> { } /// Select a resource of type TufRepo, identified by its UUID. - pub fn tuf_repo_id(self, id: Uuid) -> TufRepo<'a> { + pub fn tuf_repo_id(self, id: TypedUuid) -> TufRepo<'a> { TufRepo::PrimaryKey(Root { lookup_root: self }, id) } @@ -863,7 +865,7 @@ lookup_resource! { children = [], lookup_by_name = false, soft_deletes = false, - primary_key_columns = [ { column_name = "id", rust_type = Uuid } ] + primary_key_columns = [ { column_name = "id", uuid_kind = TufRepoKind } ] } lookup_resource! { diff --git a/nexus/macros-common/Cargo.toml b/nexus/macros-common/Cargo.toml new file mode 100644 index 0000000000..9d4390a6d2 --- /dev/null +++ b/nexus/macros-common/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nexus-macros-common" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[dependencies] +proc-macro2.workspace = true +syn = { workspace = true, features = ["extra-traits"] } +quote.workspace = true +omicron-workspace-hack.workspace = true diff --git a/nexus/macros-common/src/lib.rs b/nexus/macros-common/src/lib.rs new file mode 100644 index 0000000000..a2f51e1b5c --- /dev/null +++ b/nexus/macros-common/src/lib.rs @@ -0,0 +1,109 @@ +// 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/. + +//! Code shared by Nexus macros. + +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse_quote; + +/// The type of a primary key in a database. +#[derive(Debug)] +pub enum PrimaryKeyType { + /// A regular type. + Standard(syn::Type), + + /// A typed UUID, which requires special handling. + TypedUuid { + /// The external type. This is used almost everywhere. + external: syn::Type, + + /// The internal type used in nexus-db-model. + db: syn::Type, + }, +} + +impl PrimaryKeyType { + /// Constructs a new `TypedUuid` variant. + pub fn new_typed_uuid(kind: &syn::Ident) -> Self { + let external = parse_quote!(::omicron_uuid_kinds::TypedUuid<::omicron_uuid_kinds::#kind>); + let db = parse_quote!(crate::typed_uuid::DbTypedUuid<::omicron_uuid_kinds::#kind>); + PrimaryKeyType::TypedUuid { external, db } + } + + /// Returns the external type for this primary key. + pub fn external(&self) -> &syn::Type { + match self { + PrimaryKeyType::Standard(path) => path, + PrimaryKeyType::TypedUuid { external, .. } => external, + } + } + + /// Converts self into the external type. + pub fn into_external(self) -> syn::Type { + match self { + PrimaryKeyType::Standard(path) => path, + PrimaryKeyType::TypedUuid { external, .. } => external, + } + } + + /// Returns the database type for this primary key. + /// + /// For the `TypedUuid` variant, the db type is only accessible within the + /// `nexus-db-model` crate. + pub fn db(&self) -> &syn::Type { + match self { + PrimaryKeyType::Standard(path) => path, + PrimaryKeyType::TypedUuid { db, .. } => db, + } + } + + /// Converts self into the database type. + pub fn into_db(self) -> syn::Type { + match self { + PrimaryKeyType::Standard(path) => path, + PrimaryKeyType::TypedUuid { db, .. } => db, + } + } + + /// Returns tokens for a conversion from external to db types, given an + /// expression as input. + /// + /// This is specialized for the nexus-db-model crate. + pub fn external_to_db_nexus_db_model( + &self, + tokens: TokenStream, + ) -> TokenStream { + match self { + PrimaryKeyType::Standard(_) => tokens, + PrimaryKeyType::TypedUuid { .. } => { + quote! { crate::to_db_typed_uuid(#tokens) } + } + } + } + + /// Returns tokens for a conversion from external to db types, given an + /// expression as input. + /// + /// This is used for all crates *except* nexus-db-model. + pub fn external_to_db_other(&self, tokens: TokenStream) -> TokenStream { + match self { + PrimaryKeyType::Standard(_) => tokens, + PrimaryKeyType::TypedUuid { .. } => { + quote! { ::nexus_db_model::to_db_typed_uuid(#tokens) } + } + } + } + + /// Returns tokens for a conversion from db to external types, given an + /// expression as input. + pub fn db_to_external(&self, tokens: TokenStream) -> TokenStream { + match self { + PrimaryKeyType::Standard(_) => tokens, + PrimaryKeyType::TypedUuid { .. } => { + quote! { ::omicron_uuid_kinds::TypedUuid::from(#tokens) } + } + } + } +} diff --git a/nexus/types/Cargo.toml b/nexus/types/Cargo.toml index dff0f73be7..93716d4804 100644 --- a/nexus/types/Cargo.toml +++ b/nexus/types/Cargo.toml @@ -9,6 +9,7 @@ anyhow.workspace = true chrono.workspace = true base64.workspace = true futures.workspace = true +omicron-uuid-kinds.workspace = true openssl.workspace = true parse-display.workspace = true schemars = { workspace = true, features = ["chrono", "uuid1"] } diff --git a/nexus/types/src/identity.rs b/nexus/types/src/identity.rs index ededb926df..6f214d6f9a 100644 --- a/nexus/types/src/identity.rs +++ b/nexus/types/src/identity.rs @@ -9,6 +9,7 @@ use chrono::{DateTime, Utc}; use omicron_common::api::external::IdentityMetadata; use omicron_common::api::external::Name; +use omicron_uuid_kinds::GenericUuid; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -23,7 +24,9 @@ use uuid::Uuid; /// /// May be derived from [`macro@db-macros::Resource`]. pub trait Resource { - fn id(&self) -> Uuid; + type IdType: GenericUuid; + + fn id(&self) -> Self::IdType; fn name(&self) -> &Name; fn description(&self) -> &str; fn time_created(&self) -> DateTime; @@ -32,7 +35,7 @@ pub trait Resource { fn identity(&self) -> IdentityMetadata { IdentityMetadata { - id: self.id(), + id: self.id().into_untyped_uuid(), name: self.name().clone(), description: self.description().to_string(), time_created: self.time_created(), @@ -60,13 +63,15 @@ pub struct AssetIdentityMetadata { /// /// May be derived from [`macro@db-macros::Asset`]. pub trait Asset { - fn id(&self) -> Uuid; + type IdType: GenericUuid; + + fn id(&self) -> Self::IdType; fn time_created(&self) -> DateTime; fn time_modified(&self) -> DateTime; fn identity(&self) -> AssetIdentityMetadata { AssetIdentityMetadata { - id: self.id(), + id: self.id().into_untyped_uuid(), time_created: self.time_created(), time_modified: self.time_modified(), } diff --git a/uuid-kinds/Cargo.toml b/uuid-kinds/Cargo.toml new file mode 100644 index 0000000000..126ba8bcf8 --- /dev/null +++ b/uuid-kinds/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "omicron-uuid-kinds" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +# The dependencies and features are written so as to make it easy for no-std +# code to import omicron-uuid-kinds. All the features below are turned on +# within omicron. + +[dependencies] +newtype-uuid.workspace = true +schemars = { workspace = true, optional = true } + +[features] +default = ["std"] +serde = ["newtype-uuid/serde"] +schemars08 = ["newtype-uuid/schemars08", "schemars"] +std = ["newtype-uuid/std"] +uuid-v4 = ["newtype-uuid/v4"] diff --git a/uuid-kinds/README.adoc b/uuid-kinds/README.adoc new file mode 100644 index 0000000000..1a22477aad --- /dev/null +++ b/uuid-kinds/README.adoc @@ -0,0 +1,73 @@ +# omicron-uuid-kinds + +In omicron and other Oxide projects, we use UUIDs to identify many different +kinds of entities. Using a single `Uuid` type for all of them risks mixing up +all the different kinds of IDs. + +To address that, we're actively moving towards typed UUIDs with the +https://github.com/oxidecomputer/newtype-uuid[newtype-uuid] crate. The goal is +that each kind of entity will have a marker type representing a _UUID kind_ +associated with it. Then, the type system will make it significantly harder to +mix different kinds of entities. + +*`omicron-uuid-kinds` is a centralized registry for UUID kinds*, one that can +be shared across Oxide repos. `omicron-uuid-kinds` supports no-std so the kinds +can be shared with embedded code as well. + +## Determinations + +As part of this effort, we've made several decisions that could have gone a +different way. This section documents those choices. + +### One big registry or several smaller ones? + +Rather than having a single registry, an option is to have several smaller ones +(perhaps one to two in each repo). The best answer to that is currently +unclear, but putting them in the same crate for now has several advantages: + +* The macros updated in this PR know where uuid kinds will be found, so they +let you specify just a bare name as the kind. + +* It's simpler. + +The disadvantage of this is that any change to this registry will cause all of +omicron to be rebuilt. Hopefully, once most UUIDs are converted over, this +crate won't be touched too much. + +This decision involves some level of commitment, because splitting up a +registry will probably require several days of work. For example, macros may +need to be updated to handle multiple sources, import paths would have to be +changed across several repos, and so on. + +Crates which solve more abstract problems and are completely independent of +omicron, like https://github.com/oxidecomputer/dropshot/[Dropshot] and +https://github.com/oxidecomputer/steno[Steno], will have their own registries +(probably as a module within the crates themselves). + +### Where should the registry live? + +Once we've decided on a single registry, the next question is where this +registry should be stored. There was some debate in `#oxide-control-plane`, and +two options stood out: + +1. In omicron itself. + + * The upside of this is that it is easy to add new UUID kinds within + omicron, especially as we transition over existing untyped UUIDs. + + * The downside is that we also want to use these kinds in crucible and + propolis -- and pulling in `omicron-uuid-kinds` as a Git dependency would + typically cause two copies of `omicron-uuid-kinds` to be floating around. + That can result in a lot of pain, including confusing errors. + +2. In a separate repo. While it eliminates the issue with duplicated +dependencies, it does add a fair bit more friction. That's because users +updating omicron would now have to make two separate, coordinated PRs. + +We've chosen 1 for now, because agility within omicron outweighs other +concerns. *The downside is mitigated* by using a `[patch]` directive; see the +workspace root's `Cargo.toml` for more about this. + +This is straightforward to change in the future. If we find that 1 is too much +of a bother, we'll easily be able to switch to 2 while keeping the name of the +crate the same (minimizing churn). diff --git a/uuid-kinds/src/lib.rs b/uuid-kinds/src/lib.rs new file mode 100644 index 0000000000..12bc756d68 --- /dev/null +++ b/uuid-kinds/src/lib.rs @@ -0,0 +1,50 @@ +// 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/. + +#![cfg_attr(not(feature = "std"), no_std)] + +//! A registry for UUID kinds used in Omicron and related projects. +//! +//! See this crate's `README.md` for more information. + +// Export these types so that other users don't have to pull in newtype-uuid. +#[doc(no_inline)] +pub use newtype_uuid::{ + GenericUuid, ParseError, TagError, TypedUuid, TypedUuidKind, TypedUuidTag, +}; + +#[cfg(feature = "schemars08")] +use schemars::JsonSchema; + +macro_rules! impl_typed_uuid_kind { + ($($kind:ident => $tag:literal),* $(,)?) => { + $( + #[cfg_attr(feature = "schemars08", derive(JsonSchema))] + pub enum $kind {} + + impl TypedUuidKind for $kind { + #[inline] + fn tag() -> TypedUuidTag { + // `const` ensures that tags are validated at compile-time. + const TAG: TypedUuidTag = TypedUuidTag::new($tag); + TAG + } + } + )* + }; +} + +// NOTE: +// +// This should generally be an append-only list. Removing items from this list +// will not break things for now (because newtype-uuid does not currently alter +// any serialization formats), but it may involve some degree of churn across +// repos. +// +// Please keep this list in alphabetical order. + +impl_typed_uuid_kind! { + LoopbackAddressKind => "loopback_address", + TufRepoKind => "tuf_repo", +} From 475fb621d43b47ab3f4e9bd69fb21a695d147d73 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Fri, 16 Feb 2024 09:06:59 -0500 Subject: [PATCH 06/22] Fix bad service NIC slots (#5080) This is the second half of the fix for #5056. #5065 (already merged) fixed _how_ we were getting service NICs with nonzero slot values, and this PR adds a schema migration to apply a one-time fix to any existing service NICs with nonzero slot values. This matters to the Reconfigurator, because currently the NICs sled-agent thinks it has don't match the NICs recorded in CRDB (differing only by slot number). Closes #5056. --- nexus/db-model/src/schema.rs | 2 +- nexus/db-queries/src/db/mod.rs | 5 +++++ nexus/db-queries/src/db/pool_connection.rs | 2 +- nexus/tests/integration_tests/schema.rs | 6 ++++++ schema/crdb/35.0.0/up.sql | 6 ++++++ schema/crdb/dbinit.sql | 2 +- 6 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 schema/crdb/35.0.0/up.sql diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index 9ebf7516cf..3d9050b1af 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -13,7 +13,7 @@ use omicron_common::api::external::SemverVersion; /// /// This should be updated whenever the schema is changed. For more details, /// refer to: schema/crdb/README.adoc -pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(34, 0, 0); +pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(35, 0, 0); table! { disk (id) { diff --git a/nexus/db-queries/src/db/mod.rs b/nexus/db-queries/src/db/mod.rs index 184ebe5f8a..d5262166ee 100644 --- a/nexus/db-queries/src/db/mod.rs +++ b/nexus/db-queries/src/db/mod.rs @@ -35,6 +35,11 @@ pub mod subquery; pub(crate) mod true_or_cast_error; mod update_and_check; +/// Batch statement to disable full table scans. +// This is `pub` so tests that don't go through our connection pool can disable +// full table scans the same way pooled connections do. +pub use pool_connection::DISALLOW_FULL_TABLE_SCAN_SQL; + #[cfg(test)] mod test_utils; diff --git a/nexus/db-queries/src/db/pool_connection.rs b/nexus/db-queries/src/db/pool_connection.rs index d9c50ff26c..66fb125a7c 100644 --- a/nexus/db-queries/src/db/pool_connection.rs +++ b/nexus/db-queries/src/db/pool_connection.rs @@ -78,7 +78,7 @@ static CUSTOM_TYPE_KEYS: &'static [&'static str] = &[ ]; const CUSTOM_TYPE_SCHEMA: &'static str = "public"; -const DISALLOW_FULL_TABLE_SCAN_SQL: &str = +pub const DISALLOW_FULL_TABLE_SCAN_SQL: &str = "set disallow_full_table_scans = on; set large_full_scan_rows = 0;"; #[derive(Debug)] diff --git a/nexus/tests/integration_tests/schema.rs b/nexus/tests/integration_tests/schema.rs index 2d496fcd8e..1c23f1b842 100644 --- a/nexus/tests/integration_tests/schema.rs +++ b/nexus/tests/integration_tests/schema.rs @@ -10,6 +10,7 @@ use nexus_db_model::schema::SCHEMA_VERSION as LATEST_SCHEMA_VERSION; use nexus_db_queries::db::datastore::{ all_sql_for_version_migration, EARLIEST_SUPPORTED_VERSION, }; +use nexus_db_queries::db::DISALLOW_FULL_TABLE_SCAN_SQL; use nexus_test_utils::{db, load_test_config, ControlPlaneTestContextBuilder}; use omicron_common::api::external::SemverVersion; use omicron_common::api::internal::shared::SwitchLocation; @@ -118,6 +119,11 @@ async fn apply_update( let client = crdb.connect().await.expect("failed to connect"); + client + .batch_execute(DISALLOW_FULL_TABLE_SCAN_SQL) + .await + .expect("failed to disallow full table scans"); + // We skip this for the earliest supported version because these tables // might not exist yet. if version != EARLIEST_SUPPORTED_VERSION { diff --git a/schema/crdb/35.0.0/up.sql b/schema/crdb/35.0.0/up.sql new file mode 100644 index 0000000000..f50e8dc881 --- /dev/null +++ b/schema/crdb/35.0.0/up.sql @@ -0,0 +1,6 @@ +-- This isn't realy a schema migration, but is instead a one-off fix for +-- incorrect data (https://github.com/oxidecomputer/omicron/issues/5056) after +-- the root cause for the incorrect data has been addressed. +UPDATE omicron.public.network_interface + SET slot = 0 + WHERE kind = 'service' AND time_deleted IS NULL; diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index 254080e92d..6c82b63e6e 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -3515,7 +3515,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - ( TRUE, NOW(), NOW(), '34.0.0', NULL) + ( TRUE, NOW(), NOW(), '35.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; From e2f5eea78d0c3b59a117a3bc78bb4635ce740d94 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Fri, 16 Feb 2024 12:50:04 -0500 Subject: [PATCH 07/22] Fix typo in printing blueprint diff (#5087) This seems weird! :) ``` zone faa4010d-9979-47d3-b104-7c269def8cbb type crucible underlay IP fd00:1122:3344:101::c (unchanged) zone fb81a9cc-267e-48d8-bdb3-32dc6225a04f type crucible underlay IP fd00:1122:3344:101::8 (unchanged) + zone d86f17d2-e1a5-4744-85c7-8bb3d989ac76 type fd00:1122:3344:101::21 underlay IP nexus (added) ``` --- nexus/types/src/deployment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index 06427507d5..490097d46d 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -615,8 +615,8 @@ impl<'a> std::fmt::Display for OmicronZonesDiff<'a> { f, "+ zone {} type {} underlay IP {} (added)", zone.id, - zone.underlay_address, zone.zone_type.label(), + zone.underlay_address, )?; } } From cd901535e9a1075bdcf1f99b257cce2923ce0fb2 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:06:52 -0800 Subject: [PATCH 08/22] chore(deps): update rust crate clap to 4.5 (#5083) --- Cargo.lock | 144 ++++++++++++++++---------------------- Cargo.toml | 2 +- workspace-hack/Cargo.toml | 14 ++-- 3 files changed, 64 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fca461f24b..b339054e14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -150,12 +150,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -332,7 +332,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7b2dbe9169059af0f821e811180fddc971fc210c776c133c7819ccd6e478db" dependencies = [ - "rustix 0.38.31", + "rustix", "tempfile", "windows-sys 0.52.0", ] @@ -988,9 +988,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.3" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" dependencies = [ "clap_builder", "clap_derive", @@ -998,22 +998,22 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.2" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.10.0", + "strsim 0.11.0", "terminal_size", ] [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -1023,9 +1023,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clipboard-win" @@ -1201,7 +1201,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.3", + "clap 4.5.0", "criterion-plot", "futures", "is-terminal", @@ -1852,7 +1852,7 @@ dependencies = [ "anyhow", "camino", "chrono", - "clap 4.4.3", + "clap 4.5.0", "dns-service-client", "dropshot", "expectorate", @@ -2268,7 +2268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if", - "rustix 0.38.31", + "rustix", "windows-sys 0.48.0", ] @@ -2279,7 +2279,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", - "rustix 0.38.31", + "rustix", "windows-sys 0.52.0", ] @@ -2557,7 +2557,7 @@ name = "gateway-cli" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.4.3", + "clap 4.5.0", "futures", "gateway-client", "gateway-messages", @@ -3462,7 +3462,7 @@ dependencies = [ "bytes", "camino", "cancel-safe-futures", - "clap 4.4.3", + "clap 4.5.0", "ddm-admin-client", "display-error-chain", "futures", @@ -3522,7 +3522,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "expectorate", "hyper 0.14.27", @@ -3603,7 +3603,7 @@ name = "internal-dns-cli" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "internal-dns", "omicron-common", @@ -3613,17 +3613,6 @@ dependencies = [ "trust-dns-resolver", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.2", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipcc" version = "0.1.0" @@ -3675,7 +3664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.2", - "rustix 0.38.31", + "rustix", "windows-sys 0.48.0", ] @@ -3883,7 +3872,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d8de370f98a6cb8a4606618e53e802f93b094ddec0f96988eaec2c27e6e9ce7" dependencies = [ - "clap 4.4.3", + "clap 4.5.0", "termcolor", "threadpool", ] @@ -3911,12 +3900,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -3945,7 +3928,7 @@ version = "0.2.4" source = "git+https://github.com/oxidecomputer/lpc55_support#96f064eaae5e95930efaab6c29fd1b2e22225dac" dependencies = [ "bitfield", - "clap 4.4.3", + "clap 4.5.0", "packed_struct", "serde", ] @@ -4925,7 +4908,7 @@ dependencies = [ "anyhow", "camino", "camino-tempfile", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "expectorate", "futures", @@ -4958,7 +4941,7 @@ dependencies = [ "anyhow", "base64", "camino", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "expectorate", "futures", @@ -5009,7 +4992,7 @@ dependencies = [ "camino-tempfile", "cancel-safe-futures", "chrono", - "clap 4.4.3", + "clap 4.5.0", "criterion", "crucible-agent-client", "crucible-pantry-client", @@ -5119,7 +5102,7 @@ dependencies = [ "anyhow", "async-bb8-diesel", "chrono", - "clap 4.4.3", + "clap 4.5.0", "crossterm", "crucible-agent-client", "csv", @@ -5170,7 +5153,7 @@ version = "0.1.0" dependencies = [ "anyhow", "camino", - "clap 4.4.3", + "clap 4.5.0", "expectorate", "futures", "hex", @@ -5237,7 +5220,7 @@ dependencies = [ "cancel-safe-futures", "cfg-if", "chrono", - "clap 4.4.3", + "clap 4.5.0", "crucible-agent-client", "ddm-admin-client", "derive_more", @@ -5373,7 +5356,7 @@ dependencies = [ "bytes", "chrono", "cipher", - "clap 4.4.3", + "clap 4.5.0", "clap_builder", "console", "const-oid", @@ -5387,7 +5370,6 @@ dependencies = [ "dof 0.3.0", "either", "elliptic-curve", - "errno", "ff", "flate2", "futures", @@ -5436,7 +5418,7 @@ dependencies = [ "regex-syntax 0.8.2", "reqwest", "ring 0.17.7", - "rustix 0.38.31", + "rustix", "schemars", "semver 1.0.21", "serde", @@ -5750,7 +5732,7 @@ dependencies = [ "anyhow", "camino", "chrono", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "expectorate", "futures", @@ -5793,7 +5775,7 @@ dependencies = [ "bytes", "camino", "chrono", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "expectorate", "futures", @@ -5863,7 +5845,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "nexus-client", "omicron-common", @@ -5885,7 +5867,7 @@ dependencies = [ "anyhow", "camino", "chrono", - "clap 4.4.3", + "clap 4.5.0", "omicron-workspace-hack", "uuid", ] @@ -6689,7 +6671,7 @@ dependencies = [ "anyhow", "atty", "base64", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "futures", "hyper 0.14.27", @@ -7429,20 +7411,6 @@ dependencies = [ "toolchain_find", ] -[[package]] -name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "0.38.31" @@ -7452,7 +7420,7 @@ dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys", "windows-sys 0.52.0", ] @@ -8465,7 +8433,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "futures", "gateway-messages", @@ -8655,6 +8623,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "structmeta" version = "0.2.0" @@ -8952,7 +8926,7 @@ checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "rustix 0.38.31", + "rustix", "windows-sys 0.52.0", ] @@ -8978,11 +8952,11 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.37.23", + "rustix", "windows-sys 0.48.0", ] @@ -9643,7 +9617,7 @@ dependencies = [ "assert_cmd", "camino", "chrono", - "clap 4.4.3", + "clap 4.5.0", "console", "datatest-stable", "fs-err", @@ -9889,7 +9863,7 @@ dependencies = [ "camino", "camino-tempfile", "chrono", - "clap 4.4.3", + "clap 4.5.0", "debug-ignore", "display-error-chain", "dropshot", @@ -9920,7 +9894,7 @@ dependencies = [ "camino", "camino-tempfile", "cancel-safe-futures", - "clap 4.4.3", + "clap 4.5.0", "debug-ignore", "derive-where", "either", @@ -10334,7 +10308,7 @@ dependencies = [ "buf-list", "camino", "ciborium", - "clap 4.4.3", + "clap 4.5.0", "crossterm", "futures", "humantime", @@ -10395,7 +10369,7 @@ dependencies = [ "bytes", "camino", "ciborium", - "clap 4.4.3", + "clap 4.5.0", "crossterm", "omicron-workspace-hack", "reedline", @@ -10420,7 +10394,7 @@ dependencies = [ "bytes", "camino", "camino-tempfile", - "clap 4.4.3", + "clap 4.5.0", "ddm-admin-client", "debug-ignore", "display-error-chain", @@ -10737,7 +10711,7 @@ dependencies = [ "camino", "cargo_metadata", "cargo_toml", - "clap 4.4.3", + "clap 4.5.0", ] [[package]] @@ -10870,7 +10844,7 @@ name = "zone-network-setup" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.4.3", + "clap 4.5.0", "dropshot", "illumos-utils", "omicron-common", diff --git a/Cargo.toml b/Cargo.toml index f71bd77730..b140b3946a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,7 +177,7 @@ chacha20poly1305 = "0.10.1" ciborium = "0.2.2" cfg-if = "1.0" chrono = { version = "0.4", features = [ "serde" ] } -clap = { version = "4.4", features = ["cargo", "derive", "env", "wrap_help"] } +clap = { version = "4.5", features = ["cargo", "derive", "env", "wrap_help"] } cookie = "0.18" criterion = { version = "0.5.1", features = [ "async_tokio" ] } crossbeam = "0.8" diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 098be26460..28338ffce8 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -28,8 +28,8 @@ byteorder = { version = "1.5.0" } bytes = { version = "1.5.0", features = ["serde"] } chrono = { version = "0.4.31", features = ["alloc", "serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } -clap = { version = "4.4.3", features = ["cargo", "derive", "env", "wrap_help"] } -clap_builder = { version = "4.4.2", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } +clap = { version = "4.5.0", features = ["cargo", "derive", "env", "wrap_help"] } +clap_builder = { version = "4.5.0", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } console = { version = "0.15.8" } const-oid = { version = "0.9.5", default-features = false, features = ["db", "std"] } crossbeam-epoch = { version = "0.9.15" } @@ -134,8 +134,8 @@ byteorder = { version = "1.5.0" } bytes = { version = "1.5.0", features = ["serde"] } chrono = { version = "0.4.31", features = ["alloc", "serde"] } cipher = { version = "0.4.4", default-features = false, features = ["block-padding", "zeroize"] } -clap = { version = "4.4.3", features = ["cargo", "derive", "env", "wrap_help"] } -clap_builder = { version = "4.4.2", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } +clap = { version = "4.5.0", features = ["cargo", "derive", "env", "wrap_help"] } +clap_builder = { version = "4.5.0", default-features = false, features = ["cargo", "color", "env", "std", "suggestions", "usage", "wrap_help"] } console = { version = "0.15.8" } const-oid = { version = "0.9.5", default-features = false, features = ["db", "std"] } crossbeam-epoch = { version = "0.9.15" } @@ -242,28 +242,24 @@ rustix = { version = "0.38.31", features = ["fs", "termios"] } [target.x86_64-apple-darwin.dependencies] bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.4.0", default-features = false, features = ["std"] } -errno = { version = "0.3.8", default-features = false, features = ["std"] } mio = { version = "0.8.9", features = ["net", "os-ext"] } once_cell = { version = "1.19.0" } rustix = { version = "0.38.31", features = ["fs", "termios"] } [target.x86_64-apple-darwin.build-dependencies] bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.4.0", default-features = false, features = ["std"] } -errno = { version = "0.3.8", default-features = false, features = ["std"] } mio = { version = "0.8.9", features = ["net", "os-ext"] } once_cell = { version = "1.19.0" } rustix = { version = "0.38.31", features = ["fs", "termios"] } [target.aarch64-apple-darwin.dependencies] bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.4.0", default-features = false, features = ["std"] } -errno = { version = "0.3.8", default-features = false, features = ["std"] } mio = { version = "0.8.9", features = ["net", "os-ext"] } once_cell = { version = "1.19.0" } rustix = { version = "0.38.31", features = ["fs", "termios"] } [target.aarch64-apple-darwin.build-dependencies] bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.4.0", default-features = false, features = ["std"] } -errno = { version = "0.3.8", default-features = false, features = ["std"] } mio = { version = "0.8.9", features = ["net", "os-ext"] } once_cell = { version = "1.19.0" } rustix = { version = "0.38.31", features = ["fs", "termios"] } @@ -271,7 +267,6 @@ rustix = { version = "0.38.31", features = ["fs", "termios"] } [target.x86_64-unknown-illumos.dependencies] bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.4.0", default-features = false, features = ["std"] } dof = { version = "0.3.0", default-features = false, features = ["des"] } -errno = { version = "0.3.8", default-features = false, features = ["std"] } mio = { version = "0.8.9", features = ["net", "os-ext"] } once_cell = { version = "1.19.0" } rustix = { version = "0.38.31", features = ["fs", "termios"] } @@ -281,7 +276,6 @@ toml_edit = { version = "0.19.15", features = ["serde"] } [target.x86_64-unknown-illumos.build-dependencies] bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.4.0", default-features = false, features = ["std"] } dof = { version = "0.3.0", default-features = false, features = ["des"] } -errno = { version = "0.3.8", default-features = false, features = ["std"] } mio = { version = "0.8.9", features = ["net", "os-ext"] } once_cell = { version = "1.19.0" } rustix = { version = "0.38.31", features = ["fs", "termios"] } From 25eb244f6a05691e456838e2da2fb985c72a5a52 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 19:09:23 +0000 Subject: [PATCH 09/22] chore(deps): update rust crate either to 1.10.0 (#5084) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- workspace-hack/Cargo.toml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b339054e14..eeca299eff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2094,9 +2094,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "elliptic-curve" diff --git a/Cargo.toml b/Cargo.toml index b140b3946a..abcd0db4f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -201,7 +201,7 @@ dns-service-client = { path = "clients/dns-service-client" } dpd-client = { path = "clients/dpd-client" } dropshot = { git = "https://github.com/oxidecomputer/dropshot", branch = "main", features = [ "usdt-probes" ] } dyn-clone = "1.0.16" -either = "1.9.0" +either = "1.10.0" expectorate = "1.1.0" fatfs = "0.3.6" filetime = "0.2.23" diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 28338ffce8..6b7096b8fe 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -39,7 +39,7 @@ crypto-common = { version = "0.1.6", default-features = false, features = ["getr der = { version = "0.7.8", default-features = false, features = ["derive", "flagset", "oid", "pem", "std"] } diesel = { version = "2.1.4", features = ["chrono", "i-implement-a-third-party-backend-and-opt-into-breaking-changes", "network-address", "postgres", "r2d2", "serde_json", "uuid"] } digest = { version = "0.10.7", features = ["mac", "oid", "std"] } -either = { version = "1.9.0" } +either = { version = "1.10.0" } elliptic-curve = { version = "0.13.8", features = ["ecdh", "hazmat", "pem", "std"] } ff = { version = "0.13.0", default-features = false, features = ["alloc"] } flate2 = { version = "1.0.28" } @@ -145,7 +145,7 @@ crypto-common = { version = "0.1.6", default-features = false, features = ["getr der = { version = "0.7.8", default-features = false, features = ["derive", "flagset", "oid", "pem", "std"] } diesel = { version = "2.1.4", features = ["chrono", "i-implement-a-third-party-backend-and-opt-into-breaking-changes", "network-address", "postgres", "r2d2", "serde_json", "uuid"] } digest = { version = "0.10.7", features = ["mac", "oid", "std"] } -either = { version = "1.9.0" } +either = { version = "1.10.0" } elliptic-curve = { version = "0.13.8", features = ["ecdh", "hazmat", "pem", "std"] } ff = { version = "0.13.0", default-features = false, features = ["alloc"] } flate2 = { version = "1.0.28" } From 9e9e60c7f6456c721348e9edb1b432d310e4b7aa Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:43:26 -0800 Subject: [PATCH 10/22] chore(deps): update rust crate toml_edit to 0.22.6 (#4996) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 33 ++++++++++++++++----------------- Cargo.toml | 2 +- workspace-hack/Cargo.toml | 6 ++++-- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eeca299eff..ecdd8c235e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5442,6 +5442,7 @@ dependencies = [ "toml 0.7.8", "toml_datetime", "toml_edit 0.19.15", + "toml_edit 0.22.6", "tracing", "trust-dns-proto", "unicode-bidi", @@ -9338,7 +9339,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.4", + "toml_edit 0.22.6", ] [[package]] @@ -9360,31 +9361,20 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.15", ] [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap 2.2.3", - "toml_datetime", - "winnow", -] - -[[package]] -name = "toml_edit" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" +checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" dependencies = [ "indexmap 2.2.3", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.1", ] [[package]] @@ -10337,7 +10327,7 @@ dependencies = [ "tokio", "tokio-util", "toml 0.8.10", - "toml_edit 0.21.1", + "toml_edit 0.22.6", "tui-tree-widget", "unicode-width", "update-engine", @@ -10664,6 +10654,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d90f4e0f530c4c69f62b80d839e9ef3855edc9cba471a160c4d692deed62b401" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index abcd0db4f1..f15170e2e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -397,7 +397,7 @@ tokio-stream = "0.1.14" tokio-tungstenite = "0.20" tokio-util = { version = "0.7.10", features = ["io", "io-util"] } toml = "0.8.10" -toml_edit = "0.21.1" +toml_edit = "0.22.6" tough = { version = "0.16.0", features = [ "http" ] } trust-dns-client = "0.22" trust-dns-proto = "0.22" diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 6b7096b8fe..c204b56b4d 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -107,6 +107,7 @@ tokio-postgres = { version = "0.7.10", features = ["with-chrono-0_4", "with-serd tokio-stream = { version = "0.1.14", features = ["net"] } tokio-util = { version = "0.7.10", features = ["codec", "io-util"] } toml = { version = "0.7.8" } +toml_edit-3c51e837cfc5589a = { package = "toml_edit", version = "0.22.6", features = ["serde"] } tracing = { version = "0.1.37", features = ["log"] } trust-dns-proto = { version = "0.22.0" } unicode-bidi = { version = "0.3.13" } @@ -214,6 +215,7 @@ tokio-postgres = { version = "0.7.10", features = ["with-chrono-0_4", "with-serd tokio-stream = { version = "0.1.14", features = ["net"] } tokio-util = { version = "0.7.10", features = ["codec", "io-util"] } toml = { version = "0.7.8" } +toml_edit-3c51e837cfc5589a = { package = "toml_edit", version = "0.22.6", features = ["serde"] } tracing = { version = "0.1.37", features = ["log"] } trust-dns-proto = { version = "0.22.0" } unicode-bidi = { version = "0.3.13" } @@ -271,7 +273,7 @@ mio = { version = "0.8.9", features = ["net", "os-ext"] } once_cell = { version = "1.19.0" } rustix = { version = "0.38.31", features = ["fs", "termios"] } toml_datetime = { version = "0.6.5", default-features = false, features = ["serde"] } -toml_edit = { version = "0.19.15", features = ["serde"] } +toml_edit-cdcf2f9584511fe6 = { package = "toml_edit", version = "0.19.15", features = ["serde"] } [target.x86_64-unknown-illumos.build-dependencies] bitflags-f595c2ba2a3f28df = { package = "bitflags", version = "2.4.0", default-features = false, features = ["std"] } @@ -280,6 +282,6 @@ mio = { version = "0.8.9", features = ["net", "os-ext"] } once_cell = { version = "1.19.0" } rustix = { version = "0.38.31", features = ["fs", "termios"] } toml_datetime = { version = "0.6.5", default-features = false, features = ["serde"] } -toml_edit = { version = "0.19.15", features = ["serde"] } +toml_edit-cdcf2f9584511fe6 = { package = "toml_edit", version = "0.19.15", features = ["serde"] } ### END HAKARI SECTION From 47a416fe1ee5fcf9ade8788fabf4195369d1b9b3 Mon Sep 17 00:00:00 2001 From: bnaecker Date: Fri, 16 Feb 2024 13:31:23 -0800 Subject: [PATCH 11/22] Order `NextItem` subquery results predictably (#5067) - Fixes #5055 - Add a new column, `index` to the `NextItem` subquery, which indexes the shifts from 0..n. - Add an `ORDER BY index` clause to guarantee order. - Add test --- nexus/db-queries/src/db/queries/disk.rs | 11 +- .../src/db/queries/network_interface.rs | 104 +++++-- nexus/db-queries/src/db/queries/next_item.rs | 262 ++++++++++++++++-- nexus/db-queries/src/db/queries/vpc.rs | 8 +- 4 files changed, 331 insertions(+), 54 deletions(-) diff --git a/nexus/db-queries/src/db/queries/disk.rs b/nexus/db-queries/src/db/queries/disk.rs index 9fd56c3ce8..dc1a31dd01 100644 --- a/nexus/db-queries/src/db/queries/disk.rs +++ b/nexus/db-queries/src/db/queries/disk.rs @@ -46,11 +46,12 @@ struct NextDiskSlot { impl NextDiskSlot { fn new(instance_id: Uuid) -> Self { - let generator = DefaultShiftGenerator { - base: 0, - max_shift: i64::try_from(MAX_DISKS_PER_INSTANCE).unwrap(), - min_shift: 0, - }; + let generator = DefaultShiftGenerator::new( + 0, + i64::try_from(MAX_DISKS_PER_INSTANCE).unwrap(), + 0, + ) + .expect("invalid min/max shift"); Self { inner: NextItem::new_scoped(generator, instance_id) } } } diff --git a/nexus/db-queries/src/db/queries/network_interface.rs b/nexus/db-queries/src/db/queries/network_interface.rs index 0643089316..a22c80b232 100644 --- a/nexus/db-queries/src/db/queries/network_interface.rs +++ b/nexus/db-queries/src/db/queries/network_interface.rs @@ -516,8 +516,8 @@ impl NextIpv4Address { let subnet = IpNetwork::from(subnet); let net = IpNetwork::from(first_available_address(&subnet)); let max_shift = i64::from(last_address_offset(&subnet)); - let generator = - DefaultShiftGenerator { base: net, max_shift, min_shift: 0 }; + let generator = DefaultShiftGenerator::new(net, max_shift, 0) + .expect("invalid min/max shift"); Self { inner: NextItem::new_scoped(generator, subnet_id) } } } @@ -575,12 +575,13 @@ pub struct NextNicSlot { impl NextNicSlot { pub fn new(parent_id: Uuid) -> Self { - let generator = DefaultShiftGenerator { - base: 0, - max_shift: i64::try_from(MAX_NICS_PER_INSTANCE) + let generator = DefaultShiftGenerator::new( + 0, + i64::try_from(MAX_NICS_PER_INSTANCE) .expect("Too many network interfaces"), - min_shift: 0, - }; + 0, + ) + .expect("invalid min/max shift"); Self { inner: NextItem::new_scoped(generator, parent_id) } } } @@ -607,25 +608,62 @@ pub struct NextMacAddress { >, } +// Helper to ensure we correctly compute the min/max shifts for a next MAC +// query. +#[derive(Copy, Clone, Debug)] +struct NextMacShifts { + base: MacAddr, + min_shift: i64, + max_shift: i64, +} + +impl NextMacShifts { + fn for_guest() -> Self { + let base = MacAddr::random_guest(); + Self::shifts_for(base, MacAddr::MIN_GUEST_ADDR, MacAddr::MAX_GUEST_ADDR) + } + + fn for_system() -> NextMacShifts { + let base = MacAddr::random_system(); + Self::shifts_for( + base, + MacAddr::MIN_SYSTEM_ADDR, + MacAddr::MAX_SYSTEM_ADDR, + ) + } + + fn shifts_for(base: MacAddr, min: i64, max: i64) -> NextMacShifts { + let x = base.to_i64(); + + // The max shift is the distance to the last value. This min shift is + // always expressed as a negative number, giving the largest leftward + // shift, i.e., the distance to the first value. + let max_shift = max - x; + let min_shift = min - x; + Self { base, min_shift, max_shift } + } +} + impl NextMacAddress { pub fn new(vpc_id: Uuid, kind: NetworkInterfaceKind) -> Self { let (base, max_shift, min_shift) = match kind { NetworkInterfaceKind::Instance => { - let base = MacAddr::random_guest(); - let x = base.to_i64(); - let max_shift = MacAddr::MAX_GUEST_ADDR - x; - let min_shift = x - MacAddr::MIN_GUEST_ADDR; + let NextMacShifts { base, min_shift, max_shift } = + NextMacShifts::for_guest(); (base.into(), max_shift, min_shift) } NetworkInterfaceKind::Service => { - let base = MacAddr::random_system(); - let x = base.to_i64(); - let max_shift = MacAddr::MAX_SYSTEM_ADDR - x; - let min_shift = x - MacAddr::MAX_SYSTEM_ADDR; + let NextMacShifts { base, min_shift, max_shift } = + NextMacShifts::for_system(); (base.into(), max_shift, min_shift) } }; - let generator = DefaultShiftGenerator { base, max_shift, min_shift }; + let generator = DefaultShiftGenerator::new(base, max_shift, min_shift) + .unwrap_or_else(|| { + panic!( + "invalid min shift ({min_shift}) or max_shift ({max_shift})" + ) + }); Self { inner: NextItem::new_scoped(generator, vpc_id) } } } @@ -1713,6 +1751,7 @@ mod tests { use crate::db::model::NetworkInterface; use crate::db::model::Project; use crate::db::model::VpcSubnet; + use crate::db::queries::network_interface::NextMacShifts; use async_bb8_diesel::AsyncRunQueryDsl; use dropshot::test_util::LogContext; use ipnetwork::Ipv4Network; @@ -2801,4 +2840,37 @@ mod tests { "fd00::5".parse::().unwrap(), ); } + + #[test] + fn test_next_mac_shifts_for_system() { + let NextMacShifts { base, min_shift, max_shift } = + NextMacShifts::for_system(); + assert!(base.is_system()); + assert!( + min_shift <= 0, + "expected min shift to be negative, found {min_shift}" + ); + assert!(max_shift >= 0, "found {max_shift}"); + let x = base.to_i64(); + assert_eq!(x + min_shift, MacAddr::MIN_SYSTEM_ADDR); + assert_eq!(x + max_shift, MacAddr::MAX_SYSTEM_ADDR); + } + + #[test] + fn test_next_mac_shifts_for_guest() { + let NextMacShifts { base, min_shift, max_shift } = + NextMacShifts::for_guest(); + assert!(base.is_guest()); + assert!( + min_shift <= 0, + "expected min shift to be negative, found {min_shift}" + ); + assert!( + max_shift >= 0, + "expected max shift to be positive, found {max_shift}" + ); + let x = base.to_i64(); + assert_eq!(x + min_shift, MacAddr::MIN_GUEST_ADDR); + assert_eq!(x + max_shift, MacAddr::MAX_GUEST_ADDR); + } } diff --git a/nexus/db-queries/src/db/queries/next_item.rs b/nexus/db-queries/src/db/queries/next_item.rs index 007aec943d..769c891349 100644 --- a/nexus/db-queries/src/db/queries/next_item.rs +++ b/nexus/db-queries/src/db/queries/next_item.rs @@ -35,7 +35,7 @@ use uuid::Uuid; /// SELECT /// + shift AS ip /// FROM -/// generate_series(0, ) AS shift +/// generate_series(0, ) AS shift, /// LEFT OUTER JOIN /// network_interface /// ON @@ -43,21 +43,22 @@ use uuid::Uuid; /// (, + shift, TRUE) /// WHERE /// ip IS NULL +/// ORDER BY ip /// LIMIT 1 /// ``` /// -/// This query selects the lowest address in the IP subnet that's not already -/// allocated to a guest interface. Note that the query is linear in the number -/// of _allocated_ guest addresses. and are chosen -/// based on the subnet and its size, and take into account reserved IP -/// addresses (such as the broadcast address). +/// This query selects the next address after in the IP subnet +/// that's not already allocated to a guest interface. Note that the query is +/// linear in the number of _allocated_ guest addresses. and +/// are chosen based on the subnet and its size, and take into +/// account reserved IP addresses (such as the broadcast address). /// /// General query structure /// ----------------------- /// /// Much of the value of this type comes from the ability to specify the /// starting point for a scan for the next item. In the case above, of an IP -/// address for a guest NIC, we always try to allocate the lowest available +/// address for a guest NIC, we always try to allocate the next available /// address. This implies the search is linear in the number of allocated IP /// addresses, but that runtime cost is acceptable for a few reasons. First, the /// predictability of the addresses is nice. Second, the subnets can generally @@ -81,16 +82,16 @@ use uuid::Uuid; /// FROM /// ( /// SELECT -/// shift +/// "index", shift /// FROM -/// generate_series(0, ) -/// AS shift +/// generate_series(0, ) AS "index" +/// generate_series(0, ) AS shift, /// UNION ALL /// SELECT /// shift /// FROM -/// generate_series(, -1) -/// AS shift +/// generate_series(, ) AS "index" +/// generate_series(, -1) AS shift, /// LEFT OUTER JOIN /// /// ON @@ -98,6 +99,7 @@ use uuid::Uuid; /// (, + shift, TRUE) /// WHERE /// IS NULL +/// ORDER BY "index" /// LIMIT 1 /// ``` /// @@ -120,6 +122,18 @@ use uuid::Uuid; /// +------------------------------------------------+ /// ``` /// +/// Ordering +/// -------- +/// +/// The subquery is designed to select the next address _after_ the provided +/// base. To preserve this behavior even in situations where we wrap around the +/// end of the range, we include the `index` column when generating the shifts, +/// and order the result by that column. +/// +/// The CockroachDB docs on [ordering] specify that queries without an explicit +/// `ORDER BY` clause are returned "as the coordinating nodes receives them." +/// Without this clause, the order is non-deterministic. +/// /// Shift generators /// ---------------- /// @@ -146,6 +160,8 @@ use uuid::Uuid; /// there is no scope, which means the items must be globally unique in the /// entire table (among non-deleted items). The query is structured slightly /// differently in these two cases. +/// +/// [ordering]: https://www.cockroachlabs.com/docs/stable/select-clause#sorting-and-limiting-query-results #[derive(Debug, Clone, Copy)] pub(super) struct NextItem< Table, @@ -220,6 +236,9 @@ where } } +const SHIFT_COLUMN_IDENT: &str = "shift"; +const INDEX_COLUMN_IDENT: &str = "index"; + impl QueryFragment for NextItem @@ -262,7 +281,7 @@ where self.shift_generator.base(), )?; out.push_sql(" + "); - out.push_identifier("shift")?; + out.push_identifier(SHIFT_COLUMN_IDENT)?; out.push_sql(", TRUE) "); push_next_item_where_clause::(out.reborrow()) @@ -303,7 +322,7 @@ where self.shift_generator.base(), )?; out.push_sql(" + "); - out.push_identifier("shift")?; + out.push_identifier(SHIFT_COLUMN_IDENT)?; out.push_sql(", TRUE) "); push_next_item_where_clause::(out.reborrow()) @@ -338,7 +357,7 @@ where shift_generator.base(), )?; out.push_sql(" + "); - out.push_identifier("shift")?; + out.push_identifier(SHIFT_COLUMN_IDENT)?; out.push_sql(" AS "); out.push_identifier(ItemColumn::NAME)?; out.push_sql(" FROM ("); @@ -350,7 +369,7 @@ where // Push the final where clause shared by scoped and unscoped next item queries. // // ```sql -// WHERE IS NULL LIMIT 1 +// WHERE IS NULL ORDER BY "index" LIMIT 1 // ``` fn push_next_item_where_clause( mut out: AstPass, @@ -363,7 +382,9 @@ where { out.push_sql(" WHERE "); out.push_identifier(ItemColumn::NAME)?; - out.push_sql(" IS NULL LIMIT 1"); + out.push_sql(" IS NULL ORDER BY "); + out.push_identifier(INDEX_COLUMN_IDENT)?; + out.push_sql(" LIMIT 1"); Ok(()) } @@ -414,17 +435,24 @@ pub trait ShiftGenerator { /// Return the minimum shift from the base item for the scan. fn min_shift(&self) -> &i64; + /// Return the indices of the generated shifts for the scan. + fn shift_indices(&self) -> &ShiftIndices; + /// Insert the part of the query represented by the shift of items into the /// provided AstPass. /// /// The default implementation pushes: /// /// ```sql - /// SELECT generate_series(0, ) AS shift + /// SELECT + /// generate_series(0, ) as "index" + /// generate_series(0, ) AS shift, /// UNION ALL - /// SELECT generate_series(, -1) AS shift + /// SELECT + /// generate_series(, ) as "index" + /// generate_series(, -1) AS shift, /// ``` - fn walk_ast<'a, Table, ItemColumn>( + fn walk_ast<'a, 'b, Table, ItemColumn>( &'a self, mut out: AstPass<'_, 'a, Pg>, ) -> diesel::QueryResult<()> @@ -434,15 +462,83 @@ pub trait ShiftGenerator { Item: ToSql<::SqlType, Pg> + Copy, ItemColumn: Column
+ Copy, Pg: HasSqlType<::SqlType>, + 'a: 'b, { out.push_sql("SELECT generate_series(0, "); + out.push_bind_param::( + self.shift_indices().first_end(), + )?; + out.push_sql(") AS "); + out.push_identifier(INDEX_COLUMN_IDENT)?; + out.push_sql(", generate_series(0, "); out.push_bind_param::(self.max_shift())?; out.push_sql(") AS "); - out.push_identifier("shift")?; + out.push_identifier(SHIFT_COLUMN_IDENT)?; out.push_sql(" UNION ALL SELECT generate_series("); + out.push_bind_param::( + self.shift_indices().second_start(), + )?; + out.push_sql(", "); + out.push_bind_param::( + self.shift_indices().second_end(), + )?; + out.push_sql(") AS "); + out.push_identifier(INDEX_COLUMN_IDENT)?; + out.push_sql(", generate_series("); out.push_bind_param::(self.min_shift())?; out.push_sql(", -1) AS "); - out.push_identifier("shift") + out.push_identifier(SHIFT_COLUMN_IDENT) + } +} + +/// Helper to compute the range of the _index_ column, used to predictably sort +/// the generated items. +/// +/// This type cannot be created directly, it's generated internally by creating +/// a `DefaultShiftGenerator`. +// NOTE: This type mostly exists to satisfy annoying lifetime constraints +// imposed by Diesel's `AstPass`. One can only push bind parameters that outlive +// the AST pass itself, so you cannot push owned values or even references to +// values generated anywhere within the `walk_ast()` implementation; +#[derive(Copy, Clone, Debug)] +pub struct ShiftIndices { + // The end of the first range. + first_end: i64, + // The start of the second range. + second_start: i64, + // The end of the second range. + // + // This is equal to the number of items generated for the range. + second_end: i64, +} + +impl ShiftIndices { + fn new(max_shift: i64, min_shift: i64) -> Self { + assert!(max_shift >= 0); + assert!(min_shift <= 0); + + // We're just generating the list of indices (0, n_items), but we need + // to split it in the middle. Specifically, we'll split it at + // `max_shift`, and then generate the remainder. + let first_end = max_shift; + let second_start = first_end + 1; + let second_end = max_shift - min_shift; + Self { first_end, second_start, second_end } + } + + /// Return the end of the first set of indices. + pub fn first_end(&self) -> &i64 { + &self.first_end + } + + /// Return the start of the second set of indices. + pub fn second_start(&self) -> &i64 { + &self.second_start + } + + /// Return the end of the second set of indices. + pub fn second_end(&self) -> &i64 { + &self.second_end } } @@ -450,9 +546,24 @@ pub trait ShiftGenerator { /// implementation. #[derive(Debug, Clone, Copy)] pub struct DefaultShiftGenerator { - pub base: Item, - pub max_shift: i64, - pub min_shift: i64, + base: Item, + max_shift: i64, + min_shift: i64, + shift_indices: ShiftIndices, +} + +impl DefaultShiftGenerator { + /// Create a default generator, checking the provided ranges. + /// + /// Returns `None` if either the max_shift is less than 0, or the min_shift + /// is greater than 0. + pub fn new(base: Item, max_shift: i64, min_shift: i64) -> Option { + if max_shift < 0 || min_shift > 0 { + return None; + } + let shift_indices = ShiftIndices::new(max_shift, min_shift); + Some(Self { base, max_shift, min_shift, shift_indices }) + } } impl ShiftGenerator for DefaultShiftGenerator { @@ -467,6 +578,10 @@ impl ShiftGenerator for DefaultShiftGenerator { fn min_shift(&self) -> &i64 { &self.min_shift } + + fn shift_indices(&self) -> &ShiftIndices { + &self.shift_indices + } } #[cfg(test)] @@ -474,6 +589,7 @@ mod tests { use super::DefaultShiftGenerator; use super::NextItem; + use super::ShiftIndices; use crate::db; use async_bb8_diesel::AsyncRunQueryDsl; use async_bb8_diesel::AsyncSimpleConnection; @@ -602,8 +718,7 @@ mod tests { // // This generator should start at 0, and then select over the range [0, // 10], wrapping back to 0. - let generator = - DefaultShiftGenerator { base: 0, max_shift: 10, min_shift: 0 }; + let generator = DefaultShiftGenerator::new(0, 10, 0).unwrap(); let query = NextItemQuery::new(generator); let it = diesel::insert_into(item::dsl::item) .values(query) @@ -623,8 +738,7 @@ mod tests { assert_eq!(it.value, 1); // Insert 10, and guarantee that we get it back. - let generator = - DefaultShiftGenerator { base: 10, max_shift: 0, min_shift: -10 }; + let generator = DefaultShiftGenerator::new(10, 0, -10).unwrap(); let query = NextItemQuery::new(generator); let it = diesel::insert_into(item::dsl::item) .values(query) @@ -647,4 +761,94 @@ mod tests { db.cleanup().await.unwrap(); logctx.cleanup_successful(); } + + #[tokio::test] + async fn test_next_item_query_is_ordered_by_indices() { + // Setup the test database + let logctx = + dev::test_setup_log("test_next_item_query_is_ordered_by_indices"); + let log = logctx.log.new(o!()); + let mut db = test_setup_database(&log).await; + let cfg = crate::db::Config { url: db.pg_config().clone() }; + let pool = Arc::new(crate::db::Pool::new(&logctx.log, &cfg)); + let conn = pool.pool().get().await.unwrap(); + + // We're going to operate on a separate table, for simplicity. + setup_test_schema(&pool).await; + + // To test ordering behavior, we'll generate a range where the natural + // order of the _items_ differs from their indices. I.e., we have some + // non-zero base. We'll make sure we order everything by those indices, + // not the items themselves. + const MIN_SHIFT: i64 = -5; + const MAX_SHIFT: i64 = 5; + const BASE: i32 = 5; + let generator = + DefaultShiftGenerator::new(BASE, MAX_SHIFT, MIN_SHIFT).unwrap(); + let query = NextItemQuery::new(generator); + + // Insert all items until there are none left. + let first_range = i64::from(BASE)..=i64::from(BASE) + MAX_SHIFT; + let second_range = i64::from(BASE) + MIN_SHIFT..i64::from(BASE); + let mut expected = first_range.chain(second_range); + while let Some(expected_value) = expected.next() { + let it = diesel::insert_into(item::dsl::item) + .values(query) + .returning(Item::as_returning()) + .get_result_async(&*conn) + .await + .unwrap(); + assert_eq!(i64::from(it.value), expected_value); + } + assert!( + expected.next().is_none(), + "Should have exhausted the expected values" + ); + diesel::insert_into(item::dsl::item) + .values(query) + .returning(Item::as_returning()) + .get_result_async(&*conn) + .await + .expect_err( + "The next item query should not have further items to generate", + ); + + db.cleanup().await.unwrap(); + logctx.cleanup_successful(); + } + + #[test] + fn test_shift_indices() { + // In this case, we're generating a list of 11 items, all sequential. So + // we want the first set of arguments to `generate_series()` to be 0, + // 10, and the second set 11, 10. That means the first indices will be + // 0..=10 and the second 11..=10, i.e., empty. + let min_shift = 0; + let max_shift = 10; + let indices = ShiftIndices::new(max_shift, min_shift); + assert_eq!(indices.first_end, 10); + assert_eq!(indices.second_start, 11); + assert_eq!(indices.second_end, 10); + + // Here, the list is split in half. We want to still result in a + // sequence 0..=10, split at 5. So the arguments to generate_series() + // should be (0, 5), and (6, 10). + let min_shift = -5; + let max_shift = 5; + let indices = ShiftIndices::new(max_shift, min_shift); + assert_eq!(indices.first_end, 5); + assert_eq!(indices.second_start, 6); + assert_eq!(indices.second_end, 10); + + // This case tests where most the range is _before_ the base, i.e., the + // max shift is zero. Note that this technically still means we have one + // item in the list of available, which is the base itself (at a shift + // of 0). + let min_shift = -10; + let max_shift = 0; + let indices = ShiftIndices::new(max_shift, min_shift); + assert_eq!(indices.first_end, 0); + assert_eq!(indices.second_start, 1); + assert_eq!(indices.second_end, 10); + } } diff --git a/nexus/db-queries/src/db/queries/vpc.rs b/nexus/db-queries/src/db/queries/vpc.rs index c29a51adb0..2875ae6a05 100644 --- a/nexus/db-queries/src/db/queries/vpc.rs +++ b/nexus/db-queries/src/db/queries/vpc.rs @@ -246,8 +246,8 @@ struct NextVni { impl NextVni { fn new(vni: Vni) -> Self { let VniShifts { min_shift, max_shift } = VniShifts::new(vni); - let generator = - DefaultShiftGenerator { base: vni, max_shift, min_shift }; + let generator = DefaultShiftGenerator::new(vni, max_shift, min_shift) + .expect("invalid min/max shift"); let inner = NextItem::new_unscoped(generator); Self { inner } } @@ -262,8 +262,8 @@ impl NextVni { -i32::try_from(base_u32) .expect("Expected a valid VNI at this point"), ); - let generator = - DefaultShiftGenerator { base: vni, max_shift, min_shift }; + let generator = DefaultShiftGenerator::new(vni, max_shift, min_shift) + .expect("invalid min/max shift"); let inner = NextItem::new_unscoped(generator); Self { inner } } From 4e6af7a5b67bb10327189c137a2e9e8584e18f1b Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 04:24:42 +0000 Subject: [PATCH 12/22] chore(deps): update rust crate supports-color to v3 (#4984) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 9 ++++----- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ecdd8c235e..38a193bedb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3670,9 +3670,9 @@ dependencies = [ [[package]] name = "is_ci" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "itertools" @@ -8775,11 +8775,10 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "supports-color" -version = "2.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" +checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f" dependencies = [ - "is-terminal", "is_ci", ] diff --git a/Cargo.toml b/Cargo.toml index f15170e2e1..70d8e3ea48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -377,7 +377,7 @@ static_assertions = "1.1.0" steno = "0.4.0" strum = { version = "0.26", features = [ "derive" ] } subprocess = "0.2.9" -supports-color = "2.1.0" +supports-color = "3.0.0" swrite = "0.1.0" libsw = { version = "3.3.1", features = ["tokio"] } syn = { version = "2.0" } From aa61741059df67a3d6bf9bc0472536191420c210 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 05:15:52 +0000 Subject: [PATCH 13/22] chore(deps): update taiki-e/install-action digest to bd71f12 (#5082) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- .github/workflows/hakari.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hakari.yml b/.github/workflows/hakari.yml index c263eecca3..5927cb43e6 100644 --- a/.github/workflows/hakari.yml +++ b/.github/workflows/hakari.yml @@ -24,7 +24,7 @@ jobs: with: toolchain: stable - name: Install cargo-hakari - uses: taiki-e/install-action@14422f84f07a2d547893af56258f5e59333262df # v2 + uses: taiki-e/install-action@6943331e01261cdff7420bbc2508cb463574e404 # v2 with: tool: cargo-hakari - name: Check workspace-hack Cargo.toml is up-to-date From 754dd48946ad3bd85f11497d83a35ae5c2ee1bd5 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 05:16:51 +0000 Subject: [PATCH 14/22] chore(deps): update rust crate multimap to 0.10.0 (#5093) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38a193bedb..3b19e5cd95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4186,9 +4186,9 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" dependencies = [ "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 70d8e3ea48..2f39e41e64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -252,7 +252,7 @@ mime_guess = "2.0.4" mockall = "0.12" newtype_derive = "0.1.6" mg-admin-client = { path = "clients/mg-admin-client" } -multimap = "0.8.1" +multimap = "0.10.0" nexus-blueprint-execution = { path = "nexus/blueprint-execution" } nexus-client = { path = "clients/nexus-client" } nexus-db-model = { path = "nexus/db-model" } From 41e1c79a773834d86c5d95192deafb984e4fadfc Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 05:56:29 +0000 Subject: [PATCH 15/22] chore(deps): update rust crate reedline to 0.29.0 (#5094) --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b19e5cd95..6760688424 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4637,9 +4637,9 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "nu-ansi-term" -version = "0.49.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" +checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14" dependencies = [ "windows-sys 0.48.0", ] @@ -6984,9 +6984,9 @@ dependencies = [ [[package]] name = "reedline" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f4e89a0f80909b3ca4bca9759ed37e4bfddb6f5d2ffb1b4ceb2b1638a3e1eb" +checksum = "9e01ebfbdb1a88963121d3c928c97be7f10fec7795bec8b918c8cda1db7c29e6" dependencies = [ "chrono", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 2f39e41e64..b0d82f8fd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -323,7 +323,7 @@ rand = "0.8.5" ratatui = "0.26.1" rayon = "1.8" rcgen = "0.12.1" -reedline = "0.28.0" +reedline = "0.29.0" ref-cast = "1.0" regex = "1.10.3" regress = "0.8.0" From d177ccb6e8ab571f45de87635a5699d78d2c6b04 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 01:12:59 -0800 Subject: [PATCH 16/22] chore(deps): update rust crate ring to 0.17.8 (#5101) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 29 +++++++++++++++-------------- Cargo.toml | 2 +- workspace-hack/Cargo.toml | 4 ++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6760688424..8ed14304d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5061,7 +5061,7 @@ dependencies = [ "ref-cast", "regex", "reqwest", - "ring 0.17.7", + "ring 0.17.8", "rustls 0.22.2", "rustls-pemfile 2.0.0", "samael", @@ -5164,7 +5164,7 @@ dependencies = [ "petgraph", "rayon", "reqwest", - "ring 0.17.7", + "ring 0.17.8", "semver 1.0.21", "serde", "sled-hardware", @@ -5317,7 +5317,7 @@ dependencies = [ "rcgen", "regex", "reqwest", - "ring 0.17.7", + "ring 0.17.8", "rustls 0.22.2", "slog", "subprocess", @@ -5417,7 +5417,7 @@ dependencies = [ "regex-automata 0.4.4", "regex-syntax 0.8.2", "reqwest", - "ring 0.17.7", + "ring 0.17.8", "rustix", "schemars", "semver 1.0.21", @@ -6930,7 +6930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48406db8ac1f3cbc7dcdb56ec355343817958a356ff430259bb07baf7607e1e1" dependencies = [ "pem", - "ring 0.17.7", + "ring 0.17.8", "time", "yasna", ] @@ -7176,16 +7176,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom 0.2.10", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -7432,7 +7433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-webpki 0.101.7", "sct", ] @@ -7444,7 +7445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.1", "subtle", @@ -7495,7 +7496,7 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] @@ -7505,7 +7506,7 @@ version = "0.102.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "untrusted 0.9.0", ] @@ -9415,7 +9416,7 @@ dependencies = [ "pem", "percent-encoding", "reqwest", - "ring 0.17.7", + "ring 0.17.8", "serde", "serde_json", "serde_plain", @@ -9647,7 +9648,7 @@ dependencies = [ "omicron-workspace-hack", "parse-size", "rand 0.8.5", - "ring 0.17.7", + "ring 0.17.8", "serde", "serde_json", "serde_path_to_error", diff --git a/Cargo.toml b/Cargo.toml index b0d82f8fd6..2ba68461d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -328,7 +328,7 @@ ref-cast = "1.0" regex = "1.10.3" regress = "0.8.0" reqwest = { version = "0.11", default-features = false } -ring = "0.17.7" +ring = "0.17.8" rpassword = "7.3.1" rstest = "0.18.2" rustfmt-wrapper = "0.2" diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index c204b56b4d..bbb5191b4c 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -86,7 +86,7 @@ regex = { version = "1.10.3" } regex-automata = { version = "0.4.4", default-features = false, features = ["dfa-onepass", "hybrid", "meta", "nfa-backtrack", "perf-inline", "perf-literal", "unicode"] } regex-syntax = { version = "0.8.2" } reqwest = { version = "0.11.22", features = ["blocking", "json", "rustls-tls", "stream"] } -ring = { version = "0.17.7", features = ["std"] } +ring = { version = "0.17.8", features = ["std"] } schemars = { version = "0.8.16", features = ["bytes", "chrono", "uuid1"] } semver = { version = "1.0.21", features = ["serde"] } serde = { version = "1.0.196", features = ["alloc", "derive", "rc"] } @@ -193,7 +193,7 @@ regex = { version = "1.10.3" } regex-automata = { version = "0.4.4", default-features = false, features = ["dfa-onepass", "hybrid", "meta", "nfa-backtrack", "perf-inline", "perf-literal", "unicode"] } regex-syntax = { version = "0.8.2" } reqwest = { version = "0.11.22", features = ["blocking", "json", "rustls-tls", "stream"] } -ring = { version = "0.17.7", features = ["std"] } +ring = { version = "0.17.8", features = ["std"] } schemars = { version = "0.8.16", features = ["bytes", "chrono", "uuid1"] } semver = { version = "1.0.21", features = ["serde"] } serde = { version = "1.0.196", features = ["alloc", "derive", "rc"] } From 15e10b251ffdba73a69f972a4b278cbaa461b61d Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 01:13:10 -0800 Subject: [PATCH 17/22] chore(deps): update rust crate rustls-pemfile to 2.1.0 (#5095) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 14 +++++++------- Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ed14304d0..2c6328dc9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1984,7 +1984,7 @@ dependencies = [ "percent-encoding", "proc-macro2", "rustls 0.22.2", - "rustls-pemfile 2.0.0", + "rustls-pemfile 2.1.0", "schemars", "serde", "serde_json", @@ -5063,7 +5063,7 @@ dependencies = [ "reqwest", "ring 0.17.8", "rustls 0.22.2", - "rustls-pemfile 2.0.0", + "rustls-pemfile 2.1.0", "samael", "schemars", "semver 1.0.21", @@ -7459,7 +7459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.0.0", + "rustls-pemfile 2.1.0", "rustls-pki-types", "schannel", "security-framework", @@ -7476,9 +7476,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b" dependencies = [ "base64", "rustls-pki-types", @@ -7486,9 +7486,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a" +checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" [[package]] name = "rustls-webpki" diff --git a/Cargo.toml b/Cargo.toml index 2ba68461d2..61b9bb67fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -333,7 +333,7 @@ rpassword = "7.3.1" rstest = "0.18.2" rustfmt-wrapper = "0.2" rustls = "0.22.2" -rustls-pemfile = "2.0.0" +rustls-pemfile = "2.1.0" rustyline = "13.0.0" samael = { version = "0.0.14", features = ["xmlsec"] } schemars = "0.8.16" From 9dd2325cde45e70084ce6b10a17394ed628bce8a Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:11:45 +0000 Subject: [PATCH 18/22] fix(deps): update russh monorepo to 0.42.0 (#5098) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 23 +++++++++++++++++++---- end-to-end-tests/Cargo.toml | 4 ++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c6328dc9c..884f398a73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5885,6 +5885,20 @@ dependencies = [ "sha2", ] +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2", +] + [[package]] name = "packed_struct" version = "0.10.1" @@ -7277,9 +7291,9 @@ dependencies = [ [[package]] name = "russh" -version = "0.40.2" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93dab9e1c313d0d04a42e39c0995943fc38c037e2e3fa9c33685777a1aecdfb2" +checksum = "394cc2733c5b5ca9f342d9532b78599849633ccabdbf40f1af094cacf4d86b62" dependencies = [ "aes", "aes-gcm", @@ -7322,9 +7336,9 @@ dependencies = [ [[package]] name = "russh-keys" -version = "0.40.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0de3cb3cbfa773b7f170b6830565fac207a0d630cc666a29f80097cc374dd8" +checksum = "3e98aa03d476f8d2bf6e4525291c1eb8e22f4ae9653d7a5458fd53cb0191c741" dependencies = [ "aes", "async-trait", @@ -7345,6 +7359,7 @@ dependencies = [ "num-bigint", "num-integer", "p256", + "p521", "pbkdf2 0.11.0", "rand 0.7.3", "rand_core 0.6.4", diff --git a/end-to-end-tests/Cargo.toml b/end-to-end-tests/Cargo.toml index 5ff958dc9c..fee32a344e 100644 --- a/end-to-end-tests/Cargo.toml +++ b/end-to-end-tests/Cargo.toml @@ -16,8 +16,8 @@ omicron-test-utils.workspace = true oxide-client.workspace = true rand.workspace = true reqwest.workspace = true -russh = "0.40.2" -russh-keys = "0.40.1" +russh = "0.42.0" +russh-keys = "0.42.0" serde.workspace = true serde_json.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } From 563234e35ca18d0df64b51431bff10906ba69bf8 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 16:32:35 +0000 Subject: [PATCH 19/22] chore(deps): update rust crate textwrap to 0.16.1 (#5102) --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 884f398a73..20726b84fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5141,7 +5141,7 @@ dependencies = [ "strum 0.26.1", "subprocess", "tabled", - "textwrap 0.16.0", + "textwrap 0.16.1", "tokio", "unicode-width", "uuid", @@ -9014,9 +9014,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", @@ -10338,7 +10338,7 @@ dependencies = [ "slog-term", "supports-color", "tempfile", - "textwrap 0.16.0", + "textwrap 0.16.1", "tokio", "tokio-util", "toml 0.8.10", diff --git a/Cargo.toml b/Cargo.toml index 61b9bb67fd..c7ef94eaa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -387,7 +387,7 @@ tempdir = "0.3" tempfile = "3.10" term = "0.7" termios = "0.3" -textwrap = "0.16.0" +textwrap = "0.16.1" test-strategy = "0.3.1" thiserror = "1.0" tofino = { git = "http://github.com/oxidecomputer/tofino", branch = "main" } From 93de3fed3e82c39bed0cba9a6b128218e994af8f Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 10:04:32 -0800 Subject: [PATCH 20/22] chore(deps): update rust crate serde_json to 1.0.114 (#5106) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- workspace-hack/Cargo.toml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20726b84fe..dce0d06c3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7867,9 +7867,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", diff --git a/Cargo.toml b/Cargo.toml index c7ef94eaa2..ed2e94f8eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,7 +342,7 @@ semver = { version = "1.0.21", features = ["std", "serde"] } serde = { version = "1.0", default-features = false, features = [ "derive" ] } serde_derive = "1.0" serde_human_bytes = { git = "http://github.com/oxidecomputer/serde_human_bytes", branch = "main" } -serde_json = "1.0.113" +serde_json = "1.0.114" serde_path_to_error = "0.1.15" serde_tokenstream = "0.2" serde_urlencoded = "0.7.1" diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index bbb5191b4c..14e238ba61 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -90,7 +90,7 @@ ring = { version = "0.17.8", features = ["std"] } schemars = { version = "0.8.16", features = ["bytes", "chrono", "uuid1"] } semver = { version = "1.0.21", features = ["serde"] } serde = { version = "1.0.196", features = ["alloc", "derive", "rc"] } -serde_json = { version = "1.0.113", features = ["raw_value", "unbounded_depth"] } +serde_json = { version = "1.0.114", features = ["raw_value", "unbounded_depth"] } sha2 = { version = "0.10.8", features = ["oid"] } similar = { version = "2.3.0", features = ["inline", "unicode"] } slog = { version = "2.7.0", features = ["dynamic-keys", "max_level_trace", "release_max_level_debug", "release_max_level_trace"] } @@ -197,7 +197,7 @@ ring = { version = "0.17.8", features = ["std"] } schemars = { version = "0.8.16", features = ["bytes", "chrono", "uuid1"] } semver = { version = "1.0.21", features = ["serde"] } serde = { version = "1.0.196", features = ["alloc", "derive", "rc"] } -serde_json = { version = "1.0.113", features = ["raw_value", "unbounded_depth"] } +serde_json = { version = "1.0.114", features = ["raw_value", "unbounded_depth"] } sha2 = { version = "0.10.8", features = ["oid"] } similar = { version = "2.3.0", features = ["inline", "unicode"] } slog = { version = "2.7.0", features = ["dynamic-keys", "max_level_trace", "release_max_level_debug", "release_max_level_trace"] } From 172cf38036ce7ffd4a603c8f57b6172d3d3706d6 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 10:04:41 -0800 Subject: [PATCH 21/22] chore(deps): update rust crate semver to 1.0.22 (#5103) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- workspace-hack/Cargo.toml | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dce0d06c3d..0f901bdade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -803,7 +803,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "thiserror", @@ -2786,7 +2786,7 @@ dependencies = [ "once_cell", "pathdiff", "petgraph", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "smallvec 1.13.1", @@ -4334,7 +4334,7 @@ dependencies = [ "rand 0.8.5", "ref-cast", "schemars", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "sled-agent-client", @@ -4885,7 +4885,7 @@ dependencies = [ "regress", "reqwest", "schemars", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_human_bytes", "serde_json", @@ -5066,7 +5066,7 @@ dependencies = [ "rustls-pemfile 2.1.0", "samael", "schemars", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "serde_urlencoded", @@ -5165,7 +5165,7 @@ dependencies = [ "rayon", "reqwest", "ring 0.17.8", - "semver 1.0.21", + "semver 1.0.22", "serde", "sled-hardware", "slog", @@ -5266,7 +5266,7 @@ dependencies = [ "rcgen", "reqwest", "schemars", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_human_bytes", "serde_json", @@ -5420,7 +5420,7 @@ dependencies = [ "ring 0.17.8", "rustix", "schemars", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_json", "sha2", @@ -5475,7 +5475,7 @@ dependencies = [ "hex", "reqwest", "ring 0.16.20", - "semver 1.0.21", + "semver 1.0.22", "serde", "serde_derive", "serde_json", @@ -7412,7 +7412,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.21", + "semver 1.0.22", ] [[package]] @@ -7780,9 +7780,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] diff --git a/Cargo.toml b/Cargo.toml index ed2e94f8eb..d3f099589f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -338,7 +338,7 @@ rustyline = "13.0.0" samael = { version = "0.0.14", features = ["xmlsec"] } schemars = "0.8.16" secrecy = "0.8.0" -semver = { version = "1.0.21", features = ["std", "serde"] } +semver = { version = "1.0.22", features = ["std", "serde"] } serde = { version = "1.0", default-features = false, features = [ "derive" ] } serde_derive = "1.0" serde_human_bytes = { git = "http://github.com/oxidecomputer/serde_human_bytes", branch = "main" } diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 14e238ba61..ebe683e51a 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -88,7 +88,7 @@ regex-syntax = { version = "0.8.2" } reqwest = { version = "0.11.22", features = ["blocking", "json", "rustls-tls", "stream"] } ring = { version = "0.17.8", features = ["std"] } schemars = { version = "0.8.16", features = ["bytes", "chrono", "uuid1"] } -semver = { version = "1.0.21", features = ["serde"] } +semver = { version = "1.0.22", features = ["serde"] } serde = { version = "1.0.196", features = ["alloc", "derive", "rc"] } serde_json = { version = "1.0.114", features = ["raw_value", "unbounded_depth"] } sha2 = { version = "0.10.8", features = ["oid"] } @@ -195,7 +195,7 @@ regex-syntax = { version = "0.8.2" } reqwest = { version = "0.11.22", features = ["blocking", "json", "rustls-tls", "stream"] } ring = { version = "0.17.8", features = ["std"] } schemars = { version = "0.8.16", features = ["bytes", "chrono", "uuid1"] } -semver = { version = "1.0.21", features = ["serde"] } +semver = { version = "1.0.22", features = ["serde"] } serde = { version = "1.0.196", features = ["alloc", "derive", "rc"] } serde_json = { version = "1.0.114", features = ["raw_value", "unbounded_depth"] } sha2 = { version = "0.10.8", features = ["oid"] } From 915276d47096a9ce076453eb75aaa9de29477e80 Mon Sep 17 00:00:00 2001 From: "oxide-renovate[bot]" <146848827+oxide-renovate[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:06:11 +0000 Subject: [PATCH 22/22] chore(deps): update rust crate assert_cmd to 2.0.14 (#5105) Co-authored-by: oxide-renovate[bot] <146848827+oxide-renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f901bdade..bcbcb3e9c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -236,9 +236,9 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00ad3f3a942eee60335ab4342358c161ee296829e0d16ff42fc1d6cb07815467" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" dependencies = [ "anstyle", "bstr 1.6.0", diff --git a/Cargo.toml b/Cargo.toml index d3f099589f..db37547ea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,7 +155,7 @@ anyhow = "1.0" api_identity = { path = "api_identity" } approx = "0.5.1" assert_matches = "1.5.0" -assert_cmd = "2.0.13" +assert_cmd = "2.0.14" async-bb8-diesel = { git = "https://github.com/oxidecomputer/async-bb8-diesel", rev = "ed7ab5ef0513ba303d33efd41d3e9e381169d59b" } async-trait = "0.1.77" atomicwrites = "0.4.3"